diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 6fc75363c6..c867877ee6 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -28,6 +28,7 @@ import (
"gopkg.in/urfave/cli.v1"
"github.com/ethereum/go-ethereum/cmd/utils"
+ "github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/metrics"
@@ -143,7 +144,17 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) {
cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideBerlinFlag.Name))
}
- backend := utils.RegisterEthService(stack, &cfg.Eth)
+ backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
+
+ // Configure catalyst.
+ if ctx.GlobalBool(utils.CatalystFlag.Name) {
+ if eth == nil {
+ utils.Fatalf("Catalyst does not work in light client mode.")
+ }
+ if err := catalyst.Register(stack, eth); err != nil {
+ utils.Fatalf("%v", err)
+ }
+ }
// Configure GraphQL if requested
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 12a5ae9bfc..78e65161da 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -151,6 +151,7 @@ var (
utils.EVMInterpreterFlag,
utils.MinerNotifyFullFlag,
configFileFlag,
+ utils.CatalystFlag,
}
rpcFlags = []cli.Flag{
diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go
index d7f8fd7ab9..980794db73 100644
--- a/cmd/geth/usage.go
+++ b/cmd/geth/usage.go
@@ -235,6 +235,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.SnapshotFlag,
utils.BloomFilterSizeFlag,
cli.HelpFlag,
+ utils.CatalystFlag,
},
},
}
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index d631b8e332..59cf32c983 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -755,6 +755,11 @@ var (
Usage: "External EVM configuration (default = built-in interpreter)",
Value: "",
}
+
+ CatalystFlag = cli.BoolFlag{
+ Name: "catalyst",
+ Usage: "Catalyst mode (eth2 integration testing)",
+ }
)
// MakeDataDir retrieves the currently requested data directory, terminating
@@ -1186,10 +1191,11 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
cfg.NetRestrict = list
}
- if ctx.GlobalBool(DeveloperFlag.Name) {
+ if ctx.GlobalBool(DeveloperFlag.Name) || ctx.GlobalBool(CatalystFlag.Name) {
// --dev mode can't use p2p networking.
cfg.MaxPeers = 0
- cfg.ListenAddr = ":0"
+ cfg.ListenAddr = ""
+ cfg.NoDial = true
cfg.NoDiscovery = true
cfg.DiscoveryV5 = false
}
@@ -1693,14 +1699,16 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
}
// RegisterEthService adds an Ethereum client to the stack.
-func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) ethapi.Backend {
+// The second return value is the full node instance, which may be nil if the
+// node is running as a light client.
+func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {
if cfg.SyncMode == downloader.LightSync {
backend, err := les.New(stack, cfg)
if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
}
stack.RegisterAPIs(tracers.APIs(backend.ApiBackend))
- return backend.ApiBackend
+ return backend.ApiBackend, nil
}
backend, err := eth.New(stack, cfg)
if err != nil {
@@ -1713,7 +1721,7 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) ethapi.Backend
}
}
stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
- return backend.APIBackend
+ return backend.APIBackend, backend
}
// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to
diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go
index 011a5688ef..e23bd824af 100644
--- a/consensus/ethash/consensus.go
+++ b/consensus/ethash/consensus.go
@@ -315,6 +315,8 @@ func (ethash *Ethash) CalcDifficulty(chain consensus.ChainHeaderReader, time uin
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
next := new(big.Int).Add(parent.Number, big1)
switch {
+ case config.IsCatalyst(next):
+ return big.NewInt(1)
case config.IsMuirGlacier(next):
return calcDifficultyEip2384(time, parent)
case config.IsConstantinople(next):
diff --git a/core/blockchain.go b/core/blockchain.go
index 8c3d940a69..dfbc5ad0bb 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -1693,6 +1693,22 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
return n, err
}
+// InsertChainWithoutSealVerification works exactly the same
+// except for seal verification, seal verification is omitted
+func (bc *BlockChain) InsertChainWithoutSealVerification(block *types.Block) (int, error) {
+ bc.blockProcFeed.Send(true)
+ defer bc.blockProcFeed.Send(false)
+
+ // Pre-checks passed, start the full block imports
+ bc.wg.Add(1)
+ bc.chainmu.Lock()
+ n, err := bc.insertChain(types.Blocks([]*types.Block{block}), false)
+ bc.chainmu.Unlock()
+ bc.wg.Done()
+
+ return n, err
+}
+
// insertChain is the internal implementation of InsertChain, which assumes that
// 1) chains are contiguous, and 2) The chain mutex is held.
//
diff --git a/eth/backend.go b/eth/backend.go
index 9cf8b85663..4c7374612e 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -223,6 +223,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}); err != nil {
return nil, err
}
+
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
new file mode 100644
index 0000000000..d6ea691d02
--- /dev/null
+++ b/eth/catalyst/api.go
@@ -0,0 +1,302 @@
+// 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 implements the temporary eth1/eth2 RPC integration.
+package catalyst
+
+import (
+ "errors"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/node"
+ chainParams "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/ethereum/go-ethereum/trie"
+)
+
+// Register adds catalyst APIs to the node.
+func Register(stack *node.Node, backend *eth.Ethereum) error {
+ chainconfig := backend.BlockChain().Config()
+ if chainconfig.CatalystBlock == nil {
+ return errors.New("catalystBlock is not set in genesis config")
+ } else if chainconfig.CatalystBlock.Sign() != 0 {
+ return errors.New("catalystBlock of genesis config must be zero")
+ }
+
+ log.Warn("Catalyst mode enabled")
+ stack.RegisterAPIs([]rpc.API{
+ {
+ Namespace: "consensus",
+ Version: "1.0",
+ Service: newConsensusAPI(backend),
+ Public: true,
+ },
+ })
+ return nil
+}
+
+type consensusAPI struct {
+ eth *eth.Ethereum
+}
+
+func newConsensusAPI(eth *eth.Ethereum) *consensusAPI {
+ return &consensusAPI{eth: eth}
+}
+
+// blockExecutionEnv gathers all the data required to execute
+// a block, either when assembling it or when inserting it.
+type blockExecutionEnv struct {
+ chain *core.BlockChain
+ state *state.StateDB
+ tcount int
+ gasPool *core.GasPool
+
+ header *types.Header
+ txs []*types.Transaction
+ receipts []*types.Receipt
+}
+
+func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error {
+ vmconfig := *env.chain.GetVMConfig()
+ receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmconfig)
+ if err != nil {
+ return err
+ }
+ env.txs = append(env.txs, tx)
+ env.receipts = append(env.receipts, receipt)
+ return nil
+}
+
+func (api *consensusAPI) makeEnv(parent *types.Block, header *types.Header) (*blockExecutionEnv, error) {
+ state, err := api.eth.BlockChain().StateAt(parent.Root())
+ if err != nil {
+ return nil, err
+ }
+ env := &blockExecutionEnv{
+ chain: api.eth.BlockChain(),
+ state: state,
+ header: header,
+ gasPool: new(core.GasPool).AddGas(header.GasLimit),
+ }
+ return env, 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(params assembleBlockParams) (*executableData, error) {
+ log.Info("Producing block", "parentHash", params.ParentHash)
+
+ bc := api.eth.BlockChain()
+ parent := bc.GetBlockByHash(params.ParentHash)
+ pool := api.eth.TxPool()
+
+ if parent.Time() >= params.Timestamp {
+ return nil, fmt.Errorf("child timestamp lower than parent's: %d >= %d", parent.Time(), params.Timestamp)
+ }
+ if now := uint64(time.Now().Unix()); params.Timestamp > now+1 {
+ wait := time.Duration(params.Timestamp-now) * time.Second
+ log.Info("Producing block too far in the future", "wait", common.PrettyDuration(wait))
+ time.Sleep(wait)
+ }
+
+ pending, err := pool.Pending()
+ if err != nil {
+ return nil, err
+ }
+
+ coinbase, err := api.eth.Etherbase()
+ if err != nil {
+ return nil, err
+ }
+ num := parent.Number()
+ header := &types.Header{
+ ParentHash: parent.Hash(),
+ Number: num.Add(num, common.Big1),
+ Coinbase: coinbase,
+ GasLimit: parent.GasLimit(), // Keep the gas limit constant in this prototype
+ Extra: []byte{},
+ Time: params.Timestamp,
+ }
+ err = api.eth.Engine().Prepare(bc, header)
+ if err != nil {
+ return nil, err
+ }
+
+ env, err := api.makeEnv(parent, header)
+ if err != nil {
+ return nil, err
+ }
+
+ var (
+ signer = types.MakeSigner(bc.Config(), header.Number)
+ txHeap = types.NewTransactionsByPriceAndNonce(signer, pending)
+ transactions []*types.Transaction
+ )
+ for {
+ if env.gasPool.Gas() < chainParams.TxGas {
+ log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", chainParams.TxGas)
+ break
+ }
+ tx := txHeap.Peek()
+ if tx == nil {
+ break
+ }
+
+ // The sender is only for logging purposes, and it doesn't really matter if it's correct.
+ from, _ := types.Sender(signer, tx)
+
+ // Execute the transaction
+ env.state.Prepare(tx.Hash(), common.Hash{}, env.tcount)
+ err = env.commitTransaction(tx, coinbase)
+ switch err {
+ case core.ErrGasLimitReached:
+ // Pop the current out-of-gas transaction without shifting in the next from the account
+ log.Trace("Gas limit exceeded for current block", "sender", from)
+ txHeap.Pop()
+
+ case core.ErrNonceTooLow:
+ // New head notification data race between the transaction pool and miner, shift
+ log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
+ txHeap.Shift()
+
+ case core.ErrNonceTooHigh:
+ // Reorg notification data race between the transaction pool and miner, skip account =
+ log.Trace("Skipping account with high nonce", "sender", from, "nonce", tx.Nonce())
+ txHeap.Pop()
+
+ case nil:
+ // Everything ok, collect the logs and shift in the next transaction from the same account
+ env.tcount++
+ txHeap.Shift()
+ transactions = append(transactions, tx)
+
+ default:
+ // Strange error, discard the transaction and get the next in line (note, the
+ // nonce-too-high clause will prevent us from executing in vain).
+ log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
+ txHeap.Shift()
+ }
+ }
+
+ // Create the block.
+ block, err := api.eth.Engine().FinalizeAndAssemble(bc, header, env.state, transactions, nil /* uncles */, env.receipts)
+ if err != nil {
+ return nil, err
+ }
+ return &executableData{
+ BlockHash: block.Hash(),
+ ParentHash: block.ParentHash(),
+ Miner: block.Coinbase(),
+ StateRoot: block.Root(),
+ Number: block.NumberU64(),
+ GasLimit: block.GasLimit(),
+ GasUsed: block.GasUsed(),
+ Timestamp: block.Time(),
+ ReceiptRoot: block.ReceiptHash(),
+ LogsBloom: block.Bloom().Bytes(),
+ Transactions: encodeTransactions(block.Transactions()),
+ }, 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
+}
+
+func insertBlockParamsToBlock(params executableData) (*types.Block, error) {
+ txs, err := decodeTransactions(params.Transactions)
+ if err != nil {
+ return nil, err
+ }
+
+ number := big.NewInt(0)
+ number.SetUint64(params.Number)
+ header := &types.Header{
+ ParentHash: params.ParentHash,
+ UncleHash: types.EmptyUncleHash,
+ Coinbase: params.Miner,
+ Root: params.StateRoot,
+ TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
+ ReceiptHash: params.ReceiptRoot,
+ Bloom: types.BytesToBloom(params.LogsBloom),
+ Difficulty: big.NewInt(1),
+ Number: number,
+ GasLimit: params.GasLimit,
+ GasUsed: params.GasUsed,
+ Time: params.Timestamp,
+ }
+ block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
+ return block, nil
+}
+
+// NewBlock creates an Eth1 block, inserts it in the chain, and either returns true,
+// or false + an error. This is a bit redundant for go, but simplifies things on the
+// eth2 side.
+func (api *consensusAPI) NewBlock(params executableData) (*newBlockResponse, error) {
+ parent := api.eth.BlockChain().GetBlockByHash(params.ParentHash)
+ if parent == nil {
+ return &newBlockResponse{false}, fmt.Errorf("could not find parent %x", params.ParentHash)
+ }
+ block, err := insertBlockParamsToBlock(params)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = api.eth.BlockChain().InsertChainWithoutSealVerification(block)
+ return &newBlockResponse{err == nil}, err
+}
+
+// Used in tests to add a the list of transactions from a block to the tx pool.
+func (api *consensusAPI) addBlockTxs(block *types.Block) error {
+ for _, tx := range block.Transactions() {
+ api.eth.TxPool().AddLocal(tx)
+ }
+ return nil
+}
+
+// FinalizeBlock is called to mark a block as synchronized, so
+// that data that is no longer needed can be removed.
+func (api *consensusAPI) FinalizeBlock(blockHash common.Hash) (*genericResponse, error) {
+ return &genericResponse{true}, nil
+}
+
+// SetHead is called to perform a force choice.
+func (api *consensusAPI) SetHead(newHead common.Hash) (*genericResponse, error) {
+ return &genericResponse{true}, nil
+}
diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go
new file mode 100644
index 0000000000..456b6867bd
--- /dev/null
+++ b/eth/catalyst/api_test.go
@@ -0,0 +1,229 @@
+// 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/rawdb"
+ "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/ethconfig"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+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(2e10)
+)
+
+func generateTestChain() (*core.Genesis, []*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,
+ }
+ generate := func(i int, g *core.BlockGen) {
+ g.OffsetTime(5)
+ g.SetExtra([]byte("test"))
+ }
+ gblock := genesis.ToBlock(db)
+ engine := ethash.NewFaker()
+ blocks, _ := core.GenerateChain(config, gblock, engine, db, 10, generate)
+ blocks = append([]*types.Block{gblock}, blocks...)
+ return genesis, blocks
+}
+
+func generateTestChainWithFork(n int, fork int) (*core.Genesis, []*types.Block, []*types.Block) {
+ if fork >= n {
+ fork = n - 1
+ }
+ db := rawdb.NewMemoryDatabase()
+ //nolint:composites
+ config := ¶ms.ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, big.NewInt(0), new(params.EthashConfig), nil}
+ genesis := &core.Genesis{
+ Config: config,
+ Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}},
+ ExtraData: []byte("test genesis"),
+ Timestamp: 9000,
+ }
+ generate := func(i int, g *core.BlockGen) {
+ g.OffsetTime(5)
+ g.SetExtra([]byte("test"))
+ }
+ generateFork := func(i int, g *core.BlockGen) {
+ g.OffsetTime(5)
+ g.SetExtra([]byte("testF"))
+ }
+ gblock := genesis.ToBlock(db)
+ engine := ethash.NewFaker()
+ blocks, _ := core.GenerateChain(config, gblock, engine, db, n, generate)
+ blocks = append([]*types.Block{gblock}, blocks...)
+ forkedBlocks, _ := core.GenerateChain(config, blocks[fork], engine, db, n-fork, generateFork)
+ return genesis, blocks, forkedBlocks
+}
+
+func TestEth2AssembleBlock(t *testing.T) {
+ genesis, blocks := generateTestChain()
+ n, ethservice := startEthService(t, genesis, blocks[1:9])
+ defer n.Close()
+
+ api := newConsensusAPI(ethservice)
+ signer := types.NewEIP155Signer(ethservice.BlockChain().Config().ChainID)
+ tx, err := types.SignTx(types.NewTransaction(0, blocks[8].Coinbase(), big.NewInt(1000), params.TxGas, nil, nil), signer, testKey)
+ if err != nil {
+ t.Fatalf("error signing transaction, err=%v", err)
+ }
+ ethservice.TxPool().AddLocal(tx)
+ blockParams := assembleBlockParams{
+ ParentHash: blocks[8].ParentHash(),
+ Timestamp: blocks[8].Time(),
+ }
+ execData, err := api.AssembleBlock(blockParams)
+
+ if err != nil {
+ t.Fatalf("error producing block, err=%v", err)
+ }
+
+ if len(execData.Transactions) != 1 {
+ t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
+ }
+}
+
+func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) {
+ genesis, blocks := generateTestChain()
+ n, ethservice := startEthService(t, genesis, blocks[1:9])
+ defer n.Close()
+
+ api := newConsensusAPI(ethservice)
+
+ // Put the 10th block's tx in the pool and produce a new block
+ api.addBlockTxs(blocks[9])
+ blockParams := assembleBlockParams{
+ ParentHash: blocks[9].ParentHash(),
+ Timestamp: blocks[9].Time(),
+ }
+ execData, err := api.AssembleBlock(blockParams)
+ if err != nil {
+ t.Fatalf("error producing block, err=%v", err)
+ }
+
+ if len(execData.Transactions) != blocks[9].Transactions().Len() {
+ t.Fatalf("invalid number of transactions %d != 1", len(execData.Transactions))
+ }
+}
+
+func TestEth2NewBlock(t *testing.T) {
+ genesis, blocks, forkedBlocks := generateTestChainWithFork(10, 4)
+ n, ethservice := startEthService(t, genesis, blocks[1:5])
+ defer n.Close()
+
+ api := newConsensusAPI(ethservice)
+ for i := 5; i < 10; i++ {
+ p := executableData{
+ ParentHash: ethservice.BlockChain().CurrentBlock().Hash(),
+ Miner: blocks[i].Coinbase(),
+ StateRoot: blocks[i].Root(),
+ GasLimit: blocks[i].GasLimit(),
+ GasUsed: blocks[i].GasUsed(),
+ Transactions: encodeTransactions(blocks[i].Transactions()),
+ ReceiptRoot: blocks[i].ReceiptHash(),
+ LogsBloom: blocks[i].Bloom().Bytes(),
+ BlockHash: blocks[i].Hash(),
+ Timestamp: blocks[i].Time(),
+ Number: uint64(i),
+ }
+ success, err := api.NewBlock(p)
+ if err != nil || !success.Valid {
+ t.Fatalf("Failed to insert block: %v", err)
+ }
+ }
+
+ exp := ethservice.BlockChain().CurrentBlock().Hash()
+
+ // Introduce the fork point.
+ lastBlockNum := blocks[4].Number()
+ lastBlock := blocks[4]
+ for i := 0; i < 4; i++ {
+ lastBlockNum.Add(lastBlockNum, big.NewInt(1))
+ p := executableData{
+ ParentHash: lastBlock.Hash(),
+ Miner: forkedBlocks[i].Coinbase(),
+ StateRoot: forkedBlocks[i].Root(),
+ Number: lastBlockNum.Uint64(),
+ GasLimit: forkedBlocks[i].GasLimit(),
+ GasUsed: forkedBlocks[i].GasUsed(),
+ Transactions: encodeTransactions(blocks[i].Transactions()),
+ ReceiptRoot: forkedBlocks[i].ReceiptHash(),
+ LogsBloom: forkedBlocks[i].Bloom().Bytes(),
+ BlockHash: forkedBlocks[i].Hash(),
+ Timestamp: forkedBlocks[i].Time(),
+ }
+ success, err := api.NewBlock(p)
+ if err != nil || !success.Valid {
+ t.Fatalf("Failed to insert forked block #%d: %v", i, err)
+ }
+ lastBlock, err = insertBlockParamsToBlock(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ if ethservice.BlockChain().CurrentBlock().Hash() != exp {
+ t.Fatalf("Wrong head after inserting fork %x != %x", exp, ethservice.BlockChain().CurrentBlock().Hash())
+ }
+}
+
+// startEthService creates a full node instance for testing.
+func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) (*node.Node, *eth.Ethereum) {
+ 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}}
+ ethservice, err := eth.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 := ethservice.BlockChain().InsertChain(blocks); err != nil {
+ n.Close()
+ t.Fatal("can't import test blocks:", err)
+ }
+ ethservice.SetEtherbase(testAddr)
+
+ return n, ethservice
+}
diff --git a/eth/catalyst/api_types.go b/eth/catalyst/api_types.go
new file mode 100644
index 0000000000..d5d351a991
--- /dev/null
+++ b/eth/catalyst/api_types.go
@@ -0,0 +1,70 @@
+// 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 (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+//go:generate go run github.com/fjl/gencodec -type assembleBlockParams -field-override assembleBlockParamsMarshaling -out gen_blockparams.go
+
+// Structure described at https://hackmd.io/T9x2mMA4S7us8tJwEB3FDQ
+type assembleBlockParams struct {
+ ParentHash common.Hash `json:"parentHash" gencodec:"required"`
+ Timestamp uint64 `json:"timestamp" gencodec:"required"`
+}
+
+// JSON type overrides for assembleBlockParams.
+type assembleBlockParamsMarshaling struct {
+ Timestamp hexutil.Uint64
+}
+
+//go:generate go run github.com/fjl/gencodec -type executableData -field-override executableDataMarshaling -out gen_ed.go
+
+// Structure described at https://notes.ethereum.org/@n0ble/rayonism-the-merge-spec#Parameters1
+type executableData struct {
+ BlockHash common.Hash `json:"blockHash" gencodec:"required"`
+ ParentHash common.Hash `json:"parentHash" gencodec:"required"`
+ Miner common.Address `json:"miner" gencodec:"required"`
+ StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
+ Number uint64 `json:"number" gencodec:"required"`
+ GasLimit uint64 `json:"gasLimit" gencodec:"required"`
+ GasUsed uint64 `json:"gasUsed" gencodec:"required"`
+ Timestamp uint64 `json:"timestamp" gencodec:"required"`
+ ReceiptRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
+ LogsBloom []byte `json:"logsBloom" gencodec:"required"`
+ Transactions [][]byte `json:"transactions" gencodec:"required"`
+}
+
+// JSON type overrides for executableData.
+type executableDataMarshaling struct {
+ Number hexutil.Uint64
+ GasLimit hexutil.Uint64
+ GasUsed hexutil.Uint64
+ Timestamp hexutil.Uint64
+ LogsBloom hexutil.Bytes
+ Transactions []hexutil.Bytes
+}
+
+type newBlockResponse struct {
+ Valid bool `json:"valid"`
+}
+
+type genericResponse struct {
+ Success bool `json:"success"`
+}
diff --git a/eth/catalyst/gen_blockparams.go b/eth/catalyst/gen_blockparams.go
new file mode 100644
index 0000000000..a9a08ec3a8
--- /dev/null
+++ b/eth/catalyst/gen_blockparams.go
@@ -0,0 +1,46 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package catalyst
+
+import (
+ "encoding/json"
+ "errors"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ = (*assembleBlockParamsMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (a assembleBlockParams) MarshalJSON() ([]byte, error) {
+ type assembleBlockParams struct {
+ ParentHash common.Hash `json:"parentHash" gencodec:"required"`
+ Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
+ }
+ var enc assembleBlockParams
+ enc.ParentHash = a.ParentHash
+ enc.Timestamp = hexutil.Uint64(a.Timestamp)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (a *assembleBlockParams) UnmarshalJSON(input []byte) error {
+ type assembleBlockParams struct {
+ ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
+ Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
+ }
+ var dec assembleBlockParams
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.ParentHash == nil {
+ return errors.New("missing required field 'parentHash' for assembleBlockParams")
+ }
+ a.ParentHash = *dec.ParentHash
+ if dec.Timestamp == nil {
+ return errors.New("missing required field 'timestamp' for assembleBlockParams")
+ }
+ a.Timestamp = uint64(*dec.Timestamp)
+ return nil
+}
diff --git a/eth/catalyst/gen_ed.go b/eth/catalyst/gen_ed.go
new file mode 100644
index 0000000000..4c2e4c8ead
--- /dev/null
+++ b/eth/catalyst/gen_ed.go
@@ -0,0 +1,117 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package catalyst
+
+import (
+ "encoding/json"
+ "errors"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ = (*executableDataMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (e executableData) MarshalJSON() ([]byte, error) {
+ type executableData struct {
+ BlockHash common.Hash `json:"blockHash" gencodec:"required"`
+ ParentHash common.Hash `json:"parentHash" gencodec:"required"`
+ Miner common.Address `json:"miner" gencodec:"required"`
+ StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
+ Number hexutil.Uint64 `json:"number" gencodec:"required"`
+ GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
+ GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
+ Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
+ ReceiptRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
+ LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
+ Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
+ }
+ var enc executableData
+ enc.BlockHash = e.BlockHash
+ enc.ParentHash = e.ParentHash
+ enc.Miner = e.Miner
+ enc.StateRoot = e.StateRoot
+ enc.Number = hexutil.Uint64(e.Number)
+ enc.GasLimit = hexutil.Uint64(e.GasLimit)
+ enc.GasUsed = hexutil.Uint64(e.GasUsed)
+ enc.Timestamp = hexutil.Uint64(e.Timestamp)
+ enc.ReceiptRoot = e.ReceiptRoot
+ enc.LogsBloom = e.LogsBloom
+ if e.Transactions != nil {
+ enc.Transactions = make([]hexutil.Bytes, len(e.Transactions))
+ for k, v := range e.Transactions {
+ enc.Transactions[k] = v
+ }
+ }
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (e *executableData) UnmarshalJSON(input []byte) error {
+ type executableData struct {
+ BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
+ ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
+ Miner *common.Address `json:"miner" gencodec:"required"`
+ StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
+ Number *hexutil.Uint64 `json:"number" gencodec:"required"`
+ GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
+ GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
+ Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
+ ReceiptRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
+ LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
+ Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
+ }
+ var dec executableData
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.BlockHash == nil {
+ return errors.New("missing required field 'blockHash' for executableData")
+ }
+ e.BlockHash = *dec.BlockHash
+ if dec.ParentHash == nil {
+ return errors.New("missing required field 'parentHash' for executableData")
+ }
+ e.ParentHash = *dec.ParentHash
+ if dec.Miner == nil {
+ return errors.New("missing required field 'miner' for executableData")
+ }
+ e.Miner = *dec.Miner
+ if dec.StateRoot == nil {
+ return errors.New("missing required field 'stateRoot' for executableData")
+ }
+ e.StateRoot = *dec.StateRoot
+ if dec.Number == nil {
+ return errors.New("missing required field 'number' for executableData")
+ }
+ e.Number = uint64(*dec.Number)
+ if dec.GasLimit == nil {
+ return errors.New("missing required field 'gasLimit' for executableData")
+ }
+ e.GasLimit = uint64(*dec.GasLimit)
+ if dec.GasUsed == nil {
+ return errors.New("missing required field 'gasUsed' for executableData")
+ }
+ e.GasUsed = uint64(*dec.GasUsed)
+ if dec.Timestamp == nil {
+ return errors.New("missing required field 'timestamp' for executableData")
+ }
+ e.Timestamp = uint64(*dec.Timestamp)
+ if dec.ReceiptRoot == nil {
+ return errors.New("missing required field 'receiptsRoot' for executableData")
+ }
+ e.ReceiptRoot = *dec.ReceiptRoot
+ if dec.LogsBloom == nil {
+ return errors.New("missing required field 'logsBloom' for executableData")
+ }
+ e.LogsBloom = *dec.LogsBloom
+ if dec.Transactions == nil {
+ return errors.New("missing required field 'transactions' for executableData")
+ }
+ e.Transactions = make([][]byte, len(dec.Transactions))
+ for k, v := range dec.Transactions {
+ e.Transactions[k] = v
+ }
+ return nil
+}
diff --git a/params/config.go b/params/config.go
index 9fca534c79..f4e2f5ea67 100644
--- a/params/config.go
+++ b/params/config.go
@@ -244,16 +244,16 @@ var (
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
- AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}
+ AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil}
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus.
//
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
- AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
+ AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
- TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}
+ TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil}
TestRules = TestChainConfig.Rules(new(big.Int))
)
@@ -326,8 +326,9 @@ type ChainConfig struct {
MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated)
BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin)
- YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references
- EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated)
+ YoloV3Block *big.Int `json:"yoloV3Block,omitempty"` // YOLO v3: Gas repricings TODO @holiman add EIP references
+ EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated)
+ CatalystBlock *big.Int `json:"catalystBlock,omitempty"` // Catalyst switch block (nil = no fork, 0 = already on catalyst)
// Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"`
@@ -440,6 +441,11 @@ func (c *ChainConfig) IsBerlin(num *big.Int) bool {
return isForked(c.BerlinBlock, num) || isForked(c.YoloV3Block, num)
}
+// IsCatalyst returns whether num is either equal to the Merge fork block or greater.
+func (c *ChainConfig) IsCatalyst(num *big.Int) bool {
+ return isForked(c.CatalystBlock, num)
+}
+
// IsEWASM returns whether num represents a block number after the EWASM fork
func (c *ChainConfig) IsEWASM(num *big.Int) bool {
return isForked(c.EWASMBlock, num)
@@ -623,7 +629,7 @@ type Rules struct {
ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
- IsBerlin bool
+ IsBerlin, IsCatalyst bool
}
// Rules ensures c's ChainID is not nil.
@@ -643,5 +649,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
IsPetersburg: c.IsPetersburg(num),
IsIstanbul: c.IsIstanbul(num),
IsBerlin: c.IsBerlin(num),
+ IsCatalyst: c.IsCatalyst(num),
}
}