github.com/Cleverse/go-ethereum@v0.0.0-20220927095127-45113064e7f2/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  	"sync"
    26  	"time"
    27  
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethereum/go-ethereum/common/hexutil"
    30  	"github.com/ethereum/go-ethereum/core/beacon"
    31  	"github.com/ethereum/go-ethereum/core/rawdb"
    32  	"github.com/ethereum/go-ethereum/core/types"
    33  	"github.com/ethereum/go-ethereum/eth"
    34  	"github.com/ethereum/go-ethereum/eth/downloader"
    35  	"github.com/ethereum/go-ethereum/log"
    36  	"github.com/ethereum/go-ethereum/node"
    37  	"github.com/ethereum/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  
    64  type ConsensusAPI struct {
    65  	eth *eth.Ethereum
    66  
    67  	remoteBlocks *headerQueue  // Cache of remote payloads received
    68  	localBlocks  *payloadQueue // Cache of local payloads generated
    69  
    70  	// The forkchoice update and new payload method require us to return the
    71  	// latest valid hash in an invalid chain. To support that return, we need
    72  	// to track historical bad blocks as well as bad tipsets in case a chain
    73  	// is constantly built on it.
    74  	//
    75  	// There are a few important caveats in this mechanism:
    76  	//   - The bad block tracking is ephemeral, in-memory only. We must never
    77  	//     persist any bad block information to disk as a bug in Geth could end
    78  	//     up blocking a valid chain, even if a later Geth update would accept
    79  	//     it.
    80  	//   - Bad blocks will get forgotten after a certain threshold of import
    81  	//     attempts and will be retried. The rationale is that if the network
    82  	//     really-really-really tries to feed us a block, we should give it a
    83  	//     new chance, perhaps us being racey instead of the block being legit
    84  	//     bad (this happened in Geth at a point with import vs. pending race).
    85  	//   - Tracking all the blocks built on top of the bad one could be a bit
    86  	//     problematic, so we will only track the head chain segment of a bad
    87  	//     chain to allow discarding progressing bad chains and side chains,
    88  	//     without tracking too much bad data.
    89  	invalidBlocksHits map[common.Hash]int           // Emhemeral cache to track invalid blocks and their hit count
    90  	invalidTipsets    map[common.Hash]*types.Header // Ephemeral cache to track invalid tipsets and their bad ancestor
    91  	invalidLock       sync.Mutex                    // Protects the invalid maps from concurrent access
    92  
    93  	forkChoiceLock sync.Mutex // Lock for the forkChoiceUpdated method
    94  }
    95  
    96  // NewConsensusAPI creates a new consensus api for the given backend.
    97  // The underlying blockchain needs to have a valid terminal total difficulty set.
    98  func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
    99  	if eth.BlockChain().Config().TerminalTotalDifficulty == nil {
   100  		log.Warn("Engine API started but chain not configured for merge yet")
   101  	}
   102  	api := &ConsensusAPI{
   103  		eth:               eth,
   104  		remoteBlocks:      newHeaderQueue(),
   105  		localBlocks:       newPayloadQueue(),
   106  		invalidBlocksHits: make(map[common.Hash]int),
   107  		invalidTipsets:    make(map[common.Hash]*types.Header),
   108  	}
   109  	eth.Downloader().SetBadBlockCallback(api.setInvalidAncestor)
   110  
   111  	return api
   112  }
   113  
   114  // ForkchoiceUpdatedV1 has several responsibilities:
   115  // If the method is called with an empty head block:
   116  // 		we return success, which can be used to check if the engine API is enabled
   117  // If the total difficulty was not reached:
   118  // 		we return INVALID
   119  // If the finalizedBlockHash is set:
   120  // 		we check if we have the finalizedBlockHash in our db, if not we start a sync
   121  // We try to set our blockchain to the headBlock
   122  // If there are payloadAttributes:
   123  // 		we try to assemble a block with the payloadAttributes and return its payloadID
   124  func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) {
   125  	api.forkChoiceLock.Lock()
   126  	defer api.forkChoiceLock.Unlock()
   127  
   128  	log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash)
   129  	if update.HeadBlockHash == (common.Hash{}) {
   130  		log.Warn("Forkchoice requested update to zero hash")
   131  		return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
   132  	}
   133  
   134  	// Check whether we have the block yet in our database or not. If not, we'll
   135  	// need to either trigger a sync, or to reject this forkchoice update for a
   136  	// reason.
   137  	block := api.eth.BlockChain().GetBlockByHash(update.HeadBlockHash)
   138  	if block == nil {
   139  		// If this block was previously invalidated, keep rejecting it here too
   140  		if res := api.checkInvalidAncestor(update.HeadBlockHash, update.HeadBlockHash); res != nil {
   141  			return beacon.ForkChoiceResponse{PayloadStatus: *res, PayloadID: nil}, nil
   142  		}
   143  		// If the head hash is unknown (was not given to us in a newPayload request),
   144  		// we cannot resolve the header, so not much to do. This could be extended in
   145  		// the future to resolve from the `eth` network, but it's an unexpected case
   146  		// that should be fixed, not papered over.
   147  		header := api.remoteBlocks.get(update.HeadBlockHash)
   148  		if header == nil {
   149  			log.Warn("Forkchoice requested unknown head", "hash", update.HeadBlockHash)
   150  			return beacon.STATUS_SYNCING, nil
   151  		}
   152  		// Header advertised via a past newPayload request. Start syncing to it.
   153  		// Before we do however, make sure any legacy sync in switched off so we
   154  		// don't accidentally have 2 cycles running.
   155  		if merger := api.eth.Merger(); !merger.TDDReached() {
   156  			merger.ReachTTD()
   157  			api.eth.Downloader().Cancel()
   158  		}
   159  		log.Info("Forkchoice requested sync to new head", "number", header.Number, "hash", header.Hash())
   160  		if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), header); err != nil {
   161  			return beacon.STATUS_SYNCING, err
   162  		}
   163  		return beacon.STATUS_SYNCING, nil
   164  	}
   165  	// Block is known locally, just sanity check that the beacon client does not
   166  	// attempt to push us back to before the merge.
   167  	if block.Difficulty().BitLen() > 0 || block.NumberU64() == 0 {
   168  		var (
   169  			td  = api.eth.BlockChain().GetTd(update.HeadBlockHash, block.NumberU64())
   170  			ptd = api.eth.BlockChain().GetTd(block.ParentHash(), block.NumberU64()-1)
   171  			ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty
   172  		)
   173  		if td == nil || (block.NumberU64() > 0 && ptd == nil) {
   174  			log.Error("TDs unavailable for TTD check", "number", block.NumberU64(), "hash", update.HeadBlockHash, "td", td, "parent", block.ParentHash(), "ptd", ptd)
   175  			return beacon.STATUS_INVALID, errors.New("TDs unavailable for TDD check")
   176  		}
   177  		if td.Cmp(ttd) < 0 {
   178  			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)))
   179  			return beacon.ForkChoiceResponse{PayloadStatus: beacon.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil
   180  		}
   181  		if block.NumberU64() > 0 && ptd.Cmp(ttd) >= 0 {
   182  			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)))
   183  			return beacon.ForkChoiceResponse{PayloadStatus: beacon.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil
   184  		}
   185  	}
   186  	valid := func(id *beacon.PayloadID) beacon.ForkChoiceResponse {
   187  		return beacon.ForkChoiceResponse{
   188  			PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &update.HeadBlockHash},
   189  			PayloadID:     id,
   190  		}
   191  	}
   192  	if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
   193  		// Block is not canonical, set head.
   194  		if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
   195  			return beacon.ForkChoiceResponse{PayloadStatus: beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &latestValid}}, err
   196  		}
   197  	} else if api.eth.BlockChain().CurrentBlock().Hash() == update.HeadBlockHash {
   198  		// If the specified head matches with our local head, do nothing and keep
   199  		// generating the payload. It's a special corner case that a few slots are
   200  		// missing and we are requested to generate the payload in slot.
   201  	} else {
   202  		// If the head block is already in our canonical chain, the beacon client is
   203  		// probably resyncing. Ignore the update.
   204  		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())
   205  		return valid(nil), nil
   206  	}
   207  	api.eth.SetSynced()
   208  
   209  	// If the beacon client also advertised a finalized block, mark the local
   210  	// chain final and completely in PoS mode.
   211  	if update.FinalizedBlockHash != (common.Hash{}) {
   212  		if merger := api.eth.Merger(); !merger.PoSFinalized() {
   213  			merger.FinalizePoS()
   214  		}
   215  		// If the finalized block is not in our canonical tree, somethings wrong
   216  		finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)
   217  		if finalBlock == nil {
   218  			log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash)
   219  			return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("final block not available in database"))
   220  		} else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash {
   221  			log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", update.HeadBlockHash)
   222  			return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("final block not in canonical chain"))
   223  		}
   224  		// Set the finalized block
   225  		api.eth.BlockChain().SetFinalized(finalBlock)
   226  	}
   227  	// Check if the safe block hash is in our canonical tree, if not somethings wrong
   228  	if update.SafeBlockHash != (common.Hash{}) {
   229  		safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash)
   230  		if safeBlock == nil {
   231  			log.Warn("Safe block not available in database")
   232  			return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("safe block not available in database"))
   233  		}
   234  		if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash {
   235  			log.Warn("Safe block not in canonical chain")
   236  			return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain"))
   237  		}
   238  		// Set the safe block
   239  		api.eth.BlockChain().SetSafe(safeBlock)
   240  	}
   241  	// If payload generation was requested, create a new block to be potentially
   242  	// sealed by the beacon client. The payload will be requested later, and we
   243  	// might replace it arbitrarily many times in between.
   244  	if payloadAttributes != nil {
   245  		// Create an empty block first which can be used as a fallback
   246  		empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, true)
   247  		if err != nil {
   248  			log.Error("Failed to create empty sealing payload", "err", err)
   249  			return valid(nil), beacon.InvalidPayloadAttributes.With(err)
   250  		}
   251  		// Send a request to generate a full block in the background.
   252  		// The result can be obtained via the returned channel.
   253  		resCh, err := api.eth.Miner().GetSealingBlockAsync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, false)
   254  		if err != nil {
   255  			log.Error("Failed to create async sealing payload", "err", err)
   256  			return valid(nil), beacon.InvalidPayloadAttributes.With(err)
   257  		}
   258  		id := computePayloadId(update.HeadBlockHash, payloadAttributes)
   259  		api.localBlocks.put(id, &payload{empty: empty, result: resCh})
   260  		return valid(&id), nil
   261  	}
   262  	return valid(nil), nil
   263  }
   264  
   265  // ExchangeTransitionConfigurationV1 checks the given configuration against
   266  // the configuration of the node.
   267  func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config beacon.TransitionConfigurationV1) (*beacon.TransitionConfigurationV1, error) {
   268  	if config.TerminalTotalDifficulty == nil {
   269  		return nil, errors.New("invalid terminal total difficulty")
   270  	}
   271  	ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty
   272  	if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 {
   273  		log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty)
   274  		return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty)
   275  	}
   276  
   277  	if config.TerminalBlockHash != (common.Hash{}) {
   278  		if hash := api.eth.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash {
   279  			return &beacon.TransitionConfigurationV1{
   280  				TerminalTotalDifficulty: (*hexutil.Big)(ttd),
   281  				TerminalBlockHash:       config.TerminalBlockHash,
   282  				TerminalBlockNumber:     config.TerminalBlockNumber,
   283  			}, nil
   284  		}
   285  		return nil, fmt.Errorf("invalid terminal block hash")
   286  	}
   287  	return &beacon.TransitionConfigurationV1{TerminalTotalDifficulty: (*hexutil.Big)(ttd)}, nil
   288  }
   289  
   290  // GetPayloadV1 returns a cached payload by id.
   291  func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) {
   292  	log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
   293  	data := api.localBlocks.get(payloadID)
   294  	if data == nil {
   295  		return nil, beacon.UnknownPayload
   296  	}
   297  	return data, nil
   298  }
   299  
   300  // NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
   301  func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) {
   302  	log.Trace("Engine API request received", "method", "ExecutePayload", "number", params.Number, "hash", params.BlockHash)
   303  	block, err := beacon.ExecutableDataToBlock(params)
   304  	if err != nil {
   305  		log.Debug("Invalid NewPayload params", "params", params, "error", err)
   306  		return beacon.PayloadStatusV1{Status: beacon.INVALIDBLOCKHASH}, nil
   307  	}
   308  	// If we already have the block locally, ignore the entire execution and just
   309  	// return a fake success.
   310  	if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil {
   311  		log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
   312  		hash := block.Hash()
   313  		return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil
   314  	}
   315  	// If this block was rejected previously, keep rejecting it
   316  	if res := api.checkInvalidAncestor(block.Hash(), block.Hash()); res != nil {
   317  		return *res, nil
   318  	}
   319  	// If the parent is missing, we - in theory - could trigger a sync, but that
   320  	// would also entail a reorg. That is problematic if multiple sibling blocks
   321  	// are being fed to us, and even more so, if some semi-distant uncle shortens
   322  	// our live chain. As such, payload execution will not permit reorgs and thus
   323  	// will not trigger a sync cycle. That is fine though, if we get a fork choice
   324  	// update after legit payload executions.
   325  	parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
   326  	if parent == nil {
   327  		return api.delayPayloadImport(block)
   328  	}
   329  	// We have an existing parent, do some sanity checks to avoid the beacon client
   330  	// triggering too early
   331  	var (
   332  		ptd  = api.eth.BlockChain().GetTd(parent.Hash(), parent.NumberU64())
   333  		ttd  = api.eth.BlockChain().Config().TerminalTotalDifficulty
   334  		gptd = api.eth.BlockChain().GetTd(parent.ParentHash(), parent.NumberU64()-1)
   335  	)
   336  	if ptd.Cmp(ttd) < 0 {
   337  		log.Warn("Ignoring pre-merge payload", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
   338  		return beacon.INVALID_TERMINAL_BLOCK, nil
   339  	}
   340  	if parent.Difficulty().BitLen() > 0 && gptd != nil && gptd.Cmp(ttd) >= 0 {
   341  		log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
   342  		return beacon.INVALID_TERMINAL_BLOCK, nil
   343  	}
   344  	if block.Time() <= parent.Time() {
   345  		log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
   346  		return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil
   347  	}
   348  	// Another cornercase: if the node is in snap sync mode, but the CL client
   349  	// tries to make it import a block. That should be denied as pushing something
   350  	// into the database directly will conflict with the assumptions of snap sync
   351  	// that it has an empty db that it can fill itself.
   352  	if api.eth.SyncMode() != downloader.FullSync {
   353  		return api.delayPayloadImport(block)
   354  	}
   355  	if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
   356  		api.remoteBlocks.put(block.Hash(), block.Header())
   357  		log.Warn("State not available, ignoring new payload")
   358  		return beacon.PayloadStatusV1{Status: beacon.ACCEPTED}, nil
   359  	}
   360  	log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number)
   361  	if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
   362  		log.Warn("NewPayloadV1: inserting block failed", "error", err)
   363  
   364  		api.invalidLock.Lock()
   365  		api.invalidBlocksHits[block.Hash()] = 1
   366  		api.invalidTipsets[block.Hash()] = block.Header()
   367  		api.invalidLock.Unlock()
   368  
   369  		return api.invalid(err, parent.Header()), nil
   370  	}
   371  	// We've accepted a valid payload from the beacon client. Mark the local
   372  	// chain transitions to notify other subsystems (e.g. downloader) of the
   373  	// behavioral change.
   374  	if merger := api.eth.Merger(); !merger.TDDReached() {
   375  		merger.ReachTTD()
   376  		api.eth.Downloader().Cancel()
   377  	}
   378  	hash := block.Hash()
   379  	return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil
   380  }
   381  
   382  // computePayloadId computes a pseudo-random payloadid, based on the parameters.
   383  func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttributesV1) beacon.PayloadID {
   384  	// Hash
   385  	hasher := sha256.New()
   386  	hasher.Write(headBlockHash[:])
   387  	binary.Write(hasher, binary.BigEndian, params.Timestamp)
   388  	hasher.Write(params.Random[:])
   389  	hasher.Write(params.SuggestedFeeRecipient[:])
   390  	var out beacon.PayloadID
   391  	copy(out[:], hasher.Sum(nil)[:8])
   392  	return out
   393  }
   394  
   395  // delayPayloadImport stashes the given block away for import at a later time,
   396  // either via a forkchoice update or a sync extension. This method is meant to
   397  // be called by the newpayload command when the block seems to be ok, but some
   398  // prerequisite prevents it from being processed (e.g. no parent, or snap sync).
   399  func (api *ConsensusAPI) delayPayloadImport(block *types.Block) (beacon.PayloadStatusV1, error) {
   400  	// Sanity check that this block's parent is not on a previously invalidated
   401  	// chain. If it is, mark the block as invalid too.
   402  	if res := api.checkInvalidAncestor(block.ParentHash(), block.Hash()); res != nil {
   403  		return *res, nil
   404  	}
   405  	// Stash the block away for a potential forced forkchoice update to it
   406  	// at a later time.
   407  	api.remoteBlocks.put(block.Hash(), block.Header())
   408  
   409  	// Although we don't want to trigger a sync, if there is one already in
   410  	// progress, try to extend if with the current payload request to relieve
   411  	// some strain from the forkchoice update.
   412  	if err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()); err == nil {
   413  		log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash())
   414  		return beacon.PayloadStatusV1{Status: beacon.SYNCING}, nil
   415  	}
   416  	// Either no beacon sync was started yet, or it rejected the delivered
   417  	// payload as non-integratable on top of the existing sync. We'll just
   418  	// have to rely on the beacon client to forcefully update the head with
   419  	// a forkchoice update request.
   420  	log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash())
   421  	return beacon.PayloadStatusV1{Status: beacon.ACCEPTED}, nil
   422  }
   423  
   424  // setInvalidAncestor is a callback for the downloader to notify us if a bad block
   425  // is encountered during the async sync.
   426  func (api *ConsensusAPI) setInvalidAncestor(invalid *types.Header, origin *types.Header) {
   427  	api.invalidLock.Lock()
   428  	defer api.invalidLock.Unlock()
   429  
   430  	api.invalidTipsets[origin.Hash()] = invalid
   431  	api.invalidBlocksHits[invalid.Hash()]++
   432  }
   433  
   434  // checkInvalidAncestor checks whether the specified chain end links to a known
   435  // bad ancestor. If yes, it constructs the payload failure response to return.
   436  func (api *ConsensusAPI) checkInvalidAncestor(check common.Hash, head common.Hash) *beacon.PayloadStatusV1 {
   437  	api.invalidLock.Lock()
   438  	defer api.invalidLock.Unlock()
   439  
   440  	// If the hash to check is unknown, return valid
   441  	invalid, ok := api.invalidTipsets[check]
   442  	if !ok {
   443  		return nil
   444  	}
   445  	// If the bad hash was hit too many times, evict it and try to reprocess in
   446  	// the hopes that we have a data race that we can exit out of.
   447  	badHash := invalid.Hash()
   448  
   449  	api.invalidBlocksHits[badHash]++
   450  	if api.invalidBlocksHits[badHash] >= invalidBlockHitEviction {
   451  		log.Warn("Too many bad block import attempt, trying", "number", invalid.Number, "hash", badHash)
   452  		delete(api.invalidBlocksHits, badHash)
   453  
   454  		for descendant, badHeader := range api.invalidTipsets {
   455  			if badHeader.Hash() == badHash {
   456  				delete(api.invalidTipsets, descendant)
   457  			}
   458  		}
   459  		return nil
   460  	}
   461  	// Not too many failures yet, mark the head of the invalid chain as invalid
   462  	if check != head {
   463  		log.Warn("Marked new chain head as invalid", "hash", head, "badnumber", invalid.Number, "badhash", badHash)
   464  		for len(api.invalidTipsets) >= invalidTipsetsCap {
   465  			for key := range api.invalidTipsets {
   466  				delete(api.invalidTipsets, key)
   467  				break
   468  			}
   469  		}
   470  		api.invalidTipsets[head] = invalid
   471  	}
   472  	failure := "links to previously rejected block"
   473  	return &beacon.PayloadStatusV1{
   474  		Status:          beacon.INVALID,
   475  		LatestValidHash: &invalid.ParentHash,
   476  		ValidationError: &failure,
   477  	}
   478  }
   479  
   480  // invalid returns a response "INVALID" with the latest valid hash supplied by latest or to the current head
   481  // if no latestValid block was provided.
   482  func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) beacon.PayloadStatusV1 {
   483  	currentHash := api.eth.BlockChain().CurrentBlock().Hash()
   484  	if latestValid != nil {
   485  		// Set latest valid hash to 0x0 if parent is PoW block
   486  		currentHash = common.Hash{}
   487  		if latestValid.Difficulty.BitLen() == 0 {
   488  			// Otherwise set latest valid hash to parent hash
   489  			currentHash = latestValid.Hash()
   490  		}
   491  	}
   492  	errorMsg := err.Error()
   493  	return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &currentHash, ValidationError: &errorMsg}
   494  }