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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -34,6 +33,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params/forks"
|
||||
"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.
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if payloadAttributes != nil {
|
||||
if err := api.verifyPayloadAttributes(payloadAttributes); err != nil {
|
||||
return engine.STATUS_INVALID, engine.InvalidParams.With(err)
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if params != nil {
|
||||
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("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.
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if payloadAttributes != nil {
|
||||
if err := api.verifyPayloadAttributes(payloadAttributes); err != nil {
|
||||
return engine.STATUS_INVALID, engine.InvalidParams.With(err)
|
||||
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
|
||||
if params != nil {
|
||||
// TODO(matt): according to https://github.com/ethereum/execution-apis/pull/498,
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (api *ConsensusAPI) verifyPayloadAttributes(attr *engine.PayloadAttributes) error {
|
||||
c := api.eth.BlockChain().Config()
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// function that kicks off block construction.
|
||||
return api.forkchoiceUpdated(update, params)
|
||||
}
|
||||
|
||||
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.
|
||||
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 {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||
}
|
||||
} else if params.Withdrawals != nil {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
|
||||
} else {
|
||||
if params.Withdrawals != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if params.Withdrawals == nil {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||
}
|
||||
if params.ExcessBlobGas == nil {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
|
||||
}
|
||||
if params.BlobGasUsed == nil {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil params.BlobGasUsed post-cancun"))
|
||||
}
|
||||
|
||||
if versionedHashes == nil {
|
||||
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"))
|
||||
}
|
||||
|
||||
if !api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 called pre-cancun"))
|
||||
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.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)
|
||||
}
|
||||
|
||||
|
|
|
@ -1237,7 +1237,15 @@ func TestNilWithdrawals(t *testing.T) {
|
|||
}
|
||||
|
||||
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 err == nil {
|
||||
t.Fatal("wanted error on fcuv2 with invalid withdrawals")
|
||||
|
@ -1254,14 +1262,19 @@ func TestNilWithdrawals(t *testing.T) {
|
|||
Timestamp: test.blockParams.Timestamp,
|
||||
FeeRecipient: test.blockParams.SuggestedFeeRecipient,
|
||||
Random: test.blockParams.Random,
|
||||
BeaconRoot: test.blockParams.BeaconRoot,
|
||||
}).Id()
|
||||
execData, err := api.GetPayloadV2(payloadID)
|
||||
if err != nil {
|
||||
t.Fatalf("error getting payload, err=%v", err)
|
||||
}
|
||||
if status, err := api.NewPayloadV2(*execData.ExecutionPayload); err != nil {
|
||||
t.Fatalf("error validating payload: %v", err)
|
||||
var status engine.PayloadStatusV1
|
||||
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 {
|
||||
t.Fatalf("invalid payload")
|
||||
}
|
||||
|
@ -1587,7 +1600,7 @@ func TestParentBeaconBlockRoot(t *testing.T) {
|
|||
fcState := engine.ForkchoiceStateV1{
|
||||
HeadBlockHash: parent.Hash(),
|
||||
}
|
||||
resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams)
|
||||
resp, err := api.ForkchoiceUpdatedV3(fcState, &blockParams)
|
||||
if err != nil {
|
||||
t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData())
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/params/forks"
|
||||
)
|
||||
|
||||
// Genesis hashes to enforce below configs on.
|
||||
|
@ -750,6 +751,23 @@ func (c *ChainConfig) ElasticityMultiplier() uint64 {
|
|||
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
|
||||
// rescheduled to block s2 because head is already past the fork.
|
||||
func isForkBlockIncompatible(s1, s2, head *big.Int) bool {
|
||||
|
|
|
@ -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