Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions proto/engine/v1/electra.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,134 @@
package enginev1

import (
"crypto/sha256"
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/config/params"
)

type ExecutionPayloadElectra = ExecutionPayloadDeneb
type ExecutionPayloadHeaderElectra = ExecutionPayloadHeaderDeneb

// GetDecodedExecutionRequests decodes the byte array in ExecutionBundleElectra and returns a ExecutionRequests container object
// based on specifications from EIP-7685
func (eb *ExecutionBundleElectra) GetDecodedExecutionRequests() (*ExecutionRequests, error) {
if len(eb.ExecutionRequests) == 0 {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this error out or return empty?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be the empty requests, we need to guarantee that the slice is not nil before though.

return nil, errors.New("no execution requests found")
}
er := &ExecutionRequests{}
if err := processRequestBytes(eb.ExecutionRequests, er); err != nil {
Comment on lines +21 to +22
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you are passing an empty request container here anyway, why not use the signature
func processRequestBytes([]byte); (*ExecutionRequests, error)

return nil, err
}

if err := verifyExecutionRequests(er); err != nil {
return nil, err
}
return er, nil
}

func verifyExecutionRequests(er *ExecutionRequests) error {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did I miss any verifications?

if uint64(len(er.Deposits)) > params.BeaconConfig().MaxDepositRequestsPerPayload {
return fmt.Errorf("too many deposits requested: received %d, expected %d", len(er.Deposits), params.BeaconConfig().MaxDepositRequestsPerPayload)
}
if uint64(len(er.Withdrawals)) > params.BeaconConfig().MaxWithdrawalsPerPayload {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be withdrawals requests per payload, not withdrawals

return fmt.Errorf("too many withdrawals requested: received %d, expected %d", len(er.Withdrawals), params.BeaconConfig().MaxWithdrawalRequestsPerPayload)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

}
if uint64(len(er.Consolidations)) > params.BeaconConfig().MaxConsolidationsRequestsPerPayload {
return fmt.Errorf("too many consolidations requested: received %d, expected %d", len(er.Consolidations), params.BeaconConfig().MaxConsolidationsRequestsPerPayload)
}
return nil
}

func processRequestBytes(
requestBytes []byte,
requests *ExecutionRequests,
) error {
if len(requestBytes) == 0 {
return nil // No more requests to process
}

requestType := requestBytes[0]
remainingBytes := requestBytes[1:]

switch requestType {
case 0:
dr := &DepositRequest{}
if len(remainingBytes) < dr.SizeSSZ() {
return fmt.Errorf("invalid deposit request size: returned %d, expected %d", len(remainingBytes), dr.SizeSSZ())
}
drBytes := remainingBytes[:dr.SizeSSZ()]
remainingBytes = remainingBytes[dr.SizeSSZ():]
if err := dr.UnmarshalSSZ(drBytes); err != nil {
return errors.Wrap(err, "failed to unmarshal deposit request")
}
requests.Deposits = append(requests.Deposits, dr)
case 1:
wr := &WithdrawalRequest{}
if len(remainingBytes) < wr.SizeSSZ() {
return fmt.Errorf("invalid withdrawal request size: returned %d, expected %d", len(remainingBytes), wr.SizeSSZ())
}
wrBytes := remainingBytes[:wr.SizeSSZ()]
remainingBytes = remainingBytes[wr.SizeSSZ():]
if err := wr.UnmarshalSSZ(wrBytes); err != nil {
return errors.Wrap(err, "failed to unmarshal withdrawal request")
}
requests.Withdrawals = append(requests.Withdrawals, wr)
case 2:
cr := &ConsolidationRequest{}
if len(remainingBytes) < cr.SizeSSZ() {
return fmt.Errorf("invalid consolidation request size: returned %d, expected %d", len(remainingBytes), cr.SizeSSZ())
}
crBytes := remainingBytes[:cr.SizeSSZ()]
remainingBytes = remainingBytes[cr.SizeSSZ():]
if err := cr.UnmarshalSSZ(crBytes); err != nil {
return errors.Wrap(err, "failed to unmarshal consolidation request")
}
requests.Consolidations = append(requests.Consolidations, cr)
default:
return errors.New("invalid execution request type")
}

// Recursive call with the remaining bytes
return processRequestBytes(remainingBytes, requests)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a loop instead of a recursive algorithm is better here, the tail recursion elimination in this case is simple.

}

// EncodeExecutionRequests is a helper function that takes a ExecutionRequests container and converts it into a hash used for `engine_NewPayloadV4`
func EncodeExecutionRequests(eb *ExecutionRequests) (common.Hash, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pending questions here on whether ordering would matter to the EL. cc @fjl

var executionRequestBytes []byte
depositBytes, err := encodeExecutionRequestsByType(0, eb.Deposits)
if err != nil {
return common.Hash{}, errors.Wrap(err, "failed to encode deposit requests")
}
withdrawalBytes, err := encodeExecutionRequestsByType(1, eb.Withdrawals)
if err != nil {
return common.Hash{}, errors.Wrap(err, "failed to encode withdrawal requests")
}
consolidationBytes, err := encodeExecutionRequestsByType(2, eb.Consolidations)
if err != nil {
return common.Hash{}, errors.Wrap(err, "failed to encode consolidation requests")
}
executionRequestBytes = append(executionRequestBytes, depositBytes...)
executionRequestBytes = append(executionRequestBytes, withdrawalBytes...)
executionRequestBytes = append(executionRequestBytes, consolidationBytes...)
return sha256.Sum256(executionRequestBytes), nil
}

type marshalSSZable interface {
MarshalSSZ() ([]byte, error)
}

func encodeExecutionRequestsByType[T marshalSSZable](executionRequestType int, requests []T) ([]byte, error) {
var executionRequestBytes []byte
for _, er := range requests {
ssz, err := er.MarshalSSZ()
if err != nil {
return nil, err
}
executionRequestBytes = append(executionRequestBytes, byte(executionRequestType))
executionRequestBytes = append(executionRequestBytes, ssz...)
}
return executionRequestBytes, nil
}
Loading