github.com/bearnetworkchain/go-bearnetwork@v1.10.19-0.20220604150648-d63890c2e42b/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/bearnetworkchain/go-bearnetwork/common"
    29  	"github.com/bearnetworkchain/go-bearnetwork/common/hexutil"
    30  	"github.com/bearnetworkchain/go-bearnetwork/core/beacon"
    31  	"github.com/bearnetworkchain/go-bearnetwork/core/rawdb"
    32  	"github.com/bearnetworkchain/go-bearnetwork/core/types"
    33  	"github.com/bearnetworkchain/go-bearnetwork/eth"
    34  	"github.com/bearnetworkchain/go-bearnetwork/log"
    35  	"github.com/bearnetworkchain/go-bearnetwork/node"
    36  	"github.com/bearnetworkchain/go-bearnetwork/rpc"
    37  )
    38  
    39  // Register adds catalyst APIs to the full node.
    40  func Register(stack *node.Node, backend *eth.Ethereum) error {
    41  	log.Warn("Catalyst mode enabled", "protocol", "eth")
    42  	stack.RegisterAPIs([]rpc.API{
    43  		{
    44  			Namespace:     "engine",
    45  			Version:       "1.0",
    46  			Service:       NewConsensusAPI(backend),
    47  			Public:        true,
    48  			Authenticated: true,
    49  		},
    50  		{
    51  			Namespace:     "engine",
    52  			Version:       "1.0",
    53  			Service:       NewConsensusAPI(backend),
    54  			Public:        true,
    55  			Authenticated: false,
    56  		},
    57  	})
    58  	return nil
    59  }
    60  
    61  type ConsensusAPI struct {
    62  	eth          *eth.Ethereum
    63  	remoteBlocks *headerQueue  // Cache of remote payloads received
    64  	localBlocks  *payloadQueue // Cache of local payloads generated
    65  	// Lock for the forkChoiceUpdated method
    66  	forkChoiceLock sync.Mutex
    67  }
    68  
    69  // NewConsensusAPI creates a new consensus api for the given backend.
    70  // The underlying blockchain needs to have a valid terminal total difficulty set.
    71  func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
    72  	if eth.BlockChain().Config().TerminalTotalDifficulty == nil {
    73  		panic("Catalyst started without valid total difficulty")
    74  	}
    75  	return &ConsensusAPI{
    76  		eth:          eth,
    77  		remoteBlocks: newHeaderQueue(),
    78  		localBlocks:  newPayloadQueue(),
    79  	}
    80  }
    81  
    82  // ForkchoiceUpdatedV1 has several responsibilities:
    83  // If the method is called with an empty head block:
    84  // 		we return success, which can be used to check if the catalyst mode is enabled
    85  // If the total difficulty was not reached:
    86  // 		we return INVALID
    87  // If the finalizedBlockHash is set:
    88  // 		we check if we have the finalizedBlockHash in our db, if not we start a sync
    89  // We try to set our blockchain to the headBlock
    90  // If there are payloadAttributes:
    91  // 		we try to assemble a block with the payloadAttributes and return its payloadID
    92  func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) {
    93  	api.forkChoiceLock.Lock()
    94  	defer api.forkChoiceLock.Unlock()
    95  
    96  	log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash)
    97  	if update.HeadBlockHash == (common.Hash{}) {
    98  		log.Warn("Forkchoice requested update to zero hash")
    99  		return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
   100  	}
   101  
   102  	// Check whether we have the block yet in our database or not. If not, we'll
   103  	// need to either trigger a sync, or to reject this forkchoice update for a
   104  	// reason.
   105  	block := api.eth.BlockChain().GetBlockByHash(update.HeadBlockHash)
   106  	if block == nil {
   107  		// If the head hash is unknown (was not given to us in a newPayload request),
   108  		// we cannot resolve the header, so not much to do. This could be extended in
   109  		// the future to resolve from the `eth` network, but it's an unexpected case
   110  		// that should be fixed, not papered over.
   111  		header := api.remoteBlocks.get(update.HeadBlockHash)
   112  		if header == nil {
   113  			log.Warn("Forkchoice requested unknown head", "hash", update.HeadBlockHash)
   114  			return beacon.STATUS_SYNCING, nil
   115  		}
   116  		// Header advertised via a past newPayload request. Start syncing to it.
   117  		// Before we do however, make sure any legacy sync in switched off so we
   118  		// don't accidentally have 2 cycles running.
   119  		if merger := api.eth.Merger(); !merger.TDDReached() {
   120  			merger.ReachTTD()
   121  			api.eth.Downloader().Cancel()
   122  		}
   123  		log.Info("Forkchoice requested sync to new head", "number", header.Number, "hash", header.Hash())
   124  		if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), header); err != nil {
   125  			return beacon.STATUS_SYNCING, err
   126  		}
   127  		return beacon.STATUS_SYNCING, nil
   128  	}
   129  	// Block is known locally, just sanity check that the beacon client does not
   130  	// attempt to push us back to before the merge.
   131  	if block.Difficulty().BitLen() > 0 || block.NumberU64() == 0 {
   132  		var (
   133  			td  = api.eth.BlockChain().GetTd(update.HeadBlockHash, block.NumberU64())
   134  			ptd = api.eth.BlockChain().GetTd(block.ParentHash(), block.NumberU64()-1)
   135  			ttd = api.eth.BlockChain().Config().TerminalTotalDifficulty
   136  		)
   137  		if td == nil || (block.NumberU64() > 0 && ptd == nil) {
   138  			log.Error("TDs unavailable for TTD check", "number", block.NumberU64(), "hash", update.HeadBlockHash, "td", td, "parent", block.ParentHash(), "ptd", ptd)
   139  			return beacon.STATUS_INVALID, errors.New("TDs unavailable for TDD check")
   140  		}
   141  		if td.Cmp(ttd) < 0 {
   142  			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)))
   143  			return beacon.ForkChoiceResponse{PayloadStatus: beacon.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil
   144  		}
   145  		if block.NumberU64() > 0 && ptd.Cmp(ttd) >= 0 {
   146  			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)))
   147  			return beacon.ForkChoiceResponse{PayloadStatus: beacon.INVALID_TERMINAL_BLOCK, PayloadID: nil}, nil
   148  		}
   149  	}
   150  
   151  	if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
   152  		// Block is not canonical, set head.
   153  		if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
   154  			return beacon.ForkChoiceResponse{PayloadStatus: beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &latestValid}}, err
   155  		}
   156  	} else {
   157  		// If the head block is already in our canonical chain, the beacon client is
   158  		// probably resyncing. Ignore the update.
   159  		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())
   160  	}
   161  	api.eth.SetSynced()
   162  
   163  	// If the beacon client also advertised a finalized block, mark the local
   164  	// chain final and completely in PoS mode.
   165  	if update.FinalizedBlockHash != (common.Hash{}) {
   166  		if merger := api.eth.Merger(); !merger.PoSFinalized() {
   167  			merger.FinalizePoS()
   168  		}
   169  		// If the finalized block is not in our canonical tree, somethings wrong
   170  		finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)
   171  		if finalBlock == nil {
   172  			log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash)
   173  			return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("final block not available in database"))
   174  		} else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash {
   175  			log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", update.HeadBlockHash)
   176  			return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("final block not in canonical chain"))
   177  		}
   178  		// Set the finalized block
   179  		api.eth.BlockChain().SetFinalized(finalBlock)
   180  	}
   181  	// Check if the safe block hash is in our canonical tree, if not somethings wrong
   182  	if update.SafeBlockHash != (common.Hash{}) {
   183  		safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash)
   184  		if safeBlock == nil {
   185  			log.Warn("Safe block not available in database")
   186  			return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("safe block not available in database"))
   187  		}
   188  		if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash {
   189  			log.Warn("Safe block not in canonical chain")
   190  			return beacon.STATUS_INVALID, beacon.InvalidForkChoiceState.With(errors.New("safe block not in canonical chain"))
   191  		}
   192  	}
   193  	valid := func(id *beacon.PayloadID) beacon.ForkChoiceResponse {
   194  		return beacon.ForkChoiceResponse{
   195  			PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &update.HeadBlockHash},
   196  			PayloadID:     id,
   197  		}
   198  	}
   199  	// If payload generation was requested, create a new block to be potentially
   200  	// sealed by the beacon client. The payload will be requested later, and we
   201  	// might replace it arbitrarily many times in between.
   202  	if payloadAttributes != nil {
   203  		// Create an empty block first which can be used as a fallback
   204  		empty, err := api.eth.Miner().GetSealingBlockSync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, true)
   205  		if err != nil {
   206  			log.Error("Failed to create empty sealing payload", "err", err)
   207  			return valid(nil), beacon.InvalidPayloadAttributes.With(err)
   208  		}
   209  		// Send a request to generate a full block in the background.
   210  		// The result can be obtained via the returned channel.
   211  		resCh, err := api.eth.Miner().GetSealingBlockAsync(update.HeadBlockHash, payloadAttributes.Timestamp, payloadAttributes.SuggestedFeeRecipient, payloadAttributes.Random, false)
   212  		if err != nil {
   213  			log.Error("Failed to create async sealing payload", "err", err)
   214  			return valid(nil), beacon.InvalidPayloadAttributes.With(err)
   215  		}
   216  		id := computePayloadId(update.HeadBlockHash, payloadAttributes)
   217  		api.localBlocks.put(id, &payload{empty: empty, result: resCh})
   218  		return valid(&id), nil
   219  	}
   220  	return valid(nil), nil
   221  }
   222  
   223  // ExchangeTransitionConfigurationV1 checks the given configuration against
   224  // the configuration of the node.
   225  func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config beacon.TransitionConfigurationV1) (*beacon.TransitionConfigurationV1, error) {
   226  	if config.TerminalTotalDifficulty == nil {
   227  		return nil, errors.New("invalid terminal total difficulty")
   228  	}
   229  	ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty
   230  	if ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 {
   231  		log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty)
   232  		return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty)
   233  	}
   234  
   235  	if config.TerminalBlockHash != (common.Hash{}) {
   236  		if hash := api.eth.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash {
   237  			return &beacon.TransitionConfigurationV1{
   238  				TerminalTotalDifficulty: (*hexutil.Big)(ttd),
   239  				TerminalBlockHash:       config.TerminalBlockHash,
   240  				TerminalBlockNumber:     config.TerminalBlockNumber,
   241  			}, nil
   242  		}
   243  		return nil, fmt.Errorf("invalid terminal block hash")
   244  	}
   245  	return &beacon.TransitionConfigurationV1{TerminalTotalDifficulty: (*hexutil.Big)(ttd)}, nil
   246  }
   247  
   248  // GetPayloadV1 returns a cached payload by id.
   249  func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) {
   250  	log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
   251  	data := api.localBlocks.get(payloadID)
   252  	if data == nil {
   253  		return nil, beacon.UnknownPayload
   254  	}
   255  	return data, nil
   256  }
   257  
   258  // NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
   259  func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) {
   260  	log.Trace("Engine API request received", "method", "ExecutePayload", "number", params.Number, "hash", params.BlockHash)
   261  	block, err := beacon.ExecutableDataToBlock(params)
   262  	if err != nil {
   263  		log.Debug("Invalid NewPayload params", "params", params, "error", err)
   264  		return beacon.PayloadStatusV1{Status: beacon.INVALIDBLOCKHASH}, nil
   265  	}
   266  	// If we already have the block locally, ignore the entire execution and just
   267  	// return a fake success.
   268  	if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil {
   269  		log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
   270  		hash := block.Hash()
   271  		return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil
   272  	}
   273  	// If the parent is missing, we - in theory - could trigger a sync, but that
   274  	// would also entail a reorg. That is problematic if multiple sibling blocks
   275  	// are being fed to us, and even more so, if some semi-distant uncle shortens
   276  	// our live chain. As such, payload execution will not permit reorgs and thus
   277  	// will not trigger a sync cycle. That is fine though, if we get a fork choice
   278  	// update after legit payload executions.
   279  	parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
   280  	if parent == nil {
   281  		// Stash the block away for a potential forced forckchoice update to it
   282  		// at a later time.
   283  		api.remoteBlocks.put(block.Hash(), block.Header())
   284  
   285  		// Although we don't want to trigger a sync, if there is one already in
   286  		// progress, try to extend if with the current payload request to relieve
   287  		// some strain from the forkchoice update.
   288  		if err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()); err == nil {
   289  			log.Debug("Payload accepted for sync extension", "number", params.Number, "hash", params.BlockHash)
   290  			return beacon.PayloadStatusV1{Status: beacon.SYNCING}, nil
   291  		}
   292  		// Either no beacon sync was started yet, or it rejected the delivered
   293  		// payload as non-integratable on top of the existing sync. We'll just
   294  		// have to rely on the beacon client to forcefully update the head with
   295  		// a forkchoice update request.
   296  		log.Warn("Ignoring payload with missing parent", "number", params.Number, "hash", params.BlockHash, "parent", params.ParentHash)
   297  		return beacon.PayloadStatusV1{Status: beacon.ACCEPTED}, nil
   298  	}
   299  	// We have an existing parent, do some sanity checks to avoid the beacon client
   300  	// triggering too early
   301  	var (
   302  		ptd  = api.eth.BlockChain().GetTd(parent.Hash(), parent.NumberU64())
   303  		ttd  = api.eth.BlockChain().Config().TerminalTotalDifficulty
   304  		gptd = api.eth.BlockChain().GetTd(parent.ParentHash(), parent.NumberU64()-1)
   305  	)
   306  	if ptd.Cmp(ttd) < 0 {
   307  		log.Warn("Ignoring pre-merge payload", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
   308  		return beacon.INVALID_TERMINAL_BLOCK, nil
   309  	}
   310  	if parent.Difficulty().BitLen() > 0 && gptd != nil && gptd.Cmp(ttd) >= 0 {
   311  		log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
   312  		return beacon.INVALID_TERMINAL_BLOCK, nil
   313  	}
   314  	if block.Time() <= parent.Time() {
   315  		log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
   316  		return api.invalid(errors.New("invalid timestamp"), parent), nil
   317  	}
   318  	if !api.eth.BlockChain().HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
   319  		api.remoteBlocks.put(block.Hash(), block.Header())
   320  		log.Warn("State not available, ignoring new payload")
   321  		return beacon.PayloadStatusV1{Status: beacon.ACCEPTED}, nil
   322  	}
   323  	log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number)
   324  	if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
   325  		log.Warn("NewPayloadV1: inserting block failed", "error", err)
   326  		return api.invalid(err, parent), nil
   327  	}
   328  	// We've accepted a valid payload from the beacon client. Mark the local
   329  	// chain transitions to notify other subsystems (e.g. downloader) of the
   330  	// behavioral change.
   331  	if merger := api.eth.Merger(); !merger.TDDReached() {
   332  		merger.ReachTTD()
   333  		api.eth.Downloader().Cancel()
   334  	}
   335  	hash := block.Hash()
   336  	return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil
   337  }
   338  
   339  // computePayloadId computes a pseudo-random payloadid, based on the parameters.
   340  func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttributesV1) beacon.PayloadID {
   341  	// Hash
   342  	hasher := sha256.New()
   343  	hasher.Write(headBlockHash[:])
   344  	binary.Write(hasher, binary.BigEndian, params.Timestamp)
   345  	hasher.Write(params.Random[:])
   346  	hasher.Write(params.SuggestedFeeRecipient[:])
   347  	var out beacon.PayloadID
   348  	copy(out[:], hasher.Sum(nil)[:8])
   349  	return out
   350  }
   351  
   352  // invalid returns a response "INVALID" with the latest valid hash supplied by latest or to the current head
   353  // if no latestValid block was provided.
   354  func (api *ConsensusAPI) invalid(err error, latestValid *types.Block) beacon.PayloadStatusV1 {
   355  	currentHash := api.eth.BlockChain().CurrentBlock().Hash()
   356  	if latestValid != nil {
   357  		// Set latest valid hash to 0x0 if parent is PoW block
   358  		currentHash = common.Hash{}
   359  		if latestValid.Difficulty().BitLen() == 0 {
   360  			// Otherwise set latest valid hash to parent hash
   361  			currentHash = latestValid.Hash()
   362  		}
   363  	}
   364  	errorMsg := err.Error()
   365  	return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &currentHash, ValidationError: &errorMsg}
   366  }