github.com/LampardNguyen234/go-ethereum@v1.10.16-0.20220117140830-b6a3b0260724/eth/catalyst/api.go (about) 1 // Copyright 2020 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 "crypto/sha256" 22 "encoding/binary" 23 "errors" 24 "fmt" 25 "math/big" 26 "time" 27 28 "github.com/LampardNguyen234/go-ethereum/common" 29 "github.com/LampardNguyen234/go-ethereum/common/hexutil" 30 "github.com/LampardNguyen234/go-ethereum/consensus" 31 "github.com/LampardNguyen234/go-ethereum/consensus/beacon" 32 "github.com/LampardNguyen234/go-ethereum/consensus/misc" 33 "github.com/LampardNguyen234/go-ethereum/core" 34 "github.com/LampardNguyen234/go-ethereum/core/state" 35 "github.com/LampardNguyen234/go-ethereum/core/types" 36 "github.com/LampardNguyen234/go-ethereum/eth" 37 "github.com/LampardNguyen234/go-ethereum/les" 38 "github.com/LampardNguyen234/go-ethereum/log" 39 "github.com/LampardNguyen234/go-ethereum/node" 40 chainParams "github.com/LampardNguyen234/go-ethereum/params" 41 "github.com/LampardNguyen234/go-ethereum/rpc" 42 "github.com/LampardNguyen234/go-ethereum/trie" 43 ) 44 45 var ( 46 VALID = GenericStringResponse{"VALID"} 47 SUCCESS = GenericStringResponse{"SUCCESS"} 48 INVALID = ForkChoiceResponse{Status: "INVALID", PayloadID: nil} 49 SYNCING = ForkChoiceResponse{Status: "SYNCING", PayloadID: nil} 50 GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"} 51 UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"} 52 InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"} 53 InvalidPayloadID = rpc.CustomError{Code: 1, ValidationError: "invalid payload id"} 54 ) 55 56 // Register adds catalyst APIs to the full node. 57 func Register(stack *node.Node, backend *eth.Ethereum) error { 58 log.Warn("Catalyst mode enabled", "protocol", "eth") 59 stack.RegisterAPIs([]rpc.API{ 60 { 61 Namespace: "engine", 62 Version: "1.0", 63 Service: NewConsensusAPI(backend, nil), 64 Public: true, 65 }, 66 }) 67 return nil 68 } 69 70 // RegisterLight adds catalyst APIs to the light client. 71 func RegisterLight(stack *node.Node, backend *les.LightEthereum) error { 72 log.Warn("Catalyst mode enabled", "protocol", "les") 73 stack.RegisterAPIs([]rpc.API{ 74 { 75 Namespace: "engine", 76 Version: "1.0", 77 Service: NewConsensusAPI(nil, backend), 78 Public: true, 79 }, 80 }) 81 return nil 82 } 83 84 type ConsensusAPI struct { 85 light bool 86 eth *eth.Ethereum 87 les *les.LightEthereum 88 engine consensus.Engine // engine is the post-merge consensus engine, only for block creation 89 preparedBlocks map[uint64]*ExecutableDataV1 90 } 91 92 func NewConsensusAPI(eth *eth.Ethereum, les *les.LightEthereum) *ConsensusAPI { 93 var engine consensus.Engine 94 if eth == nil { 95 if les.BlockChain().Config().TerminalTotalDifficulty == nil { 96 panic("Catalyst started without valid total difficulty") 97 } 98 if b, ok := les.Engine().(*beacon.Beacon); ok { 99 engine = beacon.New(b.InnerEngine()) 100 } else { 101 engine = beacon.New(les.Engine()) 102 } 103 } else { 104 if eth.BlockChain().Config().TerminalTotalDifficulty == nil { 105 panic("Catalyst started without valid total difficulty") 106 } 107 if b, ok := eth.Engine().(*beacon.Beacon); ok { 108 engine = beacon.New(b.InnerEngine()) 109 } else { 110 engine = beacon.New(eth.Engine()) 111 } 112 } 113 return &ConsensusAPI{ 114 light: eth == nil, 115 eth: eth, 116 les: les, 117 engine: engine, 118 preparedBlocks: make(map[uint64]*ExecutableDataV1), 119 } 120 } 121 122 // blockExecutionEnv gathers all the data required to execute 123 // a block, either when assembling it or when inserting it. 124 type blockExecutionEnv struct { 125 chain *core.BlockChain 126 state *state.StateDB 127 tcount int 128 gasPool *core.GasPool 129 130 header *types.Header 131 txs []*types.Transaction 132 receipts []*types.Receipt 133 } 134 135 func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error { 136 vmConfig := *env.chain.GetVMConfig() 137 snap := env.state.Snapshot() 138 receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmConfig) 139 if err != nil { 140 env.state.RevertToSnapshot(snap) 141 return err 142 } 143 env.txs = append(env.txs, tx) 144 env.receipts = append(env.receipts, receipt) 145 return nil 146 } 147 148 func (api *ConsensusAPI) makeEnv(parent *types.Block, header *types.Header) (*blockExecutionEnv, error) { 149 // The parent state might be missing. It can be the special scenario 150 // that consensus layer tries to build a new block based on the very 151 // old side chain block and the relevant state is already pruned. So 152 // try to retrieve the live state from the chain, if it's not existent, 153 // do the necessary recovery work. 154 var ( 155 err error 156 state *state.StateDB 157 ) 158 if api.eth.BlockChain().HasState(parent.Root()) { 159 state, err = api.eth.BlockChain().StateAt(parent.Root()) 160 } else { 161 // The maximum acceptable reorg depth can be limited by the 162 // finalised block somehow. TODO(rjl493456442) fix the hard- 163 // coded number here later. 164 state, err = api.eth.StateAtBlock(parent, 1000, nil, false, false) 165 } 166 if err != nil { 167 return nil, err 168 } 169 env := &blockExecutionEnv{ 170 chain: api.eth.BlockChain(), 171 state: state, 172 header: header, 173 gasPool: new(core.GasPool).AddGas(header.GasLimit), 174 } 175 return env, nil 176 } 177 178 func (api *ConsensusAPI) GetPayloadV1(payloadID hexutil.Bytes) (*ExecutableDataV1, error) { 179 hash := []byte(payloadID) 180 if len(hash) < 8 { 181 return nil, &InvalidPayloadID 182 } 183 id := binary.BigEndian.Uint64(hash[:8]) 184 data, ok := api.preparedBlocks[id] 185 if !ok { 186 return nil, &UnknownPayload 187 } 188 return data, nil 189 } 190 191 func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads ForkchoiceStateV1, PayloadAttributes *PayloadAttributesV1) (ForkChoiceResponse, error) { 192 if heads.HeadBlockHash == (common.Hash{}) { 193 return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil 194 } 195 if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil { 196 if block := api.eth.BlockChain().GetBlockByHash(heads.HeadBlockHash); block == nil { 197 // TODO (MariusVanDerWijden) trigger sync 198 return SYNCING, nil 199 } 200 return INVALID, err 201 } 202 // If the finalized block is set, check if it is in our blockchain 203 if heads.FinalizedBlockHash != (common.Hash{}) { 204 if block := api.eth.BlockChain().GetBlockByHash(heads.FinalizedBlockHash); block == nil { 205 // TODO (MariusVanDerWijden) trigger sync 206 return SYNCING, nil 207 } 208 } 209 // SetHead 210 if err := api.setHead(heads.HeadBlockHash); err != nil { 211 return INVALID, err 212 } 213 // Assemble block (if needed) 214 if PayloadAttributes != nil { 215 data, err := api.assembleBlock(heads.HeadBlockHash, PayloadAttributes) 216 if err != nil { 217 return INVALID, err 218 } 219 hash := computePayloadId(heads.HeadBlockHash, PayloadAttributes) 220 id := binary.BigEndian.Uint64(hash) 221 api.preparedBlocks[id] = data 222 log.Info("Created payload", "payloadid", id) 223 // TODO (MariusVanDerWijden) do something with the payloadID? 224 hex := hexutil.Bytes(hash) 225 return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: &hex}, nil 226 } 227 return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil 228 } 229 230 func computePayloadId(headBlockHash common.Hash, params *PayloadAttributesV1) []byte { 231 // Hash 232 hasher := sha256.New() 233 hasher.Write(headBlockHash[:]) 234 binary.Write(hasher, binary.BigEndian, params.Timestamp) 235 hasher.Write(params.Random[:]) 236 hasher.Write(params.SuggestedFeeRecipient[:]) 237 return hasher.Sum([]byte{})[:8] 238 } 239 240 func (api *ConsensusAPI) invalid() ExecutePayloadResponse { 241 if api.light { 242 return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()} 243 } 244 return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()} 245 } 246 247 // ExecutePayload creates an Eth1 block, inserts it in the chain, and returns the status of the chain. 248 func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePayloadResponse, error) { 249 block, err := ExecutableDataToBlock(params) 250 if err != nil { 251 return api.invalid(), err 252 } 253 if api.light { 254 parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash) 255 if parent == nil { 256 return api.invalid(), fmt.Errorf("could not find parent %x", params.ParentHash) 257 } 258 if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil { 259 return api.invalid(), err 260 } 261 return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil 262 } 263 if !api.eth.BlockChain().HasBlock(block.ParentHash(), block.NumberU64()-1) { 264 /* 265 TODO (MariusVanDerWijden) reenable once sync is merged 266 if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), block.Header()); err != nil { 267 return SYNCING, err 268 } 269 */ 270 // TODO (MariusVanDerWijden) we should return nil here not empty hash 271 return ExecutePayloadResponse{Status: SYNCING.Status, LatestValidHash: common.Hash{}}, nil 272 } 273 parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash) 274 td := api.eth.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1) 275 ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty 276 if td.Cmp(ttd) < 0 { 277 return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd) 278 } 279 if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil { 280 return api.invalid(), err 281 } 282 283 if merger := api.merger(); !merger.TDDReached() { 284 merger.ReachTTD() 285 } 286 return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil 287 } 288 289 // AssembleBlock creates a new block, inserts it into the chain, and returns the "execution 290 // data" required for eth2 clients to process the new block. 291 func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *PayloadAttributesV1) (*ExecutableDataV1, error) { 292 if api.light { 293 return nil, errors.New("not supported") 294 } 295 log.Info("Producing block", "parentHash", parentHash) 296 297 bc := api.eth.BlockChain() 298 parent := bc.GetBlockByHash(parentHash) 299 if parent == nil { 300 log.Warn("Cannot assemble block with parent hash to unknown block", "parentHash", parentHash) 301 return nil, fmt.Errorf("cannot assemble block with unknown parent %s", parentHash) 302 } 303 304 if params.Timestamp < parent.Time() { 305 return nil, fmt.Errorf("child timestamp lower than parent's: %d < %d", params.Timestamp, parent.Time()) 306 } 307 if now := uint64(time.Now().Unix()); params.Timestamp > now+1 { 308 diff := time.Duration(params.Timestamp-now) * time.Second 309 log.Warn("Producing block too far in the future", "diff", common.PrettyDuration(diff)) 310 } 311 pending := api.eth.TxPool().Pending(true) 312 coinbase := params.SuggestedFeeRecipient 313 num := parent.Number() 314 header := &types.Header{ 315 ParentHash: parent.Hash(), 316 Number: num.Add(num, common.Big1), 317 Coinbase: coinbase, 318 GasLimit: parent.GasLimit(), // Keep the gas limit constant in this prototype 319 Extra: []byte{}, // TODO (MariusVanDerWijden) properly set extra data 320 Time: params.Timestamp, 321 MixDigest: params.Random, 322 } 323 if config := api.eth.BlockChain().Config(); config.IsLondon(header.Number) { 324 header.BaseFee = misc.CalcBaseFee(config, parent.Header()) 325 } 326 if err := api.engine.Prepare(bc, header); err != nil { 327 return nil, err 328 } 329 env, err := api.makeEnv(parent, header) 330 if err != nil { 331 return nil, err 332 } 333 var ( 334 signer = types.MakeSigner(bc.Config(), header.Number) 335 txHeap = types.NewTransactionsByPriceAndNonce(signer, pending, nil) 336 transactions []*types.Transaction 337 ) 338 for { 339 if env.gasPool.Gas() < chainParams.TxGas { 340 log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", chainParams.TxGas) 341 break 342 } 343 tx := txHeap.Peek() 344 if tx == nil { 345 break 346 } 347 348 // The sender is only for logging purposes, and it doesn't really matter if it's correct. 349 from, _ := types.Sender(signer, tx) 350 351 // Execute the transaction 352 env.state.Prepare(tx.Hash(), env.tcount) 353 err = env.commitTransaction(tx, coinbase) 354 switch err { 355 case core.ErrGasLimitReached: 356 // Pop the current out-of-gas transaction without shifting in the next from the account 357 log.Trace("Gas limit exceeded for current block", "sender", from) 358 txHeap.Pop() 359 360 case core.ErrNonceTooLow: 361 // New head notification data race between the transaction pool and miner, shift 362 log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) 363 txHeap.Shift() 364 365 case core.ErrNonceTooHigh: 366 // Reorg notification data race between the transaction pool and miner, skip account = 367 log.Trace("Skipping account with high nonce", "sender", from, "nonce", tx.Nonce()) 368 txHeap.Pop() 369 370 case nil: 371 // Everything ok, collect the logs and shift in the next transaction from the same account 372 env.tcount++ 373 txHeap.Shift() 374 transactions = append(transactions, tx) 375 376 default: 377 // Strange error, discard the transaction and get the next in line (note, the 378 // nonce-too-high clause will prevent us from executing in vain). 379 log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) 380 txHeap.Shift() 381 } 382 } 383 // Create the block. 384 block, err := api.engine.FinalizeAndAssemble(bc, header, env.state, transactions, nil /* uncles */, env.receipts) 385 if err != nil { 386 return nil, err 387 } 388 return BlockToExecutableData(block, params.Random), nil 389 } 390 391 func encodeTransactions(txs []*types.Transaction) [][]byte { 392 var enc = make([][]byte, len(txs)) 393 for i, tx := range txs { 394 enc[i], _ = tx.MarshalBinary() 395 } 396 return enc 397 } 398 399 func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { 400 var txs = make([]*types.Transaction, len(enc)) 401 for i, encTx := range enc { 402 var tx types.Transaction 403 if err := tx.UnmarshalBinary(encTx); err != nil { 404 return nil, fmt.Errorf("invalid transaction %d: %v", i, err) 405 } 406 txs[i] = &tx 407 } 408 return txs, nil 409 } 410 411 func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) { 412 txs, err := decodeTransactions(params.Transactions) 413 if err != nil { 414 return nil, err 415 } 416 if len(params.ExtraData) > 32 { 417 return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) 418 } 419 number := big.NewInt(0) 420 number.SetUint64(params.Number) 421 header := &types.Header{ 422 ParentHash: params.ParentHash, 423 UncleHash: types.EmptyUncleHash, 424 Coinbase: params.FeeRecipient, 425 Root: params.StateRoot, 426 TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), 427 ReceiptHash: params.ReceiptsRoot, 428 Bloom: types.BytesToBloom(params.LogsBloom), 429 Difficulty: common.Big0, 430 Number: number, 431 GasLimit: params.GasLimit, 432 GasUsed: params.GasUsed, 433 Time: params.Timestamp, 434 BaseFee: params.BaseFeePerGas, 435 Extra: params.ExtraData, 436 MixDigest: params.Random, 437 } 438 block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) 439 if block.Hash() != params.BlockHash { 440 return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) 441 } 442 return block, nil 443 } 444 445 func BlockToExecutableData(block *types.Block, random common.Hash) *ExecutableDataV1 { 446 return &ExecutableDataV1{ 447 BlockHash: block.Hash(), 448 ParentHash: block.ParentHash(), 449 FeeRecipient: block.Coinbase(), 450 StateRoot: block.Root(), 451 Number: block.NumberU64(), 452 GasLimit: block.GasLimit(), 453 GasUsed: block.GasUsed(), 454 BaseFeePerGas: block.BaseFee(), 455 Timestamp: block.Time(), 456 ReceiptsRoot: block.ReceiptHash(), 457 LogsBloom: block.Bloom().Bytes(), 458 Transactions: encodeTransactions(block.Transactions()), 459 Random: random, 460 ExtraData: block.Extra(), 461 } 462 } 463 464 // Used in tests to add a the list of transactions from a block to the tx pool. 465 func (api *ConsensusAPI) insertTransactions(txs types.Transactions) error { 466 for _, tx := range txs { 467 api.eth.TxPool().AddLocal(tx) 468 } 469 return nil 470 } 471 472 func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { 473 // shortcut if we entered PoS already 474 if api.merger().PoSFinalized() { 475 return nil 476 } 477 // make sure the parent has enough terminal total difficulty 478 newHeadBlock := api.eth.BlockChain().GetBlockByHash(head) 479 if newHeadBlock == nil { 480 return &GenericServerError 481 } 482 td := api.eth.BlockChain().GetTd(newHeadBlock.Hash(), newHeadBlock.NumberU64()) 483 if td != nil && td.Cmp(api.eth.BlockChain().Config().TerminalTotalDifficulty) < 0 { 484 return &InvalidTB 485 } 486 return nil 487 } 488 489 // setHead is called to perform a force choice. 490 func (api *ConsensusAPI) setHead(newHead common.Hash) error { 491 log.Info("Setting head", "head", newHead) 492 if api.light { 493 headHeader := api.les.BlockChain().CurrentHeader() 494 if headHeader.Hash() == newHead { 495 return nil 496 } 497 newHeadHeader := api.les.BlockChain().GetHeaderByHash(newHead) 498 if newHeadHeader == nil { 499 return &GenericServerError 500 } 501 if err := api.les.BlockChain().SetChainHead(newHeadHeader); err != nil { 502 return err 503 } 504 // Trigger the transition if it's the first `NewHead` event. 505 merger := api.merger() 506 if !merger.PoSFinalized() { 507 merger.FinalizePoS() 508 } 509 return nil 510 } 511 headBlock := api.eth.BlockChain().CurrentBlock() 512 if headBlock.Hash() == newHead { 513 return nil 514 } 515 newHeadBlock := api.eth.BlockChain().GetBlockByHash(newHead) 516 if newHeadBlock == nil { 517 return &GenericServerError 518 } 519 if err := api.eth.BlockChain().SetChainHead(newHeadBlock); err != nil { 520 return err 521 } 522 // Trigger the transition if it's the first `NewHead` event. 523 if merger := api.merger(); !merger.PoSFinalized() { 524 merger.FinalizePoS() 525 } 526 // TODO (MariusVanDerWijden) are we really synced now? 527 api.eth.SetSynced() 528 return nil 529 } 530 531 // Helper function, return the merger instance. 532 func (api *ConsensusAPI) merger() *consensus.Merger { 533 if api.light { 534 return api.les.Merger() 535 } 536 return api.eth.Merger() 537 }