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