Skip to content

Commit 245cff0

Browse files
eth/catalyst: error on nil withdrawals post-shanghai (#26549)
This adds explicit checks for the presence of withdrawals in the engine API. Co-authored-by: Felix Lange <fjl@twurst.com>
1 parent 55f41d1 commit 245cff0

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

eth/catalyst/api.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,18 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
164164

165165
// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload attributes.
166166
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributes) (beacon.ForkChoiceResponse, error) {
167+
if !api.eth.BlockChain().Config().IsShanghai(payloadAttributes.Timestamp) {
168+
// Reject payload attributes with withdrawals before shanghai
169+
if payloadAttributes != nil && payloadAttributes.Withdrawals != nil {
170+
return beacon.STATUS_INVALID, beacon.InvalidPayloadAttributes.With(errors.New("withdrawals before shanghai"))
171+
}
172+
} else {
173+
// Reject payload attributes with nil withdrawals after shanghai
174+
if payloadAttributes != nil && payloadAttributes.Withdrawals == nil {
175+
return beacon.STATUS_INVALID, beacon.InvalidPayloadAttributes.With(errors.New("missing withdrawals list"))
176+
}
177+
}
178+
167179
return api.forkchoiceUpdated(update, payloadAttributes)
168180
}
169181

eth/catalyst/api_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,3 +1113,115 @@ func TestWithdrawals(t *testing.T) {
11131113
}
11141114
}
11151115
}
1116+
1117+
func TestNilWithdrawals(t *testing.T) {
1118+
genesis, blocks := generateMergeChain(10, true)
1119+
// Set shanghai time to last block + 4 seconds (first post-merge block)
1120+
time := blocks[len(blocks)-1].Time() + 4
1121+
genesis.Config.ShanghaiTime = &time
1122+
1123+
n, ethservice := startEthService(t, genesis, blocks)
1124+
ethservice.Merger().ReachTTD()
1125+
defer n.Close()
1126+
1127+
api := NewConsensusAPI(ethservice)
1128+
parent := ethservice.BlockChain().CurrentHeader()
1129+
aa := common.Address{0xaa}
1130+
1131+
type test struct {
1132+
blockParams beacon.PayloadAttributes
1133+
wantErr bool
1134+
}
1135+
tests := []test{
1136+
// Before Shanghai
1137+
{
1138+
blockParams: beacon.PayloadAttributes{
1139+
Timestamp: parent.Time + 2,
1140+
Withdrawals: nil,
1141+
},
1142+
wantErr: false,
1143+
},
1144+
{
1145+
blockParams: beacon.PayloadAttributes{
1146+
Timestamp: parent.Time + 2,
1147+
Withdrawals: make([]*types.Withdrawal, 0),
1148+
},
1149+
wantErr: true,
1150+
},
1151+
{
1152+
blockParams: beacon.PayloadAttributes{
1153+
Timestamp: parent.Time + 2,
1154+
Withdrawals: []*types.Withdrawal{
1155+
{
1156+
Index: 0,
1157+
Address: aa,
1158+
Amount: 32,
1159+
},
1160+
},
1161+
},
1162+
wantErr: true,
1163+
},
1164+
// After Shanghai
1165+
{
1166+
blockParams: beacon.PayloadAttributes{
1167+
Timestamp: parent.Time + 5,
1168+
Withdrawals: nil,
1169+
},
1170+
wantErr: true,
1171+
},
1172+
{
1173+
blockParams: beacon.PayloadAttributes{
1174+
Timestamp: parent.Time + 5,
1175+
Withdrawals: make([]*types.Withdrawal, 0),
1176+
},
1177+
wantErr: false,
1178+
},
1179+
{
1180+
blockParams: beacon.PayloadAttributes{
1181+
Timestamp: parent.Time + 5,
1182+
Withdrawals: []*types.Withdrawal{
1183+
{
1184+
Index: 0,
1185+
Address: aa,
1186+
Amount: 32,
1187+
},
1188+
},
1189+
},
1190+
wantErr: false,
1191+
},
1192+
}
1193+
1194+
fcState := beacon.ForkchoiceStateV1{
1195+
HeadBlockHash: parent.Hash(),
1196+
}
1197+
1198+
for _, test := range tests {
1199+
_, err := api.ForkchoiceUpdatedV2(fcState, &test.blockParams)
1200+
if test.wantErr {
1201+
if err == nil {
1202+
t.Fatal("wanted error on fcuv2 with invalid withdrawals")
1203+
}
1204+
continue
1205+
}
1206+
if err != nil {
1207+
t.Fatalf("error preparing payload, err=%v", err)
1208+
}
1209+
1210+
// 11: verify locally build block.
1211+
payloadID := (&miner.BuildPayloadArgs{
1212+
Parent: fcState.HeadBlockHash,
1213+
Timestamp: test.blockParams.Timestamp,
1214+
FeeRecipient: test.blockParams.SuggestedFeeRecipient,
1215+
Random: test.blockParams.Random,
1216+
}).Id()
1217+
execData, err := api.GetPayloadV2(payloadID)
1218+
if err != nil {
1219+
t.Fatalf("error getting payload, err=%v", err)
1220+
}
1221+
if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
1222+
t.Fatalf("error validating payload: %v", err)
1223+
} else if status.Status != beacon.VALID {
1224+
t.Fatalf("invalid payload")
1225+
}
1226+
}
1227+
}

0 commit comments

Comments
 (0)