github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/eth/catalyst/api.go (about)

     1  // Copyright 2021 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 catalyst implements the temporary eth1/eth2 RPC integration.
    18  package catalyst
    19  
    20  import (
    21  	"errors"
    22  	"fmt"
    23  	"strconv"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/ethereum/go-ethereum/beacon/engine"
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethereum/go-ethereum/common/hexutil"
    30  	"github.com/ethereum/go-ethereum/core/rawdb"
    31  	"github.com/ethereum/go-ethereum/core/types"
    32  	"github.com/ethereum/go-ethereum/eth"
    33  	"github.com/ethereum/go-ethereum/eth/downloader"
    34  	"github.com/ethereum/go-ethereum/internal/version"
    35  	"github.com/ethereum/go-ethereum/log"
    36  	"github.com/ethereum/go-ethereum/miner"
    37  	"github.com/ethereum/go-ethereum/node"
    38  	"github.com/ethereum/go-ethereum/params"
    39  	"github.com/ethereum/go-ethereum/params/forks"
    40  	"github.com/ethereum/go-ethereum/rpc"
    41  )
    42  
    43  // Register adds the engine API to the full node.
    44  func Register(stack *node.Node, backend *eth.Ethereum) error {
    45  	log.Warn("Engine API enabled", "protocol", "eth")
    46  	stack.RegisterAPIs([]rpc.API{
    47  		{
    48  			Namespace:     "engine",
    49  			Service:       NewConsensusAPI(backend),
    50  			Authenticated: true,
    51  		},
    52  	})
    53  	return nil
    54  }
    55  
    56  const (
    57  	// invalidBlockHitEviction is the number of times an invalid block can be
    58  	// referenced in forkchoice update or new payload before it is attempted
    59  	// to be reprocessed again.
    60  	invalidBlockHitEviction = 128
    61  
    62  	// invalidTipsetsCap is the max number of recent block hashes tracked that
    63  	// have lead to some bad ancestor block. It's just an OOM protection.
    64  	invalidTipsetsCap = 512
    65  
    66  	// beaconUpdateStartupTimeout is the time to wait for a beacon client to get
    67  	// attached before starting to issue warnings.
    68  	beaconUpdateStartupTimeout = 30 * time.Second
    69  
    70  	// beaconUpdateConsensusTimeout is the max time allowed for a beacon client
    71  	// to send a consensus update before it's considered offline and the user is
    72  	// warned.
    73  	beaconUpdateConsensusTimeout = 2 * time.Minute
    74  
    75  	// beaconUpdateWarnFrequency is the frequency at which to warn the user that
    76  	// the beacon client is offline.
    77  	beaconUpdateWarnFrequency = 5 * time.Minute
    78  )
    79  
    80  // All methods provided over the engine endpoint.
    81  var caps = []string{
    82  	"engine_forkchoiceUpdatedV1",
    83  	"engine_forkchoiceUpdatedV2",
    84  	"engine_forkchoiceUpdatedV3",
    85  	"engine_exchangeTransitionConfigurationV1",
    86  	"engine_getPayloadV1",
    87  	"engine_getPayloadV2",
    88  	"engine_getPayloadV3",
    89  	"engine_newPayloadV1",
    90  	"engine_newPayloadV2",
    91  	"engine_newPayloadV3",
    92  	"engine_getPayloadBodiesByHashV1",
    93  	"engine_getPayloadBodiesByRangeV1",
    94  	"engine_getClientVersionV1",
    95  }
    96  
    97  type ConsensusAPI struct {
    98  	eth *eth.Ethereum
    99  
   100  	remoteBlocks *headerQueue  // Cache of remote payloads received
   101  	localBlocks  *payloadQueue // Cache of local payloads generated
   102  
   103  	// The forkchoice update and new payload method require us to return the
   104  	// latest valid hash in an invalid chain. To support that return, we need
   105  	// to track historical bad blocks as well as bad tipsets in case a chain
   106  	// is constantly built on it.
   107  	//
   108  	// There are a few important caveats in this mechanism:
   109  	//   - The bad block tracking is ephemeral, in-memory only. We must never
   110  	//     persist any bad block information to disk as a bug in Geth could end
   111  	//     up blocking a valid chain, even if a later Geth update would accept
   112  	//     it.
   113  	//   - Bad blocks will get forgotten after a certain threshold of import
   114  	//     attempts and will be retried. The rationale is that if the network
   115  	//     really-really-really tries to feed us a block, we should give it a
   116  	//     new chance, perhaps us being racey instead of the block being legit
   117  	//     bad (this happened in Geth at a point with import vs. pending race).
   118  	//   - Tracking all the blocks built on top of the bad one could be a bit
   119  	//     problematic, so we will only track the head chain segment of a bad
   120  	//     chain to allow discarding progressing bad chains and side chains,
   121  	//     without tracking too much bad data.
   122  	invalidBlocksHits map[common.Hash]int           // Ephemeral cache to track invalid blocks and their hit count
   123  	invalidTipsets    map[common.Hash]*types.Header // Ephemeral cache to track invalid tipsets and their bad ancestor
   124  	invalidLock       sync.Mutex                    // Protects the invalid maps from concurrent access
   125  
   126  	// Geth can appear to be stuck or do strange things if the beacon client is
   127  	// offline or is sending us strange data. Stash some update stats away so
   128  	// that we can warn the user and not have them open issues on our tracker.
   129  	lastTransitionUpdate time.Time
   130  	lastTransitionLock   sync.Mutex
   131  	lastForkchoiceUpdate time.Time
   132  	lastForkchoiceLock   sync.Mutex
   133  	lastNewPayloadUpdate time.Time
   134  	lastNewPayloadLock   sync.Mutex
   135  
   136  	forkchoiceLock sync.Mutex // Lock for the forkChoiceUpdated method
   137  	newPayloadLock sync.Mutex // Lock for the NewPayload method
   138  }
   139  
   140  // NewConsensusAPI creates a new consensus api for the given backend.
   141  // The underlying blockchain needs to have a valid terminal total difficulty set.
   142  func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
   143  	api := newConsensusAPIWithoutHeartbeat(eth)
   144  	go api.heartbeat()
   145  	return api
   146  }
   147  
   148  // newConsensusAPIWithoutHeartbeat creates a new consensus api for the SimulatedBeacon Node.
   149  func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
   150  	if eth.BlockChain().Config().TerminalTotalDifficulty == nil {
   151  		log.Warn("Engine API started but chain not configured for merge yet")
   152  	}
   153  	api := &ConsensusAPI{
   154  		eth:               eth,
   155  		remoteBlocks:      newHeaderQueue(),
   156  		localBlocks:       newPayloadQueue(),
   157  		invalidBlocksHits: make(map[common.Hash]int),
   158  		invalidTipsets:    make(map[common.Hash]*types.Header),
   159  	}
   160  	eth.Downloader().SetBadBlockCallback(api.setInvalidAncestor)
   161  	return api
   162  }
   163  
   164  // ForkchoiceUpdatedV1 has several responsibilities:
   165  //
   166  // We try to set our blockchain to the headBlock.
   167  //
   168  // If the method is called with an empty head block: we return success, which can be used
   169  // to check if the engine API is enabled.
   170  //
   171  // If the total difficulty was not reached: we return INVALID.
   172  //
   173  // If the finalizedBlockHash is set: we check if we have the finalizedBlockHash in our db,
   174  // if not we start a sync.
   175  //
   176  // If there are payloadAttributes: we try to assemble a block with the payloadAttributes
   177  // and return its payloadID.
   178  func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
   179  	if payloadAttributes != nil {
   180  		if payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil {
   181  			return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("withdrawals and beacon root not supported in V1"))
   182  		}
   183  		if api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, payloadAttributes.Timestamp) {
   184  			return engine.STATUS_INVALID, engine.InvalidParams.With(errors.New("forkChoiceUpdateV1 called post-shanghai"))
   185  		}
   186  	}
   187  	return api.forkchoiceUpdated(update, payloadAttributes, engine.PayloadV1, false)
   188  }
   189  
   190  // ForkchoiceUpdatedV2 is equivalent to V1 with the addition of withdrawals in the payload
   191  // attributes. It supports both PayloadAttributesV1 and PayloadAttributesV2.
   192  func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
   193  	if params != nil {
   194  		if params.BeaconRoot != nil {
   195  			return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("unexpected beacon root"))
   196  		}
   197  		switch api.eth.BlockChain().Config().LatestFork(params.Timestamp) {
   198  		case forks.Paris:
   199  			if params.Withdrawals != nil {
   200  				return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("withdrawals before shanghai"))
   201  			}
   202  		case forks.Shanghai:
   203  			if params.Withdrawals == nil {
   204  				return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
   205  			}
   206  		default:
   207  			return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV2 must only be called with paris and shanghai payloads"))
   208  		}
   209  	}
   210  	return api.forkchoiceUpdated(update, params, engine.PayloadV2, false)
   211  }
   212  
   213  // ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root
   214  // in the payload attributes. It supports only PayloadAttributesV3.
   215  func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
   216  	if params != nil {
   217  		if params.Withdrawals == nil {
   218  			return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing withdrawals"))
   219  		}
   220  		if params.BeaconRoot == nil {
   221  			return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("missing beacon root"))
   222  		}
   223  		if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
   224  			return engine.STATUS_INVALID, engine.UnsupportedFork.With(errors.New("forkchoiceUpdatedV3 must only be called for cancun payloads"))
   225  		}
   226  	}
   227  	// TODO(matt): the spec requires that fcu is applied when called on a valid
   228  	// hash, even if params are wrong. To do this we need to split up
   229  	// forkchoiceUpdate into a function that only updates the head and then a
   230  	// function that kicks off block construction.
   231  	return api.forkchoiceUpdated(update, params, engine.PayloadV3, false)
   232  }
   233  
   234  func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, simulatorMode bool) (engine.ForkChoiceResponse, error) {
   235  	api.forkchoiceLock.Lock()
   236  	defer api.forkchoiceLock.Unlock()
   237  
   238  	log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash)
   239  	if update.HeadBlockHash == (common.Hash{}) {
   240  		log.Warn("Forkchoice requested update to zero hash")
   241  		return engine.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
   242  	}
   243  	// Stash away the last update to warn the user if the beacon client goes offline
   244  	api.lastForkchoiceLock.Lock()
   245  	api.lastForkchoiceUpdate = time.Now()
   246  	api.lastForkchoiceLock.Unlock()
   247  
   248  	// Check whether we have the block yet in our database or not. If not, we'll
   249  	// need to either trigger a sync, or to reject this forkchoice update for a
   250  	// reason.
   251  	block := api.eth.BlockChain().GetBlockByHash(update.HeadBlockHash)
   252  	if block == nil {
   253  		// If this block was previously invalidated, keep rejecting it here too
   254  		if res := api.checkInvalidAncestor(update.HeadBlockHash, update.HeadBlockHash); res != nil {
   255  			return engine.ForkChoiceResponse{PayloadStatus: *res, PayloadID: nil}, nil
   256  		}
   257  		// If the head hash is unknown (was not given to us in a newPayload request),
   258  		// we cannot resolve the header, so not much to do. This could be extended in
   259  		// the future to resolve from the `eth` network, but it's an unexpected case
   260  		// that should be fixed, not papered over.
   261  		header := api.remoteBlocks.get(update.HeadBlockHash)
   262  		if header == nil {
   263  			log.Warn("Forkchoice requested unknown head", "hash", update.HeadBlockHash)
   264  			return engine.STATUS_SYNCING, nil
   265  		}
   266  		// If the finalized hash is known, we can direct the downloader to move
   267  		// potentially more data to the freezer from the get go.
   268  		finalized := api.remoteBlocks.get(update.FinalizedBlockHash)
   269  
   270  		// Header advertised via a past newPayload request. Start syncing to it.
   271  		context := []interface{}{"number", header.Number, "hash", header.Hash()}
   272  		if update.FinalizedBlockHash != (common.Hash{}) {
   273  			if finalized == nil {
   274  				context = append(context, []interface{}{"finalized", "unknown"}...)
   275  			} else {
   276  				context = append(context, []interface{}{"finalized", finalized.Number}...)
   277  			}
   278  		}
   279  		log.Info("Forkchoice requested sync to new head", context...)
   280  		if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), header, finalized); err != nil {
   281  			return engine.STATUS_SYNCING, err
   282  		}
   283  		return engine.STATUS_SYNCING, nil
   284  	}
   285  	// Block is known locally, just sanity check that the beacon client does not
   286  	// attempt to push us back to before the merge.
   287  	if block.Difficulty().BitLen() > 0 || block.NumberU64() == 0 {
   288  		var (
   289  			td  = api.eth.BlockChain().GetTd(update.HeadBlockHash, block.NumberU64())
   290  			ptd = api.eth.BlockChain().GetTd(block.ParentHash(), block.NumberU64()-1)
   291  			ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty
   292  		)
   293  		if td == nil || (block.NumberU64() > 0 && ptd == nil) {
   294  			log.Error("TDs unavailable for TTD check", "number", block.NumberU64(), "hash", update.HeadBlockHash, "td", td, "parent", block.ParentHash(), "ptd", ptd)
   295  			return engine.STATUS_INVALID, errors.New("TDs unavailable for TDD check")
   296  		}
   297  		if td.Cmp(ttd) < 0 {
   298  			log.Error("Refusing beacon update to pre-merge", "number", block.NumberU64(), "hash", update.HeadBlockHash, "diff", block.Difficulty(), "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
   299  			return engine.ForkChoiceResponse{PayloadStatus: engine.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil
   300  		}
   301  		if block.NumberU64() > 0 && ptd.Cmp(ttd) >= 0 {
   302  			log.Error("Parent block is already post-ttd", "number", block.NumberU64(), "hash", update.HeadBlockHash, "diff", block.Difficulty(), "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
   303  			return engine.ForkChoiceResponse{PayloadStatus: engine.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil
   304  		}
   305  	}
   306  	valid := func(id *engine.PayloadID) engine.ForkChoiceResponse {
   307  		return engine.ForkChoiceResponse{
   308  			PayloadStatus: engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &update.HeadBlockHash},
   309  			PayloadID:     id,
   310  		}
   311  	}
   312  	if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
   313  		// Block is not canonical, set head.
   314  		if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
   315  			return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
   316  		}
   317  	} else if api.eth.BlockChain().CurrentBlock().Hash() == update.HeadBlockHash {
   318  		// If the specified head matches with our local head, do nothing and keep
   319  		// generating the payload. It's a special corner case that a few slots are
   320  		// missing and we are requested to generate the payload in slot.
   321  	} else {
   322  		// If the head block is already in our canonical chain, the beacon client is
   323  		// probably resyncing. Ignore the update.
   324  		log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number)
   325  		return valid(nil), nil
   326  	}
   327  	api.eth.SetSynced()
   328  
   329  	// If the beacon client also advertised a finalized block, mark the local
   330  	// chain final and completely in PoS mode.
   331  	if update.FinalizedBlockHash != (common.Hash{}) {
   332  		// If the finalized block is not in our canonical tree, something is wrong
   333  		finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)
   334  		if finalBlock == nil {
   335  			log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash)
   336  			return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not available in database"))
   337  		} else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash {
   338  			log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", update.HeadBlockHash)
   339  			return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("final block not in canonical chain"))
   340  		}
   341  		// Set the finalized block
   342  		api.eth.BlockChain().SetFinalized(finalBlock.Header())
   343  	}
   344  	// Check if the safe block hash is in our canonical tree, if not something is wrong
   345  	if update.SafeBlockHash != (common.Hash{}) {
   346  		safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash)
   347  		if safeBlock == nil {
   348  			log.Warn("Safe block not available in database")
   349  			return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not available in database"))
   350  		}
   351  		if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash {
   352  			log.Warn("Safe block not in canonical chain")
   353  			return engine.STATUS_INVALID, engine.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain"))
   354  		}
   355  		// Set the safe block
   356  		api.eth.BlockChain().SetSafe(safeBlock.Header())
   357  	}
   358  	// If payload generation was requested, create a new block to be potentially
   359  	// sealed by the beacon client. The payload will be requested later, and we
   360  	// will replace it arbitrarily many times in between.
   361  	if payloadAttributes != nil {
   362  		args := &miner.BuildPayloadArgs{
   363  			Parent:       update.HeadBlockHash,
   364  			Timestamp:    payloadAttributes.Timestamp,
   365  			FeeRecipient: payloadAttributes.SuggestedFeeRecipient,
   366  			Random:       payloadAttributes.Random,
   367  			Withdrawals:  payloadAttributes.Withdrawals,
   368  			BeaconRoot:   payloadAttributes.BeaconRoot,
   369  			Version:      payloadVersion,
   370  		}
   371  		id := args.Id()
   372  		// If we already are busy generating this work, then we do not need
   373  		// to start a second process.
   374  		if api.localBlocks.has(id) {
   375  			return valid(&id), nil
   376  		}
   377  		// If the beacon chain is ran by a simulator, then transaction insertion,
   378  		// block insertion and block production will happen without any timing
   379  		// delay between them. This will cause flaky simulator executions due to
   380  		// the transaction pool running its internal reset operation on a back-
   381  		// ground thread. To avoid the racey behavior - in simulator mode - the
   382  		// pool will be explicitly blocked on its reset before continuing to the
   383  		// block production below.
   384  		if simulatorMode {
   385  			if err := api.eth.TxPool().Sync(); err != nil {
   386  				log.Error("Failed to sync transaction pool", "err", err)
   387  				return valid(nil), engine.InvalidPayloadAttributes.With(err)
   388  			}
   389  		}
   390  		payload, err := api.eth.Miner().BuildPayload(args)
   391  		if err != nil {
   392  			log.Error("Failed to build payload", "err", err)
   393  			return valid(nil), engine.InvalidPayloadAttributes.With(err)
   394  		}
   395  		api.localBlocks.put(id, payload)
   396  		return valid(&id), nil
   397  	}
   398  	return valid(nil), nil
   399  }
   400  
   401  // ExchangeTransitionConfigurationV1 checks the given configuration against
   402  // the configuration of the node.
   403  func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.TransitionConfigurationV1) (*engine.TransitionConfigurationV1, error) {
   404  	log.Trace("Engine API request received", "method", "ExchangeTransitionConfiguration", "ttd", config.TerminalTotalDifficulty)
   405  	if config.TerminalTotalDifficulty == nil {
   406  		return nil, errors.New("invalid terminal total difficulty")
   407  	}
   408  	// Stash away the last update to warn the user if the beacon client goes offline
   409  	api.lastTransitionLock.Lock()
   410  	api.lastTransitionUpdate = time.Now()
   411  	api.lastTransitionLock.Unlock()
   412  
   413  	ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty
   414  	if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 {
   415  		log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty)
   416  		return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty)
   417  	}
   418  	if config.TerminalBlockHash != (common.Hash{}) {
   419  		if hash := api.eth.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash {
   420  			return &engine.TransitionConfigurationV1{
   421  				TerminalTotalDifficulty: (*hexutil.Big)(ttd),
   422  				TerminalBlockHash:       config.TerminalBlockHash,
   423  				TerminalBlockNumber:     config.TerminalBlockNumber,
   424  			}, nil
   425  		}
   426  		return nil, errors.New("invalid terminal block hash")
   427  	}
   428  	return &engine.TransitionConfigurationV1{TerminalTotalDifficulty: (*hexutil.Big)(ttd)}, nil
   429  }
   430  
   431  // GetPayloadV1 returns a cached payload by id.
   432  func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.ExecutableData, error) {
   433  	if !payloadID.Is(engine.PayloadV1) {
   434  		return nil, engine.UnsupportedFork
   435  	}
   436  	data, err := api.getPayload(payloadID, false)
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  	return data.ExecutionPayload, nil
   441  }
   442  
   443  // GetPayloadV2 returns a cached payload by id.
   444  func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
   445  	if !payloadID.Is(engine.PayloadV1, engine.PayloadV2) {
   446  		return nil, engine.UnsupportedFork
   447  	}
   448  	return api.getPayload(payloadID, false)
   449  }
   450  
   451  // GetPayloadV3 returns a cached payload by id.
   452  func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
   453  	if !payloadID.Is(engine.PayloadV3) {
   454  		return nil, engine.UnsupportedFork
   455  	}
   456  	return api.getPayload(payloadID, false)
   457  }
   458  
   459  func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) {
   460  	log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
   461  	data := api.localBlocks.get(payloadID, full)
   462  	if data == nil {
   463  		return nil, engine.UnknownPayload
   464  	}
   465  	return data, nil
   466  }
   467  
   468  // NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
   469  func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
   470  	if params.Withdrawals != nil {
   471  		return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
   472  	}
   473  	return api.newPayload(params, nil, nil)
   474  }
   475  
   476  // NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
   477  func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
   478  	if api.eth.BlockChain().Config().IsCancun(api.eth.BlockChain().Config().LondonBlock, params.Timestamp) {
   479  		return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun"))
   480  	}
   481  	if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai {
   482  		if params.Withdrawals == nil {
   483  			return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
   484  		}
   485  	} else {
   486  		if params.Withdrawals != nil {
   487  			return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai"))
   488  		}
   489  	}
   490  	if params.ExcessBlobGas != nil {
   491  		return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil excessBlobGas pre-cancun"))
   492  	}
   493  	if params.BlobGasUsed != nil {
   494  		return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil blobGasUsed pre-cancun"))
   495  	}
   496  	return api.newPayload(params, nil, nil)
   497  }
   498  
   499  // NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
   500  func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
   501  	if params.Withdrawals == nil {
   502  		return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
   503  	}
   504  	if params.ExcessBlobGas == nil {
   505  		return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun"))
   506  	}
   507  	if params.BlobGasUsed == nil {
   508  		return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil blobGasUsed post-cancun"))
   509  	}
   510  
   511  	if versionedHashes == nil {
   512  		return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun"))
   513  	}
   514  	if beaconRoot == nil {
   515  		return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil beaconRoot post-cancun"))
   516  	}
   517  
   518  	if api.eth.BlockChain().Config().LatestFork(params.Timestamp) != forks.Cancun {
   519  		return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 must only be called for cancun payloads"))
   520  	}
   521  	return api.newPayload(params, versionedHashes, beaconRoot)
   522  }
   523  
   524  func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) {
   525  	// The locking here is, strictly, not required. Without these locks, this can happen:
   526  	//
   527  	// 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
   528  	//      api.eth.BlockChain().InsertBlockWithoutSetHead, where it is blocked on
   529  	//      e.g database compaction.
   530  	// 2. The call times out on the CL layer, which issues another NewPayload (execdata-N) call.
   531  	//    Similarly, this also get stuck on the same place. Importantly, since the
   532  	//    first call has not gone through, the early checks for "do we already have this block"
   533  	//    will all return false.
   534  	// 3. When the db compaction ends, then N calls inserting the same payload are processed
   535  	//    sequentially.
   536  	// Hence, we use a lock here, to be sure that the previous call has finished before we
   537  	// check whether we already have the block locally.
   538  	api.newPayloadLock.Lock()
   539  	defer api.newPayloadLock.Unlock()
   540  
   541  	log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash)
   542  	block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot)
   543  	if err != nil {
   544  		bgu := "nil"
   545  		if params.BlobGasUsed != nil {
   546  			bgu = strconv.Itoa(int(*params.BlobGasUsed))
   547  		}
   548  		ebg := "nil"
   549  		if params.BlobGasUsed != nil {
   550  			ebg = strconv.Itoa(int(*params.ExcessBlobGas))
   551  		}
   552  		log.Warn("Invalid NewPayload params",
   553  			"params.Number", params.Number,
   554  			"params.ParentHash", params.ParentHash,
   555  			"params.BlockHash", params.BlockHash,
   556  			"params.StateRoot", params.StateRoot,
   557  			"params.FeeRecipient", params.FeeRecipient,
   558  			"params.LogsBloom", common.PrettyBytes(params.LogsBloom),
   559  			"params.Random", params.Random,
   560  			"params.GasLimit", params.GasLimit,
   561  			"params.GasUsed", params.GasUsed,
   562  			"params.Timestamp", params.Timestamp,
   563  			"params.ExtraData", common.PrettyBytes(params.ExtraData),
   564  			"params.BaseFeePerGas", params.BaseFeePerGas,
   565  			"params.BlobGasUsed", bgu,
   566  			"params.ExcessBlobGas", ebg,
   567  			"len(params.Transactions)", len(params.Transactions),
   568  			"len(params.Withdrawals)", len(params.Withdrawals),
   569  			"beaconRoot", beaconRoot,
   570  			"error", err)
   571  		return api.invalid(err, nil), nil
   572  	}
   573  	// Stash away the last update to warn the user if the beacon client goes offline
   574  	api.lastNewPayloadLock.Lock()
   575  	api.lastNewPayloadUpdate = time.Now()
   576  	api.lastNewPayloadLock.Unlock()
   577  
   578  	// If we already have the block locally, ignore the entire execution and just
   579  	// return a fake success.
   580  	if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil {
   581  		log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
   582  		hash := block.Hash()
   583  		return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
   584  	}
   585  	// If this block was rejected previously, keep rejecting it
   586  	if res := api.checkInvalidAncestor(block.Hash(), block.Hash()); res != nil {
   587  		return *res, nil
   588  	}
   589  	// If the parent is missing, we - in theory - could trigger a sync, but that
   590  	// would also entail a reorg. That is problematic if multiple sibling blocks
   591  	// are being fed to us, and even more so, if some semi-distant uncle shortens
   592  	// our live chain. As such, payload execution will not permit reorgs and thus
   593  	// will not trigger a sync cycle. That is fine though, if we get a fork choice
   594  	// update after legit payload executions.
   595  	parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
   596  	if parent == nil {
   597  		return api.delayPayloadImport(block), nil
   598  	}
   599  	// We have an existing parent, do some sanity checks to avoid the beacon client
   600  	// triggering too early
   601  	var (
   602  		ptd  = api.eth.BlockChain().GetTd(parent.Hash(), parent.NumberU64())
   603  		ttd  = api.eth.BlockChain().Config().TerminalTotalDifficulty
   604  		gptd = api.eth.BlockChain().GetTd(parent.ParentHash(), parent.NumberU64()-1)
   605  	)
   606  	if ptd.Cmp(ttd) < 0 {
   607  		log.Warn("Ignoring pre-merge payload", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
   608  		return engine.INVALID_TERMINAL_BLOCK, nil
   609  	}
   610  	if parent.Difficulty().BitLen() > 0 && gptd != nil && gptd.Cmp(ttd) >= 0 {
   611  		log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
   612  		return engine.INVALID_TERMINAL_BLOCK, nil
   613  	}
   614  	if block.Time() <= parent.Time() {
   615  		log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
   616  		return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil
   617  	}
   618  	// Another corner case: if the node is in snap sync mode, but the CL client
   619  	// tries to make it import a block. That should be denied as pushing something
   620  	// into the database directly will conflict with the assumptions of snap sync
   621  	// that it has an empty db that it can fill itself.
   622  	if api.eth.SyncMode() != downloader.FullSync {
   623  		return api.delayPayloadImport(block), nil
   624  	}
   625  	if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
   626  		api.remoteBlocks.put(block.Hash(), block.Header())
   627  		log.Warn("State not available, ignoring new payload")
   628  		return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil
   629  	}
   630  	log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number())
   631  	if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
   632  		log.Warn("NewPayloadV1: inserting block failed", "error", err)
   633  
   634  		api.invalidLock.Lock()
   635  		api.invalidBlocksHits[block.Hash()] = 1
   636  		api.invalidTipsets[block.Hash()] = block.Header()
   637  		api.invalidLock.Unlock()
   638  
   639  		return api.invalid(err, parent.Header()), nil
   640  	}
   641  	hash := block.Hash()
   642  	return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil
   643  }
   644  
   645  // delayPayloadImport stashes the given block away for import at a later time,
   646  // either via a forkchoice update or a sync extension. This method is meant to
   647  // be called by the newpayload command when the block seems to be ok, but some
   648  // prerequisite prevents it from being processed (e.g. no parent, or snap sync).
   649  func (api *ConsensusAPI) delayPayloadImport(block *types.Block) engine.PayloadStatusV1 {
   650  	// Sanity check that this block's parent is not on a previously invalidated
   651  	// chain. If it is, mark the block as invalid too.
   652  	if res := api.checkInvalidAncestor(block.ParentHash(), block.Hash()); res != nil {
   653  		return *res
   654  	}
   655  	// Stash the block away for a potential forced forkchoice update to it
   656  	// at a later time.
   657  	api.remoteBlocks.put(block.Hash(), block.Header())
   658  
   659  	// Although we don't want to trigger a sync, if there is one already in
   660  	// progress, try to extend it with the current payload request to relieve
   661  	// some strain from the forkchoice update.
   662  	err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header())
   663  	if err == nil {
   664  		log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash())
   665  		return engine.PayloadStatusV1{Status: engine.SYNCING}
   666  	}
   667  	// Either no beacon sync was started yet, or it rejected the delivered
   668  	// payload as non-integratable on top of the existing sync. We'll just
   669  	// have to rely on the beacon client to forcefully update the head with
   670  	// a forkchoice update request.
   671  	if api.eth.SyncMode() == downloader.FullSync {
   672  		// In full sync mode, failure to import a well-formed block can only mean
   673  		// that the parent state is missing and the syncer rejected extending the
   674  		// current cycle with the new payload.
   675  		log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash(), "reason", err)
   676  	} else {
   677  		// In non-full sync mode (i.e. snap sync) all payloads are rejected until
   678  		// snap sync terminates as snap sync relies on direct database injections
   679  		// and cannot afford concurrent out-if-band modifications via imports.
   680  		log.Warn("Ignoring payload while snap syncing", "number", block.NumberU64(), "hash", block.Hash(), "reason", err)
   681  	}
   682  	return engine.PayloadStatusV1{Status: engine.SYNCING}
   683  }
   684  
   685  // setInvalidAncestor is a callback for the downloader to notify us if a bad block
   686  // is encountered during the async sync.
   687  func (api *ConsensusAPI) setInvalidAncestor(invalid *types.Header, origin *types.Header) {
   688  	api.invalidLock.Lock()
   689  	defer api.invalidLock.Unlock()
   690  
   691  	api.invalidTipsets[origin.Hash()] = invalid
   692  	api.invalidBlocksHits[invalid.Hash()]++
   693  }
   694  
   695  // checkInvalidAncestor checks whether the specified chain end links to a known
   696  // bad ancestor. If yes, it constructs the payload failure response to return.
   697  func (api *ConsensusAPI) checkInvalidAncestor(check common.Hash, head common.Hash) *engine.PayloadStatusV1 {
   698  	api.invalidLock.Lock()
   699  	defer api.invalidLock.Unlock()
   700  
   701  	// If the hash to check is unknown, return valid
   702  	invalid, ok := api.invalidTipsets[check]
   703  	if !ok {
   704  		return nil
   705  	}
   706  	// If the bad hash was hit too many times, evict it and try to reprocess in
   707  	// the hopes that we have a data race that we can exit out of.
   708  	badHash := invalid.Hash()
   709  
   710  	api.invalidBlocksHits[badHash]++
   711  	if api.invalidBlocksHits[badHash] >= invalidBlockHitEviction {
   712  		log.Warn("Too many bad block import attempt, trying", "number", invalid.Number, "hash", badHash)
   713  		delete(api.invalidBlocksHits, badHash)
   714  
   715  		for descendant, badHeader := range api.invalidTipsets {
   716  			if badHeader.Hash() == badHash {
   717  				delete(api.invalidTipsets, descendant)
   718  			}
   719  		}
   720  		return nil
   721  	}
   722  	// Not too many failures yet, mark the head of the invalid chain as invalid
   723  	if check != head {
   724  		log.Warn("Marked new chain head as invalid", "hash", head, "badnumber", invalid.Number, "badhash", badHash)
   725  		for len(api.invalidTipsets) >= invalidTipsetsCap {
   726  			for key := range api.invalidTipsets {
   727  				delete(api.invalidTipsets, key)
   728  				break
   729  			}
   730  		}
   731  		api.invalidTipsets[head] = invalid
   732  	}
   733  	// If the last valid hash is the terminal pow block, return 0x0 for latest valid hash
   734  	lastValid := &invalid.ParentHash
   735  	if header := api.eth.BlockChain().GetHeader(invalid.ParentHash, invalid.Number.Uint64()-1); header != nil && header.Difficulty.Sign() != 0 {
   736  		lastValid = &common.Hash{}
   737  	}
   738  	failure := "links to previously rejected block"
   739  	return &engine.PayloadStatusV1{
   740  		Status:          engine.INVALID,
   741  		LatestValidHash: lastValid,
   742  		ValidationError: &failure,
   743  	}
   744  }
   745  
   746  // invalid returns a response "INVALID" with the latest valid hash supplied by latest.
   747  func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) engine.PayloadStatusV1 {
   748  	var currentHash *common.Hash
   749  	if latestValid != nil {
   750  		if latestValid.Difficulty.BitLen() != 0 {
   751  			// Set latest valid hash to 0x0 if parent is PoW block
   752  			currentHash = &common.Hash{}
   753  		} else {
   754  			// Otherwise set latest valid hash to parent hash
   755  			h := latestValid.Hash()
   756  			currentHash = &h
   757  		}
   758  	}
   759  	errorMsg := err.Error()
   760  	return engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: currentHash, ValidationError: &errorMsg}
   761  }
   762  
   763  // heartbeat loops indefinitely, and checks if there have been beacon client updates
   764  // received in the last while. If not - or if they but strange ones - it warns the
   765  // user that something might be off with their consensus node.
   766  //
   767  // TODO(karalabe): Spin this goroutine down somehow
   768  func (api *ConsensusAPI) heartbeat() {
   769  	// Sleep a bit on startup since there's obviously no beacon client yet
   770  	// attached, so no need to print scary warnings to the user.
   771  	time.Sleep(beaconUpdateStartupTimeout)
   772  
   773  	// If the network is not yet merged/merging, don't bother continuing.
   774  	if api.eth.BlockChain().Config().TerminalTotalDifficulty == nil {
   775  		return
   776  	}
   777  
   778  	var offlineLogged time.Time
   779  
   780  	for {
   781  		// Sleep a bit and retrieve the last known consensus updates
   782  		time.Sleep(5 * time.Second)
   783  
   784  		api.lastTransitionLock.Lock()
   785  		lastTransitionUpdate := api.lastTransitionUpdate
   786  		api.lastTransitionLock.Unlock()
   787  
   788  		api.lastForkchoiceLock.Lock()
   789  		lastForkchoiceUpdate := api.lastForkchoiceUpdate
   790  		api.lastForkchoiceLock.Unlock()
   791  
   792  		api.lastNewPayloadLock.Lock()
   793  		lastNewPayloadUpdate := api.lastNewPayloadUpdate
   794  		api.lastNewPayloadLock.Unlock()
   795  
   796  		// If there have been no updates for the past while, warn the user
   797  		// that the beacon client is probably offline
   798  		if time.Since(lastForkchoiceUpdate) <= beaconUpdateConsensusTimeout || time.Since(lastNewPayloadUpdate) <= beaconUpdateConsensusTimeout {
   799  			offlineLogged = time.Time{}
   800  			continue
   801  		}
   802  		if time.Since(offlineLogged) > beaconUpdateWarnFrequency {
   803  			if lastForkchoiceUpdate.IsZero() && lastNewPayloadUpdate.IsZero() {
   804  				if lastTransitionUpdate.IsZero() {
   805  					log.Warn("Post-merge network, but no beacon client seen. Please launch one to follow the chain!")
   806  				} else {
   807  					log.Warn("Beacon client online, but never received consensus updates. Please ensure your beacon client is operational to follow the chain!")
   808  				}
   809  			} else {
   810  				log.Warn("Beacon client online, but no consensus updates received in a while. Please fix your beacon client to follow the chain!")
   811  			}
   812  			offlineLogged = time.Now()
   813  		}
   814  		continue
   815  	}
   816  }
   817  
   818  // ExchangeCapabilities returns the current methods provided by this node.
   819  func (api *ConsensusAPI) ExchangeCapabilities([]string) []string {
   820  	return caps
   821  }
   822  
   823  // GetClientVersionV1 exchanges client version data of this node.
   824  func (api *ConsensusAPI) GetClientVersionV1(info engine.ClientVersionV1) []engine.ClientVersionV1 {
   825  	log.Trace("Engine API request received", "method", "GetClientVersionV1", "info", info.String())
   826  	commit := make([]byte, 4)
   827  	if vcs, ok := version.VCS(); ok {
   828  		commit = common.FromHex(vcs.Commit)[0:4]
   829  	}
   830  	return []engine.ClientVersionV1{
   831  		{
   832  			Code:    engine.ClientCode,
   833  			Name:    engine.ClientName,
   834  			Version: params.VersionWithMeta,
   835  			Commit:  hexutil.Encode(commit),
   836  		},
   837  	}
   838  }
   839  
   840  // GetPayloadBodiesByHashV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list
   841  // of block bodies by the engine api.
   842  func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engine.ExecutionPayloadBodyV1 {
   843  	bodies := make([]*engine.ExecutionPayloadBodyV1, len(hashes))
   844  	for i, hash := range hashes {
   845  		block := api.eth.BlockChain().GetBlockByHash(hash)
   846  		bodies[i] = getBody(block)
   847  	}
   848  	return bodies
   849  }
   850  
   851  // GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
   852  // of block bodies by the engine api.
   853  func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBodyV1, error) {
   854  	if start == 0 || count == 0 {
   855  		return nil, engine.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count))
   856  	}
   857  	if count > 1024 {
   858  		return nil, engine.TooLargeRequest.With(fmt.Errorf("requested count too large: %v", count))
   859  	}
   860  	// limit count up until current
   861  	current := api.eth.BlockChain().CurrentBlock().Number.Uint64()
   862  	last := uint64(start) + uint64(count) - 1
   863  	if last > current {
   864  		last = current
   865  	}
   866  	bodies := make([]*engine.ExecutionPayloadBodyV1, 0, uint64(count))
   867  	for i := uint64(start); i <= last; i++ {
   868  		block := api.eth.BlockChain().GetBlockByNumber(i)
   869  		bodies = append(bodies, getBody(block))
   870  	}
   871  	return bodies, nil
   872  }
   873  
   874  func getBody(block *types.Block) *engine.ExecutionPayloadBodyV1 {
   875  	if block == nil {
   876  		return nil
   877  	}
   878  
   879  	var (
   880  		body        = block.Body()
   881  		txs         = make([]hexutil.Bytes, len(body.Transactions))
   882  		withdrawals = body.Withdrawals
   883  	)
   884  
   885  	for j, tx := range body.Transactions {
   886  		txs[j], _ = tx.MarshalBinary()
   887  	}
   888  
   889  	// Post-shanghai withdrawals MUST be set to empty slice instead of nil
   890  	if withdrawals == nil && block.Header().WithdrawalsHash != nil {
   891  		withdrawals = make([]*types.Withdrawal, 0)
   892  	}
   893  
   894  	return &engine.ExecutionPayloadBodyV1{
   895  		TransactionData: txs,
   896  		Withdrawals:     withdrawals,
   897  	}
   898  }