Skip to content

Commit cadaa7d

Browse files
Muzrymuzry.li
authored andcommitted
engine: return -38003 for FCUv2 payloadAttributes mismatch (#19779)
This PR updates `engine_forkchoiceUpdatedV2` to return `-38003: Invalid payload attributes` when the wrong `payloadAttributes` version is used. In particular, FCUv2 payload-attribute version mismatches such as: - missing `withdrawals` at or after Shanghai - unexpected `withdrawals` before Shanghai should be treated as `Invalid payload attributes`, not `Invalid params`. ## Why This change aligns the client with the latest Engine API spec update in: - ethereum/execution-apis#761 It also follows the implementation discussion and prior client-side change in: - ethereum/go-ethereum#33918 The spec was clarified so that FCUv2 now behaves consistently with newer forkchoiceUpdated versions for payloadAttributes structure/version mismatches. ## What changed - Updated FCUv2 payload attributes validation to return `-38003` for payloadAttributes version mismatches. - Added/updated regression coverage for the affected FCUv2 cases. ## Hive impact This fixes the Hive `engine-withdrawals` failure caused by returning the wrong error code for FCUv2 payloadAttributes mismatches. Relevant Hive failure: - https://hive.ethpandaops.io/#/test/generic/1773130326-4e173a80b2b6f0634fd0139743cbe0de After this change, the client returns the expected error code for the affected FCUv2 cases. If my understanding or interpretation of the spec change is incorrect, please let me know and I can adjust the implementation accordingly. --------- Co-authored-by: muzry.li <muzry.li1@ambergroup.io>
1 parent 6d44b29 commit cadaa7d

3 files changed

Lines changed: 105 additions & 5 deletions

File tree

.github/workflows/test-hive.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ jobs:
7474
with:
7575
repository: ethereum/hive
7676
# version hive and update periodically/on-demand to prevent upstream changes in Hive affecting us with red CI
77-
ref: 0ee187ce394720a5902c135324ac7de4240cbb37
77+
ref: 88786d9744dabb08bdaec8d8b53142cde6e6b79a
7878
path: hive
7979

8080
- name: Setup go env and cache

execution/engineapi/engine_server.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,20 @@ func (e *EngineServer) Start(
178178
}
179179

180180
func (s *EngineServer) checkWithdrawalsPresence(time uint64, withdrawals types.Withdrawals) error {
181-
if !s.config.IsShanghai(time) && withdrawals != nil {
181+
if s.isWithdrawalsPresenceValid(time, withdrawals) {
182+
return nil
183+
}
184+
if !s.config.IsShanghai(time) {
182185
return &rpc.InvalidParamsError{Message: "withdrawals before Shanghai"}
183186
}
184-
if s.config.IsShanghai(time) && withdrawals == nil {
185-
return &rpc.InvalidParamsError{Message: "missing withdrawals list"}
187+
return &rpc.InvalidParamsError{Message: "missing withdrawals list"}
188+
}
189+
190+
func (s *EngineServer) isWithdrawalsPresenceValid(time uint64, withdrawals types.Withdrawals) bool {
191+
if !s.config.IsShanghai(time) {
192+
return withdrawals == nil
186193
}
187-
return nil
194+
return withdrawals != nil
188195
}
189196

190197
func (s *EngineServer) checkRequestsPresence(version clparams.StateVersion, executionRequests []hexutil.Bytes) error {
@@ -718,6 +725,9 @@ func (s *EngineServer) forkchoiceUpdated(ctx context.Context, forkchoiceState *e
718725
if s.config.IsCancun(timestamp) && version < clparams.DenebVersion { // Not V3 after cancun
719726
return nil, &rpc.UnsupportedForkError{Message: "Unsupported fork"}
720727
}
728+
if version >= clparams.CapellaVersion && !s.isWithdrawalsPresenceValid(timestamp, payloadAttributes.Withdrawals) {
729+
return nil, &engine_helpers.InvalidPayloadAttributesErr
730+
}
721731

722732
if !s.proposing {
723733
return nil, errors.New("execution layer not running as a proposer. enable proposer by taking out the --proposer.disable flag on startup")

execution/engineapi/testing_api_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import (
2424
"github.com/holiman/uint256"
2525
"github.com/stretchr/testify/assert"
2626
"github.com/stretchr/testify/require"
27+
"google.golang.org/protobuf/types/known/emptypb"
2728

29+
"github.com/erigontech/erigon/cl/clparams"
2830
"github.com/erigontech/erigon/common"
2931
"github.com/erigontech/erigon/common/hexutil"
3032
"github.com/erigontech/erigon/common/log/v3"
@@ -50,6 +52,7 @@ type stubExecutionServer struct {
5052
getHeaderFunc func(ctx context.Context, in *executionproto.GetSegmentRequest) (*executionproto.GetHeaderResponse, error)
5153
assembleBlockFunc func(ctx context.Context, in *executionproto.AssembleBlockRequest) (*executionproto.AssembleBlockResponse, error)
5254
getAssembledBlockFunc func(ctx context.Context, in *executionproto.GetAssembledBlockRequest) (*executionproto.GetAssembledBlockResponse, error)
55+
getForkChoiceFunc func(ctx context.Context, in *emptypb.Empty) (*executionproto.ForkChoice, error)
5356
}
5457

5558
func (s *stubExecutionServer) GetHeader(ctx context.Context, in *executionproto.GetSegmentRequest) (*executionproto.GetHeaderResponse, error) {
@@ -73,6 +76,13 @@ func (s *stubExecutionServer) GetAssembledBlock(ctx context.Context, in *executi
7376
return &executionproto.GetAssembledBlockResponse{}, nil
7477
}
7578

79+
func (s *stubExecutionServer) GetForkChoice(ctx context.Context, in *emptypb.Empty) (*executionproto.ForkChoice, error) {
80+
if s.getForkChoiceFunc != nil {
81+
return s.getForkChoiceFunc(ctx, in)
82+
}
83+
return &executionproto.ForkChoice{}, nil
84+
}
85+
7686
// ---------------------------------------------------------------------------
7787
// Test helpers
7888
// ---------------------------------------------------------------------------
@@ -595,6 +605,86 @@ func TestBuildBlockV1(t *testing.T) {
595605
})
596606
}
597607

608+
func TestForkchoiceUpdatedV2PayloadAttributesWithdrawalsValidation(t *testing.T) {
609+
t.Parallel()
610+
611+
t.Run("missing withdrawals for Shanghai returns invalid payload attributes", func(t *testing.T) {
612+
forkchoiceState := &engine_types.ForkChoiceState{
613+
HeadHash: common.Hash{0x1},
614+
SafeBlockHash: common.Hash{0x2},
615+
FinalizedBlockHash: common.Hash{0x3},
616+
}
617+
srv := NewEngineServer(
618+
log.New(),
619+
preCancunChainConfig(),
620+
direct.NewExecutionClientDirect(&stubExecutionServer{
621+
getForkChoiceFunc: func(context.Context, *emptypb.Empty) (*executionproto.ForkChoice, error) {
622+
return &executionproto.ForkChoice{
623+
HeadBlockHash: gointerfaces.ConvertHashToH256(forkchoiceState.HeadHash),
624+
SafeBlockHash: gointerfaces.ConvertHashToH256(forkchoiceState.SafeBlockHash),
625+
FinalizedBlockHash: gointerfaces.ConvertHashToH256(forkchoiceState.FinalizedBlockHash),
626+
}, nil
627+
},
628+
}),
629+
nil,
630+
false,
631+
true,
632+
true,
633+
nil,
634+
0,
635+
0,
636+
)
637+
638+
resp, err := srv.forkchoiceUpdated(context.Background(), forkchoiceState, &engine_types.PayloadAttributes{
639+
Timestamp: hexutil.Uint64(1001),
640+
PrevRandao: common.Hash{0xaa},
641+
SuggestedFeeRecipient: common.HexToAddress("0x1111111111111111111111111111111111111111"),
642+
Withdrawals: nil,
643+
}, clparams.CapellaVersion)
644+
require.Nil(t, resp)
645+
require.Error(t, err)
646+
require.Equal(t, -38003, err.(rpc.Error).ErrorCode())
647+
})
648+
649+
t.Run("withdrawals before Shanghai returns invalid payload attributes", func(t *testing.T) {
650+
forkchoiceState := &engine_types.ForkChoiceState{
651+
HeadHash: common.Hash{0x4},
652+
SafeBlockHash: common.Hash{0x5},
653+
FinalizedBlockHash: common.Hash{0x6},
654+
}
655+
srv := NewEngineServer(
656+
log.New(),
657+
preShanghaiChainConfig(),
658+
direct.NewExecutionClientDirect(&stubExecutionServer{
659+
getForkChoiceFunc: func(context.Context, *emptypb.Empty) (*executionproto.ForkChoice, error) {
660+
return &executionproto.ForkChoice{
661+
HeadBlockHash: gointerfaces.ConvertHashToH256(forkchoiceState.HeadHash),
662+
SafeBlockHash: gointerfaces.ConvertHashToH256(forkchoiceState.SafeBlockHash),
663+
FinalizedBlockHash: gointerfaces.ConvertHashToH256(forkchoiceState.FinalizedBlockHash),
664+
}, nil
665+
},
666+
}),
667+
nil,
668+
false,
669+
true,
670+
true,
671+
nil,
672+
0,
673+
0,
674+
)
675+
676+
resp, err := srv.forkchoiceUpdated(context.Background(), forkchoiceState, &engine_types.PayloadAttributes{
677+
Timestamp: hexutil.Uint64(1001),
678+
PrevRandao: common.Hash{0xaa},
679+
SuggestedFeeRecipient: common.HexToAddress("0x1111111111111111111111111111111111111111"),
680+
Withdrawals: make([]*types.Withdrawal, 0),
681+
}, clparams.CapellaVersion)
682+
require.Nil(t, resp)
683+
require.Error(t, err)
684+
require.Equal(t, -38003, err.(rpc.Error).ErrorCode())
685+
})
686+
}
687+
598688
func ptrUint64(v uint64) *uint64 {
599689
return &v
600690
}

0 commit comments

Comments
 (0)