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: ¤tHash}, 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: ¤tHash} 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 }