| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- // Copyright 2022 The go-ethereum Authors
- // This file is part of the go-ethereum library.
- //
- // The go-ethereum library is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Lesser General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // The go-ethereum library is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public License
- // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
- // Package catalyst implements the temporary eth1/eth2 RPC integration.
- package catalyst
- import (
- "errors"
- "fmt"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/beacon"
- "github.com/ethereum/go-ethereum/les"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/rpc"
- )
- // Register adds catalyst APIs to the light client.
- func Register(stack *node.Node, backend *les.LightEthereum) error {
- log.Warn("Catalyst mode enabled", "protocol", "les")
- stack.RegisterAPIs([]rpc.API{
- {
- Namespace: "engine",
- Service: NewConsensusAPI(backend),
- Authenticated: true,
- },
- })
- return nil
- }
- type ConsensusAPI struct {
- les *les.LightEthereum
- }
- // NewConsensusAPI creates a new consensus api for the given backend.
- // The underlying blockchain needs to have a valid terminal total difficulty set.
- func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI {
- if les.BlockChain().Config().TerminalTotalDifficulty == nil {
- log.Warn("Catalyst started without valid total difficulty")
- }
- return &ConsensusAPI{les: les}
- }
- // ForkchoiceUpdatedV1 has several responsibilities:
- // If the method is called with an empty head block:
- // we return success, which can be used to check if the catalyst mode is enabled
- // If the total difficulty was not reached:
- // we return INVALID
- // If the finalizedBlockHash is set:
- // we check if we have the finalizedBlockHash in our db, if not we start a sync
- // We try to set our blockchain to the headBlock
- // If there are payloadAttributes:
- // we return an error since block creation is not supported in les mode
- func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) {
- if heads.HeadBlockHash == (common.Hash{}) {
- log.Warn("Forkchoice requested update to zero hash")
- return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
- }
- if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil {
- if header := api.les.BlockChain().GetHeaderByHash(heads.HeadBlockHash); header == nil {
- // TODO (MariusVanDerWijden) trigger sync
- return beacon.STATUS_SYNCING, nil
- }
- return beacon.STATUS_INVALID, err
- }
- // If the finalized block is set, check if it is in our blockchain
- if heads.FinalizedBlockHash != (common.Hash{}) {
- if header := api.les.BlockChain().GetHeaderByHash(heads.FinalizedBlockHash); header == nil {
- // TODO (MariusVanDerWijden) trigger sync
- return beacon.STATUS_SYNCING, nil
- }
- }
- // SetHead
- if err := api.setCanonical(heads.HeadBlockHash); err != nil {
- return beacon.STATUS_INVALID, err
- }
- if payloadAttributes != nil {
- return beacon.STATUS_INVALID, errors.New("not supported")
- }
- return api.validForkChoiceResponse(), nil
- }
- // GetPayloadV1 returns a cached payload by id. It's not supported in les mode.
- func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) {
- return nil, beacon.GenericServerError.With(errors.New("not supported in light client mode"))
- }
- // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
- func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) {
- block, err := beacon.ExecutableDataToBlock(params)
- if err != nil {
- return api.invalid(), err
- }
- if !api.les.BlockChain().HasHeader(block.ParentHash(), block.NumberU64()-1) {
- /*
- TODO (MariusVanDerWijden) reenable once sync is merged
- if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), block.Header()); err != nil {
- return SYNCING, err
- }
- */
- // TODO (MariusVanDerWijden) we should return nil here not empty hash
- return beacon.PayloadStatusV1{Status: beacon.SYNCING, LatestValidHash: nil}, nil
- }
- parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash)
- if parent == nil {
- return api.invalid(), fmt.Errorf("could not find parent %x", params.ParentHash)
- }
- td := api.les.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1)
- ttd := api.les.BlockChain().Config().TerminalTotalDifficulty
- if td.Cmp(ttd) < 0 {
- return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd)
- }
- if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil {
- return api.invalid(), err
- }
- if merger := api.les.Merger(); !merger.TDDReached() {
- merger.ReachTTD()
- }
- hash := block.Hash()
- return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil
- }
- func (api *ConsensusAPI) validForkChoiceResponse() beacon.ForkChoiceResponse {
- currentHash := api.les.BlockChain().CurrentHeader().Hash()
- return beacon.ForkChoiceResponse{
- PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: ¤tHash},
- }
- }
- // invalid returns a response "INVALID" with the latest valid hash set to the current head.
- func (api *ConsensusAPI) invalid() beacon.PayloadStatusV1 {
- currentHash := api.les.BlockChain().CurrentHeader().Hash()
- return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: ¤tHash}
- }
- func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
- // shortcut if we entered PoS already
- if api.les.Merger().PoSFinalized() {
- return nil
- }
- // make sure the parent has enough terminal total difficulty
- header := api.les.BlockChain().GetHeaderByHash(head)
- if header == nil {
- return errors.New("unknown header")
- }
- td := api.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64())
- if td != nil && td.Cmp(api.les.BlockChain().Config().TerminalTotalDifficulty) < 0 {
- return errors.New("invalid ttd")
- }
- return nil
- }
- // setCanonical is called to perform a force choice.
- func (api *ConsensusAPI) setCanonical(newHead common.Hash) error {
- log.Info("Setting head", "head", newHead)
- headHeader := api.les.BlockChain().CurrentHeader()
- if headHeader.Hash() == newHead {
- return nil
- }
- newHeadHeader := api.les.BlockChain().GetHeaderByHash(newHead)
- if newHeadHeader == nil {
- return errors.New("unknown header")
- }
- if err := api.les.BlockChain().SetCanonical(newHeadHeader); err != nil {
- return err
- }
- // Trigger the transition if it's the first `NewHead` event.
- if merger := api.les.Merger(); !merger.PoSFinalized() {
- merger.FinalizePoS()
- }
- return nil
- }
|