From 1e2f09136082b1e35aef204803209e565e6d25b8 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Wed, 12 Jan 2022 15:51:51 +0800 Subject: [PATCH 1/5] all: seperate catalyst package --- cmd/utils/flags.go | 7 +- core/beacon/errors.go | 30 +++ .../beacon}/gen_blockparams.go | 2 +- {eth/catalyst => core/beacon}/gen_ed.go | 2 +- .../api_types.go => core/beacon/types.go | 4 +- eth/catalyst/api.go | 185 +++---------- eth/catalyst/api_test.go | 49 ++-- eth/catalyst/queue.go | 14 +- les/catalyst/api.go | 217 ++++++++++++++++ les/catalyst/api_test.go | 244 ++++++++++++++++++ miner/stress/beacon/main.go | 90 ++++--- 11 files changed, 625 insertions(+), 219 deletions(-) create mode 100644 core/beacon/errors.go rename {eth/catalyst => core/beacon}/gen_blockparams.go (99%) rename {eth/catalyst => core/beacon}/gen_ed.go (99%) rename eth/catalyst/api_types.go => core/beacon/types.go (98%) create mode 100644 les/catalyst/api.go create mode 100644 les/catalyst/api_test.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7a0203a69382..936d0ddb384d 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -45,7 +45,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/eth/catalyst" + fcatalyst "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/gasprice" @@ -56,6 +56,7 @@ import ( "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/les" + lcatalyst "github.com/ethereum/go-ethereum/les/catalyst" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/exp" @@ -1718,7 +1719,7 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config, isCatalyst bool } stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) if isCatalyst { - if err := catalyst.RegisterLight(stack, backend); err != nil { + if err := lcatalyst.Register(stack, backend); err != nil { Fatalf("Failed to register the catalyst service: %v", err) } } @@ -1735,7 +1736,7 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config, isCatalyst bool } } if isCatalyst { - if err := catalyst.Register(stack, backend); err != nil { + if err := fcatalyst.Register(stack, backend); err != nil { Fatalf("Failed to register the catalyst service: %v", err) } } diff --git a/core/beacon/errors.go b/core/beacon/errors.go new file mode 100644 index 000000000000..2eefdc0055d6 --- /dev/null +++ b/core/beacon/errors.go @@ -0,0 +1,30 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see + +package beacon + +import "github.com/ethereum/go-ethereum/rpc" + +var ( + VALID = GenericStringResponse{"VALID"} + SUCCESS = GenericStringResponse{"SUCCESS"} + INVALID = ForkChoiceResponse{Status: "INVALID", PayloadID: nil} + SYNCING = ForkChoiceResponse{Status: "SYNCING", PayloadID: nil} + GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"} + UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"} + InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"} + InvalidPayloadID = rpc.CustomError{Code: 1, ValidationError: "invalid payload id"} +) diff --git a/eth/catalyst/gen_blockparams.go b/core/beacon/gen_blockparams.go similarity index 99% rename from eth/catalyst/gen_blockparams.go rename to core/beacon/gen_blockparams.go index ccf5c327ffa3..d3d569b7da75 100644 --- a/eth/catalyst/gen_blockparams.go +++ b/core/beacon/gen_blockparams.go @@ -1,6 +1,6 @@ // Code generated by github.com/fjl/gencodec. DO NOT EDIT. -package catalyst +package beacon import ( "encoding/json" diff --git a/eth/catalyst/gen_ed.go b/core/beacon/gen_ed.go similarity index 99% rename from eth/catalyst/gen_ed.go rename to core/beacon/gen_ed.go index 46eb45808bca..ac94f49a562a 100644 --- a/eth/catalyst/gen_ed.go +++ b/core/beacon/gen_ed.go @@ -1,6 +1,6 @@ // Code generated by github.com/fjl/gencodec. DO NOT EDIT. -package catalyst +package beacon import ( "encoding/json" diff --git a/eth/catalyst/api_types.go b/core/beacon/types.go similarity index 98% rename from eth/catalyst/api_types.go rename to core/beacon/types.go index 07636239fac3..6181c9508a7c 100644 --- a/eth/catalyst/api_types.go +++ b/core/beacon/types.go @@ -1,4 +1,4 @@ -// Copyright 2020 The go-ethereum Authors +// Copyright 2022 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package catalyst +package beacon import ( "fmt" diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 1087496d167c..b8b3e7b497c8 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -20,31 +20,20 @@ package catalyst import ( "crypto/sha256" "encoding/binary" - "errors" "fmt" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/trie" ) -var ( - VALID = GenericStringResponse{"VALID"} - SUCCESS = GenericStringResponse{"SUCCESS"} - INVALID = ForkChoiceResponse{Status: "INVALID", PayloadID: nil} - SYNCING = ForkChoiceResponse{Status: "SYNCING", PayloadID: nil} - GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"} - UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"} - InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"} -) - // Register adds catalyst APIs to the full node. func Register(stack *node.Node, backend *eth.Ethereum) error { log.Warn("Catalyst mode enabled", "protocol", "eth") @@ -52,21 +41,7 @@ func Register(stack *node.Node, backend *eth.Ethereum) error { { Namespace: "engine", Version: "1.0", - Service: NewConsensusAPI(backend, nil), - Public: true, - }, - }) - return nil -} - -// RegisterLight adds catalyst APIs to the light client. -func RegisterLight(stack *node.Node, backend *les.LightEthereum) error { - log.Warn("Catalyst mode enabled", "protocol", "les") - stack.RegisterAPIs([]rpc.API{ - { - Namespace: "engine", - Version: "1.0", - Service: NewConsensusAPI(nil, backend), + Service: NewConsensusAPI(backend), Public: true, }, }) @@ -74,143 +49,89 @@ func RegisterLight(stack *node.Node, backend *les.LightEthereum) error { } type ConsensusAPI struct { - light bool eth *eth.Ethereum - les *les.LightEthereum preparedBlocks *payloadQueue // preparedBlocks caches payloads (*ExecutableDataV1) by payload ID (PayloadID) } -func NewConsensusAPI(eth *eth.Ethereum, les *les.LightEthereum) *ConsensusAPI { - if eth == nil { - if les.BlockChain().Config().TerminalTotalDifficulty == nil { - panic("Catalyst started without valid total difficulty") - } - } else { - if eth.BlockChain().Config().TerminalTotalDifficulty == nil { - panic("Catalyst started without valid total difficulty") - } +func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI { + if eth.BlockChain().Config().TerminalTotalDifficulty == nil { + panic("Catalyst started without valid total difficulty") } - return &ConsensusAPI{ - light: eth == nil, eth: eth, - les: les, preparedBlocks: newPayloadQueue(), } } -func (api *ConsensusAPI) GetPayloadV1(payloadID PayloadID) (*ExecutableDataV1, error) { +func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) data := api.preparedBlocks.get(payloadID) if data == nil { - return nil, &UnknownPayload + return nil, &beacon.UnknownPayload } return data, nil } -func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads ForkchoiceStateV1, payloadAttributes *PayloadAttributesV1) (ForkChoiceResponse, error) { +func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { log.Trace("Engine API request received", "method", "ForkChoiceUpdated", "head", heads.HeadBlockHash, "finalized", heads.FinalizedBlockHash, "safe", heads.SafeBlockHash) if heads.HeadBlockHash == (common.Hash{}) { - return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil + return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil } if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil { - if api.light { - if header := api.les.BlockChain().GetHeaderByHash(heads.HeadBlockHash); header == nil { - // TODO (MariusVanDerWijden) trigger sync - return SYNCING, nil - } - return INVALID, err - } else { - if block := api.eth.BlockChain().GetBlockByHash(heads.HeadBlockHash); block == nil { - // TODO (MariusVanDerWijden) trigger sync - return SYNCING, nil - } - return INVALID, err + if block := api.eth.BlockChain().GetBlockByHash(heads.HeadBlockHash); block == nil { + // TODO (MariusVanDerWijden) trigger sync + return beacon.SYNCING, nil } + return beacon.INVALID, err } // If the finalized block is set, check if it is in our blockchain if heads.FinalizedBlockHash != (common.Hash{}) { - if api.light { - if header := api.les.BlockChain().GetHeaderByHash(heads.FinalizedBlockHash); header == nil { - // TODO (MariusVanDerWijden) trigger sync - return SYNCING, nil - } - } else { - if block := api.eth.BlockChain().GetBlockByHash(heads.FinalizedBlockHash); block == nil { - // TODO (MariusVanDerWijden) trigger sync - return SYNCING, nil - } + if block := api.eth.BlockChain().GetBlockByHash(heads.FinalizedBlockHash); block == nil { + // TODO (MariusVanDerWijden) trigger sync + return beacon.SYNCING, nil } } // SetHead if err := api.setHead(heads.HeadBlockHash); err != nil { - return INVALID, err + return beacon.INVALID, err } // Assemble block (if needed). It only works for full node. - if !api.light && payloadAttributes != nil { + if payloadAttributes != nil { data, err := api.assembleBlock(heads.HeadBlockHash, payloadAttributes) if err != nil { - return INVALID, err + return beacon.INVALID, err } id := computePayloadId(heads.HeadBlockHash, payloadAttributes) api.preparedBlocks.put(id, data) log.Info("Created payload", "payloadID", id) - return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: &id}, nil + return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: &id}, nil } - return ForkChoiceResponse{Status: SUCCESS.Status, PayloadID: nil}, nil + return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil } -func computePayloadId(headBlockHash common.Hash, params *PayloadAttributesV1) PayloadID { +func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttributesV1) beacon.PayloadID { // Hash hasher := sha256.New() hasher.Write(headBlockHash[:]) binary.Write(hasher, binary.BigEndian, params.Timestamp) hasher.Write(params.Random[:]) hasher.Write(params.SuggestedFeeRecipient[:]) - var out PayloadID + var out beacon.PayloadID copy(out[:], hasher.Sum(nil)[:8]) return out } -func (api *ConsensusAPI) invalid() ExecutePayloadResponse { - if api.light { - return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()} - } - return ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()} +func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { + return beacon.ExecutePayloadResponse{Status: beacon.INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()} } // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. -func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePayloadResponse, error) { +func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.ExecutePayloadResponse, error) { log.Trace("Engine API request received", "method", "ExecutePayload", params.BlockHash, "number", params.Number) block, err := ExecutableDataToBlock(params) if err != nil { return api.invalid(), err } - if api.light { - if !api.les.BlockChain().HasHeader(block.ParentHash(), block.NumberU64()-1) { - /* - TODO (MariusVanDerWijden) reenable once sync is merged - if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), block.Header()); err != nil { - return SYNCING, err - } - */ - // TODO (MariusVanDerWijden) we should return nil here not empty hash - return ExecutePayloadResponse{Status: SYNCING.Status, LatestValidHash: common.Hash{}}, nil - } - parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash) - td := api.les.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1) - ttd := api.les.BlockChain().Config().TerminalTotalDifficulty - if td.Cmp(ttd) < 0 { - return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd) - } - if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil { - return api.invalid(), err - } - if merger := api.merger(); !merger.TDDReached() { - merger.ReachTTD() - } - return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil - } if !api.eth.BlockChain().HasBlock(block.ParentHash(), block.NumberU64()-1) { /* TODO (MariusVanDerWijden) reenable once sync is merged @@ -219,7 +140,7 @@ func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePaylo } */ // TODO (MariusVanDerWijden) we should return nil here not empty hash - return ExecutePayloadResponse{Status: SYNCING.Status, LatestValidHash: common.Hash{}}, nil + return beacon.ExecutePayloadResponse{Status: beacon.SYNCING.Status, LatestValidHash: common.Hash{}}, nil } parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash) td := api.eth.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1) @@ -235,15 +156,12 @@ func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePaylo if merger := api.merger(); !merger.TDDReached() { merger.ReachTTD() } - return ExecutePayloadResponse{Status: VALID.Status, LatestValidHash: block.Hash()}, nil + return beacon.ExecutePayloadResponse{Status: beacon.VALID.Status, LatestValidHash: block.Hash()}, nil } // AssembleBlock creates a new block, inserts it into the chain, and returns the "execution // data" required for eth2 clients to process the new block. -func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *PayloadAttributesV1) (*ExecutableDataV1, error) { - if api.light { - return nil, errors.New("not supported") - } +func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) { log.Info("Producing block", "parentHash", parentHash) block, err := api.eth.Miner().GetSealingBlock(parentHash, params.Timestamp, params.SuggestedFeeRecipient, params.Random) if err != nil { @@ -272,7 +190,7 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { return txs, nil } -func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) { +func ExecutableDataToBlock(params beacon.ExecutableDataV1) (*types.Block, error) { txs, err := decodeTransactions(params.Transactions) if err != nil { return nil, err @@ -306,8 +224,8 @@ func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) { // BlockToExecutableData constructs the executableDataV1 structure by filling the // fields from the given block. It assumes the given block is post-merge block. -func BlockToExecutableData(block *types.Block) *ExecutableDataV1 { - return &ExecutableDataV1{ +func BlockToExecutableData(block *types.Block) *beacon.ExecutableDataV1 { + return &beacon.ExecutableDataV1{ BlockHash: block.Hash(), ParentHash: block.ParentHash(), FeeRecipient: block.Coinbase(), @@ -338,26 +256,14 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { if api.merger().PoSFinalized() { return nil } - if api.light { - // make sure the parent has enough terminal total difficulty - header := api.les.BlockChain().GetHeaderByHash(head) - if header == nil { - return &GenericServerError - } - td := api.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) - if td != nil && td.Cmp(api.les.BlockChain().Config().TerminalTotalDifficulty) < 0 { - return &InvalidTB - } - return nil - } // make sure the parent has enough terminal total difficulty newHeadBlock := api.eth.BlockChain().GetBlockByHash(head) if newHeadBlock == nil { - return &GenericServerError + return &beacon.GenericServerError } td := api.eth.BlockChain().GetTd(newHeadBlock.Hash(), newHeadBlock.NumberU64()) if td != nil && td.Cmp(api.eth.BlockChain().Config().TerminalTotalDifficulty) < 0 { - return &InvalidTB + return &beacon.InvalidTB } return nil } @@ -365,31 +271,13 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { // setHead is called to perform a force choice. func (api *ConsensusAPI) setHead(newHead common.Hash) error { log.Info("Setting head", "head", newHead) - if api.light { - headHeader := api.les.BlockChain().CurrentHeader() - if headHeader.Hash() == newHead { - return nil - } - newHeadHeader := api.les.BlockChain().GetHeaderByHash(newHead) - if newHeadHeader == nil { - return &GenericServerError - } - if err := api.les.BlockChain().SetChainHead(newHeadHeader); err != nil { - return err - } - // Trigger the transition if it's the first `NewHead` event. - if merger := api.merger(); !merger.PoSFinalized() { - merger.FinalizePoS() - } - return nil - } headBlock := api.eth.BlockChain().CurrentBlock() if headBlock.Hash() == newHead { return nil } newHeadBlock := api.eth.BlockChain().GetBlockByHash(newHead) if newHeadBlock == nil { - return &GenericServerError + return &beacon.GenericServerError } if err := api.eth.BlockChain().SetChainHead(newHeadBlock); err != nil { return err @@ -405,8 +293,5 @@ func (api *ConsensusAPI) setHead(newHead common.Hash) error { // Helper function, return the merger instance. func (api *ConsensusAPI) merger() *consensus.Merger { - if api.light { - return api.les.Merger() - } return api.eth.Merger() } diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index b802fb05c86b..942e9f41b4b0 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -78,14 +79,14 @@ func TestEth2AssembleBlock(t *testing.T) { n, ethservice := startEthService(t, genesis, blocks) defer n.Close() - api := NewConsensusAPI(ethservice, nil) + api := NewConsensusAPI(ethservice) signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID) tx, err := types.SignTx(types.NewTransaction(uint64(10), blocks[9].Coinbase(), big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) if err != nil { t.Fatalf("error signing transaction, err=%v", err) } ethservice.TxPool().AddLocal(tx) - blockParams := PayloadAttributesV1{ + blockParams := beacon.PayloadAttributesV1{ Timestamp: blocks[9].Time() + 5, } execData, err := api.assembleBlock(blocks[9].Hash(), &blockParams) @@ -102,11 +103,11 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { n, ethservice := startEthService(t, genesis, blocks[:9]) defer n.Close() - api := NewConsensusAPI(ethservice, nil) + api := NewConsensusAPI(ethservice) // Put the 10th block's tx in the pool and produce a new block api.insertTransactions(blocks[9].Transactions()) - blockParams := PayloadAttributesV1{ + blockParams := beacon.PayloadAttributesV1{ Timestamp: blocks[8].Time() + 5, } execData, err := api.assembleBlock(blocks[8].Hash(), &blockParams) @@ -123,8 +124,8 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) { n, ethservice := startEthService(t, genesis, blocks) defer n.Close() - api := NewConsensusAPI(ethservice, nil) - fcState := ForkchoiceStateV1{ + api := NewConsensusAPI(ethservice) + fcState := beacon.ForkchoiceStateV1{ HeadBlockHash: blocks[5].Hash(), SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, @@ -141,14 +142,14 @@ func TestEth2PrepareAndGetPayload(t *testing.T) { n, ethservice := startEthService(t, genesis, blocks[:9]) defer n.Close() - api := NewConsensusAPI(ethservice, nil) + api := NewConsensusAPI(ethservice) // Put the 10th block's tx in the pool and produce a new block api.insertTransactions(blocks[9].Transactions()) - blockParams := PayloadAttributesV1{ + blockParams := beacon.PayloadAttributesV1{ Timestamp: blocks[8].Time() + 5, } - fcState := ForkchoiceStateV1{ + fcState := beacon.ForkchoiceStateV1{ HeadBlockHash: blocks[8].Hash(), SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, @@ -166,7 +167,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) { t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions)) } // Test invalid payloadID - var invPayload PayloadID + var invPayload beacon.PayloadID copy(invPayload[:], payloadID[:]) invPayload[0] = ^invPayload[0] _, err = api.GetPayloadV1(invPayload) @@ -199,7 +200,7 @@ func TestInvalidPayloadTimestamp(t *testing.T) { ethservice.Merger().ReachTTD() defer n.Close() var ( - api = NewConsensusAPI(ethservice, nil) + api = NewConsensusAPI(ethservice) parent = ethservice.BlockChain().CurrentBlock() ) tests := []struct { @@ -215,12 +216,12 @@ func TestInvalidPayloadTimestamp(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("Timestamp test: %v", i), func(t *testing.T) { - params := PayloadAttributesV1{ + params := beacon.PayloadAttributesV1{ Timestamp: test.time, Random: crypto.Keccak256Hash([]byte{byte(123)}), SuggestedFeeRecipient: parent.Coinbase(), } - fcState := ForkchoiceStateV1{ + fcState := beacon.ForkchoiceStateV1{ HeadBlockHash: parent.Hash(), SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, @@ -242,7 +243,7 @@ func TestEth2NewBlock(t *testing.T) { defer n.Close() var ( - api = NewConsensusAPI(ethservice, nil) + api = NewConsensusAPI(ethservice) parent = preMergeBlocks[len(preMergeBlocks)-1] // This EVM code generates a log when the contract is created. @@ -260,7 +261,7 @@ func TestEth2NewBlock(t *testing.T) { tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) ethservice.TxPool().AddLocal(tx) - execData, err := api.assembleBlock(parent.Hash(), &PayloadAttributesV1{ + execData, err := api.assembleBlock(parent.Hash(), &beacon.PayloadAttributesV1{ Timestamp: parent.Time() + 5, }) if err != nil { @@ -278,7 +279,7 @@ func TestEth2NewBlock(t *testing.T) { t.Fatalf("Chain head shouldn't be updated") } checkLogEvents(t, newLogCh, rmLogsCh, 0, 0) - fcState := ForkchoiceStateV1{ + fcState := beacon.ForkchoiceStateV1{ HeadBlockHash: block.Hash(), SafeBlockHash: block.Hash(), FinalizedBlockHash: block.Hash(), @@ -300,7 +301,7 @@ func TestEth2NewBlock(t *testing.T) { ) parent = preMergeBlocks[len(preMergeBlocks)-1] for i := 0; i < 10; i++ { - execData, err := api.assembleBlock(parent.Hash(), &PayloadAttributesV1{ + execData, err := api.assembleBlock(parent.Hash(), &beacon.PayloadAttributesV1{ Timestamp: parent.Time() + 6, }) if err != nil { @@ -318,7 +319,7 @@ func TestEth2NewBlock(t *testing.T) { t.Fatalf("Chain head shouldn't be updated") } - fcState := ForkchoiceStateV1{ + fcState := beacon.ForkchoiceStateV1{ HeadBlockHash: block.Hash(), SafeBlockHash: block.Hash(), FinalizedBlockHash: block.Hash(), @@ -412,7 +413,7 @@ func TestFullAPI(t *testing.T) { ethservice.Merger().ReachTTD() defer n.Close() var ( - api = NewConsensusAPI(ethservice, nil) + api = NewConsensusAPI(ethservice) parent = ethservice.BlockChain().CurrentBlock() // This EVM code generates a log when the contract is created. logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") @@ -423,12 +424,12 @@ func TestFullAPI(t *testing.T) { tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) ethservice.TxPool().AddLocal(tx) - params := PayloadAttributesV1{ + params := beacon.PayloadAttributesV1{ Timestamp: parent.Time() + 1, Random: crypto.Keccak256Hash([]byte{byte(i)}), SuggestedFeeRecipient: parent.Coinbase(), } - fcState := ForkchoiceStateV1{ + fcState := beacon.ForkchoiceStateV1{ HeadBlockHash: parent.Hash(), SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, @@ -437,7 +438,7 @@ func TestFullAPI(t *testing.T) { if err != nil { t.Fatalf("error preparing payload, err=%v", err) } - if resp.Status != SUCCESS.Status { + if resp.Status != beacon.SUCCESS.Status { t.Fatalf("error preparing payload, invalid status: %v", resp.Status) } payloadID := computePayloadId(parent.Hash(), ¶ms) @@ -449,10 +450,10 @@ func TestFullAPI(t *testing.T) { if err != nil { t.Fatalf("can't execute payload: %v", err) } - if execResp.Status != VALID.Status { + if execResp.Status != beacon.VALID.Status { t.Fatalf("invalid status: %v", execResp.Status) } - fcState = ForkchoiceStateV1{ + fcState = beacon.ForkchoiceStateV1{ HeadBlockHash: payload.BlockHash, SafeBlockHash: payload.ParentHash, FinalizedBlockHash: payload.ParentHash, diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go index db373a6c7904..aa2ce7823d66 100644 --- a/eth/catalyst/queue.go +++ b/eth/catalyst/queue.go @@ -16,7 +16,11 @@ package catalyst -import "sync" +import ( + "sync" + + "github.com/ethereum/go-ethereum/core/beacon" +) // maxTrackedPayloads is the maximum number of prepared payloads the execution // engine tracks before evicting old ones. Ideally we should only ever track the @@ -26,8 +30,8 @@ const maxTrackedPayloads = 10 // payloadQueueItem represents an id->payload tuple to store until it's retrieved // or evicted. type payloadQueueItem struct { - id PayloadID - payload *ExecutableDataV1 + id beacon.PayloadID + payload *beacon.ExecutableDataV1 } // payloadQueue tracks the latest handful of constructed payloads to be retrieved @@ -46,7 +50,7 @@ func newPayloadQueue() *payloadQueue { } // put inserts a new payload into the queue at the given id. -func (q *payloadQueue) put(id PayloadID, data *ExecutableDataV1) { +func (q *payloadQueue) put(id beacon.PayloadID, data *beacon.ExecutableDataV1) { q.lock.Lock() defer q.lock.Unlock() @@ -58,7 +62,7 @@ func (q *payloadQueue) put(id PayloadID, data *ExecutableDataV1) { } // get retrieves a previously stored payload item or nil if it does not exist. -func (q *payloadQueue) get(id PayloadID) *ExecutableDataV1 { +func (q *payloadQueue) get(id beacon.PayloadID) *beacon.ExecutableDataV1 { q.lock.RLock() defer q.lock.RUnlock() diff --git a/les/catalyst/api.go b/les/catalyst/api.go new file mode 100644 index 000000000000..e3b26fdee492 --- /dev/null +++ b/les/catalyst/api.go @@ -0,0 +1,217 @@ +// Copyright 2022 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package catalyst implements the temporary eth1/eth2 RPC integration. +package catalyst + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/beacon" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/les" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" +) + +// Register adds catalyst APIs to the light client. +func Register(stack *node.Node, backend *les.LightEthereum) error { + log.Warn("Catalyst mode enabled", "protocol", "les") + stack.RegisterAPIs([]rpc.API{ + { + Namespace: "engine", + Version: "1.0", + Service: NewConsensusAPI(backend), + Public: true, + }, + }) + return nil +} + +type ConsensusAPI struct { + les *les.LightEthereum +} + +func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI { + if les.BlockChain().Config().TerminalTotalDifficulty == nil { + panic("Catalyst started without valid total difficulty") + } + return &ConsensusAPI{les: les} +} + +func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { + return nil, &beacon.GenericServerError +} + +func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { + if heads.HeadBlockHash == (common.Hash{}) { + return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil + } + if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil { + if header := api.les.BlockChain().GetHeaderByHash(heads.HeadBlockHash); header == nil { + // TODO (MariusVanDerWijden) trigger sync + return beacon.SYNCING, nil + } + return beacon.INVALID, err + } + // If the finalized block is set, check if it is in our blockchain + if heads.FinalizedBlockHash != (common.Hash{}) { + if header := api.les.BlockChain().GetHeaderByHash(heads.FinalizedBlockHash); header == nil { + // TODO (MariusVanDerWijden) trigger sync + return beacon.SYNCING, nil + } + } + // SetHead + if err := api.setHead(heads.HeadBlockHash); err != nil { + return beacon.INVALID, err + } + if payloadAttributes != nil { + return beacon.INVALID, errors.New("not supported") + } + return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil +} + +func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { + return beacon.ExecutePayloadResponse{Status: beacon.INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()} +} + +// ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. +func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.ExecutePayloadResponse, error) { + block, err := ExecutableDataToBlock(params) + if err != nil { + return api.invalid(), err + } + if !api.les.BlockChain().HasHeader(block.ParentHash(), block.NumberU64()-1) { + /* + TODO (MariusVanDerWijden) reenable once sync is merged + if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), block.Header()); err != nil { + return SYNCING, err + } + */ + // TODO (MariusVanDerWijden) we should return nil here not empty hash + return beacon.ExecutePayloadResponse{Status: beacon.SYNCING.Status, LatestValidHash: common.Hash{}}, nil + } + parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash) + if parent == nil { + return api.invalid(), fmt.Errorf("could not find parent %x", params.ParentHash) + } + td := api.les.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1) + ttd := api.les.BlockChain().Config().TerminalTotalDifficulty + if td.Cmp(ttd) < 0 { + return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd) + } + if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil { + return api.invalid(), err + } + if merger := api.merger(); !merger.TDDReached() { + merger.ReachTTD() + } + return beacon.ExecutePayloadResponse{Status: beacon.VALID.Status, LatestValidHash: block.Hash()}, nil +} + +func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { + var txs = make([]*types.Transaction, len(enc)) + for i, encTx := range enc { + var tx types.Transaction + if err := tx.UnmarshalBinary(encTx); err != nil { + return nil, fmt.Errorf("invalid transaction %d: %v", i, err) + } + txs[i] = &tx + } + return txs, nil +} + +func ExecutableDataToBlock(params beacon.ExecutableDataV1) (*types.Block, error) { + txs, err := decodeTransactions(params.Transactions) + if err != nil { + return nil, err + } + if len(params.ExtraData) > 32 { + return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) + } + header := &types.Header{ + ParentHash: params.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: params.FeeRecipient, + Root: params.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: params.ReceiptsRoot, + Bloom: types.BytesToBloom(params.LogsBloom), + Difficulty: common.Big0, + Number: new(big.Int).SetUint64(params.Number), + GasLimit: params.GasLimit, + GasUsed: params.GasUsed, + Time: params.Timestamp, + BaseFee: params.BaseFeePerGas, + Extra: params.ExtraData, + MixDigest: params.Random, + } + block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) + if block.Hash() != params.BlockHash { + return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) + } + return block, nil +} + +func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { + // shortcut if we entered PoS already + if api.merger().PoSFinalized() { + return nil + } + // make sure the parent has enough terminal total difficulty + header := api.les.BlockChain().GetHeaderByHash(head) + if header == nil { + return &beacon.GenericServerError + } + td := api.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) + if td != nil && td.Cmp(api.les.BlockChain().Config().TerminalTotalDifficulty) < 0 { + return &beacon.InvalidTB + } + return nil +} + +// setHead is called to perform a force choice. +func (api *ConsensusAPI) setHead(newHead common.Hash) error { + log.Info("Setting head", "head", newHead) + + headHeader := api.les.BlockChain().CurrentHeader() + if headHeader.Hash() == newHead { + return nil + } + newHeadHeader := api.les.BlockChain().GetHeaderByHash(newHead) + if newHeadHeader == nil { + return &beacon.GenericServerError + } + if err := api.les.BlockChain().SetChainHead(newHeadHeader); err != nil { + return err + } + // Trigger the transition if it's the first `NewHead` event. + if merger := api.merger(); !merger.PoSFinalized() { + merger.FinalizePoS() + } + return nil +} + +// Helper function, return the merger instance. +func (api *ConsensusAPI) merger() *consensus.Merger { + return api.les.Merger() +} diff --git a/les/catalyst/api_test.go b/les/catalyst/api_test.go new file mode 100644 index 000000000000..c1cbf645ccc8 --- /dev/null +++ b/les/catalyst/api_test.go @@ -0,0 +1,244 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package catalyst + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/beacon" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/les" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + + testBalance = big.NewInt(2e18) +) + +func generatePreMergeChain(n int) (*core.Genesis, []*types.Header, []*types.Block) { + db := rawdb.NewMemoryDatabase() + config := params.AllEthashProtocolChanges + genesis := &core.Genesis{ + Config: config, + Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, + ExtraData: []byte("test genesis"), + Timestamp: 9000, + BaseFee: big.NewInt(params.InitialBaseFee), + } + gblock := genesis.ToBlock(db) + engine := ethash.NewFaker() + blocks, _ := core.GenerateChain(config, gblock, engine, db, n, nil) + totalDifficulty := big.NewInt(0) + + var headers []*types.Header + for _, b := range blocks { + totalDifficulty.Add(totalDifficulty, b.Difficulty()) + headers = append(headers, b.Header()) + } + config.TerminalTotalDifficulty = totalDifficulty + + return genesis, headers, blocks +} + +func TestSetHeadBeforeTotalDifficulty(t *testing.T) { + genesis, headers, blocks := generatePreMergeChain(10) + n, lesService := startLesService(t, genesis, headers) + defer n.Close() + + api := NewConsensusAPI(lesService) + fcState := beacon.ForkchoiceStateV1{ + HeadBlockHash: blocks[5].Hash(), + SafeBlockHash: common.Hash{}, + FinalizedBlockHash: common.Hash{}, + } + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err == nil { + t.Errorf("fork choice updated before total terminal difficulty should fail") + } +} + +func TestExecutePayloadV1(t *testing.T) { + genesis, headers, blocks := generatePreMergeChain(10) + n, lesService := startLesService(t, genesis, headers[:9]) + lesService.Merger().ReachTTD() + defer n.Close() + + api := NewConsensusAPI(lesService) + fcState := beacon.ForkchoiceStateV1{ + HeadBlockHash: blocks[8].Hash(), + SafeBlockHash: common.Hash{}, + FinalizedBlockHash: common.Hash{}, + } + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { + t.Errorf("Failed to update head %v", err) + } + block := blocks[9] + + fakeBlock := types.NewBlock(&types.Header{ + ParentHash: block.ParentHash(), + UncleHash: crypto.Keccak256Hash(nil), + Coinbase: block.Coinbase(), + Root: block.Root(), + TxHash: crypto.Keccak256Hash(nil), + ReceiptHash: crypto.Keccak256Hash(nil), + Bloom: block.Bloom(), + Difficulty: big.NewInt(0), + Number: block.Number(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + Time: block.Time(), + Extra: block.Extra(), + MixDigest: block.MixDigest(), + Nonce: types.BlockNonce{}, + BaseFee: block.BaseFee(), + }, nil, nil, nil, trie.NewStackTrie(nil)) + + _, err := api.ExecutePayloadV1(beacon.ExecutableDataV1{ + ParentHash: fakeBlock.ParentHash(), + FeeRecipient: fakeBlock.Coinbase(), + StateRoot: fakeBlock.Root(), + ReceiptsRoot: fakeBlock.ReceiptHash(), + LogsBloom: fakeBlock.Bloom().Bytes(), + Random: fakeBlock.MixDigest(), + Number: fakeBlock.NumberU64(), + GasLimit: fakeBlock.GasLimit(), + GasUsed: fakeBlock.GasUsed(), + Timestamp: fakeBlock.Time(), + ExtraData: fakeBlock.Extra(), + BaseFeePerGas: fakeBlock.BaseFee(), + BlockHash: fakeBlock.Hash(), + Transactions: encodeTransactions(fakeBlock.Transactions()), + }) + if err != nil { + t.Errorf("Failed to execute payload %v", err) + } + headHeader := api.les.BlockChain().CurrentHeader() + if headHeader.Number.Uint64() != fakeBlock.NumberU64()-1 { + t.Fatal("Unexpected chain head update") + } + fcState = beacon.ForkchoiceStateV1{ + HeadBlockHash: fakeBlock.Hash(), + SafeBlockHash: common.Hash{}, + FinalizedBlockHash: common.Hash{}, + } + if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { + t.Fatal("Failed to update head") + } + headHeader = api.les.BlockChain().CurrentHeader() + if headHeader.Number.Uint64() != fakeBlock.NumberU64() { + t.Fatal("Failed to update chain head") + } +} + +func TestEth2DeepReorg(t *testing.T) { + // TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg + // before the totalTerminalDifficulty threshold + /* + genesis, preMergeBlocks := generatePreMergeChain(core.TriesInMemory * 2) + n, ethservice := startEthService(t, genesis, preMergeBlocks) + defer n.Close() + + var ( + api = NewConsensusAPI(ethservice, nil) + parent = preMergeBlocks[len(preMergeBlocks)-core.TriesInMemory-1] + head = ethservice.BlockChain().CurrentBlock().NumberU64() + ) + if ethservice.BlockChain().HasBlockAndState(parent.Hash(), parent.NumberU64()) { + t.Errorf("Block %d not pruned", parent.NumberU64()) + } + for i := 0; i < 10; i++ { + execData, err := api.assembleBlock(AssembleBlockParams{ + ParentHash: parent.Hash(), + Timestamp: parent.Time() + 5, + }) + if err != nil { + t.Fatalf("Failed to create the executable data %v", err) + } + block, err := ExecutableDataToBlock(ethservice.BlockChain().Config(), parent.Header(), *execData) + if err != nil { + t.Fatalf("Failed to convert executable data to block %v", err) + } + newResp, err := api.ExecutePayload(*execData) + if err != nil || newResp.Status != "VALID" { + t.Fatalf("Failed to insert block: %v", err) + } + if ethservice.BlockChain().CurrentBlock().NumberU64() != head { + t.Fatalf("Chain head shouldn't be updated") + } + if err := api.setHead(block.Hash()); err != nil { + t.Fatalf("Failed to set head: %v", err) + } + if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() { + t.Fatalf("Chain head should be updated") + } + parent, head = block, block.NumberU64() + } + */ +} + +// startEthService creates a full node instance for testing. +func startLesService(t *testing.T, genesis *core.Genesis, headers []*types.Header) (*node.Node, *les.LightEthereum) { + t.Helper() + + n, err := node.New(&node.Config{}) + if err != nil { + t.Fatal("can't create node:", err) + } + ethcfg := ðconfig.Config{ + Genesis: genesis, + Ethash: ethash.Config{PowMode: ethash.ModeFake}, + SyncMode: downloader.LightSync, + TrieDirtyCache: 256, + TrieCleanCache: 256, + LightPeers: 10, + } + lesService, err := les.New(n, ethcfg) + if err != nil { + t.Fatal("can't create eth service:", err) + } + if err := n.Start(); err != nil { + t.Fatal("can't start node:", err) + } + if _, err := lesService.BlockChain().InsertHeaderChain(headers, 0); err != nil { + n.Close() + t.Fatal("can't import test headers:", err) + } + return n, lesService +} + +func encodeTransactions(txs []*types.Transaction) [][]byte { + var enc = make([][]byte, len(txs)) + for i, tx := range txs { + enc[i], _ = tx.MarshalBinary() + } + return enc +} diff --git a/miner/stress/beacon/main.go b/miner/stress/beacon/main.go index 6a6a0a7222f9..e094e3012d77 100644 --- a/miner/stress/beacon/main.go +++ b/miner/stress/beacon/main.go @@ -32,13 +32,15 @@ import ( "github.com/ethereum/go-ethereum/common/fdlimit" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/eth/catalyst" + fcatalyst "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/les" + lcatalyst "github.com/ethereum/go-ethereum/les/catalyst" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" @@ -88,24 +90,26 @@ var ( type ethNode struct { typ nodetype - api *catalyst.ConsensusAPI - ethBackend *eth.Ethereum - lesBackend *les.LightEthereum stack *node.Node enode *enode.Node + api *fcatalyst.ConsensusAPI + ethBackend *eth.Ethereum + lapi *lcatalyst.ConsensusAPI + lesBackend *les.LightEthereum } func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode { var ( err error - api *catalyst.ConsensusAPI + api *fcatalyst.ConsensusAPI + lapi *lcatalyst.ConsensusAPI stack *node.Node ethBackend *eth.Ethereum lesBackend *les.LightEthereum ) // Start the node and wait until it's up if typ == eth2LightClient { - stack, lesBackend, api, err = makeLightNode(genesis) + stack, lesBackend, lapi, err = makeLightNode(genesis) } else { stack, ethBackend, api, err = makeFullNode(genesis) } @@ -131,13 +135,14 @@ func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode typ: typ, api: api, ethBackend: ethBackend, + lapi: lapi, lesBackend: lesBackend, stack: stack, enode: enode, } } -func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) (*catalyst.ExecutableDataV1, error) { +func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) (*beacon.ExecutableDataV1, error) { if n.typ != eth2MiningNode { return nil, errors.New("invalid node type") } @@ -145,12 +150,12 @@ func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) if timestamp <= parentTimestamp { timestamp = parentTimestamp + 1 } - payloadAttribute := catalyst.PayloadAttributesV1{ + payloadAttribute := beacon.PayloadAttributesV1{ Timestamp: timestamp, Random: common.Hash{}, SuggestedFeeRecipient: common.HexToAddress("0xdeadbeef"), } - fcState := catalyst.ForkchoiceStateV1{ + fcState := beacon.ForkchoiceStateV1{ HeadBlockHash: parentHash, SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, @@ -162,39 +167,62 @@ func (n *ethNode) assembleBlock(parentHash common.Hash, parentTimestamp uint64) return n.api.GetPayloadV1(*payload.PayloadID) } -func (n *ethNode) insertBlock(eb catalyst.ExecutableDataV1) error { +func (n *ethNode) insertBlock(eb beacon.ExecutableDataV1) error { if !eth2types(n.typ) { return errors.New("invalid node type") } - newResp, err := n.api.ExecutePayloadV1(eb) - if err != nil { - return err - } else if newResp.Status != "VALID" { - return errors.New("failed to insert block") + switch n.typ { + case eth2NormalNode, eth2MiningNode: + newResp, err := n.api.ExecutePayloadV1(eb) + if err != nil { + return err + } else if newResp.Status != "VALID" { + return errors.New("failed to insert block") + } + return nil + case eth2LightClient: + newResp, err := n.lapi.ExecutePayloadV1(eb) + if err != nil { + return err + } else if newResp.Status != "VALID" { + return errors.New("failed to insert block") + } + return nil + default: + return errors.New("undefined node") } - return nil } -func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed catalyst.ExecutableDataV1) error { +func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed beacon.ExecutableDataV1) error { if !eth2types(n.typ) { return errors.New("invalid node type") } if err := n.insertBlock(ed); err != nil { return err } - block, err := catalyst.ExecutableDataToBlock(ed) + block, err := fcatalyst.ExecutableDataToBlock(ed) if err != nil { return err } - fcState := catalyst.ForkchoiceStateV1{ + fcState := beacon.ForkchoiceStateV1{ HeadBlockHash: block.ParentHash(), SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } - if _, err := n.api.ForkchoiceUpdatedV1(fcState, nil); err != nil { - return err + switch n.typ { + case eth2NormalNode, eth2MiningNode: + if _, err := n.api.ForkchoiceUpdatedV1(fcState, nil); err != nil { + return err + } + return nil + case eth2LightClient: + if _, err := n.lapi.ForkchoiceUpdatedV1(fcState, nil); err != nil { + return err + } + return nil + default: + return errors.New("undefined node") } - return nil } type nodeManager struct { @@ -290,7 +318,7 @@ func (mgr *nodeManager) run() { nodes = append(nodes, mgr.getNodes(eth2NormalNode)...) nodes = append(nodes, mgr.getNodes(eth2LightClient)...) for _, node := range append(nodes) { - fcState := catalyst.ForkchoiceStateV1{ + fcState := beacon.ForkchoiceStateV1{ HeadBlockHash: oldest.Hash(), SafeBlockHash: common.Hash{}, FinalizedBlockHash: oldest.Hash(), @@ -336,20 +364,16 @@ func (mgr *nodeManager) run() { log.Error("Failed to assemble the block", "err", err) continue } - block, _ := catalyst.ExecutableDataToBlock(*ed) + block, _ := fcatalyst.ExecutableDataToBlock(*ed) nodes := mgr.getNodes(eth2MiningNode) nodes = append(nodes, mgr.getNodes(eth2NormalNode)...) + nodes = append(nodes, mgr.getNodes(eth2LightClient)...) for _, node := range nodes { if err := node.insertBlockAndSetHead(parentBlock.Header(), *ed); err != nil { log.Error("Failed to insert block", "type", node.typ, "err", err) } } - for _, node := range mgr.getNodes(eth2LightClient) { - if err := node.insertBlock(*ed); err != nil { - log.Error("Failed to insert block", "type", node.typ, "err", err) - } - } log.Info("Create and insert eth2 block", "number", ed.Number) parentBlock = block waitFinalise = append(waitFinalise, block) @@ -435,7 +459,7 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { return genesis } -func makeFullNode(genesis *core.Genesis) (*node.Node, *eth.Ethereum, *catalyst.ConsensusAPI, error) { +func makeFullNode(genesis *core.Genesis) (*node.Node, *eth.Ethereum, *fcatalyst.ConsensusAPI, error) { // Define the basic configurations for the Ethereum node datadir, _ := ioutil.TempDir("", "") @@ -483,10 +507,10 @@ func makeFullNode(genesis *core.Genesis) (*node.Node, *eth.Ethereum, *catalyst.C log.Crit("Failed to create the LES server", "err", err) } err = stack.Start() - return stack, ethBackend, catalyst.NewConsensusAPI(ethBackend, nil), err + return stack, ethBackend, fcatalyst.NewConsensusAPI(ethBackend), err } -func makeLightNode(genesis *core.Genesis) (*node.Node, *les.LightEthereum, *catalyst.ConsensusAPI, error) { +func makeLightNode(genesis *core.Genesis) (*node.Node, *les.LightEthereum, *lcatalyst.ConsensusAPI, error) { // Define the basic configurations for the Ethereum node datadir, _ := ioutil.TempDir("", "") @@ -521,7 +545,7 @@ func makeLightNode(genesis *core.Genesis) (*node.Node, *les.LightEthereum, *cata return nil, nil, nil, err } err = stack.Start() - return stack, lesBackend, catalyst.NewConsensusAPI(nil, lesBackend), err + return stack, lesBackend, lcatalyst.NewConsensusAPI(lesBackend), err } func eth2types(typ nodetype) bool { From 4dddcc69ef68f19565730dc0aae4ee092ff70f8d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Mon, 24 Jan 2022 14:47:06 +0100 Subject: [PATCH 2/5] eth/catalyst: moved some methods, added docs --- core/beacon/errors.go | 1 - eth/catalyst/api.go | 71 ++++++++++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/core/beacon/errors.go b/core/beacon/errors.go index 2eefdc0055d6..5b95c38a23ba 100644 --- a/core/beacon/errors.go +++ b/core/beacon/errors.go @@ -26,5 +26,4 @@ var ( GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"} UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"} InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"} - InvalidPayloadID = rpc.CustomError{Code: 1, ValidationError: "invalid payload id"} ) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index b8b3e7b497c8..aecd86c0a1be 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -53,6 +53,8 @@ type ConsensusAPI struct { preparedBlocks *payloadQueue // preparedBlocks caches payloads (*ExecutableDataV1) by payload ID (PayloadID) } +// NewConsensusAPI creates a new consensus api for the given backend. +// The underlying blockchain needs to have a valid terminal total difficulty set. func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI { if eth.BlockChain().Config().TerminalTotalDifficulty == nil { panic("Catalyst started without valid total difficulty") @@ -63,15 +65,16 @@ func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI { } } -func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { - log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) - data := api.preparedBlocks.get(payloadID) - if data == nil { - return nil, &beacon.UnknownPayload - } - return data, nil -} - +// ForkchoiceUpdatedV1 has several responsibilities: +// If the method is called with an empty head block: +// we return success, which can be used to check if the catalyst mode is enabled +// If the total difficulty was not reached: +// we return INVALID +// If the finalizedBlockHash is set: +// we check if we have the finalizedBlockHash in our db, if not we start a sync +// We try to set our blockchain to the headBlock +// If there are payloadAttributes: +// we try to assemble a block with the payloadAttributes and return its payloadID func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { log.Trace("Engine API request received", "method", "ForkChoiceUpdated", "head", heads.HeadBlockHash, "finalized", heads.FinalizedBlockHash, "safe", heads.SafeBlockHash) if heads.HeadBlockHash == (common.Hash{}) { @@ -109,20 +112,14 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, pay return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil } -func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttributesV1) beacon.PayloadID { - // Hash - hasher := sha256.New() - hasher.Write(headBlockHash[:]) - binary.Write(hasher, binary.BigEndian, params.Timestamp) - hasher.Write(params.Random[:]) - hasher.Write(params.SuggestedFeeRecipient[:]) - var out beacon.PayloadID - copy(out[:], hasher.Sum(nil)[:8]) - return out -} - -func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { - return beacon.ExecutePayloadResponse{Status: beacon.INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()} +// GetPayloadV1 returns a cached payload by id. +func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { + log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) + data := api.preparedBlocks.get(payloadID) + if data == nil { + return nil, &beacon.UnknownPayload + } + return data, nil } // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. @@ -159,8 +156,26 @@ func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beaco return beacon.ExecutePayloadResponse{Status: beacon.VALID.Status, LatestValidHash: block.Hash()}, nil } -// AssembleBlock creates a new block, inserts it into the chain, and returns the "execution -// data" required for eth2 clients to process the new block. +// computePayloadId computes a pseudo-random payloadid, based on the parameters. +func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttributesV1) beacon.PayloadID { + // Hash + hasher := sha256.New() + hasher.Write(headBlockHash[:]) + binary.Write(hasher, binary.BigEndian, params.Timestamp) + hasher.Write(params.Random[:]) + hasher.Write(params.SuggestedFeeRecipient[:]) + var out beacon.PayloadID + copy(out[:], hasher.Sum(nil)[:8]) + return out +} + +// invalid returns a response "INVALID" with the latest valid hash set to the current head. +func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { + return beacon.ExecutePayloadResponse{Status: beacon.INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()} +} + +// assembleBlock creates a new block and returns the "execution +// data" required for beacon clients to process the new block. func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) { log.Info("Producing block", "parentHash", parentHash) block, err := api.eth.Miner().GetSealingBlock(parentHash, params.Timestamp, params.SuggestedFeeRecipient, params.Random) @@ -190,6 +205,12 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { return txs, nil } +// ExecutableDataToBlock constructs a block from executable data. +// It verifies that the following fields: +// len(extraData) <= 32 +// uncleHash = emptyUncleHash +// difficulty = 0 +// and that the blockhash of the constructed block matches the parameters. func ExecutableDataToBlock(params beacon.ExecutableDataV1) (*types.Block, error) { txs, err := decodeTransactions(params.Transactions) if err != nil { From 0f6ae20b6629e9d7b944f098ce7672a5ea3959c7 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 25 Jan 2022 11:12:37 +0800 Subject: [PATCH 3/5] eth/catalyst, les/catalyst: add method docs --- eth/catalyst/api.go | 12 +++--------- les/catalyst/api.go | 44 +++++++++++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index aecd86c0a1be..ce585cf52fe4 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -24,7 +24,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" @@ -150,7 +149,7 @@ func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beaco return api.invalid(), err } - if merger := api.merger(); !merger.TDDReached() { + if merger := api.eth.Merger(); !merger.TDDReached() { merger.ReachTTD() } return beacon.ExecutePayloadResponse{Status: beacon.VALID.Status, LatestValidHash: block.Hash()}, nil @@ -274,7 +273,7 @@ func (api *ConsensusAPI) insertTransactions(txs types.Transactions) error { func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { // shortcut if we entered PoS already - if api.merger().PoSFinalized() { + if api.eth.Merger().PoSFinalized() { return nil } // make sure the parent has enough terminal total difficulty @@ -304,15 +303,10 @@ func (api *ConsensusAPI) setHead(newHead common.Hash) error { return err } // Trigger the transition if it's the first `NewHead` event. - if merger := api.merger(); !merger.PoSFinalized() { + if merger := api.eth.Merger(); !merger.PoSFinalized() { merger.FinalizePoS() } // TODO (MariusVanDerWijden) are we really synced now? api.eth.SetSynced() return nil } - -// Helper function, return the merger instance. -func (api *ConsensusAPI) merger() *consensus.Merger { - return api.eth.Merger() -} diff --git a/les/catalyst/api.go b/les/catalyst/api.go index e3b26fdee492..484128e3b770 100644 --- a/les/catalyst/api.go +++ b/les/catalyst/api.go @@ -23,7 +23,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/les" @@ -51,6 +50,8 @@ type ConsensusAPI struct { les *les.LightEthereum } +// NewConsensusAPI creates a new consensus api for the given backend. +// The underlying blockchain needs to have a valid terminal total difficulty set. func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI { if les.BlockChain().Config().TerminalTotalDifficulty == nil { panic("Catalyst started without valid total difficulty") @@ -58,10 +59,16 @@ func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI { return &ConsensusAPI{les: les} } -func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { - return nil, &beacon.GenericServerError -} - +// ForkchoiceUpdatedV1 has several responsibilities: +// If the method is called with an empty head block: +// we return success, which can be used to check if the catalyst mode is enabled +// If the total difficulty was not reached: +// we return INVALID +// If the finalizedBlockHash is set: +// we check if we have the finalizedBlockHash in our db, if not we start a sync +// We try to set our blockchain to the headBlock +// If there are payloadAttributes: +// we return an error since block creation is not supported in les mode func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { if heads.HeadBlockHash == (common.Hash{}) { return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil @@ -90,8 +97,9 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, pay return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil } -func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { - return beacon.ExecutePayloadResponse{Status: beacon.INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()} +// GetPayloadV1 returns a cached payload by id. It's not supported in les mode. +func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.ExecutableDataV1, error) { + return nil, &beacon.GenericServerError } // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. @@ -122,12 +130,17 @@ func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beaco if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil { return api.invalid(), err } - if merger := api.merger(); !merger.TDDReached() { + if merger := api.les.Merger(); !merger.TDDReached() { merger.ReachTTD() } return beacon.ExecutePayloadResponse{Status: beacon.VALID.Status, LatestValidHash: block.Hash()}, nil } +// invalid returns a response "INVALID" with the latest valid hash set to the current head. +func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { + return beacon.ExecutePayloadResponse{Status: beacon.INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()} +} + func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { var txs = make([]*types.Transaction, len(enc)) for i, encTx := range enc { @@ -140,6 +153,12 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { return txs, nil } +// ExecutableDataToBlock constructs a block from executable data. +// It verifies that the following fields: +// len(extraData) <= 32 +// uncleHash = emptyUncleHash +// difficulty = 0 +// and that the blockhash of the constructed block matches the parameters. func ExecutableDataToBlock(params beacon.ExecutableDataV1) (*types.Block, error) { txs, err := decodeTransactions(params.Transactions) if err != nil { @@ -174,7 +193,7 @@ func ExecutableDataToBlock(params beacon.ExecutableDataV1) (*types.Block, error) func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { // shortcut if we entered PoS already - if api.merger().PoSFinalized() { + if api.les.Merger().PoSFinalized() { return nil } // make sure the parent has enough terminal total difficulty @@ -205,13 +224,8 @@ func (api *ConsensusAPI) setHead(newHead common.Hash) error { return err } // Trigger the transition if it's the first `NewHead` event. - if merger := api.merger(); !merger.PoSFinalized() { + if merger := api.les.Merger(); !merger.PoSFinalized() { merger.FinalizePoS() } return nil } - -// Helper function, return the merger instance. -func (api *ConsensusAPI) merger() *consensus.Merger { - return api.les.Merger() -} From cbb85e011c272437c1148ece05370031372c135f Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Tue, 25 Jan 2022 14:06:13 +0800 Subject: [PATCH 4/5] core, eth, les, miner: move common function to beacon package --- core/beacon/types.go | 81 ++++++++++++++++++++++++++++++++++ eth/catalyst/api.go | 86 +------------------------------------ eth/catalyst/api_test.go | 4 +- les/catalyst/api.go | 55 +----------------------- miner/stress/beacon/main.go | 4 +- 5 files changed, 88 insertions(+), 142 deletions(-) diff --git a/core/beacon/types.go b/core/beacon/types.go index 6181c9508a7c..d7f6ba535e5f 100644 --- a/core/beacon/types.go +++ b/core/beacon/types.go @@ -22,6 +22,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" ) //go:generate go run github.com/fjl/gencodec -type PayloadAttributesV1 -field-override payloadAttributesMarshaling -out gen_blockparams.go @@ -121,3 +123,82 @@ type ForkchoiceStateV1 struct { SafeBlockHash common.Hash `json:"safeBlockHash"` FinalizedBlockHash common.Hash `json:"finalizedBlockHash"` } + +func encodeTransactions(txs []*types.Transaction) [][]byte { + var enc = make([][]byte, len(txs)) + for i, tx := range txs { + enc[i], _ = tx.MarshalBinary() + } + return enc +} + +func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { + var txs = make([]*types.Transaction, len(enc)) + for i, encTx := range enc { + var tx types.Transaction + if err := tx.UnmarshalBinary(encTx); err != nil { + return nil, fmt.Errorf("invalid transaction %d: %v", i, err) + } + txs[i] = &tx + } + return txs, nil +} + +// ExecutableDataToBlock constructs a block from executable data. +// It verifies that the following fields: +// len(extraData) <= 32 +// uncleHash = emptyUncleHash +// difficulty = 0 +// and that the blockhash of the constructed block matches the parameters. +func ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) { + txs, err := decodeTransactions(params.Transactions) + if err != nil { + return nil, err + } + if len(params.ExtraData) > 32 { + return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) + } + header := &types.Header{ + ParentHash: params.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: params.FeeRecipient, + Root: params.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: params.ReceiptsRoot, + Bloom: types.BytesToBloom(params.LogsBloom), + Difficulty: common.Big0, + Number: new(big.Int).SetUint64(params.Number), + GasLimit: params.GasLimit, + GasUsed: params.GasUsed, + Time: params.Timestamp, + BaseFee: params.BaseFeePerGas, + Extra: params.ExtraData, + MixDigest: params.Random, + } + block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) + if block.Hash() != params.BlockHash { + return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) + } + return block, nil +} + +// BlockToExecutableData constructs the executableDataV1 structure by filling the +// fields from the given block. It assumes the given block is post-merge block. +func BlockToExecutableData(block *types.Block) *ExecutableDataV1 { + return &ExecutableDataV1{ + BlockHash: block.Hash(), + ParentHash: block.ParentHash(), + FeeRecipient: block.Coinbase(), + StateRoot: block.Root(), + Number: block.NumberU64(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + BaseFeePerGas: block.BaseFee(), + Timestamp: block.Time(), + ReceiptsRoot: block.ReceiptHash(), + LogsBloom: block.Bloom().Bytes(), + Transactions: encodeTransactions(block.Transactions()), + Random: block.MixDigest(), + ExtraData: block.Extra(), + } +} diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index ce585cf52fe4..ec7441c32d79 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -21,8 +21,6 @@ import ( "crypto/sha256" "encoding/binary" "fmt" - "math/big" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" @@ -30,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/trie" ) // Register adds catalyst APIs to the full node. @@ -124,7 +121,7 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.ExecutePayloadResponse, error) { log.Trace("Engine API request received", "method", "ExecutePayload", params.BlockHash, "number", params.Number) - block, err := ExecutableDataToBlock(params) + block, err := beacon.ExecutableDataToBlock(params) if err != nil { return api.invalid(), err } @@ -181,86 +178,7 @@ func (api *ConsensusAPI) assembleBlock(parentHash common.Hash, params *beacon.Pa if err != nil { return nil, err } - return BlockToExecutableData(block), nil -} - -func encodeTransactions(txs []*types.Transaction) [][]byte { - var enc = make([][]byte, len(txs)) - for i, tx := range txs { - enc[i], _ = tx.MarshalBinary() - } - return enc -} - -func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { - var txs = make([]*types.Transaction, len(enc)) - for i, encTx := range enc { - var tx types.Transaction - if err := tx.UnmarshalBinary(encTx); err != nil { - return nil, fmt.Errorf("invalid transaction %d: %v", i, err) - } - txs[i] = &tx - } - return txs, nil -} - -// ExecutableDataToBlock constructs a block from executable data. -// It verifies that the following fields: -// len(extraData) <= 32 -// uncleHash = emptyUncleHash -// difficulty = 0 -// and that the blockhash of the constructed block matches the parameters. -func ExecutableDataToBlock(params beacon.ExecutableDataV1) (*types.Block, error) { - txs, err := decodeTransactions(params.Transactions) - if err != nil { - return nil, err - } - if len(params.ExtraData) > 32 { - return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) - } - header := &types.Header{ - ParentHash: params.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: params.FeeRecipient, - Root: params.StateRoot, - TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), - ReceiptHash: params.ReceiptsRoot, - Bloom: types.BytesToBloom(params.LogsBloom), - Difficulty: common.Big0, - Number: new(big.Int).SetUint64(params.Number), - GasLimit: params.GasLimit, - GasUsed: params.GasUsed, - Time: params.Timestamp, - BaseFee: params.BaseFeePerGas, - Extra: params.ExtraData, - MixDigest: params.Random, - } - block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) - if block.Hash() != params.BlockHash { - return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) - } - return block, nil -} - -// BlockToExecutableData constructs the executableDataV1 structure by filling the -// fields from the given block. It assumes the given block is post-merge block. -func BlockToExecutableData(block *types.Block) *beacon.ExecutableDataV1 { - return &beacon.ExecutableDataV1{ - BlockHash: block.Hash(), - ParentHash: block.ParentHash(), - FeeRecipient: block.Coinbase(), - StateRoot: block.Root(), - Number: block.NumberU64(), - GasLimit: block.GasLimit(), - GasUsed: block.GasUsed(), - BaseFeePerGas: block.BaseFee(), - Timestamp: block.Time(), - ReceiptsRoot: block.ReceiptHash(), - LogsBloom: block.Bloom().Bytes(), - Transactions: encodeTransactions(block.Transactions()), - Random: block.MixDigest(), - ExtraData: block.Extra(), - } + return beacon.BlockToExecutableData(block), nil } // Used in tests to add a the list of transactions from a block to the tx pool. diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 942e9f41b4b0..b824d22f84b4 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -267,7 +267,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to create the executable data %v", err) } - block, err := ExecutableDataToBlock(*execData) + block, err := beacon.ExecutableDataToBlock(*execData) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -307,7 +307,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to create the executable data %v", err) } - block, err := ExecutableDataToBlock(*execData) + block, err := beacon.ExecutableDataToBlock(*execData) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } diff --git a/les/catalyst/api.go b/les/catalyst/api.go index 484128e3b770..5f5193c3bbc9 100644 --- a/les/catalyst/api.go +++ b/les/catalyst/api.go @@ -20,16 +20,13 @@ package catalyst import ( "errors" "fmt" - "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/beacon" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/trie" ) // Register adds catalyst APIs to the light client. @@ -104,7 +101,7 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.ExecutePayloadResponse, error) { - block, err := ExecutableDataToBlock(params) + block, err := beacon.ExecutableDataToBlock(params) if err != nil { return api.invalid(), err } @@ -141,56 +138,6 @@ func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { return beacon.ExecutePayloadResponse{Status: beacon.INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()} } -func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { - var txs = make([]*types.Transaction, len(enc)) - for i, encTx := range enc { - var tx types.Transaction - if err := tx.UnmarshalBinary(encTx); err != nil { - return nil, fmt.Errorf("invalid transaction %d: %v", i, err) - } - txs[i] = &tx - } - return txs, nil -} - -// ExecutableDataToBlock constructs a block from executable data. -// It verifies that the following fields: -// len(extraData) <= 32 -// uncleHash = emptyUncleHash -// difficulty = 0 -// and that the blockhash of the constructed block matches the parameters. -func ExecutableDataToBlock(params beacon.ExecutableDataV1) (*types.Block, error) { - txs, err := decodeTransactions(params.Transactions) - if err != nil { - return nil, err - } - if len(params.ExtraData) > 32 { - return nil, fmt.Errorf("invalid extradata length: %v", len(params.ExtraData)) - } - header := &types.Header{ - ParentHash: params.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: params.FeeRecipient, - Root: params.StateRoot, - TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), - ReceiptHash: params.ReceiptsRoot, - Bloom: types.BytesToBloom(params.LogsBloom), - Difficulty: common.Big0, - Number: new(big.Int).SetUint64(params.Number), - GasLimit: params.GasLimit, - GasUsed: params.GasUsed, - Time: params.Timestamp, - BaseFee: params.BaseFeePerGas, - Extra: params.ExtraData, - MixDigest: params.Random, - } - block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) - if block.Hash() != params.BlockHash { - return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash()) - } - return block, nil -} - func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { // shortcut if we entered PoS already if api.les.Merger().PoSFinalized() { diff --git a/miner/stress/beacon/main.go b/miner/stress/beacon/main.go index e094e3012d77..279cd8b75d81 100644 --- a/miner/stress/beacon/main.go +++ b/miner/stress/beacon/main.go @@ -200,7 +200,7 @@ func (n *ethNode) insertBlockAndSetHead(parent *types.Header, ed beacon.Executab if err := n.insertBlock(ed); err != nil { return err } - block, err := fcatalyst.ExecutableDataToBlock(ed) + block, err := beacon.ExecutableDataToBlock(ed) if err != nil { return err } @@ -364,7 +364,7 @@ func (mgr *nodeManager) run() { log.Error("Failed to assemble the block", "err", err) continue } - block, _ := fcatalyst.ExecutableDataToBlock(*ed) + block, _ := beacon.ExecutableDataToBlock(*ed) nodes := mgr.getNodes(eth2MiningNode) nodes = append(nodes, mgr.getNodes(eth2NormalNode)...) From e8ba29a50119ee8b42475b85ef59d9258dbe4dee Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 25 Jan 2022 19:35:53 +0100 Subject: [PATCH 5/5] core/beacon: eth/catalyst: updated engine api to new version --- core/beacon/errors.go | 12 +++++++---- core/beacon/types.go | 9 ++++---- eth/catalyst/api.go | 42 +++++++++++++++++++++---------------- eth/catalyst/api_test.go | 18 +++++++++------- les/catalyst/api.go | 24 ++++++++++----------- miner/stress/beacon/main.go | 2 +- 6 files changed, 60 insertions(+), 47 deletions(-) diff --git a/core/beacon/errors.go b/core/beacon/errors.go index 5b95c38a23ba..58d52467acc5 100644 --- a/core/beacon/errors.go +++ b/core/beacon/errors.go @@ -19,10 +19,14 @@ package beacon import "github.com/ethereum/go-ethereum/rpc" var ( - VALID = GenericStringResponse{"VALID"} - SUCCESS = GenericStringResponse{"SUCCESS"} - INVALID = ForkChoiceResponse{Status: "INVALID", PayloadID: nil} - SYNCING = ForkChoiceResponse{Status: "SYNCING", PayloadID: nil} + VALID = "VALID" + INVALIDBLOCKHASH = "INVALID_BLOCK_HASH" + ACCEPTED = "ACCEPTED" + INVALID = "INVALID" + SYNCING = "SYNCING" + STATUS_SUCCESS = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: VALID}, PayloadID: nil} + STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil} + STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil} GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"} UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"} InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"} diff --git a/core/beacon/types.go b/core/beacon/types.go index d7f6ba535e5f..2d06fed01c7b 100644 --- a/core/beacon/types.go +++ b/core/beacon/types.go @@ -47,7 +47,7 @@ type ExecutableDataV1 struct { ParentHash common.Hash `json:"parentHash" gencodec:"required"` FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` StateRoot common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` LogsBloom []byte `json:"logsBloom" gencodec:"required"` Random common.Hash `json:"random" gencodec:"required"` Number uint64 `json:"blockNumber" gencodec:"required"` @@ -84,9 +84,10 @@ type GenericStringResponse struct { Status string `json:"status"` } -type ExecutePayloadResponse struct { +type PayloadStatusV1 struct { Status string `json:"status"` LatestValidHash common.Hash `json:"latestValidHash"` + ValidationError string `json:"validationError"` } type ConsensusValidatedParams struct { @@ -114,8 +115,8 @@ func (b *PayloadID) UnmarshalText(input []byte) error { } type ForkChoiceResponse struct { - Status string `json:"status"` - PayloadID *PayloadID `json:"payloadId"` + PayloadStatus PayloadStatusV1 `json:"payloadStatus"` + PayloadID *PayloadID `json:"payloadId"` } type ForkchoiceStateV1 struct { diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index ec7441c32d79..7bd5631b603e 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -21,6 +21,7 @@ import ( "crypto/sha256" "encoding/binary" "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/types" @@ -74,38 +75,42 @@ func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI { func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { log.Trace("Engine API request received", "method", "ForkChoiceUpdated", "head", heads.HeadBlockHash, "finalized", heads.FinalizedBlockHash, "safe", heads.SafeBlockHash) if heads.HeadBlockHash == (common.Hash{}) { - return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil + return beacon.STATUS_SUCCESS, nil } if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil { if block := api.eth.BlockChain().GetBlockByHash(heads.HeadBlockHash); block == nil { // TODO (MariusVanDerWijden) trigger sync - return beacon.SYNCING, nil + return beacon.STATUS_SYNCING, nil } - return beacon.INVALID, err + return beacon.ForkChoiceResponse{PayloadStatus: api.invalid(err)}, nil } // If the finalized block is set, check if it is in our blockchain if heads.FinalizedBlockHash != (common.Hash{}) { if block := api.eth.BlockChain().GetBlockByHash(heads.FinalizedBlockHash); block == nil { // TODO (MariusVanDerWijden) trigger sync - return beacon.SYNCING, nil + return beacon.STATUS_SYNCING, nil } } + if block := api.eth.BlockChain().GetBlockByHash(heads.HeadBlockHash); block == nil { + // TODO (MariusVanDerWijden) trigger sync + return beacon.STATUS_SYNCING, nil + } // SetHead if err := api.setHead(heads.HeadBlockHash); err != nil { - return beacon.INVALID, err + return beacon.ForkChoiceResponse{PayloadStatus: api.invalid(err)}, nil } // Assemble block (if needed). It only works for full node. if payloadAttributes != nil { data, err := api.assembleBlock(heads.HeadBlockHash, payloadAttributes) if err != nil { - return beacon.INVALID, err + return beacon.STATUS_INVALID, err } id := computePayloadId(heads.HeadBlockHash, payloadAttributes) api.preparedBlocks.put(id, data) log.Info("Created payload", "payloadID", id) - return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: &id}, nil + return beacon.ForkChoiceResponse{PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID}, PayloadID: &id}, nil } - return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil + return beacon.STATUS_SUCCESS, nil } // GetPayloadV1 returns a cached payload by id. @@ -118,38 +123,39 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu return data, nil } -// ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. -func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.ExecutePayloadResponse, error) { +// NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. +func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) { log.Trace("Engine API request received", "method", "ExecutePayload", params.BlockHash, "number", params.Number) block, err := beacon.ExecutableDataToBlock(params) if err != nil { - return api.invalid(), err + return beacon.PayloadStatusV1{Status: beacon.INVALIDBLOCKHASH}, nil } if !api.eth.BlockChain().HasBlock(block.ParentHash(), block.NumberU64()-1) { /* - TODO (MariusVanDerWijden) reenable once sync is merged + TODO (MariusVanDerWijden) maybe reenable once sync is merged if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), block.Header()); err != nil { return SYNCING, err } */ // TODO (MariusVanDerWijden) we should return nil here not empty hash - return beacon.ExecutePayloadResponse{Status: beacon.SYNCING.Status, LatestValidHash: common.Hash{}}, nil + return beacon.PayloadStatusV1{Status: beacon.ACCEPTED}, nil } parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash) td := api.eth.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1) ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty if td.Cmp(ttd) < 0 { - return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd) + err := fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd) + return api.invalid(err), nil } log.Trace("Inserting block without head", "hash", block.Hash(), "number", block.Number) if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil { - return api.invalid(), err + return api.invalid(err), nil } if merger := api.eth.Merger(); !merger.TDDReached() { merger.ReachTTD() } - return beacon.ExecutePayloadResponse{Status: beacon.VALID.Status, LatestValidHash: block.Hash()}, nil + return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: block.Hash()}, nil } // computePayloadId computes a pseudo-random payloadid, based on the parameters. @@ -166,8 +172,8 @@ func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttribute } // invalid returns a response "INVALID" with the latest valid hash set to the current head. -func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { - return beacon.ExecutePayloadResponse{Status: beacon.INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()} +func (api *ConsensusAPI) invalid(err error) beacon.PayloadStatusV1 { + return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash(), ValidationError: err.Error()} } // assembleBlock creates a new block and returns the "execution diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index b824d22f84b4..6b561f3a9a06 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -130,8 +130,10 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) { SafeBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{}, } - if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err == nil { - t.Errorf("fork choice updated before total terminal difficulty should fail") + if resp, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { + t.Errorf("fork choice updated should not error") + } else if resp.PayloadStatus.Status != beacon.INVALID { + t.Errorf("fork choice updated before total terminal difficulty should be INVALID") } } @@ -271,7 +273,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } - newResp, err := api.ExecutePayloadV1(*execData) + newResp, err := api.NewPayloadV1(*execData) if err != nil || newResp.Status != "VALID" { t.Fatalf("Failed to insert block: %v", err) } @@ -311,7 +313,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } - newResp, err := api.ExecutePayloadV1(*execData) + newResp, err := api.NewPayloadV1(*execData) if err != nil || newResp.Status != "VALID" { t.Fatalf("Failed to insert block: %v", err) } @@ -438,19 +440,19 @@ func TestFullAPI(t *testing.T) { if err != nil { t.Fatalf("error preparing payload, err=%v", err) } - if resp.Status != beacon.SUCCESS.Status { - t.Fatalf("error preparing payload, invalid status: %v", resp.Status) + if resp.PayloadStatus.Status != beacon.SUCCESS { + t.Fatalf("error preparing payload, invalid status: %v", resp.PayloadStatus.Status) } payloadID := computePayloadId(parent.Hash(), ¶ms) payload, err := api.GetPayloadV1(payloadID) if err != nil { t.Fatalf("can't get payload: %v", err) } - execResp, err := api.ExecutePayloadV1(*payload) + execResp, err := api.NewPayloadV1(*payload) if err != nil { t.Fatalf("can't execute payload: %v", err) } - if execResp.Status != beacon.VALID.Status { + if execResp.Status != beacon.VALID { t.Fatalf("invalid status: %v", execResp.Status) } fcState = beacon.ForkchoiceStateV1{ diff --git a/les/catalyst/api.go b/les/catalyst/api.go index 5f5193c3bbc9..0e5c07995b5a 100644 --- a/les/catalyst/api.go +++ b/les/catalyst/api.go @@ -68,30 +68,30 @@ func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI { // we return an error since block creation is not supported in les mode func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { if heads.HeadBlockHash == (common.Hash{}) { - return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil + return beacon.STATUS_SUCCESS, nil } if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil { if header := api.les.BlockChain().GetHeaderByHash(heads.HeadBlockHash); header == nil { // TODO (MariusVanDerWijden) trigger sync - return beacon.SYNCING, nil + return beacon.STATUS_SYNCING, nil } - return beacon.INVALID, err + return beacon.STATUS_INVALID, err } // If the finalized block is set, check if it is in our blockchain if heads.FinalizedBlockHash != (common.Hash{}) { if header := api.les.BlockChain().GetHeaderByHash(heads.FinalizedBlockHash); header == nil { // TODO (MariusVanDerWijden) trigger sync - return beacon.SYNCING, nil + return beacon.STATUS_SYNCING, nil } } // SetHead if err := api.setHead(heads.HeadBlockHash); err != nil { - return beacon.INVALID, err + return beacon.STATUS_INVALID, err } if payloadAttributes != nil { - return beacon.INVALID, errors.New("not supported") + return beacon.STATUS_INVALID, errors.New("not supported") } - return beacon.ForkChoiceResponse{Status: beacon.SUCCESS.Status, PayloadID: nil}, nil + return beacon.STATUS_SUCCESS, nil } // GetPayloadV1 returns a cached payload by id. It's not supported in les mode. @@ -100,7 +100,7 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu } // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. -func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.ExecutePayloadResponse, error) { +func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) { block, err := beacon.ExecutableDataToBlock(params) if err != nil { return api.invalid(), err @@ -113,7 +113,7 @@ func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beaco } */ // TODO (MariusVanDerWijden) we should return nil here not empty hash - return beacon.ExecutePayloadResponse{Status: beacon.SYNCING.Status, LatestValidHash: common.Hash{}}, nil + return beacon.PayloadStatusV1{Status: beacon.SYNCING, LatestValidHash: common.Hash{}}, nil } parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash) if parent == nil { @@ -130,12 +130,12 @@ func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beaco if merger := api.les.Merger(); !merger.TDDReached() { merger.ReachTTD() } - return beacon.ExecutePayloadResponse{Status: beacon.VALID.Status, LatestValidHash: block.Hash()}, nil + return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: block.Hash()}, nil } // invalid returns a response "INVALID" with the latest valid hash set to the current head. -func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { - return beacon.ExecutePayloadResponse{Status: beacon.INVALID.Status, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()} +func (api *ConsensusAPI) invalid() beacon.PayloadStatusV1 { + return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()} } func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { diff --git a/miner/stress/beacon/main.go b/miner/stress/beacon/main.go index 279cd8b75d81..5c3d29919c25 100644 --- a/miner/stress/beacon/main.go +++ b/miner/stress/beacon/main.go @@ -173,7 +173,7 @@ func (n *ethNode) insertBlock(eb beacon.ExecutableDataV1) error { } switch n.typ { case eth2NormalNode, eth2MiningNode: - newResp, err := n.api.ExecutePayloadV1(eb) + newResp, err := n.api.NewPayloadV1(eb) if err != nil { return err } else if newResp.Status != "VALID" {