forked from cerc-io/plugeth
eth/catalyst: add timestamp checks to fcu and new payload and improve param checks (#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 #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.
This commit is contained in:
parent
2dc74770a7
commit
98eaa57e6f
@ -20,7 +20,6 @@ package catalyst
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -34,6 +33,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/miner"
|
"github.com/ethereum/go-ethereum/miner"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/params/forks"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -184,47 +184,43 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload attributes.
|
// ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload attributes.
|
||||||
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
if payloadAttributes != nil {
|
if params != nil {
|
||||||
if err := api.verifyPayloadAttributes(payloadAttributes); err != nil {
|
if params.Withdrawals == nil {
|
||||||
return engine.STATUS_INVALID, engine.InvalidParams.With(err)
|
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("missing withdrawals"))
|
||||||
|
}
|
||||||
|
if params.BeaconRoot != nil {
|
||||||
|
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("unexpected beacon root"))
|
||||||
|
}
|
||||||
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Shanghai {
|
||||||
|
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called for shanghai payloads"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return api.forkchoiceUpdated(update, payloadAttributes)
|
return api.forkchoiceUpdated(update, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root in the payload attributes.
|
// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root in the payload attributes.
|
||||||
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
if payloadAttributes != nil {
|
if params != nil {
|
||||||
if err := api.verifyPayloadAttributes(payloadAttributes); err != nil {
|
// TODO(matt): according to https://github.com/ethereum/execution-apis/pull/498,
|
||||||
return engine.STATUS_INVALID, engine.InvalidParams.With(err)
|
// payload attributes that are invalid should return error
|
||||||
|
// engine.InvalidPayloadAttributes. Once hive updates this, we should update
|
||||||
|
// on our end.
|
||||||
|
if params.Withdrawals == nil {
|
||||||
|
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("missing withdrawals"))
|
||||||
|
}
|
||||||
|
if params.BeaconRoot == nil {
|
||||||
|
return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("missing beacon root"))
|
||||||
|
}
|
||||||
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
|
||||||
|
return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return api.forkchoiceUpdated(update, payloadAttributes)
|
// TODO(matt): the spec requires that fcu is applied when called on a valid
|
||||||
}
|
// hash, even if params are wrong. To do this we need to split up
|
||||||
|
// forkchoiceUpdate into a function that only updates the head and then a
|
||||||
func (api *ConsensusAPI) verifyPayloadAttributes(attr *engine.PayloadAttributes) error {
|
// function that kicks off block construction.
|
||||||
c := api.eth.BlockChain().Config()
|
return api.forkchoiceUpdated(update, params)
|
||||||
|
|
||||||
// Verify withdrawals attribute for Shanghai.
|
|
||||||
if err := checkAttribute(c.IsShanghai, attr.Withdrawals != nil, c.LondonBlock, attr.Timestamp); err != nil {
|
|
||||||
return fmt.Errorf("invalid withdrawals: %w", err)
|
|
||||||
}
|
|
||||||
// Verify beacon root attribute for Cancun.
|
|
||||||
if err := checkAttribute(c.IsCancun, attr.BeaconRoot != nil, c.LondonBlock, attr.Timestamp); err != nil {
|
|
||||||
return fmt.Errorf("invalid parent beacon block root: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkAttribute(active func(*big.Int, uint64) bool, exists bool, block *big.Int, time uint64) error {
|
|
||||||
if active(block, time) && !exists {
|
|
||||||
return errors.New("fork active, missing expected attribute")
|
|
||||||
}
|
|
||||||
if !active(block, time) && exists {
|
|
||||||
return errors.New("fork inactive, unexpected attribute set")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||||
@ -457,27 +453,39 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl
|
|||||||
|
|
||||||
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||||
func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
|
func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
|
||||||
if api.eth.BlockChain().Config().IsShanghai(new(big.Int).SetUint64(params.Number), params.Timestamp) {
|
if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use new payload v2 post-shanghai"))
|
||||||
|
}
|
||||||
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai {
|
||||||
if params.Withdrawals == nil {
|
if params.Withdrawals == nil {
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||||
}
|
}
|
||||||
} else if params.Withdrawals != nil {
|
} else {
|
||||||
|
if params.Withdrawals != nil {
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
|
||||||
}
|
}
|
||||||
if api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
|
}
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV2 called post-cancun"))
|
if params.ExcessBlobGas != nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun"))
|
||||||
|
}
|
||||||
|
if params.BlobGasUsed != nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil params.BlobGasUsed pre-cancun"))
|
||||||
}
|
}
|
||||||
return api.newPayload(params, nil, nil)
|
return api.newPayload(params, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||||
func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
|
func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
|
||||||
|
if params.Withdrawals == nil {
|
||||||
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||||
|
}
|
||||||
if params.ExcessBlobGas == nil {
|
if params.ExcessBlobGas == nil {
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
|
||||||
}
|
}
|
||||||
if params.BlobGasUsed == nil {
|
if params.BlobGasUsed == nil {
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil params.BlobGasUsed post-cancun"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil params.BlobGasUsed post-cancun"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if versionedHashes == nil {
|
if versionedHashes == nil {
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
|
||||||
}
|
}
|
||||||
@ -485,10 +493,9 @@ func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHas
|
|||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil parentBeaconBlockRoot post-cancun"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil parentBeaconBlockRoot post-cancun"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
|
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
|
||||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 called pre-cancun"))
|
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 must only be called for cancun payloads"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return api.newPayload(params, versionedHashes, beaconRoot)
|
return api.newPayload(params, versionedHashes, beaconRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1237,7 +1237,15 @@ func TestNilWithdrawals(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
_, err := api.ForkchoiceUpdatedV2(fcState, &test.blockParams)
|
var (
|
||||||
|
err error
|
||||||
|
shanghai = genesis.Config.IsShanghai(genesis.Config.LondonBlock, test.blockParams.Timestamp)
|
||||||
|
)
|
||||||
|
if !shanghai {
|
||||||
|
_, err = api.ForkchoiceUpdatedV1(fcState, &test.blockParams)
|
||||||
|
} else {
|
||||||
|
_, err = api.ForkchoiceUpdatedV2(fcState, &test.blockParams)
|
||||||
|
}
|
||||||
if test.wantErr {
|
if test.wantErr {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("wanted error on fcuv2 with invalid withdrawals")
|
t.Fatal("wanted error on fcuv2 with invalid withdrawals")
|
||||||
@ -1254,14 +1262,19 @@ func TestNilWithdrawals(t *testing.T) {
|
|||||||
Timestamp: test.blockParams.Timestamp,
|
Timestamp: test.blockParams.Timestamp,
|
||||||
FeeRecipient: test.blockParams.SuggestedFeeRecipient,
|
FeeRecipient: test.blockParams.SuggestedFeeRecipient,
|
||||||
Random: test.blockParams.Random,
|
Random: test.blockParams.Random,
|
||||||
BeaconRoot: test.blockParams.BeaconRoot,
|
|
||||||
}).Id()
|
}).Id()
|
||||||
execData, err := api.GetPayloadV2(payloadID)
|
execData, err := api.GetPayloadV2(payloadID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error getting payload, err=%v", err)
|
t.Fatalf("error getting payload, err=%v", err)
|
||||||
}
|
}
|
||||||
if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
|
var status engine.PayloadStatusV1
|
||||||
t.Fatalf("error validating payload: %v", err)
|
if !shanghai {
|
||||||
|
status, err = api.NewPayloadV1(*execData.ExecutionPayload)
|
||||||
|
} else {
|
||||||
|
status, err = api.NewPayloadV2(*execData.ExecutionPayload)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error validating payload: %v", err.(*engine.EngineAPIError).ErrorData())
|
||||||
} else if status.Status != engine.VALID {
|
} else if status.Status != engine.VALID {
|
||||||
t.Fatalf("invalid payload")
|
t.Fatalf("invalid payload")
|
||||||
}
|
}
|
||||||
@ -1587,7 +1600,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
|
|||||||
fcState := engine.ForkchoiceStateV1{
|
fcState := engine.ForkchoiceStateV1{
|
||||||
HeadBlockHash: parent.Hash(),
|
HeadBlockHash: parent.Hash(),
|
||||||
}
|
}
|
||||||
resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
|
resp, err := api.ForkchoiceUpdatedV3(fcState, &blockParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
|
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/params/forks"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Genesis hashes to enforce below configs on.
|
// Genesis hashes to enforce below configs on.
|
||||||
@ -750,6 +751,23 @@ func (c *ChainConfig) ElasticityMultiplier() uint64 {
|
|||||||
return DefaultElasticityMultiplier
|
return DefaultElasticityMultiplier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LatestFork returns the latest time-based fork that would be active for the given time.
|
||||||
|
func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
|
||||||
|
// Assume last non-time-based fork has passed.
|
||||||
|
london := c.LondonBlock
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case c.IsPrague(london, time):
|
||||||
|
return forks.Prague
|
||||||
|
case c.IsCancun(london, time):
|
||||||
|
return forks.Cancun
|
||||||
|
case c.IsShanghai(london, time):
|
||||||
|
return forks.Shanghai
|
||||||
|
default:
|
||||||
|
return forks.Paris
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// isForkBlockIncompatible returns true if a fork scheduled at block s1 cannot be
|
// isForkBlockIncompatible returns true if a fork scheduled at block s1 cannot be
|
||||||
// rescheduled to block s2 because head is already past the fork.
|
// rescheduled to block s2 because head is already past the fork.
|
||||||
func isForkBlockIncompatible(s1, s2, head *big.Int) bool {
|
func isForkBlockIncompatible(s1, s2, head *big.Int) bool {
|
||||||
|
42
params/forks/forks.go
Normal file
42
params/forks/forks.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2023 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package forks
|
||||||
|
|
||||||
|
// Fork is a numerical identifier of specific network upgrades (forks).
|
||||||
|
type Fork int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Frontier = iota
|
||||||
|
FrontierThawing
|
||||||
|
Homestead
|
||||||
|
DAO
|
||||||
|
TangerineWhistle
|
||||||
|
SpuriousDragon
|
||||||
|
Byzantium
|
||||||
|
Constantinople
|
||||||
|
Petersburg
|
||||||
|
Istanbul
|
||||||
|
MuirGlacier
|
||||||
|
Berlin
|
||||||
|
London
|
||||||
|
ArrowGlacier
|
||||||
|
GrayGlacier
|
||||||
|
Paris
|
||||||
|
Shanghai
|
||||||
|
Cancun
|
||||||
|
Prague
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user