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