diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 57d9b87958ce..586eeca400cb 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"
+ ethcatalyst "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"
+ lescatalyst "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"
@@ -1724,7 +1725,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 := lescatalyst.Register(stack, backend); err != nil {
Fatalf("Failed to register the catalyst service: %v", err)
}
}
@@ -1741,7 +1742,7 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config, isCatalyst bool
}
}
if isCatalyst {
- if err := catalyst.Register(stack, backend); err != nil {
+ if err := ethcatalyst.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..5b95c38a23ba
--- /dev/null
+++ b/core/beacon/errors.go
@@ -0,0 +1,29 @@
+// 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"}
+)
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 60%
rename from eth/catalyst/api_types.go
rename to core/beacon/types.go
index 07636239fac3..d7f6ba535e5f 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"
@@ -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 1087496d167c..a8b20d75896a 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -20,29 +20,15 @@ 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.
@@ -52,21 +38,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 +46,86 @@ 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")
- }
+// 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")
}
-
return &ConsensusAPI{
- light: eth == nil,
eth: eth,
- les: les,
preparedBlocks: newPayloadQueue(),
}
}
-func (api *ConsensusAPI) GetPayloadV1(payloadID PayloadID) (*ExecutableDataV1, error) {
- log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID)
- data := api.preparedBlocks.get(payloadID)
- if data == nil {
- return nil, &UnknownPayload
- }
- return data, nil
-}
-
-func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads ForkchoiceStateV1, payloadAttributes *PayloadAttributesV1) (ForkChoiceResponse, error) {
+// 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{}) {
- 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 {
- // 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
- 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()}
+// 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 ExecutePayloadResponse{Status: INVALID.Status, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()}
+ 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 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)
+ block, err := beacon.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 +134,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)
@@ -232,97 +147,39 @@ func (api *ConsensusAPI) ExecutePayloadV1(params ExecutableDataV1) (ExecutePaylo
return api.invalid(), err
}
- if merger := api.merger(); !merger.TDDReached() {
+ if merger := api.eth.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")
- }
- log.Info("Producing block", "parentHash", parentHash)
- block, err := api.eth.Miner().GetSealingBlock(parentHash, params.Timestamp, params.SuggestedFeeRecipient, params.Random)
- 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
+// 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
}
-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
+// 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 ExecutableDataToBlock(params ExecutableDataV1) (*types.Block, error) {
- txs, err := decodeTransactions(params.Transactions)
+// 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)
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(),
- }
+ return beacon.BlockToExecutableData(block), nil
}
// Used in tests to add a the list of transactions from a block to the tx pool.
@@ -335,29 +192,17 @@ 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() {
- 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
- }
+ if api.eth.Merger().PoSFinalized() {
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,48 +210,22 @@ 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
}
// 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 {
- 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..b824d22f84b4 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,13 +261,13 @@ 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 {
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)
}
@@ -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,13 +301,13 @@ 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 {
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)
}
@@ -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..5f5193c3bbc9
--- /dev/null
+++ b/les/catalyst/api.go
@@ -0,0 +1,178 @@
+// 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"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/beacon"
+ "github.com/ethereum/go-ethereum/les"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+// 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
+}
+
+// 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")
+ }
+ return &ConsensusAPI{les: les}
+}
+
+// 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
+ }
+ 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
+}
+
+// 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.
+func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.ExecutePayloadResponse, error) {
+ block, err := beacon.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.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 (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
+ // shortcut if we entered PoS already
+ if api.les.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.les.Merger(); !merger.PoSFinalized() {
+ merger.FinalizePoS()
+ }
+ return nil
+}
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..9fa63281c659 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"
+ ethcatalyst "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"
+ lescatalyst "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 *ethcatalyst.ConsensusAPI
+ ethBackend *eth.Ethereum
+ lapi *lescatalyst.ConsensusAPI
+ lesBackend *les.LightEthereum
}
func newNode(typ nodetype, genesis *core.Genesis, enodes []*enode.Node) *ethNode {
var (
err error
- api *catalyst.ConsensusAPI
+ api *ethcatalyst.ConsensusAPI
+ lapi *lescatalyst.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 := beacon.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, _ := beacon.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, *ethcatalyst.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, ethcatalyst.NewConsensusAPI(ethBackend), err
}
-func makeLightNode(genesis *core.Genesis) (*node.Node, *les.LightEthereum, *catalyst.ConsensusAPI, error) {
+func makeLightNode(genesis *core.Genesis) (*node.Node, *les.LightEthereum, *lescatalyst.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, lescatalyst.NewConsensusAPI(lesBackend), err
}
func eth2types(typ nodetype) bool {