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