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