diff --git a/common/test_utils.go b/common/test_utils.go index 4d55463e..0ba0e5fb 100644 --- a/common/test_utils.go +++ b/common/test_utils.go @@ -10,16 +10,22 @@ import ( "testing" "time" + "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" "github.com/attestantio/go-eth2-client/spec/bellatrix" + capellaspec "github.com/attestantio/go-eth2-client/spec/capella" consensuscapella "github.com/attestantio/go-eth2-client/spec/capella" + consensusdeneb "github.com/attestantio/go-eth2-client/spec/deneb" + 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" "github.com/flashbots/go-boost-utils/utils" + "github.com/holiman/uint256" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" ) @@ -74,9 +80,32 @@ var ValidPayloadRegisterValidator = apiv1.SignedValidatorRegistration{ "0xaf12df007a0c78abb5575067e5f8b089cfcc6227e4a91db7dd8cf517fe86fb944ead859f0781277d9b78c672e4a18c5d06368b603374673cf2007966cece9540f3a1b3f6f9e1bf421d779c4e8010368e6aac134649c7a009210780d401a778a5"), } -func TestBuilderSubmitBlockRequest(sk *bls.SecretKey, bid *BidTraceV2) VersionedSubmitBlockRequest { +func TestBuilderSubmitBlockRequest(sk *bls.SecretKey, bid *BidTraceV2, version consensusspec.DataVersion) VersionedSubmitBlockRequest { signature, err := ssz.SignMessage(bid, ssz.DomainBuilder, sk) check(err, " SignMessage: ", bid, sk) + if version == consensusspec.DataVersionDeneb { + return VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct + Version: consensusspec.DataVersionDeneb, + Deneb: &deneb.SubmitBlockRequest{ + Message: &bid.BidTrace, + Signature: [96]byte(signature), + ExecutionPayload: &consensusdeneb.ExecutionPayload{ //nolint:exhaustruct + Transactions: []bellatrix.Transaction{[]byte{0x03}}, + Timestamp: bid.Slot * 12, // 12 seconds per slot. + PrevRandao: _HexToHash("0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"), + Withdrawals: []*consensuscapella.Withdrawal{}, + BaseFeePerGas: uint256.NewInt(0), + }, + BlobsBundle: &deneb.BlobsBundle{ + Commitments: []consensusdeneb.KzgCommitment{}, + Proofs: []consensusdeneb.KzgProof{}, + Blobs: []consensusdeneb.Blob{}, + }, + }, + }, + } + } return VersionedSubmitBlockRequest{ VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct Version: consensusspec.DataVersionCapella, @@ -94,6 +123,97 @@ func TestBuilderSubmitBlockRequest(sk *bls.SecretKey, bid *BidTraceV2) Versioned } } +type CreateTestBlockSubmissionOpts struct { + relaySk bls.SecretKey + relayPk phase0.BLSPubKey + domain phase0.Domain + + Version consensusspec.DataVersion + Slot uint64 + ParentHash string + ProposerPubkey string +} + +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 + + slot := uint64(0) + relaySk := bls.SecretKey{} + relayPk := phase0.BLSPubKey{} + domain := phase0.Domain{} + proposerPk := phase0.BLSPubKey{} + parentHash := phase0.Hash32{} + version := consensusspec.DataVersionCapella + + if opts != nil { + relaySk = opts.relaySk + relayPk = opts.relayPk + domain = opts.domain + slot = opts.Slot + + if opts.ProposerPubkey != "" { + proposerPk, err = StrToPhase0Pubkey(opts.ProposerPubkey) + require.NoError(t, err) + } + + if opts.ParentHash != "" { + parentHash, err = StrToPhase0Hash(opts.ParentHash) + require.NoError(t, err) + } + + if opts.Version != consensusspec.DataVersionUnknown { + version = opts.Version + } + } + + builderPk, err := StrToPhase0Pubkey(builderPubkey) + require.NoError(t, err) + + bidTrace := &apiv1.BidTrace{ //nolint:exhaustruct + BuilderPubkey: builderPk, + Value: value, + Slot: slot, + ParentHash: parentHash, + ProposerPubkey: proposerPk, + } + + if version == consensusspec.DataVersionDeneb { + payload = &VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct + Version: version, + Deneb: &deneb.SubmitBlockRequest{ + Message: bidTrace, + ExecutionPayload: &denebspec.ExecutionPayload{ //nolint:exhaustruct + BaseFeePerGas: uint256.NewInt(0), + }, + BlobsBundle: &deneb.BlobsBundle{}, //nolint:exhaustruct + Signature: phase0.BLSSignature{}, + }, + }, + } + } else { + payload = &VersionedSubmitBlockRequest{ + VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct + Version: version, + Capella: &capella.SubmitBlockRequest{ + Message: bidTrace, + ExecutionPayload: &capellaspec.ExecutionPayload{}, //nolint:exhaustruct + Signature: phase0.BLSSignature{}, + }, + }, + } + } + + getHeaderResponse, err = BuildGetHeaderResponse(payload, &relaySk, &relayPk, domain) + require.NoError(t, err) + + getPayloadResponse, err = BuildGetPayloadResponse(payload) + require.NoError(t, err) + + return payload, getPayloadResponse, getHeaderResponse +} + func LoadGzippedBytes(t *testing.T, filename string) []byte { t.Helper() fi, err := os.Open(filename) diff --git a/common/utils.go b/common/utils.go index 211786e9..b6da48d5 100644 --- a/common/utils.go +++ b/common/utils.go @@ -12,25 +12,17 @@ import ( "os" "strconv" "strings" - "testing" "time" "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" - 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" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/flashbots/go-boost-utils/bls" "github.com/flashbots/go-boost-utils/ssz" "github.com/flashbots/go-boost-utils/types" "github.com/holiman/uint256" - "github.com/stretchr/testify/require" ) var ( @@ -177,97 +169,6 @@ func StrToPhase0Hash(s string) (ret phase0.Hash32, err error) { return ret, nil } -type CreateTestBlockSubmissionOpts struct { - relaySk bls.SecretKey - relayPk phase0.BLSPubKey - domain phase0.Domain - - Version consensusspec.DataVersion - Slot uint64 - ParentHash string - ProposerPubkey string -} - -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 - - slot := uint64(0) - relaySk := bls.SecretKey{} - relayPk := phase0.BLSPubKey{} - domain := phase0.Domain{} - proposerPk := phase0.BLSPubKey{} - parentHash := phase0.Hash32{} - version := consensusspec.DataVersionCapella - - if opts != nil { - relaySk = opts.relaySk - relayPk = opts.relayPk - domain = opts.domain - slot = opts.Slot - - if opts.ProposerPubkey != "" { - proposerPk, err = StrToPhase0Pubkey(opts.ProposerPubkey) - require.NoError(t, err) - } - - if opts.ParentHash != "" { - parentHash, err = StrToPhase0Hash(opts.ParentHash) - require.NoError(t, err) - } - - if opts.Version != consensusspec.DataVersionUnknown { - version = opts.Version - } - } - - builderPk, err := StrToPhase0Pubkey(builderPubkey) - require.NoError(t, err) - - bidTrace := &apiv1.BidTrace{ //nolint:exhaustruct - BuilderPubkey: builderPk, - Value: value, - Slot: slot, - ParentHash: parentHash, - ProposerPubkey: proposerPk, - } - - if version == consensusspec.DataVersionDeneb { - payload = &VersionedSubmitBlockRequest{ - VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct - Version: version, - Deneb: &deneb.SubmitBlockRequest{ - Message: bidTrace, - ExecutionPayload: &denebspec.ExecutionPayload{ //nolint:exhaustruct - BaseFeePerGas: uint256.NewInt(0), - }, - BlobsBundle: &deneb.BlobsBundle{}, //nolint:exhaustruct - Signature: phase0.BLSSignature{}, - }, - }, - } - } else { - payload = &VersionedSubmitBlockRequest{ - VersionedSubmitBlockRequest: spec.VersionedSubmitBlockRequest{ //nolint:exhaustruct - Version: version, - Capella: &capella.SubmitBlockRequest{ - Message: bidTrace, - ExecutionPayload: &capellaspec.ExecutionPayload{}, //nolint:exhaustruct - Signature: phase0.BLSSignature{}, - }, - }, - } - } - - getHeaderResponse, err = BuildGetHeaderResponse(payload, &relaySk, &relayPk, domain) - require.NoError(t, err) - - getPayloadResponse, err = BuildGetPayloadResponse(payload) - require.NoError(t, err) - - return payload, getPayloadResponse, getHeaderResponse -} - // GetEnvDurationSec returns the value of the environment variable as duration in seconds, // or defaultValue if the environment variable doesn't exist or is not a valid integer func GetEnvDurationSec(key string, defaultValueSec int) time.Duration { diff --git a/database/database_test.go b/database/database_test.go index 2de0bb31..065d30dc 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -9,6 +9,7 @@ import ( apiv1 "github.com/attestantio/go-builder-client/api/v1" consensusapi "github.com/attestantio/go-eth2-client/api" + "github.com/attestantio/go-eth2-client/api/v1/deneb" 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" @@ -85,7 +86,7 @@ func insertTestBuilder(t *testing.T, db IDatabaseService) string { ProposerFeeRecipient: feeRecipient, Value: uint256.NewInt(collateral), }, - }) + }, consensusspec.DataVersionDeneb) entry, err := db.SaveBuilderBlockSubmission(&req, nil, nil, time.Now(), time.Now().Add(time.Second), true, true, profile, optimisticSubmission) require.NoError(t, err) err = db.UpsertBlockBuilderEntryAfterSubmission(entry, false) @@ -292,7 +293,6 @@ func TestSetBlockBuilderCollateral(t *testing.T) { } func TestInsertBuilderDemotion(t *testing.T) { - db := resetDatabase(t) pk, sk := getTestKeyPair(t) var testBlockHash phase0.Hash32 hashSlice, err := hexutil.Decode(blockHashStr) @@ -308,20 +308,37 @@ func TestInsertBuilderDemotion(t *testing.T) { Value: uint256.NewInt(collateral), }, } - req := common.TestBuilderSubmitBlockRequest(sk, trace) - err = db.InsertBuilderDemotion(&req, errFoo) - require.NoError(t, err) - entry, err := db.GetBuilderDemotion(trace) - require.NoError(t, err) - require.Equal(t, slot, entry.Slot) - require.Equal(t, pk.String(), entry.BuilderPubkey) - require.Equal(t, blockHashStr, entry.BlockHash) + cases := []struct { + name string + req common.VersionedSubmitBlockRequest + }{ + { + name: "Capella", + req: common.TestBuilderSubmitBlockRequest(sk, trace, consensusspec.DataVersionCapella), + }, { + name: "Deneb", + req: common.TestBuilderSubmitBlockRequest(sk, trace, consensusspec.DataVersionDeneb), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + db := resetDatabase(t) + + err = db.InsertBuilderDemotion(&c.req, errFoo) + require.NoError(t, err) + + entry, err := db.GetBuilderDemotion(trace) + require.NoError(t, err) + require.Equal(t, slot, entry.Slot) + require.Equal(t, pk.String(), entry.BuilderPubkey) + require.Equal(t, blockHashStr, entry.BlockHash) + }) + } } func TestUpdateBuilderDemotion(t *testing.T) { - db := resetDatabase(t) - pk, sk := getTestKeyPair(t) var testBlockHash phase0.Hash32 hashSlice, err := hexutil.Decode(blockHashStr) @@ -336,43 +353,68 @@ func TestUpdateBuilderDemotion(t *testing.T) { Value: uint256.NewInt(collateral), }, } - req := common.TestBuilderSubmitBlockRequest(sk, bt) - // Should return ErrNoRows because there is no demotion yet. - demotion, err := db.GetBuilderDemotion(bt) - require.Equal(t, sql.ErrNoRows, err) - require.Nil(t, demotion) - - // Insert demotion - err = db.InsertBuilderDemotion(&req, errFoo) - require.NoError(t, err) - - // Now demotion should show up. - demotion, err = db.GetBuilderDemotion(bt) - require.NoError(t, err) - - // Signed block and validation should be invalid and empty. - require.False(t, demotion.SignedBeaconBlock.Valid) - require.Empty(t, demotion.SignedBeaconBlock.String) - require.False(t, demotion.SignedValidatorRegistration.Valid) - require.Empty(t, demotion.SignedValidatorRegistration.String) - // Update demotion with the signedBlock and signedRegistration. - bb := &common.VersionedSignedBlockRequest{ - VersionedBlockRequest: consensusapi.VersionedBlockRequest{ - Version: consensusspec.DataVersionCapella, - Capella: &consensuscapella.SignedBeaconBlock{}, + cases := []struct { + name string + req common.VersionedSubmitBlockRequest + beaconBlock common.VersionedSignedBlockRequest + }{ + { + name: "Capella", + req: common.TestBuilderSubmitBlockRequest(sk, bt, consensusspec.DataVersionCapella), + beaconBlock: common.VersionedSignedBlockRequest{ + VersionedBlockRequest: consensusapi.VersionedBlockRequest{ + Version: consensusspec.DataVersionCapella, + Capella: &consensuscapella.SignedBeaconBlock{}, + }, + }, + }, { + name: "Deneb", + req: common.TestBuilderSubmitBlockRequest(sk, bt, consensusspec.DataVersionDeneb), + beaconBlock: common.VersionedSignedBlockRequest{ + VersionedBlockRequest: consensusapi.VersionedBlockRequest{ + Version: consensusspec.DataVersionDeneb, + Deneb: &deneb.SignedBlockContents{}, + }, + }, }, } - err = db.UpdateBuilderDemotion(bt, bb, &apiv1.SignedValidatorRegistration{}) - require.NoError(t, err) - // Signed block and validation should now be valid and non-empty. - demotion, err = db.GetBuilderDemotion(bt) - require.NoError(t, err) - require.True(t, demotion.SignedBeaconBlock.Valid) - require.NotEmpty(t, demotion.SignedBeaconBlock.String) - require.True(t, demotion.SignedValidatorRegistration.Valid) - require.NotEmpty(t, demotion.SignedValidatorRegistration.String) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + db := resetDatabase(t) + // Should return ErrNoRows because there is no demotion yet. + demotion, err := db.GetBuilderDemotion(bt) + require.Equal(t, sql.ErrNoRows, err) + require.Nil(t, demotion) + + // Insert demotion + err = db.InsertBuilderDemotion(&c.req, errFoo) + require.NoError(t, err) + + // Now demotion should show up. + demotion, err = db.GetBuilderDemotion(bt) + require.NoError(t, err) + + // Signed block and validation should be invalid and empty. + require.False(t, demotion.SignedBeaconBlock.Valid) + require.Empty(t, demotion.SignedBeaconBlock.String) + require.False(t, demotion.SignedValidatorRegistration.Valid) + require.Empty(t, demotion.SignedValidatorRegistration.String) + + // Update demotion with the signedBlock and signedRegistration. + err = db.UpdateBuilderDemotion(bt, &c.beaconBlock, &apiv1.SignedValidatorRegistration{}) + require.NoError(t, err) + + // Signed block and validation should now be valid and non-empty. + demotion, err = db.GetBuilderDemotion(bt) + require.NoError(t, err) + require.True(t, demotion.SignedBeaconBlock.Valid) + require.NotEmpty(t, demotion.SignedBeaconBlock.String) + require.True(t, demotion.SignedValidatorRegistration.Valid) + require.NotEmpty(t, demotion.SignedValidatorRegistration.String) + }) + } } func TestGetBlockSubmissionEntry(t *testing.T) { diff --git a/services/api/blocksim_ratelimiter.go b/services/api/blocksim_ratelimiter.go index c5633185..8de564bd 100644 --- a/services/api/blocksim_ratelimiter.go +++ b/services/api/blocksim_ratelimiter.go @@ -12,6 +12,7 @@ import ( "sync/atomic" "time" + "github.com/attestantio/go-eth2-client/spec" "github.com/flashbots/go-utils/cli" "github.com/flashbots/go-utils/jsonrpc" "github.com/flashbots/mev-boost-relay/common" @@ -73,7 +74,7 @@ func (b *BlockSimulationRateLimiter) Send(context context.Context, payload *comm if payload.Capella == nil { return ErrNoCapellaPayload, nil } - // TODO: add deneb support. + submission, err := common.GetBlockSubmissionInfo(&payload.VersionedSubmitBlockRequest) if err != nil { return err, nil @@ -90,7 +91,12 @@ func (b *BlockSimulationRateLimiter) Send(context context.Context, payload *comm } // Create and fire off JSON-RPC request - simReq = jsonrpc.NewJSONRPCRequest("1", "flashbots_validateBuilderSubmissionV2", payload) + switch payload.Version { + case spec.DataVersionCapella: + simReq = jsonrpc.NewJSONRPCRequest("1", "flashbots_validateBuilderSubmissionV2", payload) + case spec.DataVersionDeneb: + simReq = jsonrpc.NewJSONRPCRequest("1", "flashbots_validateBuilderSubmissionV3", payload) + } _, requestErr, validationErr = SendJSONRPCRequest(&b.client, *simReq, b.blockSimURL, headers) return requestErr, validationErr } diff --git a/services/api/optimistic_test.go b/services/api/optimistic_test.go index 61ebf166..6e560793 100644 --- a/services/api/optimistic_test.go +++ b/services/api/optimistic_test.go @@ -13,6 +13,7 @@ import ( "github.com/alicebob/miniredis/v2" apiv1 "github.com/attestantio/go-builder-client/api/v1" + "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/attestantio/go-eth2-client/spec/phase0" @@ -41,7 +42,7 @@ var ( errFake = fmt.Errorf("foo error") ) -func getTestBidTrace(pubkey phase0.BLSPubKey, value uint64) *common.BidTraceV2 { +func getTestBidTrace(pubkey phase0.BLSPubKey, value, slot uint64) *common.BidTraceV2 { return &common.BidTraceV2{ BidTrace: apiv1.BidTrace{ Slot: slot, @@ -56,7 +57,9 @@ type blockRequestOpts struct { pubkey phase0.BLSPubKey secretkey *bls.SecretKey blockValue uint64 + slot uint64 domain phase0.Domain + version spec.DataVersion } func startTestBackend(t *testing.T) (*phase0.BLSPubKey, *bls.SecretKey, *testBackend) { @@ -134,8 +137,8 @@ func runOptimisticBlockSubmission(t *testing.T, opts blockRequestOpts, simErr er simulationError: simErr, } - req := common.TestBuilderSubmitBlockRequest(opts.secretkey, getTestBidTrace(opts.pubkey, opts.blockValue)) - rr := backend.request(http.MethodPost, pathSubmitNewBlock, req.Capella) + req := common.TestBuilderSubmitBlockRequest(opts.secretkey, getTestBidTrace(opts.pubkey, opts.blockValue, opts.slot), opts.version) + rr := backend.request(http.MethodPost, pathSubmitNewBlock, &req) // Let updates happen async. time.Sleep(100 * time.Millisecond) @@ -145,23 +148,49 @@ func runOptimisticBlockSubmission(t *testing.T, opts blockRequestOpts, simErr er func TestSimulateBlock(t *testing.T) { cases := []struct { description string + version spec.DataVersion + slot uint64 simulationError error expectError bool }{ { - description: "success", + description: "success_capella", + version: spec.DataVersionCapella, }, { - description: "simulation_error", + description: "simulation_error_capella", + version: spec.DataVersionCapella, simulationError: errFake, expectError: true, }, { - description: "block_already_known", + description: "block_already_known_capella", + version: spec.DataVersionCapella, simulationError: fmt.Errorf(ErrBlockAlreadyKnown), //nolint:goerr113 }, { - description: "missing_trie_node", + description: "missing_trie_node_capella", + version: spec.DataVersionCapella, + simulationError: fmt.Errorf(ErrMissingTrieNode + "23e21f94cd97b3b27ae5c758277639dd387a6e3da5923c5485f24ec6c71e16b8 (path ) "), //nolint:goerr113 + }, + { + description: "success_deneb", + version: spec.DataVersionDeneb, + }, + { + description: "simulation_error_deneb", + version: spec.DataVersionDeneb, + simulationError: errFake, + expectError: true, + }, + { + description: "block_already_known_deneb", + version: spec.DataVersionDeneb, + simulationError: fmt.Errorf(ErrBlockAlreadyKnown), //nolint:goerr113 + }, + { + description: "missing_trie_node_deneb", + version: spec.DataVersionDeneb, simulationError: fmt.Errorf(ErrMissingTrieNode + "23e21f94cd97b3b27ae5c758277639dd387a6e3da5923c5485f24ec6c71e16b8 (path ) "), //nolint:goerr113 }, } @@ -181,7 +210,7 @@ func TestSimulateBlock(t *testing.T) { }, req: &common.BuilderBlockValidationRequest{ VersionedSubmitBlockRequest: common.TestBuilderSubmitBlockRequest( - secretkey, getTestBidTrace(*pubkey, collateral)), + secretkey, getTestBidTrace(*pubkey, collateral, slot), tc.version), }, }) if tc.expectError { @@ -195,21 +224,41 @@ func TestProcessOptimisticBlock(t *testing.T) { cases := []struct { description string wantStatus common.BuilderStatus + version spec.DataVersion simulationError error }{ { - description: "success", + description: "success_capella", wantStatus: common.BuilderStatus{ IsOptimistic: true, IsHighPrio: true, }, + version: spec.DataVersionCapella, }, { - description: "simulation_error", + description: "simulation_error_capella", wantStatus: common.BuilderStatus{ IsOptimistic: false, IsHighPrio: true, }, + version: spec.DataVersionCapella, + simulationError: errFake, + }, + { + description: "success_deneb", + wantStatus: common.BuilderStatus{ + IsOptimistic: true, + IsHighPrio: true, + }, + version: spec.DataVersionDeneb, + }, + { + description: "simulation_error_deneb", + wantStatus: common.BuilderStatus{ + IsOptimistic: false, + IsHighPrio: true, + }, + version: spec.DataVersionDeneb, simulationError: errFake, }, } @@ -231,7 +280,7 @@ func TestProcessOptimisticBlock(t *testing.T) { }, req: &common.BuilderBlockValidationRequest{ VersionedSubmitBlockRequest: common.TestBuilderSubmitBlockRequest( - secretkey, getTestBidTrace(*pubkey, collateral)), + secretkey, getTestBidTrace(*pubkey, collateral, slot), tc.version), }, }, simResultC) @@ -260,25 +309,48 @@ func TestProcessOptimisticBlock(t *testing.T) { } func TestDemoteBuilder(t *testing.T) { - wantStatus := common.BuilderStatus{ - IsOptimistic: false, - IsHighPrio: true, + cases := []struct { + description string + wantStatus common.BuilderStatus + version spec.DataVersion + }{ + { + description: "capella", + wantStatus: common.BuilderStatus{ + IsOptimistic: false, + IsHighPrio: true, + }, + version: spec.DataVersionCapella, + }, + { + description: "deneb", + wantStatus: common.BuilderStatus{ + IsOptimistic: false, + IsHighPrio: true, + }, + version: spec.DataVersionDeneb, + }, } - pubkey, secretkey, backend := startTestBackend(t) - pkStr := pubkey.String() - req := common.TestBuilderSubmitBlockRequest(secretkey, getTestBidTrace(*pubkey, collateral)) - backend.relay.demoteBuilder(pkStr, &req, errFake) - // Check status in db. - builder, err := backend.relay.db.GetBlockBuilderByPubkey(pkStr) - require.NoError(t, err) - require.Equal(t, wantStatus.IsOptimistic, builder.IsOptimistic) - require.Equal(t, wantStatus.IsHighPrio, builder.IsHighPrio) + for _, tc := range cases { + t.Run(tc.description, func(t *testing.T) { + pubkey, secretkey, backend := startTestBackend(t) + pkStr := pubkey.String() + req := common.TestBuilderSubmitBlockRequest(secretkey, getTestBidTrace(*pubkey, collateral, slot), tc.version) + backend.relay.demoteBuilder(pkStr, &req, errFake) - // Check demotion and refund statuses. - mockDB, ok := backend.relay.db.(*database.MockDB) - require.True(t, ok) - require.True(t, mockDB.Demotions[pkStr]) + // Check status in db. + builder, err := backend.relay.db.GetBlockBuilderByPubkey(pkStr) + require.NoError(t, err) + require.Equal(t, tc.wantStatus.IsOptimistic, builder.IsOptimistic) + require.Equal(t, tc.wantStatus.IsHighPrio, builder.IsHighPrio) + + // Check demotion and refund statuses. + mockDB, ok := backend.relay.db.(*database.MockDB) + require.True(t, ok) + require.True(t, mockDB.Demotions[pkStr]) + }) + } } func TestPrepareBuildersForSlot(t *testing.T) { @@ -303,9 +375,50 @@ func TestBuilderApiSubmitNewBlockOptimistic(t *testing.T) { expectDemotion bool httpCode uint64 blockValue uint64 + slot uint64 + version spec.DataVersion }{ { - description: "success_value_less_than_collateral", + description: "success_value_less_than_collateral_capella", + wantStatus: common.BuilderStatus{ + IsOptimistic: true, + IsHighPrio: true, + }, + simulationError: nil, + expectDemotion: false, + httpCode: 200, // success + blockValue: collateral - 1, + slot: slot, + version: spec.DataVersionCapella, + }, + { + description: "success_value_greater_than_collateral_capella", + wantStatus: common.BuilderStatus{ + IsOptimistic: true, + IsHighPrio: true, + }, + simulationError: nil, + expectDemotion: false, + httpCode: 200, // success + blockValue: collateral + 1, + slot: slot, + version: spec.DataVersionCapella, + }, + { + description: "failure_value_more_than_collateral_capella", + wantStatus: common.BuilderStatus{ + IsOptimistic: true, + IsHighPrio: true, + }, + simulationError: errFake, + expectDemotion: false, + httpCode: 400, // failure (in pessimistic mode, block sim failure happens in response path) + blockValue: collateral + 1, + slot: slot, + version: spec.DataVersionCapella, + }, + { + description: "success_value_less_than_collateral_deneb", wantStatus: common.BuilderStatus{ IsOptimistic: true, IsHighPrio: true, @@ -314,9 +427,11 @@ func TestBuilderApiSubmitNewBlockOptimistic(t *testing.T) { expectDemotion: false, httpCode: 200, // success blockValue: collateral - 1, + slot: slot + 32, + version: spec.DataVersionDeneb, }, { - description: "success_value_greater_than_collateral", + description: "success_value_greater_than_collateral_deneb", wantStatus: common.BuilderStatus{ IsOptimistic: true, IsHighPrio: true, @@ -325,9 +440,11 @@ func TestBuilderApiSubmitNewBlockOptimistic(t *testing.T) { expectDemotion: false, httpCode: 200, // success blockValue: collateral + 1, + slot: slot + 32, + version: spec.DataVersionDeneb, }, { - description: "failure_value_more_than_collateral", + description: "failure_value_more_than_collateral_deneb", wantStatus: common.BuilderStatus{ IsOptimistic: true, IsHighPrio: true, @@ -336,21 +453,25 @@ func TestBuilderApiSubmitNewBlockOptimistic(t *testing.T) { expectDemotion: false, httpCode: 400, // failure (in pessimistic mode, block sim failure happens in response path) blockValue: collateral + 1, + slot: slot + 32, + version: spec.DataVersionDeneb, }, } for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { pubkey, secretkey, backend := startTestBackend(t) - backend.relay.optimisticSlot.Store(slot) + backend.relay.optimisticSlot.Store(tc.slot) backend.relay.capellaEpoch = 1 + backend.relay.denebEpoch = 2 + backend.relay.proposerDutiesMap[tc.slot] = backend.relay.proposerDutiesMap[slot] randaoHash, err := utils.HexToHash(randao) require.NoError(t, err) withRoot, err := ComputeWithdrawalsRoot([]*consensuscapella.Withdrawal{}) require.NoError(t, err) backend.relay.payloadAttributes[emptyHash] = payloadAttributesHelper{ - slot: slot, + slot: tc.slot, withdrawalsRoot: withRoot, payloadAttributes: beaconclient.PayloadAttributes{ PrevRandao: randaoHash.String(), @@ -361,7 +482,9 @@ func TestBuilderApiSubmitNewBlockOptimistic(t *testing.T) { secretkey: secretkey, pubkey: *pubkey, blockValue: tc.blockValue, + slot: tc.slot, domain: backend.relay.opts.EthNetDetails.DomainBuilder, + version: tc.version, }, tc.simulationError, backend) // Check http code. diff --git a/services/api/service.go b/services/api/service.go index 304737dd..a2bb2fc4 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -536,6 +536,14 @@ func (api *RelayAPI) StopServer() (err error) { return api.srv.Shutdown(context.Background()) } +func (api *RelayAPI) isCapella(slot uint64) bool { + return hasReachedFork(slot, api.capellaEpoch) && !hasReachedFork(slot, api.denebEpoch) +} + +func (api *RelayAPI) isDeneb(slot uint64) bool { + return hasReachedFork(slot, api.denebEpoch) +} + func (api *RelayAPI) startValidatorRegistrationDBProcessor() { for valReg := range api.validatorRegC { err := api.datastore.SaveValidatorRegistration(valReg) @@ -1627,11 +1635,13 @@ func (api *RelayAPI) checkSubmissionPayloadAttrs(w http.ResponseWriter, log *log } func (api *RelayAPI) checkSubmissionSlotDetails(w http.ResponseWriter, log *logrus.Entry, headSlot uint64, payload *common.VersionedSubmitBlockRequest, submission *common.BlockSubmissionInfo) bool { - if hasReachedFork(submission.Slot, api.denebEpoch) && payload.Deneb == nil { + if api.isDeneb(submission.Slot) && payload.Deneb == nil { log.Info("rejecting submission - non deneb payload for deneb fork") api.RespondError(w, http.StatusBadRequest, "not deneb payload") return false - } else if hasReachedFork(submission.Slot, api.capellaEpoch) && payload.Capella == nil { + } + + if api.isCapella(submission.Slot) && payload.Capella == nil { log.Info("rejecting submission - non capella payload for capella fork") api.RespondError(w, http.StatusBadRequest, "not capella payload") return false