Skip to content

Commit 98eaa57

Browse files
authored
eth/catalyst: add timestamp checks to fcu and new payload and improve param checks (ethereum#28230)
This PR introduces a few changes with respect to payload verification in fcu and new payload requests: * First of all, it undoes the `verifyPayloadAttributes(..)` simplification I attempted in ethereum#27872. * Adds timestamp validation to fcu payload attributes [as required](https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#specification-1) (section 2) by the Engine API spec. * For the new payload methods, I also update the verification of the executable data. For `newPayloadV2`, it does not currently ensure that cancun values are `nil`. Which could make it possible to submit cancun payloads through it. * On `newPayloadV3` the same types of checks are added. All shanghai and cancun related fields in the executable data must be non-nil, with the addition that the timestamp is _only_ with cancun. * Finally it updates a newly failing catalyst test to call the correct fcu and new payload methods depending on the fork.
1 parent 2dc7477 commit 98eaa57

File tree

4 files changed

+128
-48
lines changed

4 files changed

+128
-48
lines changed

eth/catalyst/api.go

+50-43
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ package catalyst
2020
import (
2121
"errors"
2222
"fmt"
23-
"math/big"
2423
"sync"
2524
"time"
2625

@@ -34,6 +33,7 @@ import (
3433
"github.com/ethereum/go-ethereum/log"
3534
"github.com/ethereum/go-ethereum/miner"
3635
"github.com/ethereum/go-ethereum/node"
36+
"github.com/ethereum/go-ethereum/params/forks"
3737
"github.com/ethereum/go-ethereum/rpc"
3838
)
3939

@@ -184,47 +184,43 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, pa
184184
}
185185

186186
// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload attributes.
187-
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
188-
if payloadAttributes != nil {
189-
if err := api.verifyPayloadAttributes(payloadAttributes); err != nil {
190-
return engine.STATUS_INVALID, engine.InvalidParams.With(err)
187+
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
188+
if params != nil {
189+
if params.Withdrawals == nil {
190+
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("missing withdrawals"))
191+
}
192+
if params.BeaconRoot != nil {
193+
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("unexpected beacon root"))
194+
}
195+
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Shanghai {
196+
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called for shanghai payloads"))
191197
}
192198
}
193-
return api.forkchoiceUpdated(update, payloadAttributes)
199+
return api.forkchoiceUpdated(update, params)
194200
}
195201

196202
// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root in the payload attributes.
197-
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
198-
if payloadAttributes != nil {
199-
if err := api.verifyPayloadAttributes(payloadAttributes); err != nil {
200-
return engine.STATUS_INVALID, engine.InvalidParams.With(err)
203+
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
204+
if params != nil {
205+
// TODO(matt): according to https://github.com/ethereum/execution-apis/pull/498,
206+
// payload attributes that are invalid should return error
207+
// engine.InvalidPayloadAttributes. Once hive updates this, we should update
208+
// on our end.
209+
if params.Withdrawals == nil {
210+
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("missing withdrawals"))
211+
}
212+
if params.BeaconRoot == nil {
213+
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("missing beacon root"))
214+
}
215+
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
216+
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads"))
201217
}
202218
}
203-
return api.forkchoiceUpdated(update, payloadAttributes)
204-
}
205-
206-
func (api *ConsensusAPI) verifyPayloadAttributes(attr *engine.PayloadAttributes) error {
207-
c := api.eth.BlockChain().Config()
208-
209-
// Verify withdrawals attribute for Shanghai.
210-
if err := checkAttribute(c.IsShanghai, attr.Withdrawals != nil, c.LondonBlock, attr.Timestamp); err != nil {
211-
return fmt.Errorf("invalid withdrawals: %w", err)
212-
}
213-
// Verify beacon root attribute for Cancun.
214-
if err := checkAttribute(c.IsCancun, attr.BeaconRoot != nil, c.LondonBlock, attr.Timestamp); err != nil {
215-
return fmt.Errorf("invalid parent beacon block root: %w", err)
216-
}
217-
return nil
218-
}
219-
220-
func checkAttribute(active func(*big.Int, uint64) bool, exists bool, block *big.Int, time uint64) error {
221-
if active(block, time) && !exists {
222-
return errors.New("fork active, missing expected attribute")
223-
}
224-
if !active(block, time) && exists {
225-
return errors.New("fork inactive, unexpected attribute set")
226-
}
227-
return nil
219+
// TODO(matt): the spec requires that fcu is applied when called on a valid
220+
// hash, even if params are wrong. To do this we need to split up
221+
// forkchoiceUpdate into a function that only updates the head and then a
222+
// function that kicks off block construction.
223+
return api.forkchoiceUpdated(update, params)
228224
}
229225

230226
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
@@ -457,38 +453,49 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl
457453

458454
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
459455
func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
460-
if api.eth.BlockChain().Config().IsShanghai(new(big.Int).SetUint64(params.Number), params.Timestamp) {
456+
if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) {
457+
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use new payload v2 post-shanghai"))
458+
}
459+
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai {
461460
if params.Withdrawals == nil {
462461
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
463462
}
464-
} else if params.Withdrawals != nil {
465-
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
463+
} else {
464+
if params.Withdrawals != nil {
465+
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
466+
}
467+
}
468+
if params.ExcessBlobGas != nil {
469+
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun"))
466470
}
467-
if api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
468-
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV2 called post-cancun"))
471+
if params.BlobGasUsed != nil {
472+
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil params.BlobGasUsed pre-cancun"))
469473
}
470474
return api.newPayload(params, nil, nil)
471475
}
472476

473477
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
474478
func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
479+
if params.Withdrawals == nil {
480+
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
481+
}
475482
if params.ExcessBlobGas == nil {
476483
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
477484
}
478485
if params.BlobGasUsed == nil {
479486
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil params.BlobGasUsed post-cancun"))
480487
}
488+
481489
if versionedHashes == nil {
482490
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
483491
}
484492
if beaconRoot == nil {
485493
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil parentBeaconBlockRoot post-cancun"))
486494
}
487495

488-
if !api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
489-
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 called pre-cancun"))
496+
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
497+
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 must only be called for cancun payloads"))
490498
}
491-
492499
return api.newPayload(params, versionedHashes, beaconRoot)
493500
}
494501

eth/catalyst/api_test.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -1237,7 +1237,15 @@ func TestNilWithdrawals(t *testing.T) {
12371237
}
12381238

12391239
for _, test := range tests {
1240-
_, err := api.ForkchoiceUpdatedV2(fcState, &test.blockParams)
1240+
var (
1241+
err error
1242+
shanghai = genesis.Config.IsShanghai(genesis.Config.LondonBlock, test.blockParams.Timestamp)
1243+
)
1244+
if !shanghai {
1245+
_, err = api.ForkchoiceUpdatedV1(fcState, &test.blockParams)
1246+
} else {
1247+
_, err = api.ForkchoiceUpdatedV2(fcState, &test.blockParams)
1248+
}
12411249
if test.wantErr {
12421250
if err == nil {
12431251
t.Fatal("wanted error on fcuv2 with invalid withdrawals")
@@ -1254,14 +1262,19 @@ func TestNilWithdrawals(t *testing.T) {
12541262
Timestamp: test.blockParams.Timestamp,
12551263
FeeRecipient: test.blockParams.SuggestedFeeRecipient,
12561264
Random: test.blockParams.Random,
1257-
BeaconRoot: test.blockParams.BeaconRoot,
12581265
}).Id()
12591266
execData, err := api.GetPayloadV2(payloadID)
12601267
if err != nil {
12611268
t.Fatalf("error getting payload, err=%v", err)
12621269
}
1263-
if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
1264-
t.Fatalf("error validating payload: %v", err)
1270+
var status engine.PayloadStatusV1
1271+
if !shanghai {
1272+
status, err = api.NewPayloadV1(*execData.ExecutionPayload)
1273+
} else {
1274+
status, err = api.NewPayloadV2(*execData.ExecutionPayload)
1275+
}
1276+
if err != nil {
1277+
t.Fatalf("error validating payload: %v", err.(*engine.EngineAPIError).ErrorData())
12651278
} else if status.Status != engine.VALID {
12661279
t.Fatalf("invalid payload")
12671280
}
@@ -1587,7 +1600,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
15871600
fcState := engine.ForkchoiceStateV1{
15881601
HeadBlockHash: parent.Hash(),
15891602
}
1590-
resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
1603+
resp, err := api.ForkchoiceUpdatedV3(fcState, &blockParams)
15911604
if err != nil {
15921605
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
15931606
}

params/config.go

+18
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"math/big"
2222

2323
"github.com/ethereum/go-ethereum/common"
24+
"github.com/ethereum/go-ethereum/params/forks"
2425
)
2526

2627
// Genesis hashes to enforce below configs on.
@@ -750,6 +751,23 @@ func (c *ChainConfig) ElasticityMultiplier() uint64 {
750751
return DefaultElasticityMultiplier
751752
}
752753

754+
// LatestFork returns the latest time-based fork that would be active for the given time.
755+
func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
756+
// Assume last non-time-based fork has passed.
757+
london := c.LondonBlock
758+
759+
switch {
760+
case c.IsPrague(london, time):
761+
return forks.Prague
762+
case c.IsCancun(london, time):
763+
return forks.Cancun
764+
case c.IsShanghai(london, time):
765+
return forks.Shanghai
766+
default:
767+
return forks.Paris
768+
}
769+
}
770+
753771
// isForkBlockIncompatible returns true if a fork scheduled at block s1 cannot be
754772
// rescheduled to block s2 because head is already past the fork.
755773
func isForkBlockIncompatible(s1, s2, head *big.Int) bool {

params/forks/forks.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2023 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package forks
18+
19+
// Fork is a numerical identifier of specific network upgrades (forks).
20+
type Fork int
21+
22+
const (
23+
Frontier = iota
24+
FrontierThawing
25+
Homestead
26+
DAO
27+
TangerineWhistle
28+
SpuriousDragon
29+
Byzantium
30+
Constantinople
31+
Petersburg
32+
Istanbul
33+
MuirGlacier
34+
Berlin
35+
London
36+
ArrowGlacier
37+
GrayGlacier
38+
Paris
39+
Shanghai
40+
Cancun
41+
Prague
42+
)

0 commit comments

Comments
 (0)