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