From f9236513d845c2c6ad5ecae2dcef7bcd0358f5e4 Mon Sep 17 00:00:00 2001 From: shana Date: Wed, 26 Jul 2023 00:45:28 -0700 Subject: [PATCH] Remove submit block request wrapper (#485) * remove submit block request wrapper types * fix tests * fix lint --- .golangci.yaml | 1 + common/test_utils.go | 7 +- common/types.go | 188 +++------------------------ common/types_spec.go | 20 ++- common/utils.go | 111 +++++++++++++++- database/database.go | 68 +++++----- database/database_test.go | 4 +- database/mockdb.go | 16 ++- database/typesconv.go | 15 ++- datastore/memcached_test.go | 55 +++++--- datastore/redis.go | 31 +++-- go.mod | 2 + go.sum | 4 +- services/api/blocksim_ratelimiter.go | 6 +- services/api/optimistic_test.go | 6 +- services/api/service.go | 125 ++++++++++-------- services/api/service_test.go | 10 +- services/api/utils.go | 11 +- 18 files changed, 362 insertions(+), 318 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 7ef8310d..baa7e72a 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -71,6 +71,7 @@ linters-settings: gomoddirectives: replace-allow-list: - github.com/attestantio/go-eth2-client + - github.com/attestantio/go-builder-client maintidx: under: 5 diff --git a/common/test_utils.go b/common/test_utils.go index 26f991f1..29aae959 100644 --- a/common/test_utils.go +++ b/common/test_utils.go @@ -10,6 +10,8 @@ import ( "testing" "github.com/attestantio/go-builder-client/api/capella" + "github.com/attestantio/go-builder-client/spec" + consensusspec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" consensuscapella "github.com/attestantio/go-eth2-client/spec/capella" "github.com/flashbots/go-boost-utils/bls" @@ -64,10 +66,11 @@ var ValidPayloadRegisterValidator = boostTypes.SignedValidatorRegistration{ "0xaf12df007a0c78abb5575067e5f8b089cfcc6227e4a91db7dd8cf517fe86fb944ead859f0781277d9b78c672e4a18c5d06368b603374673cf2007966cece9540f3a1b3f6f9e1bf421d779c4e8010368e6aac134649c7a009210780d401a778a5"), } -func TestBuilderSubmitBlockRequest(sk *bls.SecretKey, bid *BidTraceV2) BuilderSubmitBlockRequest { +func TestBuilderSubmitBlockRequest(sk *bls.SecretKey, bid *BidTraceV2) spec.VersionedSubmitBlockRequest { signature, err := boostTypes.SignMessage(bid, boostTypes.DomainBuilder, sk) check(err, " SignMessage: ", bid, sk) - return BuilderSubmitBlockRequest{ + return spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct + Version: consensusspec.DataVersionCapella, Capella: &capella.SubmitBlockRequest{ Message: &bid.BidTrace, Signature: [96]byte(signature), diff --git a/common/types.go b/common/types.go index 344f901b..544203eb 100644 --- a/common/types.go +++ b/common/types.go @@ -4,16 +4,14 @@ import ( "encoding/json" "errors" "fmt" - "math/big" "os" - "github.com/attestantio/go-builder-client/api" - "github.com/attestantio/go-builder-client/api/capella" apiv1 "github.com/attestantio/go-builder-client/api/v1" - 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/attestantio/go-eth2-client/spec/phase0" boostTypes "github.com/flashbots/go-boost-utils/types" + "github.com/holiman/uint256" ) var ( @@ -271,167 +269,23 @@ func (b *BidTraceV2WithTimestampJSON) ToCSVRecord() []string { } } -type BuilderSubmitBlockRequest struct { - Capella *capella.SubmitBlockRequest -} - -func (b *BuilderSubmitBlockRequest) MarshalJSON() ([]byte, error) { - if b.Capella != nil { - return json.Marshal(b.Capella) - } - return nil, ErrEmptyPayload -} - -func (b *BuilderSubmitBlockRequest) UnmarshalJSON(data []byte) error { - capella := new(capella.SubmitBlockRequest) - err := json.Unmarshal(data, capella) - if err != nil { - return err - } - b.Capella = capella - return nil -} - -func (b *BuilderSubmitBlockRequest) HasExecutionPayload() bool { - if b.Capella != nil { - return b.Capella.ExecutionPayload != nil - } - return false -} - -func (b *BuilderSubmitBlockRequest) ExecutionPayloadResponse() (*api.VersionedExecutionPayload, error) { - if b.Capella != nil { - return &api.VersionedExecutionPayload{ - Version: consensusspec.DataVersionCapella, - Capella: b.Capella.ExecutionPayload, - }, nil - } - - return nil, ErrEmptyPayload -} - -func (b *BuilderSubmitBlockRequest) Slot() uint64 { - if b.Capella != nil { - return b.Capella.Message.Slot - } - return 0 -} - -func (b *BuilderSubmitBlockRequest) BlockHash() string { - if b.Capella != nil { - return b.Capella.Message.BlockHash.String() - } - return "" -} - -func (b *BuilderSubmitBlockRequest) ExecutionPayloadBlockHash() string { - if b.Capella != nil { - return b.Capella.ExecutionPayload.BlockHash.String() - } - return "" -} - -func (b *BuilderSubmitBlockRequest) BuilderPubkey() phase0.BLSPubKey { - if b.Capella != nil { - return b.Capella.Message.BuilderPubkey - } - return phase0.BLSPubKey{} -} - -func (b *BuilderSubmitBlockRequest) ProposerFeeRecipient() string { - if b.Capella != nil { - return b.Capella.Message.ProposerFeeRecipient.String() - } - return "" -} - -func (b *BuilderSubmitBlockRequest) Timestamp() uint64 { - if b.Capella != nil { - return b.Capella.ExecutionPayload.Timestamp - } - return 0 -} - -func (b *BuilderSubmitBlockRequest) ProposerPubkey() string { - if b.Capella != nil { - return b.Capella.Message.ProposerPubkey.String() - } - return "" -} - -func (b *BuilderSubmitBlockRequest) ParentHash() string { - if b.Capella != nil { - return b.Capella.Message.ParentHash.String() - } - return "" -} - -func (b *BuilderSubmitBlockRequest) ExecutionPayloadParentHash() string { - if b.Capella != nil { - return b.Capella.ExecutionPayload.ParentHash.String() - } - return "" -} - -func (b *BuilderSubmitBlockRequest) Value() *big.Int { - if b.Capella != nil { - return b.Capella.Message.Value.ToBig() - } - return nil -} - -func (b *BuilderSubmitBlockRequest) NumTx() int { - if b.Capella != nil { - return len(b.Capella.ExecutionPayload.Transactions) - } - return 0 -} - -func (b *BuilderSubmitBlockRequest) BlockNumber() uint64 { - if b.Capella != nil { - return b.Capella.ExecutionPayload.BlockNumber - } - return 0 -} - -func (b *BuilderSubmitBlockRequest) GasUsed() uint64 { - if b.Capella != nil { - return b.Capella.ExecutionPayload.GasUsed - } - return 0 -} - -func (b *BuilderSubmitBlockRequest) GasLimit() uint64 { - if b.Capella != nil { - return b.Capella.ExecutionPayload.GasLimit - } - return 0 -} - -func (b *BuilderSubmitBlockRequest) Signature() phase0.BLSSignature { - if b.Capella != nil { - return b.Capella.Signature - } - return phase0.BLSSignature{} -} - -func (b *BuilderSubmitBlockRequest) Random() string { - if b.Capella != nil { - return fmt.Sprintf("%#x", b.Capella.ExecutionPayload.PrevRandao) - } - return "" -} - -func (b *BuilderSubmitBlockRequest) Message() *apiv1.BidTrace { - if b.Capella != nil { - return b.Capella.Message - } - return nil -} - -func (b *BuilderSubmitBlockRequest) Withdrawals() []*consensuscapella.Withdrawal { - if b.Capella != nil { - return b.Capella.ExecutionPayload.Withdrawals - } - return nil +type BlockSubmissionInfo struct { + BidTrace *apiv1.BidTrace + Slot uint64 + BlockHash phase0.Hash32 + ParentHash phase0.Hash32 + ExecutionPayloadBlockHash phase0.Hash32 + ExecutionPayloadParentHash phase0.Hash32 + Builder phase0.BLSPubKey + Proposer phase0.BLSPubKey + ProposerFeeRecipient consensusbellatrix.ExecutionAddress + GasUsed uint64 + GasLimit uint64 + Timestamp uint64 + BlockNumber uint64 + Value *uint256.Int + PrevRandao phase0.Hash32 + Signature phase0.BLSSignature + Transactions []consensusbellatrix.Transaction + Withdrawals []*consensuscapella.Withdrawal } diff --git a/common/types_spec.go b/common/types_spec.go index e37cb955..26f02d1e 100644 --- a/common/types_spec.go +++ b/common/types_spec.go @@ -2,7 +2,7 @@ package common import ( "encoding/json" - "errors" + "fmt" "github.com/attestantio/go-builder-client/api" "github.com/attestantio/go-builder-client/api/capella" @@ -15,12 +15,14 @@ import ( utilcapella "github.com/attestantio/go-eth2-client/util/capella" "github.com/flashbots/go-boost-utils/bls" boostTypes "github.com/flashbots/go-boost-utils/types" + "github.com/pkg/errors" ) var ( ErrMissingRequest = errors.New("req is nil") ErrMissingSecretKey = errors.New("secret key is nil") ErrInvalidTransaction = errors.New("invalid transaction") + ErrInvalidVersion = errors.New("invalid version") ) type HTTPErrorResp struct { @@ -32,7 +34,7 @@ var NilResponse = struct{}{} var ZeroU256 = boostTypes.IntToU256(0) -func BuildGetHeaderResponse(payload *BuilderSubmitBlockRequest, sk *bls.SecretKey, pubkey *boostTypes.PublicKey, domain boostTypes.Domain) (*spec.VersionedSignedBuilderBid, error) { +func BuildGetHeaderResponse(payload *spec.VersionedSubmitBlockRequest, sk *bls.SecretKey, pubkey *boostTypes.PublicKey, domain boostTypes.Domain) (*spec.VersionedSignedBuilderBid, error) { if payload == nil { return nil, ErrMissingRequest } @@ -55,7 +57,7 @@ func BuildGetHeaderResponse(payload *BuilderSubmitBlockRequest, sk *bls.SecretKe return nil, ErrEmptyPayload } -func BuildGetPayloadResponse(payload *BuilderSubmitBlockRequest) (*api.VersionedExecutionPayload, error) { +func BuildGetPayloadResponse(payload *spec.VersionedSubmitBlockRequest) (*api.VersionedExecutionPayload, error) { if payload.Capella != nil { return &api.VersionedExecutionPayload{ Version: consensusspec.DataVersionCapella, @@ -179,12 +181,20 @@ func SignedBlindedBeaconBlockToBeaconBlock(signedBlindedBeaconBlock *consensusap } type BuilderBlockValidationRequest struct { - BuilderSubmitBlockRequest + spec.VersionedSubmitBlockRequest RegisteredGasLimit uint64 `json:"registered_gas_limit,string"` } func (r *BuilderBlockValidationRequest) MarshalJSON() ([]byte, error) { - blockRequest, err := r.BuilderSubmitBlockRequest.MarshalJSON() + var blockRequest []byte + var err error + + switch r.VersionedSubmitBlockRequest.Version { //nolint:exhaustive + case consensusspec.DataVersionCapella: + blockRequest, err = r.VersionedSubmitBlockRequest.Capella.MarshalJSON() + default: + return nil, errors.Wrap(ErrInvalidVersion, fmt.Sprintf("%d is not supported", r.VersionedSubmitBlockRequest.Version)) + } if err != nil { return nil, err } diff --git a/common/utils.go b/common/utils.go index 77ad2ecb..76ee403e 100644 --- a/common/utils.go +++ b/common/utils.go @@ -19,6 +19,7 @@ import ( "github.com/attestantio/go-builder-client/api/capella" v1 "github.com/attestantio/go-builder-client/api/v1" "github.com/attestantio/go-builder-client/spec" + consensusspec "github.com/attestantio/go-eth2-client/spec" capellaspec "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/phase0" ethcommon "github.com/ethereum/go-ethereum/common" @@ -183,7 +184,7 @@ type CreateTestBlockSubmissionOpts struct { ProposerPubkey string } -func CreateTestBlockSubmission(t *testing.T, builderPubkey string, value *uint256.Int, opts *CreateTestBlockSubmissionOpts) (payload *BuilderSubmitBlockRequest, getPayloadResponse *api.VersionedExecutionPayload, getHeaderResponse *spec.VersionedSignedBuilderBid) { +func CreateTestBlockSubmission(t *testing.T, builderPubkey string, value *uint256.Int, opts *CreateTestBlockSubmissionOpts) (payload *spec.VersionedSubmitBlockRequest, getPayloadResponse *api.VersionedExecutionPayload, getHeaderResponse *spec.VersionedSignedBuilderBid) { t.Helper() var err error @@ -214,7 +215,8 @@ func CreateTestBlockSubmission(t *testing.T, builderPubkey string, value *uint25 builderPk, err := StrToPhase0Pubkey(builderPubkey) require.NoError(t, err) - payload = &BuilderSubmitBlockRequest{ + payload = &spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct + Version: consensusspec.DataVersionCapella, Capella: &capella.SubmitBlockRequest{ Message: &v1.BidTrace{ //nolint:exhaustruct BuilderPubkey: builderPk, @@ -248,3 +250,108 @@ func GetEnvDurationSec(key string, defaultValueSec int) time.Duration { } return time.Duration(defaultValueSec) * time.Second } + +func GetBlockSubmissionInfo(submission *spec.VersionedSubmitBlockRequest) (*BlockSubmissionInfo, error) { + bidTrace, err := submission.BidTrace() + if err != nil { + return nil, err + } + signature, err := submission.Signature() + if err != nil { + return nil, err + } + slot, err := submission.Slot() + if err != nil { + return nil, err + } + blockHash, err := submission.BlockHash() + if err != nil { + return nil, err + } + parentHash, err := submission.ParentHash() + if err != nil { + return nil, err + } + executionPayloadBlockHash, err := submission.ExecutionPayloadBlockHash() + if err != nil { + return nil, err + } + executionPayloadParentHash, err := submission.ExecutionPayloadParentHash() + if err != nil { + return nil, err + } + builder, err := submission.Builder() + if err != nil { + return nil, err + } + proposerPubkey, err := submission.ProposerPubKey() + if err != nil { + return nil, err + } + proposerFeeRecipient, err := submission.ProposerFeeRecipient() + if err != nil { + return nil, err + } + gasUsed, err := submission.GasUsed() + if err != nil { + return nil, err + } + gasLimit, err := submission.GasLimit() + if err != nil { + return nil, err + } + timestamp, err := submission.Timestamp() + if err != nil { + return nil, err + } + txs, err := submission.Transactions() + if err != nil { + return nil, err + } + value, err := submission.Value() + if err != nil { + return nil, err + } + blockNumber, err := submission.BlockNumber() + if err != nil { + return nil, err + } + prevRandao, err := submission.PrevRandao() + if err != nil { + return nil, err + } + withdrawals, err := submission.Withdrawals() + if err != nil { + return nil, err + } + return &BlockSubmissionInfo{ + BidTrace: bidTrace, + Signature: signature, + Slot: slot, + BlockHash: blockHash, + ParentHash: parentHash, + ExecutionPayloadBlockHash: executionPayloadBlockHash, + ExecutionPayloadParentHash: executionPayloadParentHash, + Builder: builder, + Proposer: proposerPubkey, + ProposerFeeRecipient: proposerFeeRecipient, + GasUsed: gasUsed, + GasLimit: gasLimit, + Timestamp: timestamp, + Transactions: txs, + Value: value, + PrevRandao: prevRandao, + BlockNumber: blockNumber, + Withdrawals: withdrawals, + }, nil +} + +func GetBlockSubmissionExecutionPayload(submission *spec.VersionedSubmitBlockRequest) (*api.VersionedExecutionPayload, error) { + if submission.Capella != nil { + return &api.VersionedExecutionPayload{ + Version: consensusspec.DataVersionCapella, + Capella: submission.Capella.ExecutionPayload, + }, nil + } + return nil, ErrEmptyPayload +} diff --git a/database/database.go b/database/database.go index afdac7b6..555da5f7 100644 --- a/database/database.go +++ b/database/database.go @@ -9,8 +9,9 @@ import ( "strings" "time" + "github.com/attestantio/go-builder-client/spec" consensusapi "github.com/attestantio/go-eth2-client/api" - "github.com/attestantio/go-eth2-client/spec" + consensusspec "github.com/attestantio/go-eth2-client/spec" "github.com/flashbots/go-boost-utils/types" "github.com/flashbots/mev-boost-relay/common" "github.com/flashbots/mev-boost-relay/database/migrations" @@ -27,7 +28,7 @@ type IDatabaseService interface { GetValidatorRegistration(pubkey string) (*ValidatorRegistrationEntry, error) GetValidatorRegistrationsForPubkeys(pubkeys []string) ([]*ValidatorRegistrationEntry, error) - SaveBuilderBlockSubmission(payload *common.BuilderSubmitBlockRequest, requestError, validationError error, receivedAt, eligibleAt time.Time, wasSimulated, saveExecPayload bool, profile common.Profile, optimisticSubmission bool) (entry *BuilderBlockSubmissionEntry, err error) + SaveBuilderBlockSubmission(payload *spec.VersionedSubmitBlockRequest, requestError, validationError error, receivedAt, eligibleAt time.Time, wasSimulated, saveExecPayload bool, profile common.Profile, optimisticSubmission bool) (entry *BuilderBlockSubmissionEntry, err error) GetBlockSubmissionEntry(slot uint64, proposerPubkey, blockHash string) (entry *BuilderBlockSubmissionEntry, err error) GetBuilderSubmissions(filters GetBuilderSubmissionsFilters) ([]*BuilderBlockSubmissionEntry, error) GetBuilderSubmissionsBySlots(slotFrom, slotTo uint64) (entries []*BuilderBlockSubmissionEntry, err error) @@ -49,8 +50,8 @@ type IDatabaseService interface { UpsertBlockBuilderEntryAfterSubmission(lastSubmission *BuilderBlockSubmissionEntry, isError bool) error IncBlockBuilderStatsAfterGetPayload(builderPubkey string) error - InsertBuilderDemotion(submitBlockRequest *common.BuilderSubmitBlockRequest, simError error) error - UpdateBuilderDemotion(trace *common.BidTraceV2, signedBlock *spec.VersionedSignedBeaconBlock, signedRegistration *types.SignedValidatorRegistration) error + InsertBuilderDemotion(submitBlockRequest *spec.VersionedSubmitBlockRequest, simError error) error + UpdateBuilderDemotion(trace *common.BidTraceV2, signedBlock *consensusspec.VersionedSignedBeaconBlock, signedRegistration *types.SignedValidatorRegistration) error GetBuilderDemotion(trace *common.BidTraceV2) (*BuilderDemotionEntry, error) GetTooLateGetPayload(slot uint64) (entries []*TooLateGetPayloadEntry, err error) @@ -177,7 +178,7 @@ func (s *DatabaseService) GetLatestValidatorRegistrations(timestampOnly bool) ([ return registrations, err } -func (s *DatabaseService) SaveBuilderBlockSubmission(payload *common.BuilderSubmitBlockRequest, requestError, validationError error, receivedAt, eligibleAt time.Time, wasSimulated, saveExecPayload bool, profile common.Profile, optimisticSubmission bool) (entry *BuilderBlockSubmissionEntry, err error) { +func (s *DatabaseService) SaveBuilderBlockSubmission(payload *spec.VersionedSubmitBlockRequest, requestError, validationError error, receivedAt, eligibleAt time.Time, wasSimulated, saveExecPayload bool, profile common.Profile, optimisticSubmission bool) (entry *BuilderBlockSubmissionEntry, err error) { // Save execution_payload: insert, or if already exists update to be able to return the id ('on conflict do nothing' doesn't return an id) execPayloadEntry, err := PayloadToExecPayloadEntry(payload) if err != nil { @@ -202,6 +203,11 @@ func (s *DatabaseService) SaveBuilderBlockSubmission(payload *common.BuilderSubm requestErrStr = requestError.Error() } + submission, err := common.GetBlockSubmissionInfo(payload) + if err != nil { + return nil, err + } + blockSubmissionEntry := &BuilderBlockSubmissionEntry{ ReceivedAt: NewNullTime(receivedAt), EligibleAt: NewNullTime(eligibleAt), @@ -212,24 +218,24 @@ func (s *DatabaseService) SaveBuilderBlockSubmission(payload *common.BuilderSubm SimError: simErrStr, SimReqError: requestErrStr, - Signature: payload.Signature().String(), + Signature: submission.Signature.String(), - Slot: payload.Slot(), - BlockHash: payload.BlockHash(), - ParentHash: payload.ParentHash(), + Slot: submission.Slot, + BlockHash: submission.BlockHash.String(), + ParentHash: submission.ParentHash.String(), - BuilderPubkey: payload.BuilderPubkey().String(), - ProposerPubkey: payload.ProposerPubkey(), - ProposerFeeRecipient: payload.ProposerFeeRecipient(), + BuilderPubkey: submission.Builder.String(), + ProposerPubkey: submission.Proposer.String(), + ProposerFeeRecipient: submission.ProposerFeeRecipient.String(), - GasUsed: payload.GasUsed(), - GasLimit: payload.GasLimit(), + GasUsed: submission.GasUsed, + GasLimit: submission.GasLimit, - NumTx: uint64(payload.NumTx()), - Value: payload.Value().String(), + NumTx: uint64(len(submission.Transactions)), + Value: submission.Value.Dec(), - Epoch: payload.Slot() / common.SlotsPerEpoch, - BlockNumber: payload.BlockNumber(), + Epoch: submission.Slot / common.SlotsPerEpoch, + BlockNumber: submission.BlockNumber, DecodeDuration: profile.Decode, PrechecksDuration: profile.Prechecks, @@ -538,24 +544,28 @@ func (s *DatabaseService) DeleteExecutionPayloads(idFirst, idLast uint64) error return err } -func (s *DatabaseService) InsertBuilderDemotion(submitBlockRequest *common.BuilderSubmitBlockRequest, simError error) error { - _submitBlockRequest, err := json.Marshal(submitBlockRequest) +func (s *DatabaseService) InsertBuilderDemotion(submitBlockRequest *spec.VersionedSubmitBlockRequest, simError error) error { + _submitBlockRequest, err := json.Marshal(submitBlockRequest.Capella) + if err != nil { + return err + } + submission, err := common.GetBlockSubmissionInfo(submitBlockRequest) if err != nil { return err } builderDemotionEntry := BuilderDemotionEntry{ SubmitBlockRequest: NewNullString(string(_submitBlockRequest)), - Epoch: submitBlockRequest.Slot() / common.SlotsPerEpoch, - Slot: submitBlockRequest.Slot(), + Epoch: submission.Slot / common.SlotsPerEpoch, + Slot: submission.Slot, - BuilderPubkey: submitBlockRequest.BuilderPubkey().String(), - ProposerPubkey: submitBlockRequest.ProposerPubkey(), + BuilderPubkey: submission.Builder.String(), + ProposerPubkey: submission.Proposer.String(), - Value: submitBlockRequest.Value().String(), - FeeRecipient: submitBlockRequest.ProposerFeeRecipient(), + Value: submission.Value.Dec(), + FeeRecipient: submission.ProposerFeeRecipient.String(), - BlockHash: submitBlockRequest.BlockHash(), + BlockHash: submission.BlockHash.String(), SimError: simError.Error(), } @@ -567,8 +577,8 @@ func (s *DatabaseService) InsertBuilderDemotion(submitBlockRequest *common.Build return err } -func (s *DatabaseService) UpdateBuilderDemotion(trace *common.BidTraceV2, signedBlock *spec.VersionedSignedBeaconBlock, signedRegistration *types.SignedValidatorRegistration) error { - _signedBeaconBlock, err := json.Marshal(signedBlock) +func (s *DatabaseService) UpdateBuilderDemotion(trace *common.BidTraceV2, signedBlock *consensusspec.VersionedSignedBeaconBlock, signedRegistration *types.SignedValidatorRegistration) error { + _signedBeaconBlock, err := json.Marshal(signedBlock.Capella) if err != nil { return err } diff --git a/database/database_test.go b/database/database_test.go index 3381d4e5..2648b325 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -90,7 +90,9 @@ func insertTestBuilder(t *testing.T, db IDatabaseService) string { require.NoError(t, err) err = db.UpsertBlockBuilderEntryAfterSubmission(entry, false) require.NoError(t, err) - return req.BuilderPubkey().String() + builderPubkey, err := req.Builder() + require.NoError(t, err) + return builderPubkey.String() } func resetDatabase(t *testing.T) *DatabaseService { diff --git a/database/mockdb.go b/database/mockdb.go index 5679b242..f2c9c28b 100644 --- a/database/mockdb.go +++ b/database/mockdb.go @@ -5,8 +5,9 @@ import ( "fmt" "time" + "github.com/attestantio/go-builder-client/spec" consensusapi "github.com/attestantio/go-eth2-client/api" - "github.com/attestantio/go-eth2-client/spec" + consensusspec "github.com/attestantio/go-eth2-client/spec" "github.com/flashbots/go-boost-utils/types" "github.com/flashbots/mev-boost-relay/common" ) @@ -38,7 +39,7 @@ func (db MockDB) GetLatestValidatorRegistrations(timestampOnly bool) ([]*Validat return nil, nil } -func (db MockDB) SaveBuilderBlockSubmission(payload *common.BuilderSubmitBlockRequest, requestError, validationError error, receivedAt, eligibleAt time.Time, wasSimulated, saveExecPayload bool, profile common.Profile, optimisticSubmission bool) (entry *BuilderBlockSubmissionEntry, err error) { +func (db MockDB) SaveBuilderBlockSubmission(payload *spec.VersionedSubmitBlockRequest, requestError, validationError error, receivedAt, eligibleAt time.Time, wasSimulated, saveExecPayload bool, profile common.Profile, optimisticSubmission bool) (entry *BuilderBlockSubmissionEntry, err error) { return nil, nil } @@ -155,13 +156,16 @@ func (db MockDB) IncBlockBuilderStatsAfterGetPayload(builderPubkey string) error return nil } -func (db MockDB) InsertBuilderDemotion(submitBlockRequest *common.BuilderSubmitBlockRequest, simError error) error { - pubkey := submitBlockRequest.BuilderPubkey().String() - db.Demotions[pubkey] = true +func (db MockDB) InsertBuilderDemotion(submitBlockRequest *spec.VersionedSubmitBlockRequest, simError error) error { + pubkey, err := submitBlockRequest.Builder() + if err != nil { + return err + } + db.Demotions[pubkey.String()] = true return nil } -func (db MockDB) UpdateBuilderDemotion(trace *common.BidTraceV2, signedBlock *spec.VersionedSignedBeaconBlock, signedRegistration *types.SignedValidatorRegistration) error { +func (db MockDB) UpdateBuilderDemotion(trace *common.BidTraceV2, signedBlock *consensusspec.VersionedSignedBeaconBlock, signedRegistration *types.SignedValidatorRegistration) error { pubkey := trace.BuilderPubkey.String() _, ok := db.Builders[pubkey] if !ok { diff --git a/database/typesconv.go b/database/typesconv.go index 1df7abc2..08e205e4 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/spec" consensusspec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/flashbots/mev-boost-relay/common" @@ -12,7 +13,7 @@ import ( var ErrUnsupportedExecutionPayload = errors.New("unsupported execution payload version") -func PayloadToExecPayloadEntry(payload *common.BuilderSubmitBlockRequest) (*ExecutionPayloadEntry, error) { +func PayloadToExecPayloadEntry(payload *spec.VersionedSubmitBlockRequest) (*ExecutionPayloadEntry, error) { var _payload []byte var version string var err error @@ -23,10 +24,16 @@ func PayloadToExecPayloadEntry(payload *common.BuilderSubmitBlockRequest) (*Exec } version = common.ForkVersionStringCapella } + + submission, err := common.GetBlockSubmissionInfo(payload) + if err != nil { + return nil, err + } + return &ExecutionPayloadEntry{ - Slot: payload.Slot(), - ProposerPubkey: payload.ProposerPubkey(), - BlockHash: payload.BlockHash(), + Slot: submission.Slot, + ProposerPubkey: submission.Proposer.String(), + BlockHash: submission.BlockHash.String(), Version: version, Payload: string(_payload), diff --git a/datastore/memcached_test.go b/datastore/memcached_test.go index 4ec0fbda..5bdf8db8 100644 --- a/datastore/memcached_test.go +++ b/datastore/memcached_test.go @@ -11,6 +11,7 @@ import ( "github.com/attestantio/go-builder-client/api" "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" "github.com/attestantio/go-eth2-client/spec/bellatrix" capellaspec "github.com/attestantio/go-eth2-client/spec/capella" @@ -31,10 +32,10 @@ var ( ErrNoMemcachedServers = errors.New("no memcached servers specified") ) -func testBuilderSubmitBlockRequest(pubkey phase0.BLSPubKey, signature phase0.BLSSignature, version consensusspec.DataVersion) common.BuilderSubmitBlockRequest { +func testBuilderSubmitBlockRequest(pubkey phase0.BLSPubKey, signature phase0.BLSSignature, version consensusspec.DataVersion) spec.VersionedSubmitBlockRequest { switch version { case consensusspec.DataVersionCapella: - return common.BuilderSubmitBlockRequest{ + return spec.VersionedSubmitBlockRequest{ Capella: &capella.SubmitBlockRequest{ Signature: signature, Message: &apiv1.BidTrace{ @@ -71,7 +72,7 @@ func testBuilderSubmitBlockRequest(pubkey phase0.BLSPubKey, signature phase0.BLS case consensusspec.DataVersionUnknown, consensusspec.DataVersionPhase0, consensusspec.DataVersionAltair, consensusspec.DataVersionBellatrix: fallthrough default: - return common.BuilderSubmitBlockRequest{ + return spec.VersionedSubmitBlockRequest{ Capella: nil, } } @@ -108,7 +109,7 @@ func initMemcached(t *testing.T) (mem *Memcached, err error) { // RUN_INTEGRATION_TESTS=1 MEMCACHED_URIS="localhost:11211" go test -v -run ".*Memcached.*" ./... func TestMemcached(t *testing.T) { type test struct { - Input common.BuilderSubmitBlockRequest + Input spec.VersionedSubmitBlockRequest Description string TestSuite func(tc *test) func(*testing.T) } @@ -135,7 +136,7 @@ func TestMemcached(t *testing.T) { TestSuite: func(tc *test) func(*testing.T) { return func(t *testing.T) { t.Helper() - payload, err := tc.Input.ExecutionPayloadResponse() + payload, err := common.GetBlockSubmissionExecutionPayload(&tc.Input) require.Error(t, err) require.Equal(t, err, common.ErrEmptyPayload) require.Nil(t, payload) @@ -149,7 +150,7 @@ func TestMemcached(t *testing.T) { return func(t *testing.T) { t.Helper() - payload, err := tc.Input.ExecutionPayloadResponse() + payload, err := common.GetBlockSubmissionExecutionPayload(&tc.Input) require.NoError( t, err, @@ -175,15 +176,20 @@ func TestMemcached(t *testing.T) { require.NoError(t, err) require.True(t, bytes.Equal(inputBytes, outputBytes)) + submission, err := common.GetBlockSubmissionInfo(&tc.Input) + require.NoError(t, err) + // key should not exist in cache yet - empty, err := mem.GetExecutionPayload(tc.Input.Slot(), tc.Input.ProposerPubkey(), tc.Input.BlockHash()) + empty, err := mem.GetExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String()) require.NoError(t, err) require.Nil(t, empty) - err = mem.SaveExecutionPayload(tc.Input.Slot(), tc.Input.ProposerPubkey(), tc.Input.BlockHash(), payload) + submission, err = common.GetBlockSubmissionInfo(&tc.Input) + require.NoError(t, err) + err = mem.SaveExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String(), payload) require.NoError(t, err) - get, err := mem.GetExecutionPayload(tc.Input.Slot(), tc.Input.ProposerPubkey(), tc.Input.BlockHash()) + get, err := mem.GetExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String()) require.NoError(t, err, "expected no error when fetching execution payload from memcached but found [%v]", err) getBytes, err := get.MarshalJSON() @@ -200,27 +206,30 @@ func TestMemcached(t *testing.T) { return func(t *testing.T) { t.Helper() - payload, err := tc.Input.ExecutionPayloadResponse() + payload, err := common.GetBlockSubmissionExecutionPayload(&tc.Input) require.NoError( t, err, "expected valid execution payload response for builder's submit block request but found [%v]", err, ) - err = mem.SaveExecutionPayload(tc.Input.Slot(), tc.Input.ProposerPubkey(), tc.Input.BlockHash(), payload) + submission, err := common.GetBlockSubmissionInfo(&tc.Input) + require.NoError(t, err) + + err = mem.SaveExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String(), payload) require.NoError(t, err) - prev, err := mem.GetExecutionPayload(tc.Input.Slot(), tc.Input.ProposerPubkey(), tc.Input.BlockHash()) + prev, err := mem.GetExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String()) require.NoError(t, err) - require.Equal(t, len(prev.Capella.Transactions), tc.Input.NumTx()) + require.Equal(t, len(prev.Capella.Transactions), len(submission.Transactions)) payload.Bellatrix.GasLimit++ require.NotEqual(t, prev.Bellatrix.GasLimit, payload.Bellatrix.GasLimit) - err = mem.SaveExecutionPayload(tc.Input.Slot(), tc.Input.ProposerPubkey(), tc.Input.BlockHash(), payload) + err = mem.SaveExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String(), payload) require.NoError(t, err) - current, err := mem.GetExecutionPayload(tc.Input.Slot(), tc.Input.ProposerPubkey(), tc.Input.BlockHash()) + current, err := mem.GetExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String()) require.NoError(t, err) require.Equal(t, current.Bellatrix.GasLimit, payload.Bellatrix.GasLimit) require.NotEqual(t, current.Bellatrix.GasLimit, prev.Bellatrix.GasLimit) @@ -242,23 +251,27 @@ func TestMemcached(t *testing.T) { require.NoError(t, err) tc.Input.Capella.Message.ProposerPubkey = phase0.BLSPubKey(pk) - payload, err := tc.Input.ExecutionPayloadResponse() + payload, err := common.GetBlockSubmissionExecutionPayload(&tc.Input) require.NoError( t, err, "expected valid execution payload response for builder's submit block request but found [%v]", err, ) - require.Equal(t, tc.Input.ProposerPubkey(), pk.String()) - err = mem.SaveExecutionPayload(tc.Input.Slot(), tc.Input.ProposerPubkey(), tc.Input.BlockHash(), payload) + submission, err := common.GetBlockSubmissionInfo(&tc.Input) + require.NoError(t, err) + + require.Equal(t, submission.Proposer.String(), pk.String()) + + err = mem.SaveExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String(), payload) require.NoError(t, err) - ret, err := mem.GetExecutionPayload(tc.Input.Slot(), tc.Input.ProposerPubkey(), tc.Input.BlockHash()) + ret, err := mem.GetExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String()) require.NoError(t, err) - require.Equal(t, len(ret.Capella.Transactions), tc.Input.NumTx()) + require.Equal(t, len(ret.Capella.Transactions), len(submission.Transactions)) time.Sleep((time.Duration(defaultMemcachedExpirySeconds) + 2) * time.Second) - expired, err := mem.GetExecutionPayload(tc.Input.Slot(), tc.Input.ProposerPubkey(), tc.Input.BlockHash()) + expired, err := mem.GetExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String()) require.NoError(t, err) require.NotEqual(t, ret, expired) require.Nil(t, expired) diff --git a/datastore/redis.go b/datastore/redis.go index 37cfd563..f7ca3777 100644 --- a/datastore/redis.go +++ b/datastore/redis.go @@ -462,19 +462,24 @@ type SaveBidAndUpdateTopBidResponse struct { TimeUpdateFloor time.Duration } -func (r *RedisCache) SaveBidAndUpdateTopBid(ctx context.Context, tx redis.Pipeliner, trace *common.BidTraceV2, payload *common.BuilderSubmitBlockRequest, 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 *spec.VersionedSubmitBlockRequest, getPayloadResponse *api.VersionedExecutionPayload, getHeaderResponse *spec.VersionedSignedBuilderBid, reqReceivedAt time.Time, isCancellationEnabled bool, floorValue *big.Int) (state SaveBidAndUpdateTopBidResponse, err error) { var prevTime, nextTime time.Time prevTime = time.Now() + submission, err := common.GetBlockSubmissionInfo(payload) + if err != nil { + return state, err + } + // Load latest bids for a given slot+parent+proposer - builderBids, err := NewBuilderBidsFromRedis(ctx, r, tx, payload.Slot(), payload.ParentHash(), payload.ProposerPubkey()) + builderBids, err := NewBuilderBidsFromRedis(ctx, r, tx, submission.Slot, submission.ParentHash.String(), submission.Proposer.String()) if err != nil { return state, err } // Load floor value (if not passed in already) if floorValue == nil { - floorValue, err = r.GetFloorBidValue(ctx, tx, payload.Slot(), payload.ParentHash(), payload.ProposerPubkey()) + floorValue, err = r.GetFloorBidValue(ctx, tx, submission.Slot, submission.ParentHash.String(), submission.Proposer.String()) if err != nil { return state, err } @@ -488,7 +493,7 @@ func (r *RedisCache) SaveBidAndUpdateTopBid(ctx context.Context, tx redis.Pipeli state.PrevTopBidValue = state.TopBidValue // Abort now if non-cancellation bid is lower than floor value - isBidAboveFloor := payload.Value().Cmp(floorValue) == 1 + isBidAboveFloor := submission.Value.ToBig().Cmp(floorValue) == 1 if !isCancellationEnabled && !isBidAboveFloor { return state, nil } @@ -502,7 +507,7 @@ func (r *RedisCache) SaveBidAndUpdateTopBid(ctx context.Context, tx redis.Pipeli // Time to save things in Redis // // 1. Save the execution payload - err = r.SaveExecutionPayloadCapella(ctx, tx, payload.Slot(), payload.ProposerPubkey(), payload.BlockHash(), getPayloadResponse.Capella) + err = r.SaveExecutionPayloadCapella(ctx, tx, submission.Slot, submission.ParentHash.String(), submission.Proposer.String(), getPayloadResponse.Capella) if err != nil { return state, err } @@ -513,12 +518,12 @@ func (r *RedisCache) SaveBidAndUpdateTopBid(ctx context.Context, tx redis.Pipeli prevTime = nextTime // 2. Save latest bid for this builder - err = r.SaveBuilderBid(ctx, tx, payload.Slot(), payload.ParentHash(), payload.ProposerPubkey(), payload.BuilderPubkey().String(), reqReceivedAt, getHeaderResponse) + err = r.SaveBuilderBid(ctx, tx, submission.Slot, submission.ParentHash.String(), submission.Proposer.String(), submission.Builder.String(), reqReceivedAt, getHeaderResponse) if err != nil { return state, err } state.WasBidSaved = true - builderBids.bidValues[payload.BuilderPubkey().String()] = payload.Value() + builderBids.bidValues[submission.Builder.String()] = submission.Value.ToBig() // Record time needed to save bid nextTime = time.Now().UTC() @@ -542,11 +547,11 @@ func (r *RedisCache) SaveBidAndUpdateTopBid(ctx context.Context, tx redis.Pipeli return state, nil } - state, err = r._updateTopBid(ctx, tx, state, builderBids, payload.Slot(), payload.ParentHash(), payload.ProposerPubkey(), floorValue) + state, err = r._updateTopBid(ctx, tx, state, builderBids, submission.Slot, submission.ParentHash.String(), submission.Proposer.String(), floorValue) if err != nil { return state, err } - state.IsNewTopBid = payload.Value().Cmp(state.TopBidValue) == 0 + state.IsNewTopBid = submission.Value.ToBig().Cmp(state.TopBidValue) == 0 // Record time needed to update top bid nextTime = time.Now().UTC() @@ -558,8 +563,8 @@ func (r *RedisCache) SaveBidAndUpdateTopBid(ctx context.Context, tx redis.Pipeli } // Non-cancellable bid above floor should set new floor - keyBidSource := r.keyLatestBidByBuilder(payload.Slot(), payload.ParentHash(), payload.ProposerPubkey(), payload.BuilderPubkey().String()) - keyFloorBid := r.keyFloorBid(payload.Slot(), payload.ParentHash(), payload.ProposerPubkey()) + keyBidSource := r.keyLatestBidByBuilder(submission.Slot, submission.ParentHash.String(), submission.Proposer.String(), submission.Builder.String()) + keyFloorBid := r.keyFloorBid(submission.Slot, submission.ParentHash.String(), submission.Proposer.String()) c := tx.Copy(ctx, keyBidSource, keyFloorBid, 0, true) _, err = tx.Exec(ctx) if err != nil { @@ -577,8 +582,8 @@ func (r *RedisCache) SaveBidAndUpdateTopBid(ctx context.Context, tx redis.Pipeli return state, err } - keyFloorBidValue := r.keyFloorBidValue(payload.Slot(), payload.ParentHash(), payload.ProposerPubkey()) - err = tx.Set(ctx, keyFloorBidValue, payload.Value().String(), expiryBidCache).Err() + keyFloorBidValue := r.keyFloorBidValue(submission.Slot, submission.ParentHash.String(), submission.Proposer.String()) + err = tx.Set(ctx, keyFloorBidValue, submission.Value.Dec(), expiryBidCache).Err() if err != nil { return state, err } diff --git a/go.mod b/go.mod index 29f86f30..38da6a89 100644 --- a/go.mod +++ b/go.mod @@ -116,3 +116,5 @@ retract ( ) replace github.com/attestantio/go-eth2-client => github.com/avalonche/go-eth2-client v0.0.0-20230720061256-82081347600e + +replace github.com/attestantio/go-builder-client => github.com/avalonche/go-builder-client v0.0.0-20230711071258-c1ca05d6e8b7 diff --git a/go.sum b/go.sum index 103908bc..911c9b4e 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/attestantio/go-builder-client v0.3.2-0.20230701110827-d0ecfee1ab62 h1:jtNd8modhHUKfgLcQBv6ajgBNldwkIXT4U2iqWtbyA0= -github.com/attestantio/go-builder-client v0.3.2-0.20230701110827-d0ecfee1ab62/go.mod h1:DwesMTOqnCp4u+n3uZ+fWL8wwnSBZVD9VMIVPDR+AZE= +github.com/avalonche/go-builder-client v0.0.0-20230711071258-c1ca05d6e8b7 h1:NQt83tMbCmK2V1OuPUUEIudvi8EeULl3iWG3Ht1bdCk= +github.com/avalonche/go-builder-client v0.0.0-20230711071258-c1ca05d6e8b7/go.mod h1:DwesMTOqnCp4u+n3uZ+fWL8wwnSBZVD9VMIVPDR+AZE= github.com/avalonche/go-eth2-client v0.0.0-20230720061256-82081347600e h1:WhEhbE/udrh+M8MltkFhXy4BCTB+Q3ubinTdTidt10I= github.com/avalonche/go-eth2-client v0.0.0-20230720061256-82081347600e/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/blocksim_ratelimiter.go b/services/api/blocksim_ratelimiter.go index 74fdbb8a..c5633185 100644 --- a/services/api/blocksim_ratelimiter.go +++ b/services/api/blocksim_ratelimiter.go @@ -74,10 +74,14 @@ func (b *BlockSimulationRateLimiter) Send(context context.Context, payload *comm return ErrNoCapellaPayload, nil } // TODO: add deneb support. + submission, err := common.GetBlockSubmissionInfo(&payload.VersionedSubmitBlockRequest) + if err != nil { + return err, nil + } // Prepare headers headers := http.Header{} - headers.Add("X-Request-ID", fmt.Sprintf("%d/%s", payload.Slot(), payload.BlockHash())) + headers.Add("X-Request-ID", fmt.Sprintf("%d/%s", submission.Slot, submission.BlockHash.String())) if isHighPrio { headers.Add("X-High-Priority", "true") } diff --git a/services/api/optimistic_test.go b/services/api/optimistic_test.go index b0f6e491..6cdcc489 100644 --- a/services/api/optimistic_test.go +++ b/services/api/optimistic_test.go @@ -144,7 +144,7 @@ func runOptimisticBlockSubmission(t *testing.T, opts blockRequestOpts, simErr er } req := common.TestBuilderSubmitBlockRequest(opts.secretkey, getTestBidTrace(opts.pubkey, opts.blockValue)) - rr := backend.request(http.MethodPost, pathSubmitNewBlock, &req) + rr := backend.request(http.MethodPost, pathSubmitNewBlock, req.Capella) // Let updates happen async. time.Sleep(100 * time.Millisecond) @@ -189,7 +189,7 @@ func TestSimulateBlock(t *testing.T) { }, }, req: &common.BuilderBlockValidationRequest{ - BuilderSubmitBlockRequest: common.TestBuilderSubmitBlockRequest( + VersionedSubmitBlockRequest: common.TestBuilderSubmitBlockRequest( secretkey, getTestBidTrace(*pubkey, collateral)), }, }) @@ -239,7 +239,7 @@ func TestProcessOptimisticBlock(t *testing.T) { }, }, req: &common.BuilderBlockValidationRequest{ - BuilderSubmitBlockRequest: common.TestBuilderSubmitBlockRequest( + VersionedSubmitBlockRequest: common.TestBuilderSubmitBlockRequest( secretkey, getTestBidTrace(*pubkey, collateral)), }, }, simResultC) diff --git a/services/api/service.go b/services/api/service.go index 5581830e..66b357a5 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -22,8 +22,10 @@ import ( "github.com/NYTimes/gziphandler" builderCapella "github.com/attestantio/go-builder-client/api/capella" + "github.com/attestantio/go-builder-client/spec" consensusapi "github.com/attestantio/go-eth2-client/api" "github.com/attestantio/go-eth2-client/api/v1/capella" + consensusspec "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/buger/jsonparser" "github.com/flashbots/go-boost-utils/bls" @@ -582,7 +584,7 @@ func (api *RelayAPI) simulateBlock(ctx context.Context, opts blockSimOptions) (r return nil, nil } -func (api *RelayAPI) demoteBuilder(pubkey string, req *common.BuilderSubmitBlockRequest, simError error) { +func (api *RelayAPI) demoteBuilder(pubkey string, req *spec.VersionedSubmitBlockRequest, simError error) { builderEntry, ok := api.blockBuildersCache[pubkey] if !ok { api.log.Warnf("builder %v not in the builder cache", pubkey) @@ -599,10 +601,14 @@ func (api *RelayAPI) demoteBuilder(pubkey string, req *common.BuilderSubmitBlock } // Write to demotions table. api.log.WithFields(logrus.Fields{"builder_pubkey": pubkey}).Info("demoting builder") + bidTrace, err := req.BidTrace() + if err != nil { + api.log.WithError(err).Warn("failed to get bid trace from submit block request") + } if err := api.db.InsertBuilderDemotion(req, simError); err != nil { api.log.WithError(err).WithFields(logrus.Fields{ "errorWritingDemotionToDB": true, - "bidTrace": req.Message, + "bidTrace": bidTrace, "simError": simError, }).Error("failed to save demotion to database") } @@ -617,14 +623,19 @@ func (api *RelayAPI) processOptimisticBlock(opts blockSimOptions, simResultC cha defer api.optimisticBlocksWG.Done() ctx := context.Background() - builderPubkey := opts.req.BuilderPubkey().String() + submission, err := common.GetBlockSubmissionInfo(&opts.req.VersionedSubmitBlockRequest) + if err != nil { + opts.log.WithError(err).Error("error getting block submission info") + return + } + builderPubkey := submission.Builder.String() opts.log.WithFields(logrus.Fields{ "builderPubkey": builderPubkey, // NOTE: this value is just an estimate because many goroutines could be // updating api.optimisticBlocksInFlight concurrently. Since we just use // it for logging, it is not atomic to avoid the performance impact. "optBlocksInFlight": api.optimisticBlocksInFlight, - }).Infof("simulating optimistic block with hash: %v", opts.req.BuilderSubmitBlockRequest.BlockHash()) + }).Infof("simulating optimistic block with hash: %v", submission.BlockHash.String()) reqErr, simErr := api.simulateBlock(ctx, opts) simResultC <- &blockSimResult{reqErr == nil, true, reqErr, simErr} if reqErr != nil || simErr != nil { @@ -640,7 +651,7 @@ func (api *RelayAPI) processOptimisticBlock(opts blockSimOptions, simResultC cha } // Demote the builder. - api.demoteBuilder(builderPubkey, &opts.req.BuilderSubmitBlockRequest, demotionErr) + api.demoteBuilder(builderPubkey, &opts.req.VersionedSubmitBlockRequest, demotionErr) } } @@ -1580,18 +1591,20 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque return } - payload := new(common.BuilderSubmitBlockRequest) + payload := &spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct + Version: consensusspec.DataVersionCapella, + } + payload.Capella = new(builderCapella.SubmitBlockRequest) // Check for SSZ encoding contentType := req.Header.Get("Content-Type") if contentType == "application/octet-stream" { log = log.WithField("reqContentType", "ssz") - payload.Capella = new(builderCapella.SubmitBlockRequest) if err = payload.Capella.UnmarshalSSZ(requestPayloadBytes); err != nil { log.WithError(err).Warn("could not decode payload - SSZ") // SSZ decoding failed. try JSON as fallback (some builders used octet-stream for json before) - if err2 := json.Unmarshal(requestPayloadBytes, payload); err2 != nil { + if err2 := json.Unmarshal(requestPayloadBytes, payload.Capella); err2 != nil { log.WithError(fmt.Errorf("%w / %w", err, err2)).Warn("could not decode payload - SSZ or JSON") api.RespondError(w, http.StatusBadRequest, err.Error()) return @@ -1602,7 +1615,7 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque } } else { log = log.WithField("reqContentType", "json") - if err := json.Unmarshal(requestPayloadBytes, payload); err != nil { + if err := json.Unmarshal(requestPayloadBytes, payload.Capella); err != nil { log.WithError(err).Warn("could not decode payload - JSON") api.RespondError(w, http.StatusBadRequest, err.Error()) return @@ -1614,24 +1627,26 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque prevTime = nextTime isLargeRequest := len(requestPayloadBytes) > fastTrackPayloadSizeLimit + // getting block submission info also validates bid trace and execution submission are not empty + submission, err := common.GetBlockSubmissionInfo(payload) + if err != nil { + log.WithError(err).Warn("missing fields in submit block request") + api.RespondError(w, http.StatusBadRequest, err.Error()) + return + } log = log.WithFields(logrus.Fields{ "timestampAfterDecoding": time.Now().UTC().UnixMilli(), - "slot": payload.Slot(), - "builderPubkey": payload.BuilderPubkey().String(), - "blockHash": payload.BlockHash(), - "proposerPubkey": payload.ProposerPubkey(), - "parentHash": payload.ParentHash(), - "value": payload.Value().String(), - "numTx": payload.NumTx(), + "slot": submission.Slot, + "builderPubkey": submission.Builder.String(), + "blockHash": submission.BlockHash.String(), + "proposerPubkey": submission.Proposer.String(), + "parentHash": submission.ParentHash.String(), + "value": submission.Value.Dec(), + "numTx": len(submission.Transactions), "payloadBytes": len(requestPayloadBytes), "isLargeRequest": isLargeRequest, }) - if payload.Message() == nil || !payload.HasExecutionPayload() { - api.RespondError(w, http.StatusBadRequest, "missing parts of the payload") - return - } - // TODO: add deneb support. if payload.Capella == nil { log.Info("rejecting submission - non capella payload for capella fork") @@ -1639,13 +1654,13 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque return } - if payload.Slot() <= headSlot { + if submission.Slot <= headSlot { log.Info("submitNewBlock failed: submission for past slot") api.RespondError(w, http.StatusBadRequest, "submission for past slot") return } - builderPubkey := payload.BuilderPubkey() + builderPubkey := submission.Builder builderEntry, ok := api.blockBuildersCache[builderPubkey.String()] if !ok { log.Warnf("unable to read builder: %s from the builder cache, using low-prio and no collateral", builderPubkey.String()) @@ -1661,10 +1676,10 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque log = log.WithField("builderIsHighPrio", builderEntry.status.IsHighPrio) // Timestamp check - expectedTimestamp := api.genesisInfo.Data.GenesisTime + (payload.Slot() * common.SecondsPerSlot) - if payload.Timestamp() != expectedTimestamp { - log.Warnf("incorrect timestamp. got %d, expected %d", payload.Timestamp(), expectedTimestamp) - api.RespondError(w, http.StatusBadRequest, fmt.Sprintf("incorrect timestamp. got %d, expected %d", payload.Timestamp(), expectedTimestamp)) + expectedTimestamp := api.genesisInfo.Data.GenesisTime + (submission.Slot * common.SecondsPerSlot) + if submission.Timestamp != expectedTimestamp { + log.Warnf("incorrect timestamp. got %d, expected %d", submission.Timestamp, expectedTimestamp) + api.RespondError(w, http.StatusBadRequest, fmt.Sprintf("incorrect timestamp. got %d, expected %d", submission.Timestamp, expectedTimestamp)) return } @@ -1687,23 +1702,23 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque // ensure correct feeRecipient is used api.proposerDutiesLock.RLock() - slotDuty := api.proposerDutiesMap[payload.Slot()] + slotDuty := api.proposerDutiesMap[submission.Slot] api.proposerDutiesLock.RUnlock() if slotDuty == nil { log.Warn("could not find slot duty") api.RespondError(w, http.StatusBadRequest, "could not find slot duty") return - } else if !strings.EqualFold(slotDuty.Entry.Message.FeeRecipient.String(), payload.ProposerFeeRecipient()) { + } else if !strings.EqualFold(slotDuty.Entry.Message.FeeRecipient.String(), submission.ProposerFeeRecipient.String()) { log.WithFields(logrus.Fields{ "expectedFeeRecipient": slotDuty.Entry.Message.FeeRecipient.String(), - "actualFeeRecipient": payload.ProposerFeeRecipient(), + "actualFeeRecipient": submission.ProposerFeeRecipient.String(), }).Info("fee recipient does not match") api.RespondError(w, http.StatusBadRequest, "fee recipient does not match") return } // Don't accept blocks with 0 value - if payload.Value().Cmp(ZeroU256.BigInt()) == 0 || payload.NumTx() == 0 { + if submission.Value.ToBig().Cmp(ZeroU256.BigInt()) == 0 || len(submission.Transactions) == 0 { log.Info("submitNewBlock failed: block with 0 value or no txs") w.WriteHeader(http.StatusOK) return @@ -1720,23 +1735,23 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque log = log.WithField("timestampBeforeAttributesCheck", time.Now().UTC().UnixMilli()) api.payloadAttributesLock.RLock() - attrs, ok := api.payloadAttributes[payload.ParentHash()] + attrs, ok := api.payloadAttributes[submission.ParentHash.String()] api.payloadAttributesLock.RUnlock() - if !ok || payload.Slot() != attrs.slot { + if !ok || submission.Slot != attrs.slot { log.Warn("payload attributes not (yet) known") api.RespondError(w, http.StatusBadRequest, "payload attributes not (yet) known") return } - if payload.Random() != attrs.payloadAttributes.PrevRandao { - msg := fmt.Sprintf("incorrect prev_randao - got: %s, expected: %s", payload.Random(), attrs.payloadAttributes.PrevRandao) + if submission.PrevRandao.String() != attrs.payloadAttributes.PrevRandao { + msg := fmt.Sprintf("incorrect prev_randao - got: %s, expected: %s", submission.PrevRandao.String(), attrs.payloadAttributes.PrevRandao) log.Info(msg) api.RespondError(w, http.StatusBadRequest, msg) return } - if api.isCapella(payload.Slot()) { // Capella requires correct withdrawals - withdrawalsRoot, err := ComputeWithdrawalsRoot(payload.Withdrawals()) + if api.isCapella(submission.Slot) { // Capella requires correct withdrawals + withdrawalsRoot, err := ComputeWithdrawalsRoot(submission.Withdrawals) if err != nil { log.WithError(err).Warn("could not compute withdrawals root from payload") api.RespondError(w, http.StatusBadRequest, "could not compute withdrawals root") @@ -1753,8 +1768,8 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque // Verify the signature log = log.WithField("timestampBeforeSignatureCheck", time.Now().UTC().UnixMilli()) - signature := payload.Signature() - ok, err = boostTypes.VerifySignature(payload.Message(), api.opts.EthNetDetails.DomainBuilder, builderPubkey[:], signature[:]) + signature := submission.Signature + ok, err = boostTypes.VerifySignature(submission.BidTrace, api.opts.EthNetDetails.DomainBuilder, builderPubkey[:], signature[:]) log = log.WithField("timestampAfterSignatureCheck", time.Now().UTC().UnixMilli()) if err != nil { log.WithError(err).Warn("failed verifying builder signature") @@ -1773,7 +1788,7 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque slotLastPayloadDelivered, err := api.redis.GetLastSlotDelivered(context.Background(), tx) if err != nil && !errors.Is(err, redis.Nil) { log.WithError(err).Error("failed to get delivered payload slot from redis") - } else if payload.Slot() <= slotLastPayloadDelivered { + } else if submission.Slot <= slotLastPayloadDelivered { log.Info("rejecting submission because payload for this slot was already delivered") api.RespondError(w, http.StatusBadRequest, "payload for this slot was already delivered") return @@ -1811,7 +1826,7 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque }() // Grab floor bid value - floorBidValue, err := api.redis.GetFloorBidValue(context.Background(), tx, payload.Slot(), payload.ParentHash(), payload.ProposerPubkey()) + floorBidValue, err := api.redis.GetFloorBidValue(context.Background(), tx, submission.Slot, submission.ParentHash.String(), submission.Proposer.String()) if err != nil { log.WithError(err).Error("failed to get floor bid value from redis") } else { @@ -1821,12 +1836,12 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque // -------------------------------------------- // Skip submission if below the floor bid value // -------------------------------------------- - isBidBelowFloor := floorBidValue != nil && payload.Value().Cmp(floorBidValue) == -1 - isBidAtOrBelowFloor := floorBidValue != nil && payload.Value().Cmp(floorBidValue) < 1 + isBidBelowFloor := floorBidValue != nil && submission.Value.ToBig().Cmp(floorBidValue) == -1 + isBidAtOrBelowFloor := floorBidValue != nil && submission.Value.ToBig().Cmp(floorBidValue) < 1 if isCancellationEnabled && isBidBelowFloor { // with cancellations: if below floor -> delete previous bid simResultC <- &blockSimResult{false, false, nil, nil} log.Info("submission below floor bid value, with cancellation") - err := api.redis.DelBuilderBid(context.Background(), tx, payload.Slot(), payload.ParentHash(), payload.ProposerPubkey(), payload.BuilderPubkey().String()) + err := api.redis.DelBuilderBid(context.Background(), tx, submission.Slot, submission.ParentHash.String(), submission.Proposer.String(), submission.Builder.String()) if err != nil { log.WithError(err).Error("failed processing cancellable bid below floor") api.RespondError(w, http.StatusInternalServerError, "failed processing cancellable bid below floor") @@ -1847,11 +1862,11 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque // Get the latest top bid value from Redis bidIsTopBid := false - topBidValue, err := api.redis.GetTopBidValue(context.Background(), tx, payload.Slot(), payload.ParentHash(), payload.ProposerPubkey()) + topBidValue, err := api.redis.GetTopBidValue(context.Background(), tx, submission.Slot, submission.ParentHash.String(), submission.Proposer.String()) if err != nil { log.WithError(err).Error("failed to get top bid value from redis") } else { - bidIsTopBid = payload.Value().Cmp(topBidValue) == 1 + bidIsTopBid = submission.Value.ToBig().Cmp(topBidValue) == 1 log = log.WithFields(logrus.Fields{ "topBidValue": topBidValue.String(), "newBidIsTopBid": bidIsTopBid, @@ -1878,14 +1893,14 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque log: log, builder: builderEntry, req: &common.BuilderBlockValidationRequest{ - BuilderSubmitBlockRequest: *payload, - RegisteredGasLimit: slotDuty.Entry.Message.GasLimit, + VersionedSubmitBlockRequest: *payload, + RegisteredGasLimit: slotDuty.Entry.Message.GasLimit, }, } // With sufficient collateral, process the block optimistically. if builderEntry.status.IsOptimistic && - builderEntry.collateral.Cmp(payload.Value()) >= 0 && - payload.Slot() == api.optimisticSlot.Load() { + builderEntry.collateral.Cmp(submission.Value.ToBig()) >= 0 && + submission.Slot == api.optimisticSlot.Load() { go api.processOptimisticBlock(opts, simResultC) } else { // Simulate block (synchronously). @@ -1926,7 +1941,7 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque // latency will make it impossible to predict which arrives first. Thus a high bid could unintentionally be overwritten by a low bid that happened // to arrive a few microseconds later. If builders are submitting blocks at a frequency where they cannot reliably predict which bid will arrive at // the relay first, they should instead use multiple pubkeys to avoid uninitentionally overwriting their own bids. - latestPayloadReceivedAt, err := api.redis.GetBuilderLatestPayloadReceivedAt(context.Background(), tx, payload.Slot(), payload.BuilderPubkey().String(), payload.ParentHash(), payload.ProposerPubkey()) + latestPayloadReceivedAt, err := api.redis.GetBuilderLatestPayloadReceivedAt(context.Background(), tx, submission.Slot, submission.Builder.String(), submission.ParentHash.String(), submission.Proposer.String()) if err != nil { log.WithError(err).Error("failed getting latest payload receivedAt from redis") } else if receivedAt.UnixMilli() < latestPayloadReceivedAt { @@ -1952,9 +1967,9 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque } bidTrace := common.BidTraceV2{ - BidTrace: *payload.Message(), - BlockNumber: payload.BlockNumber(), - NumTx: uint64(payload.NumTx()), + BidTrace: *submission.BidTrace, + BlockNumber: submission.BlockNumber, + NumTx: uint64(len(submission.Transactions)), } // @@ -1987,7 +2002,7 @@ func (api *RelayAPI) handleSubmitNewBlock(w http.ResponseWriter, req *http.Reque // Save to memcache in the background if api.memcached != nil { go func() { - err = api.memcached.SaveExecutionPayload(payload.Slot(), payload.ProposerPubkey(), payload.BlockHash(), getPayloadResponse) + err = api.memcached.SaveExecutionPayload(submission.Slot, submission.Proposer.String(), submission.BlockHash.String(), getPayloadResponse) if err != nil { log.WithError(err).Error("failed saving execution payload in memcached") } diff --git a/services/api/service_test.go b/services/api/service_test.go index 64c4c08d..f55ed5e7 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -381,8 +381,9 @@ func TestDataApiGetDataProposerPayloadDelivered(t *testing.T) { func TestBuilderSubmitBlockSSZ(t *testing.T) { requestPayloadJSONBytes := common.LoadGzippedBytes(t, "../../testdata/submitBlockPayloadCapella_Goerli.json.gz") - req := new(common.BuilderSubmitBlockRequest) - err := json.Unmarshal(requestPayloadJSONBytes, &req) + req := new(spec.VersionedSubmitBlockRequest) + req.Capella = new(builderCapella.SubmitBlockRequest) + err := json.Unmarshal(requestPayloadJSONBytes, req.Capella) require.NoError(t, err) reqSSZ, err := req.Capella.MarshalSSZ() @@ -435,10 +436,11 @@ func TestBuilderSubmitBlock(t *testing.T) { } // Prepare the request payload - req := new(common.BuilderSubmitBlockRequest) + req := new(spec.VersionedSubmitBlockRequest) + req.Capella = new(builderCapella.SubmitBlockRequest) requestPayloadJSONBytes := common.LoadGzippedBytes(t, payloadJSONFilename) require.NoError(t, err) - err = json.Unmarshal(requestPayloadJSONBytes, &req) + err = json.Unmarshal(requestPayloadJSONBytes, req.Capella) require.NoError(t, err) // Update diff --git a/services/api/utils.go b/services/api/utils.go index acdd4ad4..7000dbee 100644 --- a/services/api/utils.go +++ b/services/api/utils.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/attestantio/go-builder-client/api" + "github.com/attestantio/go-builder-client/spec" consensusapi "github.com/attestantio/go-eth2-client/api" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/phase0" @@ -24,12 +25,16 @@ var ( ErrHeaderHTRMismatch = errors.New("beacon-block and payload header mismatch") ) -func SanityCheckBuilderBlockSubmission(payload *common.BuilderSubmitBlockRequest) error { - if payload.BlockHash() != payload.ExecutionPayloadBlockHash() { +func SanityCheckBuilderBlockSubmission(payload *spec.VersionedSubmitBlockRequest) error { + submission, err := common.GetBlockSubmissionInfo(payload) + if err != nil { + return err + } + if submission.BlockHash.String() != submission.ExecutionPayloadBlockHash.String() { return ErrBlockHashMismatch } - if payload.ParentHash() != payload.ExecutionPayloadParentHash() { + if submission.ParentHash.String() != submission.ExecutionPayloadParentHash.String() { return ErrParentHashMismatch }