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