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