github.com/theQRL/go-zond@v0.2.1/zond/catalyst/api.go (about)

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