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