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