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