diff --git a/.golangci.yaml b/.golangci.yaml index 7cb84f25..fe09be26 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -93,6 +93,7 @@ linters-settings: # # Easier to read with only one of the versioned payloads. # + - 'VersionedSubmitBlindedBlockResponse' - 'VersionedExecutionPayload' - 'VersionedSignedBuilderBid' diff --git a/common/types_spec.go b/common/types_spec.go index bff5a524..7342199d 100644 --- a/common/types_spec.go +++ b/common/types_spec.go @@ -13,9 +13,9 @@ import ( apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" consensusspec "github.com/attestantio/go-eth2-client/spec" consensuscapella "github.com/attestantio/go-eth2-client/spec/capella" + consensusdeneb "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" - utilbellatrix "github.com/attestantio/go-eth2-client/util/bellatrix" - utilcapella "github.com/attestantio/go-eth2-client/util/capella" + utildeneb "github.com/attestantio/go-eth2-client/util/deneb" "github.com/flashbots/go-boost-utils/bls" "github.com/flashbots/go-boost-utils/ssz" boostTypes "github.com/flashbots/go-boost-utils/types" @@ -48,23 +48,46 @@ func BuildGetHeaderResponse(payload *VersionedSubmitBlockRequest, sk *bls.Secret return nil, ErrMissingSecretKey } - if payload.Capella != nil { - signedBuilderBid, err := CapellaBuilderSubmitBlockRequestToSignedBuilderBid(payload.Capella, sk, pubkey, domain) + versionedPayload := &api.VersionedExecutionPayload{Version: payload.Version} + switch payload.Version { + case consensusspec.DataVersionCapella: + versionedPayload.Capella = payload.Capella.ExecutionPayload + header, err := utils.PayloadToPayloadHeader(versionedPayload) + if err != nil { + return nil, err + } + signedBuilderBid, err := BuilderBlockRequestToSignedBuilderBid(payload, header, sk, pubkey, domain) if err != nil { return nil, err } return &spec.VersionedSignedBuilderBid{ - Version: consensusspec.DataVersionCapella, - Capella: signedBuilderBid, - Bellatrix: nil, + Version: consensusspec.DataVersionCapella, + Capella: signedBuilderBid.Capella, }, nil + case consensusspec.DataVersionDeneb: + versionedPayload.Deneb = payload.Deneb.ExecutionPayload + header, err := utils.PayloadToPayloadHeader(versionedPayload) + if err != nil { + return nil, err + } + signedBuilderBid, err := BuilderBlockRequestToSignedBuilderBid(payload, header, sk, pubkey, domain) + if err != nil { + return nil, err + } + return &spec.VersionedSignedBuilderBid{ + Version: consensusspec.DataVersionDeneb, + Deneb: signedBuilderBid.Deneb, + }, nil + case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix: + return nil, ErrInvalidVersion + default: + return nil, ErrEmptyPayload } - return nil, ErrEmptyPayload } -func BuildGetPayloadResponse(payload *VersionedSubmitBlockRequest) (*api.VersionedExecutionPayload, error) { +func BuildGetPayloadResponse(payload *VersionedSubmitBlockRequest) (*api.VersionedSubmitBlindedBlockResponse, error) { if payload.Capella != nil { - return &api.VersionedExecutionPayload{ + return &api.VersionedSubmitBlindedBlockResponse{ Version: consensusspec.DataVersionCapella, Capella: payload.Capella.ExecutionPayload, }, nil @@ -73,19 +96,14 @@ func BuildGetPayloadResponse(payload *VersionedSubmitBlockRequest) (*api.Version return nil, ErrEmptyPayload } -func BuilderSubmitBlockRequestToSignedBuilderBid(req *VersionedSubmitBlockRequest, sk *bls.SecretKey, pubkey *phase0.BLSPubKey, domain phase0.Domain) (*spec.VersionedSignedBuilderBid, error) { - value, err := req.Value() +func BuilderBlockRequestToSignedBuilderBid(payload *VersionedSubmitBlockRequest, header *api.VersionedExecutionPayloadHeader, sk *bls.SecretKey, pubkey *phase0.BLSPubKey, domain phase0.Domain) (*spec.VersionedSignedBuilderBid, error) { + value, err := payload.Value() if err != nil { return nil, err } - switch req.Version { + switch payload.Version { case consensusspec.DataVersionCapella: - header, err := utils.PayloadToPayloadHeader(&api.VersionedExecutionPayload{Version: req.Version, Capella: req.Capella.ExecutionPayload}) - if err != nil { - return nil, err - } - builderBid := capella.BuilderBid{ Value: value, Header: header.Capella, @@ -104,100 +122,137 @@ func BuilderSubmitBlockRequestToSignedBuilderBid(req *VersionedSubmitBlockReques Signature: sig, }, }, nil - case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix, consensusspec.DataVersionDeneb: - return nil, errors.Wrap(ErrInvalidVersion, fmt.Sprintf("%s is not supported", req.Version.String())) - default: - return nil, errors.Wrap(ErrInvalidVersion, fmt.Sprintf("%s is not supported", req.Version.String())) - } -} + case consensusspec.DataVersionDeneb: + var blobRoots []phase0.Root + for i, blob := range payload.Deneb.BlobsBundle.Blobs { + blobRootHelper := utildeneb.BeaconBlockBlob{Blob: blob} + root, err := blobRootHelper.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("failed to calculate blob root at blob index %d", i)) + } + blobRoots = append(blobRoots, root) + } + blindedBlobRoots := deneb.BlindedBlobsBundle{ + Commitments: payload.Deneb.BlobsBundle.Commitments, + Proofs: payload.Deneb.BlobsBundle.Proofs, + BlobRoots: blobRoots, + } -func CapellaBuilderSubmitBlockRequestToSignedBuilderBid(req *capella.SubmitBlockRequest, sk *bls.SecretKey, pubkey *phase0.BLSPubKey, domain phase0.Domain) (*capella.SignedBuilderBid, error) { - header, err := CapellaPayloadToPayloadHeader(req.ExecutionPayload) - if err != nil { - return nil, err - } + builderBid := deneb.BuilderBid{ + Value: value, + Header: header.Deneb, + BlindedBlobsBundle: &blindedBlobRoots, + Pubkey: *pubkey, + } - builderBid := capella.BuilderBid{ - Value: req.Message.Value, - Header: header, - Pubkey: *pubkey, - } + sig, err := ssz.SignMessage(&builderBid, domain, sk) + if err != nil { + return nil, err + } - sig, err := ssz.SignMessage(&builderBid, domain, sk) - if err != nil { - return nil, err + return &spec.VersionedSignedBuilderBid{ + Version: consensusspec.DataVersionDeneb, + Deneb: &deneb.SignedBuilderBid{ + Message: &builderBid, + Signature: sig, + }, + }, nil + case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix: + fallthrough + default: + return nil, errors.Wrap(ErrInvalidVersion, fmt.Sprintf("%s is not supported", payload.Version.String())) } - - return &capella.SignedBuilderBid{ - Message: &builderBid, - Signature: sig, - }, nil } -func CapellaPayloadToPayloadHeader(p *consensuscapella.ExecutionPayload) (*consensuscapella.ExecutionPayloadHeader, error) { - if p == nil { - return nil, ErrEmptyPayload +func SignedBlindedBeaconBlockToBeaconBlock(signedBlindedBeaconBlock *VersionedSignedBlindedBlockRequest, blockPayload *api.VersionedSubmitBlindedBlockResponse) (*VersionedSignedBlockRequest, error) { + signedBeaconBlock := VersionedSignedBlockRequest{ + consensusapi.VersionedBlockRequest{ //nolint:exhaustruct + Version: signedBlindedBeaconBlock.Version, + }, } - - transactions := utilbellatrix.ExecutionPayloadTransactions{Transactions: p.Transactions} - transactionsRoot, err := transactions.HashTreeRoot() - if err != nil { - return nil, err + switch signedBlindedBeaconBlock.Version { + case consensusspec.DataVersionCapella: + capellaBlindedBlock := signedBlindedBeaconBlock.Capella + signedBeaconBlock.Capella = CapellaUnblindSignedBlock(capellaBlindedBlock, blockPayload.Capella) + case consensusspec.DataVersionDeneb: + denebBlindedBlock := signedBlindedBeaconBlock.Deneb.SignedBlindedBlock + blockRoot, err := denebBlindedBlock.Message.HashTreeRoot() + if err != nil { + return nil, err + } + signedBeaconBlock.Deneb = DenebUnblindSignedBlock(denebBlindedBlock, blockPayload.Deneb, blockRoot) + case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix: + return nil, errors.Wrap(ErrInvalidVersion, fmt.Sprintf("%s is not supported", signedBlindedBeaconBlock.Version.String())) } + return &signedBeaconBlock, nil +} - withdrawals := utilcapella.ExecutionPayloadWithdrawals{Withdrawals: p.Withdrawals} - withdrawalsRoot, err := withdrawals.HashTreeRoot() - if err != nil { - return nil, err +func CapellaUnblindSignedBlock(blindedBlock *apiv1capella.SignedBlindedBeaconBlock, executionPayload *consensuscapella.ExecutionPayload) *consensuscapella.SignedBeaconBlock { + return &consensuscapella.SignedBeaconBlock{ + Signature: blindedBlock.Signature, + Message: &consensuscapella.BeaconBlock{ + Slot: blindedBlock.Message.Slot, + ProposerIndex: blindedBlock.Message.ProposerIndex, + ParentRoot: blindedBlock.Message.ParentRoot, + StateRoot: blindedBlock.Message.StateRoot, + Body: &consensuscapella.BeaconBlockBody{ + BLSToExecutionChanges: blindedBlock.Message.Body.BLSToExecutionChanges, + RANDAOReveal: blindedBlock.Message.Body.RANDAOReveal, + ETH1Data: blindedBlock.Message.Body.ETH1Data, + Graffiti: blindedBlock.Message.Body.Graffiti, + ProposerSlashings: blindedBlock.Message.Body.ProposerSlashings, + AttesterSlashings: blindedBlock.Message.Body.AttesterSlashings, + Attestations: blindedBlock.Message.Body.Attestations, + Deposits: blindedBlock.Message.Body.Deposits, + VoluntaryExits: blindedBlock.Message.Body.VoluntaryExits, + SyncAggregate: blindedBlock.Message.Body.SyncAggregate, + ExecutionPayload: executionPayload, + }, + }, } - - return &consensuscapella.ExecutionPayloadHeader{ - ParentHash: p.ParentHash, - FeeRecipient: p.FeeRecipient, - StateRoot: p.StateRoot, - ReceiptsRoot: p.ReceiptsRoot, - LogsBloom: p.LogsBloom, - PrevRandao: p.PrevRandao, - BlockNumber: p.BlockNumber, - GasLimit: p.GasLimit, - GasUsed: p.GasUsed, - Timestamp: p.Timestamp, - ExtraData: p.ExtraData, - BaseFeePerGas: p.BaseFeePerGas, - BlockHash: p.BlockHash, - TransactionsRoot: transactionsRoot, - WithdrawalsRoot: withdrawalsRoot, - }, nil } -func SignedBlindedBeaconBlockToBeaconBlock(signedBlindedBeaconBlock *VersionedSignedBlindedBlockRequest, executionPayload *api.VersionedExecutionPayload) *VersionedSignedBlockRequest { - var signedBeaconBlock VersionedSignedBlockRequest - capellaBlindedBlock := signedBlindedBeaconBlock.Capella - if capellaBlindedBlock != nil { - signedBeaconBlock.Capella = &consensuscapella.SignedBeaconBlock{ - Signature: capellaBlindedBlock.Signature, - Message: &consensuscapella.BeaconBlock{ - Slot: capellaBlindedBlock.Message.Slot, - ProposerIndex: capellaBlindedBlock.Message.ProposerIndex, - ParentRoot: capellaBlindedBlock.Message.ParentRoot, - StateRoot: capellaBlindedBlock.Message.StateRoot, - Body: &consensuscapella.BeaconBlockBody{ - BLSToExecutionChanges: capellaBlindedBlock.Message.Body.BLSToExecutionChanges, - RANDAOReveal: capellaBlindedBlock.Message.Body.RANDAOReveal, - ETH1Data: capellaBlindedBlock.Message.Body.ETH1Data, - Graffiti: capellaBlindedBlock.Message.Body.Graffiti, - ProposerSlashings: capellaBlindedBlock.Message.Body.ProposerSlashings, - AttesterSlashings: capellaBlindedBlock.Message.Body.AttesterSlashings, - Attestations: capellaBlindedBlock.Message.Body.Attestations, - Deposits: capellaBlindedBlock.Message.Body.Deposits, - VoluntaryExits: capellaBlindedBlock.Message.Body.VoluntaryExits, - SyncAggregate: capellaBlindedBlock.Message.Body.SyncAggregate, - ExecutionPayload: executionPayload.Capella, +func DenebUnblindSignedBlock(blindedBlock *apiv1deneb.SignedBlindedBeaconBlock, blockPayload *deneb.ExecutionPayloadAndBlobsBundle, blockRoot phase0.Root) *apiv1deneb.SignedBlockContents { + denebBlobSidecars := make([]*consensusdeneb.BlobSidecar, len(blockPayload.BlobsBundle.Blobs)) + + for i := range denebBlobSidecars { + denebBlobSidecars[i] = &consensusdeneb.BlobSidecar{ + BlockRoot: blockRoot, + Index: consensusdeneb.BlobIndex(i), + Slot: blindedBlock.Message.Slot, + BlockParentRoot: blindedBlock.Message.ParentRoot, + ProposerIndex: blindedBlock.Message.ProposerIndex, + Blob: blockPayload.BlobsBundle.Blobs[i], + KzgCommitment: blockPayload.BlobsBundle.Commitments[i], + KzgProof: blockPayload.BlobsBundle.Proofs[i], + } + } + return &apiv1deneb.SignedBlockContents{ + Message: &apiv1deneb.BlockContents{ + Block: &consensusdeneb.BeaconBlock{ + Slot: blindedBlock.Message.Slot, + ProposerIndex: blindedBlock.Message.ProposerIndex, + ParentRoot: blindedBlock.Message.ParentRoot, + StateRoot: blindedBlock.Message.StateRoot, + Body: &consensusdeneb.BeaconBlockBody{ + BLSToExecutionChanges: blindedBlock.Message.Body.BLSToExecutionChanges, + RANDAOReveal: blindedBlock.Message.Body.RANDAOReveal, + ETH1Data: blindedBlock.Message.Body.ETH1Data, + Graffiti: blindedBlock.Message.Body.Graffiti, + ProposerSlashings: blindedBlock.Message.Body.ProposerSlashings, + AttesterSlashings: blindedBlock.Message.Body.AttesterSlashings, + Attestations: blindedBlock.Message.Body.Attestations, + Deposits: blindedBlock.Message.Body.Deposits, + VoluntaryExits: blindedBlock.Message.Body.VoluntaryExits, + SyncAggregate: blindedBlock.Message.Body.SyncAggregate, + ExecutionPayload: blockPayload.ExecutionPayload, + BlobKzgCommitments: blockPayload.BlobsBundle.Commitments, }, }, - } + BlobSidecars: denebBlobSidecars, + }, + Signature: blindedBlock.Signature, } - return &signedBeaconBlock } type BuilderBlockValidationRequest struct { diff --git a/common/utils.go b/common/utils.go index 11a946c3..f4aa0553 100644 --- a/common/utils.go +++ b/common/utils.go @@ -17,6 +17,7 @@ import ( "github.com/attestantio/go-builder-client/api" "github.com/attestantio/go-builder-client/api/capella" + "github.com/attestantio/go-builder-client/api/deneb" apiv1 "github.com/attestantio/go-builder-client/api/v1" "github.com/attestantio/go-builder-client/spec" consensusspec "github.com/attestantio/go-eth2-client/spec" @@ -185,7 +186,7 @@ type CreateTestBlockSubmissionOpts struct { ProposerPubkey string } -func CreateTestBlockSubmission(t *testing.T, builderPubkey string, value *uint256.Int, opts *CreateTestBlockSubmissionOpts) (payload *VersionedSubmitBlockRequest, getPayloadResponse *api.VersionedExecutionPayload, getHeaderResponse *spec.VersionedSignedBuilderBid) { +func CreateTestBlockSubmission(t *testing.T, builderPubkey string, value *uint256.Int, opts *CreateTestBlockSubmissionOpts) (payload *VersionedSubmitBlockRequest, getPayloadResponse *api.VersionedSubmitBlindedBlockResponse, getHeaderResponse *spec.VersionedSignedBuilderBid) { t.Helper() var err error @@ -349,12 +350,23 @@ func GetBlockSubmissionInfo(submission *VersionedSubmitBlockRequest) (*BlockSubm }, nil } -func GetBlockSubmissionExecutionPayload(submission *VersionedSubmitBlockRequest) (*api.VersionedExecutionPayload, error) { - if submission.Capella != nil { - return &api.VersionedExecutionPayload{ +func GetBlockSubmissionExecutionPayload(submission *VersionedSubmitBlockRequest) (*api.VersionedSubmitBlindedBlockResponse, error) { + switch submission.Version { + case consensusspec.DataVersionCapella: + return &api.VersionedSubmitBlindedBlockResponse{ Version: consensusspec.DataVersionCapella, Capella: submission.Capella.ExecutionPayload, }, nil + case consensusspec.DataVersionDeneb: + return &api.VersionedSubmitBlindedBlockResponse{ + Version: consensusspec.DataVersionDeneb, + Deneb: &deneb.ExecutionPayloadAndBlobsBundle{ + ExecutionPayload: submission.Deneb.ExecutionPayload, + BlobsBundle: submission.Deneb.BlobsBundle, + }, + }, nil + case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix: + return nil, ErrInvalidForkVersion } return nil, ErrEmptyPayload } diff --git a/common/utils_test.go b/common/utils_test.go index 80f88504..003b362a 100644 --- a/common/utils_test.go +++ b/common/utils_test.go @@ -7,6 +7,13 @@ import ( "os" "testing" + "github.com/attestantio/go-builder-client/api/bellatrix" + "github.com/attestantio/go-builder-client/api/capella" + apiv1 "github.com/attestantio/go-builder-client/api/v1" + "github.com/attestantio/go-builder-client/spec" + consensusspec "github.com/attestantio/go-eth2-client/spec" + consensusbellatrix "github.com/attestantio/go-eth2-client/spec/bellatrix" + consensuscapella "github.com/attestantio/go-eth2-client/spec/capella" "github.com/ethereum/go-ethereum/common" boostTypes "github.com/flashbots/go-boost-utils/types" "github.com/stretchr/testify/require" @@ -98,3 +105,90 @@ func TestGetEnvStrSlice(t *testing.T) { require.Equal(t, "str2", r[1]) os.Unsetenv(testEnvVar) } + +func TestGetBlockSubmissionInfo(t *testing.T) { + cases := []struct { + name string + payload *VersionedSubmitBlockRequest + expected *BlockSubmissionInfo + err string + }{ + { + name: "valid capella", + payload: &VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ + Version: consensusspec.DataVersionCapella, + Capella: &capella.SubmitBlockRequest{ + Message: &apiv1.BidTrace{}, + ExecutionPayload: &consensuscapella.ExecutionPayload{}, + }, + }, + }, + expected: &BlockSubmissionInfo{ + BidTrace: &apiv1.BidTrace{}, + }, + }, + { + name: "unsupported version", + payload: &VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ + Version: consensusspec.DataVersionBellatrix, + Bellatrix: &bellatrix.SubmitBlockRequest{ + Message: &apiv1.BidTrace{}, + ExecutionPayload: &consensusbellatrix.ExecutionPayload{}, + }, + }, + }, + expected: nil, + err: "unsupported version", + }, + { + name: "missing data", + payload: &VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ + Version: consensusspec.DataVersionCapella, + }, + }, + expected: nil, + err: "no data", + }, + { + name: "missing message", + payload: &VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ + Version: consensusspec.DataVersionCapella, + Capella: &capella.SubmitBlockRequest{ + ExecutionPayload: &consensuscapella.ExecutionPayload{}, + }, + }, + }, + expected: nil, + err: "no data message", + }, + { + name: "missing execution payload", + payload: &VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ + Version: consensusspec.DataVersionCapella, + Capella: &capella.SubmitBlockRequest{ + Message: &apiv1.BidTrace{}, + }, + }, + }, + expected: nil, + err: "no data execution payload", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + submission, err := GetBlockSubmissionInfo(tc.payload) + require.Equal(t, tc.expected, submission) + if tc.err == "" { + require.Nil(t, err) + } else { + require.Equal(t, tc.err, err.Error()) + } + }) + } +} diff --git a/database/typesconv.go b/database/typesconv.go index 135d0ad8..216f7f8a 100644 --- a/database/typesconv.go +++ b/database/typesconv.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/attestantio/go-builder-client/api" + "github.com/attestantio/go-builder-client/api/deneb" consensusspec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/flashbots/mev-boost-relay/common" @@ -16,12 +17,25 @@ func PayloadToExecPayloadEntry(payload *common.VersionedSubmitBlockRequest) (*Ex var _payload []byte var version string var err error - if payload.Capella != nil { + + switch payload.Version { + case consensusspec.DataVersionCapella: _payload, err = json.Marshal(payload.Capella.ExecutionPayload) if err != nil { return nil, err } version = common.ForkVersionStringCapella + case consensusspec.DataVersionDeneb: + _payload, err = json.Marshal(deneb.ExecutionPayloadAndBlobsBundle{ + ExecutionPayload: payload.Deneb.ExecutionPayload, + BlobsBundle: payload.Deneb.BlobsBundle, + }) + if err != nil { + return nil, err + } + version = common.ForkVersionStringDeneb + case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix: + return nil, ErrUnsupportedExecutionPayload } submission, err := common.GetBlockSubmissionInfo(payload) @@ -81,17 +95,25 @@ func BuilderSubmissionEntryToBidTraceV2WithTimestampJSON(payload *BuilderBlockSu } } -func ExecutionPayloadEntryToExecutionPayload(executionPayloadEntry *ExecutionPayloadEntry) (payload *api.VersionedExecutionPayload, err error) { +func ExecutionPayloadEntryToExecutionPayload(executionPayloadEntry *ExecutionPayloadEntry) (payload *api.VersionedSubmitBlindedBlockResponse, err error) { payloadVersion := executionPayloadEntry.Version if payloadVersion == common.ForkVersionStringDeneb { - return nil, ErrUnsupportedExecutionPayload + executionPayload := new(deneb.ExecutionPayloadAndBlobsBundle) + err = json.Unmarshal([]byte(executionPayloadEntry.Payload), executionPayload) + if err != nil { + return nil, err + } + return &api.VersionedSubmitBlindedBlockResponse{ + Version: consensusspec.DataVersionDeneb, + Deneb: executionPayload, + }, nil } else if payloadVersion == common.ForkVersionStringCapella { executionPayload := new(capella.ExecutionPayload) err = json.Unmarshal([]byte(executionPayloadEntry.Payload), executionPayload) if err != nil { return nil, err } - return &api.VersionedExecutionPayload{ + return &api.VersionedSubmitBlindedBlockResponse{ Version: consensusspec.DataVersionCapella, Capella: executionPayload, }, nil diff --git a/datastore/datastore.go b/datastore/datastore.go index eccfecb9..c29b9cd0 100644 --- a/datastore/datastore.go +++ b/datastore/datastore.go @@ -191,7 +191,7 @@ func (ds *Datastore) SaveValidatorRegistration(entry apiv1.SignedValidatorRegist } // GetGetPayloadResponse returns the getPayload response from memory or Redis or Database -func (ds *Datastore) GetGetPayloadResponse(log *logrus.Entry, slot uint64, proposerPubkey, blockHash string) (*api.VersionedExecutionPayload, error) { +func (ds *Datastore) GetGetPayloadResponse(log *logrus.Entry, slot uint64, proposerPubkey, blockHash string) (*api.VersionedSubmitBlindedBlockResponse, error) { log = log.WithField("datastoreMethod", "GetGetPayloadResponse") _proposerPubkey := strings.ToLower(proposerPubkey) _blockHash := strings.ToLower(blockHash) diff --git a/datastore/execution_payload.go b/datastore/execution_payload.go index 0f49421a..21c72a0d 100644 --- a/datastore/execution_payload.go +++ b/datastore/execution_payload.go @@ -6,6 +6,6 @@ import ( // ExecutionPayloadRepository defines methods to fetch and store execution engine payloads type ExecutionPayloadRepository interface { - GetExecutionPayload(slot uint64, proposerPubKey, blockHash string) (*api.VersionedExecutionPayload, error) - SaveExecutionPayload(slot uint64, proposerPubKey, blockHash string, payload *api.VersionedExecutionPayload) error + GetExecutionPayload(slot uint64, proposerPubKey, blockHash string) (*api.VersionedSubmitBlindedBlockResponse, error) + SaveExecutionPayload(slot uint64, proposerPubKey, blockHash string, payload *api.VersionedSubmitBlindedBlockResponse) error } diff --git a/datastore/memcached.go b/datastore/memcached.go index cc71beac..e65bfecd 100644 --- a/datastore/memcached.go +++ b/datastore/memcached.go @@ -24,7 +24,7 @@ type Memcached struct { // SaveExecutionPayload attempts to insert execution engine payload to memcached using composite key of slot, // proposer public key, block hash, and cache prefix if specified. Note that writes to the same key value // (i.e. same slot, proposer public key, and block hash) will overwrite the existing entry. -func (m *Memcached) SaveExecutionPayload(slot uint64, proposerPubKey, blockHash string, payload *api.VersionedExecutionPayload) error { +func (m *Memcached) SaveExecutionPayload(slot uint64, proposerPubKey, blockHash string, payload *api.VersionedSubmitBlindedBlockResponse) error { // TODO: standardize key format with redis cache and re-use the same function(s) key := fmt.Sprintf("boost-relay/%s:cache-getpayload-response:%d_%s_%s", m.keyPrefix, slot, proposerPubKey, blockHash) @@ -39,7 +39,7 @@ func (m *Memcached) SaveExecutionPayload(slot uint64, proposerPubKey, blockHash // GetExecutionPayload attempts to fetch execution engine payload from memcached using composite key of slot, // proposer public key, block hash, and cache prefix if specified. -func (m *Memcached) GetExecutionPayload(slot uint64, proposerPubKey, blockHash string) (*api.VersionedExecutionPayload, error) { +func (m *Memcached) GetExecutionPayload(slot uint64, proposerPubKey, blockHash string) (*api.VersionedSubmitBlindedBlockResponse, error) { // TODO: standardize key format with redis cache and re-use the same function(s) key := fmt.Sprintf("boost-relay/%s:cache-getpayload-response:%d_%s_%s", m.keyPrefix, slot, proposerPubKey, blockHash) item, err := m.client.Get(key) @@ -47,7 +47,7 @@ func (m *Memcached) GetExecutionPayload(slot uint64, proposerPubKey, blockHash s return nil, err } - result := new(api.VersionedExecutionPayload) + result := new(api.VersionedSubmitBlindedBlockResponse) if err = result.UnmarshalJSON(item.Value); err != nil { return nil, err } diff --git a/datastore/memcached_test.go b/datastore/memcached_test.go index ad54809b..da9a178f 100644 --- a/datastore/memcached_test.go +++ b/datastore/memcached_test.go @@ -166,7 +166,7 @@ func TestMemcached(t *testing.T) { "expected no error when marshalling execution payload response but found [%v]", err, ) - out := new(api.VersionedExecutionPayload) + out := new(api.VersionedSubmitBlindedBlockResponse) err = out.UnmarshalJSON(inputBytes) require.NoError( t, diff --git a/datastore/redis.go b/datastore/redis.go index 9ecbf3be..021b0c1b 100644 --- a/datastore/redis.go +++ b/datastore/redis.go @@ -363,7 +363,7 @@ func (r *RedisCache) SaveExecutionPayloadCapella(ctx context.Context, tx redis.P return tx.Set(ctx, key, b, expiryBidCache).Err() } -func (r *RedisCache) GetExecutionPayloadCapella(slot uint64, proposerPubkey, blockHash string) (*api.VersionedExecutionPayload, error) { +func (r *RedisCache) GetExecutionPayloadCapella(slot uint64, proposerPubkey, blockHash string) (*api.VersionedSubmitBlindedBlockResponse, error) { capellaPayload := new(capella.ExecutionPayload) key := r.keyExecPayloadCapella(slot, proposerPubkey, blockHash) @@ -377,7 +377,7 @@ func (r *RedisCache) GetExecutionPayloadCapella(slot uint64, proposerPubkey, blo return nil, err } - return &api.VersionedExecutionPayload{ + return &api.VersionedSubmitBlindedBlockResponse{ Version: consensusspec.DataVersionCapella, Capella: capellaPayload, }, nil @@ -457,7 +457,7 @@ type SaveBidAndUpdateTopBidResponse struct { TimeUpdateFloor time.Duration } -func (r *RedisCache) SaveBidAndUpdateTopBid(ctx context.Context, tx redis.Pipeliner, trace *common.BidTraceV2, payload *common.VersionedSubmitBlockRequest, getPayloadResponse *api.VersionedExecutionPayload, getHeaderResponse *spec.VersionedSignedBuilderBid, reqReceivedAt time.Time, isCancellationEnabled bool, floorValue *big.Int) (state SaveBidAndUpdateTopBidResponse, err error) { +func (r *RedisCache) SaveBidAndUpdateTopBid(ctx context.Context, tx redis.Pipeliner, trace *common.BidTraceV2, payload *common.VersionedSubmitBlockRequest, getPayloadResponse *api.VersionedSubmitBlindedBlockResponse, getHeaderResponse *spec.VersionedSignedBuilderBid, reqReceivedAt time.Time, isCancellationEnabled bool, floorValue *big.Int) (state SaveBidAndUpdateTopBidResponse, err error) { var prevTime, nextTime time.Time prevTime = time.Now() diff --git a/go.mod b/go.mod index d4146009..1dc98084 100644 --- a/go.mod +++ b/go.mod @@ -115,6 +115,6 @@ retract ( v1.0.0-alpha1 ) -replace github.com/attestantio/go-builder-client => github.com/avalonche/go-builder-client v0.0.0-20230731215616-c49675dd2f87 +replace github.com/attestantio/go-builder-client => github.com/avalonche/go-builder-client v0.0.0-20230802181655-003921d7bf6f replace github.com/attestantio/go-eth2-client => github.com/avalonche/go-eth2-client v0.0.0-20230801235812-ab38c7f8cba1 diff --git a/go.sum b/go.sum index 8125d9d7..6631cc74 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/alicebob/miniredis/v2 v2.30.4/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6u github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/avalonche/go-builder-client v0.0.0-20230731215616-c49675dd2f87 h1:+GPbCP8XCYteTDwr+e2vWsilPj/7rfXnfAE4ZqjVJQk= -github.com/avalonche/go-builder-client v0.0.0-20230731215616-c49675dd2f87/go.mod h1:8NZx9L/rC7nLhSMbbVR6PXr37tC28wTE4u65n+Pa3BQ= +github.com/avalonche/go-builder-client v0.0.0-20230802181655-003921d7bf6f h1:gPR5+3MP+lwj2v2VTKlmHJloNwxQJ3QwUvYy+Hc2T04= +github.com/avalonche/go-builder-client v0.0.0-20230802181655-003921d7bf6f/go.mod h1:8NZx9L/rC7nLhSMbbVR6PXr37tC28wTE4u65n+Pa3BQ= github.com/avalonche/go-eth2-client v0.0.0-20230801235812-ab38c7f8cba1 h1:jLmNo92ji/g8pdnA+Nuc9b7rTC6BZCv0POhmiFqX9cY= github.com/avalonche/go-eth2-client v0.0.0-20230801235812-ab38c7f8cba1/go.mod h1:KSVlZSW1A3jUg5H8O89DLtqxgJprRfTtI7k89fLdhu0= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= diff --git a/services/api/service.go b/services/api/service.go index 2e746d14..8ad36720 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -1431,8 +1431,8 @@ func (api *RelayAPI) handleGetPayload(w http.ResponseWriter, req *http.Request) return } - // Check that ExecutionPayloadHeader fields (sent by the proposer) match our known ExecutionPayload - err = EqExecutionPayloadToHeader(payload, getPayloadResp) + // Check that BlindedBlockContent fields (sent by the proposer) match our known BlockContents + err = EqBlindedBlockContentsToBlockContents(payload, getPayloadResp) if err != nil { log.WithError(err).Warn("ExecutionPayloadHeader not matching known ExecutionPayload") api.RespondError(w, http.StatusBadRequest, "invalid execution payload header") @@ -1442,7 +1442,12 @@ func (api *RelayAPI) handleGetPayload(w http.ResponseWriter, req *http.Request) // Publish the signed beacon block via beacon-node timeBeforePublish := time.Now().UTC().UnixMilli() log = log.WithField("timestampBeforePublishing", timeBeforePublish) - signedBeaconBlock := common.SignedBlindedBeaconBlockToBeaconBlock(payload, getPayloadResp) + signedBeaconBlock, err := common.SignedBlindedBeaconBlockToBeaconBlock(payload, getPayloadResp) + if err != nil { + log.WithError(err).Error("failed to convert signed blinded beacon block to beacon block") + api.RespondError(w, http.StatusInternalServerError, "failed to convert signed blinded beacon block to beacon block") + return + } code, err := api.beaconClient.PublishBlock(signedBeaconBlock) // errors are logged inside if err != nil || code != http.StatusOK { log.WithError(err).WithField("code", code).Error("failed to publish block") @@ -1519,7 +1524,11 @@ func (api *RelayAPI) handleGetPayload(w http.ResponseWriter, req *http.Request) log.Warn("demotion found in getPayload, inserting refund justification") // Prepare refund data. - signedBeaconBlock := common.SignedBlindedBeaconBlockToBeaconBlock(payload, getPayloadResp) + signedBeaconBlock, err := common.SignedBlindedBeaconBlockToBeaconBlock(payload, getPayloadResp) + if err != nil { + log.WithError(err).Error("failed to convert signed blinded beacon block to beacon block") + return + } // Get registration entry from the DB. registrationEntry, err := api.db.GetValidatorRegistration(proposerPubkey.String()) diff --git a/services/api/service_test.go b/services/api/service_test.go index e61990c3..0279f78d 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -13,11 +13,13 @@ import ( "github.com/alicebob/miniredis/v2" builderCapella "github.com/attestantio/go-builder-client/api/capella" + builderDeneb "github.com/attestantio/go-builder-client/api/deneb" apiv1 "github.com/attestantio/go-builder-client/api/v1" "github.com/attestantio/go-builder-client/spec" consensusspec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/flashbots/go-boost-utils/bls" "github.com/flashbots/go-boost-utils/utils" @@ -701,6 +703,40 @@ func TestCheckSubmissionSlotDetails(t *testing.T) { }, expectCont: true, }, + { + description: "non_capella_slot", + payload: &common.VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ + Version: consensusspec.DataVersionCapella, + Capella: &builderCapella.SubmitBlockRequest{ + ExecutionPayload: &capella.ExecutionPayload{ + Timestamp: testSlot * common.SecondsPerSlot, + }, + Message: &apiv1.BidTrace{ + Slot: testSlot + 32, + }, + }, + }, + }, + expectCont: false, + }, + { + description: "non_deneb_slot", + payload: &common.VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ + Version: consensusspec.DataVersionDeneb, + Deneb: &builderDeneb.SubmitBlockRequest{ + ExecutionPayload: &deneb.ExecutionPayload{ + Timestamp: testSlot * common.SecondsPerSlot, + }, + Message: &apiv1.BidTrace{ + Slot: testSlot, + }, + }, + }, + }, + expectCont: false, + }, { description: "failure_past_slot", payload: &common.VersionedSubmitBlockRequest{ @@ -737,7 +773,8 @@ func TestCheckSubmissionSlotDetails(t *testing.T) { for _, tc := range cases { t.Run(tc.description, func(t *testing.T) { _, _, backend := startTestBackend(t) - + backend.relay.capellaEpoch = 1 + backend.relay.denebEpoch = 2 headSlot := testSlot - 1 w := httptest.NewRecorder() logger := logrus.New() diff --git a/services/api/types_test.go b/services/api/types_test.go index ffb36775..f6e5a1b8 100644 --- a/services/api/types_test.go +++ b/services/api/types_test.go @@ -4,9 +4,13 @@ import ( "testing" "github.com/attestantio/go-builder-client/api/capella" + "github.com/attestantio/go-builder-client/api/deneb" apiv1 "github.com/attestantio/go-builder-client/api/v1" + "github.com/attestantio/go-builder-client/spec" + consensusspec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" capellaspec "github.com/attestantio/go-eth2-client/spec/capella" + denebspec "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/flashbots/go-boost-utils/bls" "github.com/flashbots/go-boost-utils/ssz" @@ -17,56 +21,126 @@ import ( "github.com/stretchr/testify/require" ) -func TestCapellaBuilderBlockRequestToSignedBuilderBid(t *testing.T) { +func TestBuilderBlockRequestToSignedBuilderBid(t *testing.T) { builderPk, err := utils.HexToPubkey("0xf9716c94aab536227804e859d15207aa7eaaacd839f39dcbdb5adc942842a8d2fb730f9f49fc719fdb86f1873e0ed1c2") require.NoError(t, err) builderSk, err := utils.HexToSignature("0x8209b5391cd69f392b1f02dbc03bab61f574bb6bb54bf87b59e2a85bdc0756f7db6a71ce1b41b727a1f46ccc77b213bf0df1426177b5b29926b39956114421eaa36ec4602969f6f6370a44de44a6bce6dae2136e5fb594cce2a476354264d1ea") require.NoError(t, err) - reqPayload := capella.SubmitBlockRequest{ - ExecutionPayload: &capellaspec.ExecutionPayload{ - ParentHash: phase0.Hash32{0x01}, - FeeRecipient: bellatrix.ExecutionAddress{0x02}, - StateRoot: phase0.Root{0x03}, - ReceiptsRoot: phase0.Root{0x04}, - LogsBloom: [256]byte{0x05}, - PrevRandao: phase0.Hash32{0x06}, - BlockNumber: 5001, - GasLimit: 5002, - GasUsed: 5003, - Timestamp: 5004, - ExtraData: []byte{0x07}, - BaseFeePerGas: types.IntToU256(123), - BlockHash: phase0.Hash32{0x09}, - Transactions: []bellatrix.Transaction{}, + cases := []struct { + name string + reqPayload *common.VersionedSubmitBlockRequest + }{ + { + name: "Capella", + reqPayload: &common.VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ + Version: consensusspec.DataVersionCapella, + Capella: &capella.SubmitBlockRequest{ + ExecutionPayload: &capellaspec.ExecutionPayload{ + ParentHash: phase0.Hash32{0x01}, + FeeRecipient: bellatrix.ExecutionAddress{0x02}, + StateRoot: phase0.Root{0x03}, + ReceiptsRoot: phase0.Root{0x04}, + LogsBloom: [256]byte{0x05}, + PrevRandao: phase0.Hash32{0x06}, + BlockNumber: 5001, + GasLimit: 5002, + GasUsed: 5003, + Timestamp: 5004, + ExtraData: []byte{0x07}, + BaseFeePerGas: types.IntToU256(123), + BlockHash: phase0.Hash32{0x09}, + Transactions: []bellatrix.Transaction{}, + }, + Message: &apiv1.BidTrace{ + Slot: 1, + ParentHash: phase0.Hash32{0x01}, + BlockHash: phase0.Hash32{0x09}, + BuilderPubkey: builderPk, + ProposerPubkey: phase0.BLSPubKey{0x03}, + ProposerFeeRecipient: bellatrix.ExecutionAddress{0x04}, + Value: uint256.NewInt(123), + GasLimit: 5002, + GasUsed: 5003, + }, + Signature: builderSk, + }, + }, + }, }, - Message: &apiv1.BidTrace{ - Slot: 1, - ParentHash: phase0.Hash32{0x01}, - BlockHash: phase0.Hash32{0x09}, - BuilderPubkey: builderPk, - ProposerPubkey: phase0.BLSPubKey{0x03}, - ProposerFeeRecipient: bellatrix.ExecutionAddress{0x04}, - Value: uint256.NewInt(123), - GasLimit: 5002, - GasUsed: 5003, + { + name: "Deneb", + reqPayload: &common.VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ + Version: consensusspec.DataVersionDeneb, + Deneb: &deneb.SubmitBlockRequest{ + ExecutionPayload: &denebspec.ExecutionPayload{ + ParentHash: phase0.Hash32{0x01}, + FeeRecipient: bellatrix.ExecutionAddress{0x02}, + StateRoot: phase0.Root{0x03}, + ReceiptsRoot: phase0.Root{0x04}, + LogsBloom: [256]byte{0x05}, + PrevRandao: phase0.Hash32{0x06}, + BlockNumber: 5001, + GasLimit: 5002, + GasUsed: 5003, + Timestamp: 5004, + ExtraData: []byte{0x07}, + BaseFeePerGas: uint256.NewInt(123), + BlockHash: phase0.Hash32{0x09}, + Transactions: []bellatrix.Transaction{}, + }, + BlobsBundle: &deneb.BlobsBundle{ + Commitments: []denebspec.KzgCommitment{}, + Proofs: []denebspec.KzgProof{}, + Blobs: []denebspec.Blob{}, + }, + Message: &apiv1.BidTrace{ + Slot: 1, + ParentHash: phase0.Hash32{0x01}, + BlockHash: phase0.Hash32{0x09}, + BuilderPubkey: builderPk, + ProposerPubkey: phase0.BLSPubKey{0x03}, + ProposerFeeRecipient: bellatrix.ExecutionAddress{0x04}, + Value: uint256.NewInt(123), + GasLimit: 5002, + GasUsed: 5003, + }, + Signature: builderSk, + }, + }, + }, }, - Signature: builderSk, } - sk, _, err := bls.GenerateNewKeypair() - require.NoError(t, err) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + sk, _, err := bls.GenerateNewKeypair() + require.NoError(t, err) - pubkey, err := bls.PublicKeyFromSecretKey(sk) - require.NoError(t, err) + pubkey, err := bls.PublicKeyFromSecretKey(sk) + require.NoError(t, err) - publicKey, err := utils.BlsPublicKeyToPublicKey(pubkey) - require.NoError(t, err) + publicKey, err := utils.BlsPublicKeyToPublicKey(pubkey) + require.NoError(t, err) - signedBuilderBid, err := common.CapellaBuilderSubmitBlockRequestToSignedBuilderBid(&reqPayload, sk, &publicKey, ssz.DomainBuilder) - require.NoError(t, err) + signedBuilderBid, err := common.BuildGetHeaderResponse(tc.reqPayload, sk, &publicKey, ssz.DomainBuilder) + require.NoError(t, err) - require.Equal(t, 0, signedBuilderBid.Message.Value.Cmp(reqPayload.Message.Value)) - require.Equal(t, reqPayload.Message.BlockHash, signedBuilderBid.Message.Header.BlockHash) + bidValue, err := signedBuilderBid.Value() + require.NoError(t, err) + respValue, err := tc.reqPayload.Value() + require.NoError(t, err) + + bidHash, err := signedBuilderBid.BlockHash() + require.NoError(t, err) + respHash, err := tc.reqPayload.BlockHash() + require.NoError(t, err) + + require.Equal(t, 0, bidValue.Cmp(respValue)) + require.Equal(t, respHash, bidHash) + }) + } } diff --git a/services/api/utils.go b/services/api/utils.go index 5de0f096..7b0d8c89 100644 --- a/services/api/utils.go +++ b/services/api/utils.go @@ -1,26 +1,29 @@ package api import ( - "errors" + "fmt" "github.com/attestantio/go-builder-client/api" + "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/phase0" utilcapella "github.com/attestantio/go-eth2-client/util/capella" + utildeneb "github.com/attestantio/go-eth2-client/util/deneb" "github.com/flashbots/go-boost-utils/bls" "github.com/flashbots/go-boost-utils/utils" "github.com/flashbots/mev-boost-relay/common" + "github.com/pkg/errors" ) var ( ErrBlockHashMismatch = errors.New("blockHash mismatch") ErrParentHashMismatch = errors.New("parentHash mismatch") - ErrNoPayloads = errors.New("no payloads") - ErrNoWithdrawals = errors.New("no withdrawals") - ErrPayloadMismatchBellatrix = errors.New("bellatrix beacon-block but no bellatrix payload") - ErrPayloadMismatchCapella = errors.New("capella beacon-block but no capella payload") - ErrHeaderHTRMismatch = errors.New("beacon-block and payload header mismatch") + ErrUnsupportedPayload = errors.New("unsupported payload version") + ErrNoWithdrawals = errors.New("no withdrawals") + ErrPayloadMismatch = errors.New("beacon-block and payload version mismatch") + ErrHeaderHTRMismatch = errors.New("beacon-block and payload header mismatch") + ErrBlobMismatch = errors.New("beacon-block and payload blob contents mismatch") ) func SanityCheckBuilderBlockSubmission(payload *common.VersionedSubmitBlockRequest) error { @@ -47,22 +50,28 @@ func ComputeWithdrawalsRoot(w []*capella.Withdrawal) (phase0.Root, error) { return withdrawals.HashTreeRoot() } -func EqExecutionPayloadToHeader(bb *common.VersionedSignedBlindedBlockRequest, payload *api.VersionedExecutionPayload) error { - if bb.Capella != nil { // process Capella beacon block - if payload.Capella == nil { - return ErrPayloadMismatchCapella - } +func EqBlindedBlockContentsToBlockContents(bb *common.VersionedSignedBlindedBlockRequest, payload *api.VersionedSubmitBlindedBlockResponse) error { + if bb.Version != payload.Version { + return errors.Wrap(ErrPayloadMismatch, fmt.Sprintf("beacon block version %d does not match payload version %d", bb.Version, payload.Version)) + } + versionedPayload := &api.VersionedExecutionPayload{ //nolint:exhaustivestruct + Version: payload.Version, + } + switch bb.Version { + case spec.DataVersionCapella: bbHeaderHtr, err := bb.Capella.Message.Body.ExecutionPayloadHeader.HashTreeRoot() if err != nil { return err } - payloadHeader, err := common.CapellaPayloadToPayloadHeader(payload.Capella) + versionedPayload.Capella = payload.Capella + payloadHeader, err := utils.PayloadToPayloadHeader(versionedPayload) if err != nil { return err } - payloadHeaderHtr, err := payloadHeader.HashTreeRoot() + + payloadHeaderHtr, err := payloadHeader.Capella.HashTreeRoot() if err != nil { return err } @@ -70,12 +79,61 @@ func EqExecutionPayloadToHeader(bb *common.VersionedSignedBlindedBlockRequest, p if bbHeaderHtr != payloadHeaderHtr { return ErrHeaderHTRMismatch } + case spec.DataVersionDeneb: + block := bb.Deneb.SignedBlindedBlock.Message + bbHeaderHtr, err := block.Body.ExecutionPayloadHeader.HashTreeRoot() + if err != nil { + return err + } - // capella block and payload are equal - return nil - } + versionedPayload.Deneb = payload.Deneb.ExecutionPayload + payloadHeader, err := utils.PayloadToPayloadHeader(versionedPayload) + if err != nil { + return err + } + + payloadHeaderHtr, err := payloadHeader.Deneb.HashTreeRoot() + if err != nil { + return err + } - return ErrNoPayloads + if bbHeaderHtr != payloadHeaderHtr { + return ErrHeaderHTRMismatch + } + + if len(bb.Deneb.SignedBlindedBlobSidecars) != len(payload.Deneb.BlobsBundle.Commitments) { + return errors.Wrap(ErrBlobMismatch, "mismatched number of KZG commitments") + } + if len(bb.Deneb.SignedBlindedBlobSidecars) != len(payload.Deneb.BlobsBundle.Proofs) { + return errors.Wrap(ErrBlobMismatch, "mismatched number of KZG proofs length") + } + if len(bb.Deneb.SignedBlindedBlobSidecars) != len(payload.Deneb.BlobsBundle.Blobs) { + return errors.Wrap(ErrBlobMismatch, "mismatched number of blobs") + } + + for i, blindedSidecar := range bb.Deneb.SignedBlindedBlobSidecars { + if blindedSidecar.Message.KzgCommitment != payload.Deneb.BlobsBundle.Commitments[i] { + return errors.Wrap(ErrBlobMismatch, fmt.Sprintf("mismatched KZG commitment at index %d", i)) + } + if blindedSidecar.Message.KzgProof != payload.Deneb.BlobsBundle.Proofs[i] { + return errors.Wrap(ErrBlobMismatch, fmt.Sprintf("mismatched KZG proof at index %d", i)) + } + blobRootHelper := utildeneb.BeaconBlockBlob{Blob: payload.Deneb.BlobsBundle.Blobs[i]} + blobRoot, err := blobRootHelper.HashTreeRoot() + if err != nil { + return errors.New(fmt.Sprintf("failed to compute blob root at index %d", i)) + } + if blindedSidecar.Message.BlobRoot != blobRoot { + return errors.Wrap(ErrBlobMismatch, fmt.Sprintf("mismatched blob root at index %d", i)) + } + } + case spec.DataVersionUnknown, spec.DataVersionPhase0, spec.DataVersionAltair, spec.DataVersionBellatrix: + fallthrough + default: + return ErrUnsupportedPayload + } + // block and payload are equal + return nil } func checkBLSPublicKeyHex(pkHex string) error {