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