github.com/carter-ya/go-ethereum@v0.0.0-20230628080049-d2309be3983b/les/catalyst/api.go (about)

     1  // Copyright 2022 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  
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/common/hexutil"
    26  	"github.com/ethereum/go-ethereum/core/beacon"
    27  	"github.com/ethereum/go-ethereum/les"
    28  	"github.com/ethereum/go-ethereum/log"
    29  	"github.com/ethereum/go-ethereum/node"
    30  	"github.com/ethereum/go-ethereum/rpc"
    31  )
    32  
    33  // Register adds catalyst APIs to the light client.
    34  func Register(stack *node.Node, backend *les.LightEthereum) error {
    35  	log.Warn("Catalyst mode enabled", "protocol", "les")
    36  	stack.RegisterAPIs([]rpc.API{
    37  		{
    38  			Namespace:     "engine",
    39  			Service:       NewConsensusAPI(backend),
    40  			Authenticated: true,
    41  		},
    42  	})
    43  	return nil
    44  }
    45  
    46  type ConsensusAPI struct {
    47  	les *les.LightEthereum
    48  }
    49  
    50  // NewConsensusAPI creates a new consensus api for the given backend.
    51  // The underlying blockchain needs to have a valid terminal total difficulty set.
    52  func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI {
    53  	if les.BlockChain().Config().TerminalTotalDifficulty == nil {
    54  		log.Warn("Catalyst started without valid total difficulty")
    55  	}
    56  	return &ConsensusAPI{les: les}
    57  }
    58  
    59  // ForkchoiceUpdatedV1 has several responsibilities:
    60  //
    61  // We try to set our blockchain to the headBlock.
    62  //
    63  // If the method is called with an empty head block: we return success, which can be used
    64  // to check if the catalyst mode is enabled.
    65  //
    66  // If the total difficulty was not reached: we return INVALID.
    67  //
    68  // If the finalizedBlockHash is set: we check if we have the finalizedBlockHash in our db,
    69  // if not we start a sync.
    70  //
    71  // If there are payloadAttributes: we return an error since block creation is not
    72  // supported in les mode.
    73  func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) {
    74  	if heads.HeadBlockHash == (common.Hash{}) {
    75  		log.Warn("Forkchoice requested update to zero hash")
    76  		return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
    77  	}
    78  	if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil {
    79  		if header := api.les.BlockChain().GetHeaderByHash(heads.HeadBlockHash); header == nil {
    80  			// TODO (MariusVanDerWijden) trigger sync
    81  			return beacon.STATUS_SYNCING, nil
    82  		}
    83  		return beacon.STATUS_INVALID, err
    84  	}
    85  	// If the finalized block is set, check if it is in our blockchain
    86  	if heads.FinalizedBlockHash != (common.Hash{}) {
    87  		if header := api.les.BlockChain().GetHeaderByHash(heads.FinalizedBlockHash); header == nil {
    88  			// TODO (MariusVanDerWijden) trigger sync
    89  			return beacon.STATUS_SYNCING, nil
    90  		}
    91  	}
    92  	// SetHead
    93  	if err := api.setCanonical(heads.HeadBlockHash); err != nil {
    94  		return beacon.STATUS_INVALID, err
    95  	}
    96  	if payloadAttributes != nil {
    97  		return beacon.STATUS_INVALID, errors.New("not supported")
    98  	}
    99  	return api.validForkChoiceResponse(), nil
   100  }
   101  
   102  // GetPayloadV1 returns a cached payload by id. It's not supported in les mode.
   103  func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) {
   104  	return nil, beacon.GenericServerError.With(errors.New("not supported in light client mode"))
   105  }
   106  
   107  // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
   108  func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) {
   109  	block, err := beacon.ExecutableDataToBlock(params)
   110  	if err != nil {
   111  		return api.invalid(), err
   112  	}
   113  	if !api.les.BlockChain().HasHeader(block.ParentHash(), block.NumberU64()-1) {
   114  		/*
   115  			TODO (MariusVanDerWijden) reenable once sync is merged
   116  			if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), block.Header()); err != nil {
   117  				return SYNCING, err
   118  			}
   119  		*/
   120  		// TODO (MariusVanDerWijden) we should return nil here not empty hash
   121  		return beacon.PayloadStatusV1{Status: beacon.SYNCING, LatestValidHash: nil}, nil
   122  	}
   123  	parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash)
   124  	if parent == nil {
   125  		return api.invalid(), fmt.Errorf("could not find parent %x", params.ParentHash)
   126  	}
   127  	td := api.les.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1)
   128  	ttd := api.les.BlockChain().Config().TerminalTotalDifficulty
   129  	if td.Cmp(ttd) < 0 {
   130  		return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd)
   131  	}
   132  	if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil {
   133  		return api.invalid(), err
   134  	}
   135  	if merger := api.les.Merger(); !merger.TDDReached() {
   136  		merger.ReachTTD()
   137  	}
   138  	hash := block.Hash()
   139  	return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil
   140  }
   141  
   142  func (api *ConsensusAPI) validForkChoiceResponse() beacon.ForkChoiceResponse {
   143  	currentHash := api.les.BlockChain().CurrentHeader().Hash()
   144  	return beacon.ForkChoiceResponse{
   145  		PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &currentHash},
   146  	}
   147  }
   148  
   149  // invalid returns a response "INVALID" with the latest valid hash set to the current head.
   150  func (api *ConsensusAPI) invalid() beacon.PayloadStatusV1 {
   151  	currentHash := api.les.BlockChain().CurrentHeader().Hash()
   152  	return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &currentHash}
   153  }
   154  
   155  func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
   156  	// shortcut if we entered PoS already
   157  	if api.les.Merger().PoSFinalized() {
   158  		return nil
   159  	}
   160  	// make sure the parent has enough terminal total difficulty
   161  	header := api.les.BlockChain().GetHeaderByHash(head)
   162  	if header == nil {
   163  		return errors.New("unknown header")
   164  	}
   165  	td := api.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
   166  	if td != nil && td.Cmp(api.les.BlockChain().Config().TerminalTotalDifficulty) < 0 {
   167  		return errors.New("invalid ttd")
   168  	}
   169  	return nil
   170  }
   171  
   172  // setCanonical is called to perform a force choice.
   173  func (api *ConsensusAPI) setCanonical(newHead common.Hash) error {
   174  	log.Info("Setting head", "head", newHead)
   175  
   176  	headHeader := api.les.BlockChain().CurrentHeader()
   177  	if headHeader.Hash() == newHead {
   178  		return nil
   179  	}
   180  	newHeadHeader := api.les.BlockChain().GetHeaderByHash(newHead)
   181  	if newHeadHeader == nil {
   182  		return errors.New("unknown header")
   183  	}
   184  	if err := api.les.BlockChain().SetCanonical(newHeadHeader); err != nil {
   185  		return err
   186  	}
   187  	// Trigger the transition if it's the first `NewHead` event.
   188  	if merger := api.les.Merger(); !merger.PoSFinalized() {
   189  		merger.FinalizePoS()
   190  	}
   191  	return nil
   192  }
   193  
   194  // ExchangeTransitionConfigurationV1 checks the given configuration against
   195  // the configuration of the node.
   196  func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config beacon.TransitionConfigurationV1) (*beacon.TransitionConfigurationV1, error) {
   197  	log.Trace("Engine API request received", "method", "ExchangeTransitionConfiguration", "ttd", config.TerminalTotalDifficulty)
   198  	if config.TerminalTotalDifficulty == nil {
   199  		return nil, errors.New("invalid terminal total difficulty")
   200  	}
   201  
   202  	ttd := api.les.BlockChain().Config().TerminalTotalDifficulty
   203  	if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 {
   204  		log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty)
   205  		return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty)
   206  	}
   207  
   208  	if config.TerminalBlockHash != (common.Hash{}) {
   209  		if hash := api.les.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash {
   210  			return &beacon.TransitionConfigurationV1{
   211  				TerminalTotalDifficulty: (*hexutil.Big)(ttd),
   212  				TerminalBlockHash:       config.TerminalBlockHash,
   213  				TerminalBlockNumber:     config.TerminalBlockNumber,
   214  			}, nil
   215  		}
   216  		return nil, fmt.Errorf("invalid terminal block hash")
   217  	}
   218  
   219  	return &beacon.TransitionConfigurationV1{TerminalTotalDifficulty: (*hexutil.Big)(ttd)}, nil
   220  }