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