diff --git a/beacon-chain/blockchain/BUILD.bazel b/beacon-chain/blockchain/BUILD.bazel index e9fd0c258e33..86d90592aa86 100644 --- a/beacon-chain/blockchain/BUILD.bazel +++ b/beacon-chain/blockchain/BUILD.bazel @@ -64,6 +64,7 @@ go_library( "//config/params:go_default_library", "//consensus-types/blocks:go_default_library", "//consensus-types/interfaces:go_default_library", + "//consensus-types/payload-attribute:go_default_library", "//consensus-types/primitives:go_default_library", "//crypto/bls:go_default_library", "//encoding/bytesutil:go_default_library", diff --git a/beacon-chain/blockchain/error.go b/beacon-chain/blockchain/error.go index e5aca02f506c..1e3a332f0a2b 100644 --- a/beacon-chain/blockchain/error.go +++ b/beacon-chain/blockchain/error.go @@ -19,8 +19,6 @@ var ( errInvalidNilSummary = errors.New("nil summary returned from the DB") // errWrongBlockCount is returned when the wrong number of blocks or block roots is used errWrongBlockCount = errors.New("wrong number of blocks or block roots") - // block is not a valid optimistic candidate block - errNotOptimisticCandidate = errors.New("block is not suitable for optimistic sync") // errBlockNotFoundInCacheOrDB is returned when a block is not found in the cache or DB. errBlockNotFoundInCacheOrDB = errors.New("block not found in cache or db") // errNilStateFromStategen is returned when a nil state is returned from the state generator. diff --git a/beacon-chain/blockchain/execution_engine.go b/beacon-chain/blockchain/execution_engine.go index 8ff601beb376..bf35cc61dd1e 100644 --- a/beacon-chain/blockchain/execution_engine.go +++ b/beacon-chain/blockchain/execution_engine.go @@ -15,9 +15,11 @@ import ( "github.com/prysmaticlabs/prysm/v3/config/params" consensusblocks "github.com/prysmaticlabs/prysm/v3/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v3/consensus-types/interfaces" + payloadattribute "github.com/prysmaticlabs/prysm/v3/consensus-types/payload-attribute" types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" + "github.com/prysmaticlabs/prysm/v3/runtime/version" "github.com/prysmaticlabs/prysm/v3/time/slots" "github.com/sirupsen/logrus" "go.opencensus.io/trace" @@ -67,10 +69,7 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *notifyForkcho } nextSlot := s.CurrentSlot() + 1 // Cache payload ID for next slot proposer. - hasAttr, attr, proposerId, err := s.getPayloadAttribute(ctx, arg.headState, nextSlot) - if err != nil { - log.WithError(err).Error("Could not get head payload attribute") - } + hasAttr, attr, proposerId := s.getPayloadAttribute(ctx, arg.headState, nextSlot) payloadID, lastValidHash, err := s.cfg.ExecutionEngineCaller.ForkchoiceUpdated(ctx, fcs, attr) if err != nil { @@ -149,7 +148,8 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *notifyForkcho log.WithError(err).Error("Could not set head root to valid") return nil, nil } - if hasAttr && payloadID != nil { // If the forkchoice update call has an attribute, update the proposer payload ID cache. + // If the forkchoice update call has an attribute, update the proposer payload ID cache. + if hasAttr && payloadID != nil { var pId [8]byte copy(pId[:], payloadID[:]) s.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(nextSlot, proposerId, pId, arg.headRoot) @@ -250,22 +250,25 @@ func (s *Service) notifyNewPayload(ctx context.Context, postStateVersion int, // getPayloadAttributes returns the payload attributes for the given state and slot. // The attribute is required to initiate a payload build process in the context of an `engine_forkchoiceUpdated` call. -func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, slot types.Slot) (bool, *enginev1.PayloadAttributes, types.ValidatorIndex, error) { +func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, slot types.Slot) (bool, payloadattribute.Attributer, types.ValidatorIndex) { + emptyAttri := payloadattribute.EmptyWithVersion(st.Version()) // Root is `[32]byte{}` since we are retrieving proposer ID of a given slot. During insertion at assignment the root was not known. proposerID, _, ok := s.cfg.ProposerSlotIndexCache.GetProposerPayloadIDs(slot, [32]byte{} /* root */) if !ok { // There's no need to build attribute if there is no proposer for slot. - return false, nil, 0, nil + return false, emptyAttri, 0 } // Get previous randao. st = st.Copy() st, err := transition.ProcessSlotsIfPossible(ctx, st, slot) if err != nil { - return false, nil, 0, err + log.WithError(err).Error("Could not process slots to get payload attribute") + return false, emptyAttri, 0 } prevRando, err := helpers.RandaoMix(st, time.CurrentEpoch(st)) if err != nil { - return false, nil, 0, nil + log.WithError(err).Error("Could not get randao mix to get payload attribute") + return false, emptyAttri, 0 } // Get fee recipient. @@ -283,7 +286,8 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, "Please refer to our documentation for instructions") } case err != nil: - return false, nil, 0, errors.Wrap(err, "could not get fee recipient in db") + log.WithError(err).Error("Could not get fee recipient to get payload attribute") + return false, emptyAttri, 0 default: feeRecipient = recipient } @@ -291,14 +295,44 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, // Get timestamp. t, err := slots.ToTime(uint64(s.genesisTime.Unix()), slot) if err != nil { - return false, nil, 0, err + log.WithError(err).Error("Could not get timestamp to get payload attribute") + return false, emptyAttri, 0 } - attr := &enginev1.PayloadAttributes{ - Timestamp: uint64(t.Unix()), - PrevRandao: prevRando, - SuggestedFeeRecipient: feeRecipient.Bytes(), + + var attr payloadattribute.Attributer + switch st.Version() { + case version.Capella: + withdrawals, err := st.ExpectedWithdrawals() + if err != nil { + log.WithError(err).Error("Could not get expected withdrawals to get payload attribute") + return false, emptyAttri, 0 + } + attr, err = payloadattribute.New(&enginev1.PayloadAttributesV2{ + Timestamp: uint64(t.Unix()), + PrevRandao: prevRando, + SuggestedFeeRecipient: feeRecipient.Bytes(), + Withdrawals: withdrawals, + }) + if err != nil { + log.WithError(err).Error("Could not get payload attribute") + return false, emptyAttri, 0 + } + case version.Bellatrix: + attr, err = payloadattribute.New(&enginev1.PayloadAttributes{ + Timestamp: uint64(t.Unix()), + PrevRandao: prevRando, + SuggestedFeeRecipient: feeRecipient.Bytes(), + }) + if err != nil { + log.WithError(err).Error("Could not get payload attribute") + return false, emptyAttri, 0 + } + default: + log.WithField("version", st.Version()).Error("Could not get payload attribute due to unknown state version") + return false, emptyAttri, 0 } - return true, attr, proposerID, nil + + return true, attr, proposerID } // removeInvalidBlockAndState removes the invalid block and its corresponding state from the cache and DB. diff --git a/beacon-chain/blockchain/execution_engine_test.go b/beacon-chain/blockchain/execution_engine_test.go index 31f960ec3f12..8d044bd4cc30 100644 --- a/beacon-chain/blockchain/execution_engine_test.go +++ b/beacon-chain/blockchain/execution_engine_test.go @@ -792,8 +792,8 @@ func Test_GetPayloadAttribute(t *testing.T) { // Cache miss service, err := NewService(ctx, opts...) require.NoError(t, err) - hasPayload, _, vId, err := service.getPayloadAttribute(ctx, nil, 0) - require.NoError(t, err) + st, _ := util.DeterministicGenesisStateBellatrix(t, 1) + hasPayload, _, vId := service.getPayloadAttribute(ctx, st, 0) require.Equal(t, false, hasPayload) require.Equal(t, types.ValidatorIndex(0), vId) @@ -801,24 +801,65 @@ func Test_GetPayloadAttribute(t *testing.T) { suggestedVid := types.ValidatorIndex(1) slot := types.Slot(1) service.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, suggestedVid, [8]byte{}, [32]byte{}) - st, _ := util.DeterministicGenesisState(t, 1) hook := logTest.NewGlobal() - hasPayload, attr, vId, err := service.getPayloadAttribute(ctx, st, slot) - require.NoError(t, err) + hasPayload, attr, vId := service.getPayloadAttribute(ctx, st, slot) require.Equal(t, true, hasPayload) require.Equal(t, suggestedVid, vId) - require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient).String()) + require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String()) require.LogsContain(t, hook, "Fee recipient is currently using the burn address") // Cache hit, advance state, has fee recipient suggestedAddr := common.HexToAddress("123") require.NoError(t, service.cfg.BeaconDB.SaveFeeRecipientsByValidatorIDs(ctx, []types.ValidatorIndex{suggestedVid}, []common.Address{suggestedAddr})) service.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, suggestedVid, [8]byte{}, [32]byte{}) - hasPayload, attr, vId, err = service.getPayloadAttribute(ctx, st, slot) + hasPayload, attr, vId = service.getPayloadAttribute(ctx, st, slot) + require.Equal(t, true, hasPayload) + require.Equal(t, suggestedVid, vId) + require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient())) +} + +func Test_GetPayloadAttributeV2(t *testing.T) { + ctx := context.Background() + beaconDB := testDB.SetupDB(t) + opts := []Option{ + WithDatabase(beaconDB), + WithStateGen(stategen.New(beaconDB, doublylinkedtree.New())), + WithProposerIdsCache(cache.NewProposerPayloadIDsCache()), + } + + // Cache miss + service, err := NewService(ctx, opts...) + require.NoError(t, err) + st, _ := util.DeterministicGenesisStateCapella(t, 1) + hasPayload, _, vId := service.getPayloadAttribute(ctx, st, 0) + require.Equal(t, false, hasPayload) + require.Equal(t, types.ValidatorIndex(0), vId) + + // Cache hit, advance state, no fee recipient + suggestedVid := types.ValidatorIndex(1) + slot := types.Slot(1) + service.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, suggestedVid, [8]byte{}, [32]byte{}) + hook := logTest.NewGlobal() + hasPayload, attr, vId := service.getPayloadAttribute(ctx, st, slot) + require.Equal(t, true, hasPayload) + require.Equal(t, suggestedVid, vId) + require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String()) + require.LogsContain(t, hook, "Fee recipient is currently using the burn address") + a, err := attr.Withdrawals() require.NoError(t, err) + require.Equal(t, 0, len(a)) + + // Cache hit, advance state, has fee recipient + suggestedAddr := common.HexToAddress("123") + require.NoError(t, service.cfg.BeaconDB.SaveFeeRecipientsByValidatorIDs(ctx, []types.ValidatorIndex{suggestedVid}, []common.Address{suggestedAddr})) + service.cfg.ProposerSlotIndexCache.SetProposerAndPayloadIDs(slot, suggestedVid, [8]byte{}, [32]byte{}) + hasPayload, attr, vId = service.getPayloadAttribute(ctx, st, slot) require.Equal(t, true, hasPayload) require.Equal(t, suggestedVid, vId) - require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient)) + require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient())) + a, err = attr.Withdrawals() + require.NoError(t, err) + require.Equal(t, 0, len(a)) } func Test_UpdateLastValidatedCheckpoint(t *testing.T) { diff --git a/beacon-chain/blockchain/receive_block_test.go b/beacon-chain/blockchain/receive_block_test.go index ef0dec85fbb6..2483d87a0ab5 100644 --- a/beacon-chain/blockchain/receive_block_test.go +++ b/beacon-chain/blockchain/receive_block_test.go @@ -33,7 +33,7 @@ func TestService_ReceiveBlock(t *testing.T) { assert.NoError(t, err) return blk } - params.SetupTestConfigCleanupWithLock(t) + //params.SetupTestConfigCleanupWithLock(t) bc := params.BeaconConfig().Copy() bc.ShardCommitteePeriod = 0 // Required for voluntary exits test in reasonable time. params.OverrideBeaconConfig(bc) diff --git a/beacon-chain/execution/BUILD.bazel b/beacon-chain/execution/BUILD.bazel index 51d801a032c5..8b363460a6a1 100644 --- a/beacon-chain/execution/BUILD.bazel +++ b/beacon-chain/execution/BUILD.bazel @@ -41,6 +41,7 @@ go_library( "//config/params:go_default_library", "//consensus-types/blocks:go_default_library", "//consensus-types/interfaces:go_default_library", + "//consensus-types/payload-attribute:go_default_library", "//container/trie:go_default_library", "//contracts/deposit:go_default_library", "//crypto/hash:go_default_library", @@ -52,6 +53,7 @@ go_library( "//network/authorization:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "//time:go_default_library", "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//:go_default_library", @@ -108,6 +110,7 @@ go_test( "//config/params:go_default_library", "//consensus-types/blocks:go_default_library", "//consensus-types/interfaces:go_default_library", + "//consensus-types/payload-attribute:go_default_library", "//consensus-types/primitives:go_default_library", "//container/trie:go_default_library", "//contracts/deposit:go_default_library", diff --git a/beacon-chain/execution/engine_client.go b/beacon-chain/execution/engine_client.go index fb2ea1b06fb2..1d00d2619e68 100644 --- a/beacon-chain/execution/engine_client.go +++ b/beacon-chain/execution/engine_client.go @@ -19,8 +19,10 @@ import ( "github.com/prysmaticlabs/prysm/v3/config/params" "github.com/prysmaticlabs/prysm/v3/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v3/consensus-types/interfaces" + payloadattribute "github.com/prysmaticlabs/prysm/v3/consensus-types/payload-attribute" "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" pb "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" + "github.com/prysmaticlabs/prysm/v3/runtime/version" "github.com/sirupsen/logrus" "go.opencensus.io/trace" ) @@ -30,6 +32,8 @@ const ( NewPayloadMethod = "engine_newPayloadV1" // ForkchoiceUpdatedMethod v1 request string for JSON-RPC. ForkchoiceUpdatedMethod = "engine_forkchoiceUpdatedV1" + // ForkchoiceUpdatedMethodV2 v2 request string for JSON-RPC. + ForkchoiceUpdatedMethodV2 = "engine_forkchoiceUpdatedV2" // GetPayloadMethod v1 request string for JSON-RPC. GetPayloadMethod = "engine_getPayloadV1" // ExchangeTransitionConfigurationMethod v1 request string for JSON-RPC. @@ -66,7 +70,7 @@ type ExecutionPayloadReconstructor interface { type EngineCaller interface { NewPayload(ctx context.Context, payload interfaces.ExecutionData) ([]byte, error) ForkchoiceUpdated( - ctx context.Context, state *pb.ForkchoiceState, attrs *pb.PayloadAttributes, + ctx context.Context, state *pb.ForkchoiceState, attrs payloadattribute.Attributer, ) (*pb.PayloadIDBytes, []byte, error) GetPayload(ctx context.Context, payloadId [8]byte) (*pb.ExecutionPayload, error) ExchangeTransitionConfiguration( @@ -114,7 +118,7 @@ func (s *Service) NewPayload(ctx context.Context, payload interfaces.ExecutionDa // ForkchoiceUpdated calls the engine_forkchoiceUpdatedV1 method via JSON-RPC. func (s *Service) ForkchoiceUpdated( - ctx context.Context, state *pb.ForkchoiceState, attrs *pb.PayloadAttributes, + ctx context.Context, state *pb.ForkchoiceState, attrs payloadattribute.Attributer, ) (*pb.PayloadIDBytes, []byte, error) { ctx, span := trace.StartSpan(ctx, "powchain.engine-api-client.ForkchoiceUpdated") defer span.End() @@ -127,9 +131,31 @@ func (s *Service) ForkchoiceUpdated( ctx, cancel := context.WithDeadline(ctx, d) defer cancel() result := &ForkchoiceUpdatedResponse{} - err := s.rpcClient.CallContext(ctx, result, ForkchoiceUpdatedMethod, state, attrs) - if err != nil { - return nil, nil, handleRPCError(err) + + if attrs == nil { + return nil, nil, errors.New("nil payload attributer") + } + switch attrs.Version() { + case version.Bellatrix: + a, err := attrs.PbV1() + if err != nil { + return nil, nil, err + } + err = s.rpcClient.CallContext(ctx, result, ForkchoiceUpdatedMethod, state, a) + if err != nil { + return nil, nil, handleRPCError(err) + } + case version.Capella: + a, err := attrs.PbV2() + if err != nil { + return nil, nil, err + } + err = s.rpcClient.CallContext(ctx, result, ForkchoiceUpdatedMethodV2, state, a) + if err != nil { + return nil, nil, handleRPCError(err) + } + default: + return nil, nil, fmt.Errorf("unknown payload attribute version: %v", attrs.Version()) } if result.Status == nil { diff --git a/beacon-chain/execution/engine_client_test.go b/beacon-chain/execution/engine_client_test.go index 06a282c9d697..efb1b7e8225a 100644 --- a/beacon-chain/execution/engine_client_test.go +++ b/beacon-chain/execution/engine_client_test.go @@ -24,6 +24,7 @@ import ( "github.com/prysmaticlabs/prysm/v3/config/params" "github.com/prysmaticlabs/prysm/v3/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v3/consensus-types/interfaces" + payloadattribute "github.com/prysmaticlabs/prysm/v3/consensus-types/payload-attribute" "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" pb "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" "github.com/prysmaticlabs/prysm/v3/testing/assert" @@ -72,7 +73,19 @@ func TestClient_IPC(t *testing.T) { t.Run(ForkchoiceUpdatedMethod, func(t *testing.T) { want, ok := fix["ForkchoiceUpdatedResponse"].(*ForkchoiceUpdatedResponse) require.Equal(t, true, ok) - payloadID, validHash, err := srv.ForkchoiceUpdated(ctx, &pb.ForkchoiceState{}, &pb.PayloadAttributes{}) + p, err := payloadattribute.New(&pb.PayloadAttributes{}) + require.NoError(t, err) + payloadID, validHash, err := srv.ForkchoiceUpdated(ctx, &pb.ForkchoiceState{}, p) + require.NoError(t, err) + require.DeepEqual(t, want.Status.LatestValidHash, validHash) + require.DeepEqual(t, want.PayloadId, payloadID) + }) + t.Run(ForkchoiceUpdatedMethodV2, func(t *testing.T) { + want, ok := fix["ForkchoiceUpdatedResponse"].(*ForkchoiceUpdatedResponse) + require.Equal(t, true, ok) + p, err := payloadattribute.New(&pb.PayloadAttributesV2{}) + require.NoError(t, err) + payloadID, validHash, err := srv.ForkchoiceUpdated(ctx, &pb.ForkchoiceState{}, p) require.NoError(t, err) require.DeepEqual(t, want.Status.LatestValidHash, validHash) require.DeepEqual(t, want.PayloadId, payloadID) @@ -168,12 +181,38 @@ func TestClient_HTTP(t *testing.T) { PrevRandao: []byte("random"), SuggestedFeeRecipient: []byte("suggestedFeeRecipient"), } + p, err := payloadattribute.New(payloadAttributes) + require.NoError(t, err) want, ok := fix["ForkchoiceUpdatedResponse"].(*ForkchoiceUpdatedResponse) require.Equal(t, true, ok) srv := forkchoiceUpdateSetup(t, forkChoiceState, payloadAttributes, want) // We call the RPC method via HTTP and expect a proper result. - payloadID, validHash, err := srv.ForkchoiceUpdated(ctx, forkChoiceState, payloadAttributes) + payloadID, validHash, err := srv.ForkchoiceUpdated(ctx, forkChoiceState, p) + require.NoError(t, err) + require.DeepEqual(t, want.Status.LatestValidHash, validHash) + require.DeepEqual(t, want.PayloadId, payloadID) + }) + t.Run(ForkchoiceUpdatedMethodV2+" VALID status", func(t *testing.T) { + forkChoiceState := &pb.ForkchoiceState{ + HeadBlockHash: []byte("head"), + SafeBlockHash: []byte("safe"), + FinalizedBlockHash: []byte("finalized"), + } + payloadAttributes := &pb.PayloadAttributesV2{ + Timestamp: 1, + PrevRandao: []byte("random"), + SuggestedFeeRecipient: []byte("suggestedFeeRecipient"), + Withdrawals: []*pb.Withdrawal{{ValidatorIndex: 1, Amount: 1}}, + } + p, err := payloadattribute.New(payloadAttributes) + require.NoError(t, err) + want, ok := fix["ForkchoiceUpdatedResponse"].(*ForkchoiceUpdatedResponse) + require.Equal(t, true, ok) + srv := forkchoiceUpdateSetupV2(t, forkChoiceState, payloadAttributes, want) + + // We call the RPC method via HTTP and expect a proper result. + payloadID, validHash, err := srv.ForkchoiceUpdated(ctx, forkChoiceState, p) require.NoError(t, err) require.DeepEqual(t, want.Status.LatestValidHash, validHash) require.DeepEqual(t, want.PayloadId, payloadID) @@ -189,12 +228,38 @@ func TestClient_HTTP(t *testing.T) { PrevRandao: []byte("random"), SuggestedFeeRecipient: []byte("suggestedFeeRecipient"), } + p, err := payloadattribute.New(payloadAttributes) + require.NoError(t, err) want, ok := fix["ForkchoiceUpdatedSyncingResponse"].(*ForkchoiceUpdatedResponse) require.Equal(t, true, ok) client := forkchoiceUpdateSetup(t, forkChoiceState, payloadAttributes, want) // We call the RPC method via HTTP and expect a proper result. - payloadID, validHash, err := client.ForkchoiceUpdated(ctx, forkChoiceState, payloadAttributes) + payloadID, validHash, err := client.ForkchoiceUpdated(ctx, forkChoiceState, p) + require.ErrorIs(t, err, ErrAcceptedSyncingPayloadStatus) + require.DeepEqual(t, (*pb.PayloadIDBytes)(nil), payloadID) + require.DeepEqual(t, []byte(nil), validHash) + }) + t.Run(ForkchoiceUpdatedMethodV2+" SYNCING status", func(t *testing.T) { + forkChoiceState := &pb.ForkchoiceState{ + HeadBlockHash: []byte("head"), + SafeBlockHash: []byte("safe"), + FinalizedBlockHash: []byte("finalized"), + } + payloadAttributes := &pb.PayloadAttributesV2{ + Timestamp: 1, + PrevRandao: []byte("random"), + SuggestedFeeRecipient: []byte("suggestedFeeRecipient"), + Withdrawals: []*pb.Withdrawal{{ValidatorIndex: 1, Amount: 1}}, + } + p, err := payloadattribute.New(payloadAttributes) + require.NoError(t, err) + want, ok := fix["ForkchoiceUpdatedSyncingResponse"].(*ForkchoiceUpdatedResponse) + require.Equal(t, true, ok) + srv := forkchoiceUpdateSetupV2(t, forkChoiceState, payloadAttributes, want) + + // We call the RPC method via HTTP and expect a proper result. + payloadID, validHash, err := srv.ForkchoiceUpdated(ctx, forkChoiceState, p) require.ErrorIs(t, err, ErrAcceptedSyncingPayloadStatus) require.DeepEqual(t, (*pb.PayloadIDBytes)(nil), payloadID) require.DeepEqual(t, []byte(nil), validHash) @@ -210,12 +275,14 @@ func TestClient_HTTP(t *testing.T) { PrevRandao: []byte("random"), SuggestedFeeRecipient: []byte("suggestedFeeRecipient"), } + p, err := payloadattribute.New(payloadAttributes) + require.NoError(t, err) want, ok := fix["ForkchoiceUpdatedInvalidResponse"].(*ForkchoiceUpdatedResponse) require.Equal(t, true, ok) client := forkchoiceUpdateSetup(t, forkChoiceState, payloadAttributes, want) // We call the RPC method via HTTP and expect a proper result. - payloadID, validHash, err := client.ForkchoiceUpdated(ctx, forkChoiceState, payloadAttributes) + payloadID, validHash, err := client.ForkchoiceUpdated(ctx, forkChoiceState, p) require.ErrorIs(t, err, ErrInvalidPayloadStatus) require.DeepEqual(t, (*pb.PayloadIDBytes)(nil), payloadID) require.DeepEqual(t, want.Status.LatestValidHash, validHash) @@ -231,12 +298,14 @@ func TestClient_HTTP(t *testing.T) { PrevRandao: []byte("random"), SuggestedFeeRecipient: []byte("suggestedFeeRecipient"), } + p, err := payloadattribute.New(payloadAttributes) + require.NoError(t, err) want, ok := fix["ForkchoiceUpdatedAcceptedResponse"].(*ForkchoiceUpdatedResponse) require.Equal(t, true, ok) client := forkchoiceUpdateSetup(t, forkChoiceState, payloadAttributes, want) // We call the RPC method via HTTP and expect a proper result. - payloadID, validHash, err := client.ForkchoiceUpdated(ctx, forkChoiceState, payloadAttributes) + payloadID, validHash, err := client.ForkchoiceUpdated(ctx, forkChoiceState, p) require.ErrorIs(t, err, ErrUnknownPayloadStatus) require.DeepEqual(t, (*pb.PayloadIDBytes)(nil), payloadID) require.DeepEqual(t, []byte(nil), validHash) @@ -1362,6 +1431,18 @@ func (*testEngineService) ForkchoiceUpdatedV1( return item } +func (*testEngineService) ForkchoiceUpdatedV2( + _ context.Context, _ *pb.ForkchoiceState, _ *pb.PayloadAttributes, +) *ForkchoiceUpdatedResponse { + fix := fixtures() + item, ok := fix["ForkchoiceUpdatedResponse"].(*ForkchoiceUpdatedResponse) + if !ok { + panic("not found") + } + item.Status.Status = pb.PayloadStatus_VALID + return item +} + func (*testEngineService) NewPayloadV1( _ context.Context, _ *pb.ExecutionPayload, ) *pb.PayloadStatus { @@ -1412,6 +1493,45 @@ func forkchoiceUpdateSetup(t *testing.T, fcs *pb.ForkchoiceState, att *pb.Payloa return service } +func forkchoiceUpdateSetupV2(t *testing.T, fcs *pb.ForkchoiceState, att *pb.PayloadAttributesV2, res *ForkchoiceUpdatedResponse) *Service { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + defer func() { + require.NoError(t, r.Body.Close()) + }() + enc, err := io.ReadAll(r.Body) + require.NoError(t, err) + jsonRequestString := string(enc) + + forkChoiceStateReq, err := json.Marshal(fcs) + require.NoError(t, err) + payloadAttrsReq, err := json.Marshal(att) + require.NoError(t, err) + + // We expect the JSON string RPC request contains the right arguments. + require.Equal(t, true, strings.Contains( + jsonRequestString, string(forkChoiceStateReq), + )) + require.Equal(t, true, strings.Contains( + jsonRequestString, string(payloadAttrsReq), + )) + resp := map[string]interface{}{ + "jsonrpc": "2.0", + "id": 1, + "result": res, + } + err = json.NewEncoder(w).Encode(resp) + require.NoError(t, err) + })) + + rpcClient, err := rpc.DialHTTP(srv.URL) + require.NoError(t, err) + + service := &Service{} + service.rpcClient = rpcClient + return service +} + func newPayloadSetup(t *testing.T, status *pb.PayloadStatus, payload *pb.ExecutionPayload) *Service { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") diff --git a/beacon-chain/execution/testing/BUILD.bazel b/beacon-chain/execution/testing/BUILD.bazel index 0447c0085c69..ce490c70769f 100644 --- a/beacon-chain/execution/testing/BUILD.bazel +++ b/beacon-chain/execution/testing/BUILD.bazel @@ -20,6 +20,7 @@ go_library( "//config/params:go_default_library", "//consensus-types/blocks:go_default_library", "//consensus-types/interfaces:go_default_library", + "//consensus-types/payload-attribute:go_default_library", "//encoding/bytesutil:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", diff --git a/beacon-chain/execution/testing/mock_engine_client.go b/beacon-chain/execution/testing/mock_engine_client.go index b797041757fd..b857ffc28112 100644 --- a/beacon-chain/execution/testing/mock_engine_client.go +++ b/beacon-chain/execution/testing/mock_engine_client.go @@ -11,6 +11,7 @@ import ( "github.com/prysmaticlabs/prysm/v3/config/params" "github.com/prysmaticlabs/prysm/v3/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v3/consensus-types/interfaces" + payloadattribute "github.com/prysmaticlabs/prysm/v3/consensus-types/payload-attribute" "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" pb "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" ) @@ -43,7 +44,7 @@ func (e *EngineClient) NewPayload(_ context.Context, _ interfaces.ExecutionData) // ForkchoiceUpdated -- func (e *EngineClient) ForkchoiceUpdated( - _ context.Context, fcs *pb.ForkchoiceState, _ *pb.PayloadAttributes, + _ context.Context, fcs *pb.ForkchoiceState, _ payloadattribute.Attributer, ) (*pb.PayloadIDBytes, []byte, error) { if e.OverrideValidHash != [32]byte{} && bytesutil.ToBytes32(fcs.HeadBlockHash) == e.OverrideValidHash { return e.PayloadIDBytes, e.ForkChoiceUpdatedResp, nil diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel b/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel index 446b019faeb1..2bd95ff63bca 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel @@ -59,6 +59,7 @@ go_library( "//consensus-types/blocks:go_default_library", "//consensus-types/interfaces:go_default_library", "//consensus-types/primitives:go_default_library", + "//consensus-types/payload-attribute:go_default_library", "//container/trie:go_default_library", "//contracts/deposit:go_default_library", "//crypto/bls:go_default_library", diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_execution_payload.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_execution_payload.go index 6538b2b89f5a..0d88f5a76c13 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_execution_payload.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_execution_payload.go @@ -17,6 +17,7 @@ import ( fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams" "github.com/prysmaticlabs/prysm/v3/config/params" consensusblocks "github.com/prysmaticlabs/prysm/v3/consensus-types/blocks" + payloadattribute "github.com/prysmaticlabs/prysm/v3/consensus-types/payload-attribute" types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" @@ -154,7 +155,13 @@ func (vs *Server) getExecutionPayload(ctx context.Context, slot types.Slot, vIdx PrevRandao: random, SuggestedFeeRecipient: feeRecipient.Bytes(), } - payloadID, _, err := vs.ExecutionEngineCaller.ForkchoiceUpdated(ctx, f, p) + + // This will change in subsequent hardforks like Capella. + pa, err := payloadattribute.New(p) + if err != nil { + return nil, err + } + payloadID, _, err := vs.ExecutionEngineCaller.ForkchoiceUpdated(ctx, f, pa) if err != nil { return nil, errors.Wrap(err, "could not prepare payload") } diff --git a/runtime/prereqs/prereq.go b/runtime/prereqs/prereq.go index b50d01430ea1..cd3b7742e1b2 100644 --- a/runtime/prereqs/prereq.go +++ b/runtime/prereqs/prereq.go @@ -39,6 +39,7 @@ func supportedPlatforms() []platform { {os: "linux", arch: "amd64"}, {os: "linux", arch: "arm64"}, {os: "darwin", arch: "amd64", majorVersion: 10, minorVersion: 14}, + {os: "darwin", arch: "arm64", majorVersion: 12, minorVersion: 5}, {os: "windows", arch: "amd64"}, } } diff --git a/testing/spectest/shared/common/forkchoice/BUILD.bazel b/testing/spectest/shared/common/forkchoice/BUILD.bazel index ea69462fa53e..05a70edb63d8 100644 --- a/testing/spectest/shared/common/forkchoice/BUILD.bazel +++ b/testing/spectest/shared/common/forkchoice/BUILD.bazel @@ -29,6 +29,7 @@ go_library( "//config/params:go_default_library", "//consensus-types/blocks:go_default_library", "//consensus-types/interfaces:go_default_library", + "//consensus-types/payload-attribute:go_default_library", "//consensus-types/primitives:go_default_library", "//encoding/bytesutil:go_default_library", "//proto/engine/v1:go_default_library", diff --git a/testing/spectest/shared/common/forkchoice/service.go b/testing/spectest/shared/common/forkchoice/service.go index 3dacc77e073a..d29e4ff284d4 100644 --- a/testing/spectest/shared/common/forkchoice/service.go +++ b/testing/spectest/shared/common/forkchoice/service.go @@ -18,6 +18,7 @@ import ( "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen" "github.com/prysmaticlabs/prysm/v3/consensus-types/interfaces" + payloadattribute "github.com/prysmaticlabs/prysm/v3/consensus-types/payload-attribute" "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" pb "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" @@ -82,10 +83,7 @@ func (m *engineMock) GetPayload(context.Context, [8]byte) (*pb.ExecutionPayload, func (m *engineMock) GetPayloadV2(context.Context, [8]byte) (*pb.ExecutionPayloadCapella, error) { return nil, nil } -func (m *engineMock) ForkchoiceUpdated(context.Context, *pb.ForkchoiceState, *pb.PayloadAttributes) (*pb.PayloadIDBytes, []byte, error) { - return nil, m.latestValidHash, m.payloadStatus -} -func (m *engineMock) ForkchoiceUpdatedV2(context.Context, *pb.ForkchoiceState, *pb.PayloadAttributesV2) (*pb.PayloadIDBytes, []byte, error) { +func (m *engineMock) ForkchoiceUpdated(context.Context, *pb.ForkchoiceState, payloadattribute.Attributer) (*pb.PayloadIDBytes, []byte, error) { return nil, m.latestValidHash, m.payloadStatus } func (m *engineMock) NewPayload(context.Context, interfaces.ExecutionData) ([]byte, error) {