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