eth/catalyst: add catalyst API prototype (#22641)

This change adds the --catalyst flag, enabling an RPC API for eth2 integration.
In this initial version, catalyst mode also disables all peer-to-peer networking.

Co-authored-by: Mikhail Kalinin <noblesse.knight@gmail.com>
Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
Guillaume Ballet 2021-04-16 21:29:22 +02:00 committed by GitHub
parent 4f3ba6742f
commit f79cce5de9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 823 additions and 12 deletions

@ -28,6 +28,7 @@ import (
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
"github.com/ethereum/go-ethereum/cmd/utils" "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/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/metrics" "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) { if ctx.GlobalIsSet(utils.OverrideBerlinFlag.Name) {
cfg.Eth.OverrideBerlin = new(big.Int).SetUint64(ctx.GlobalUint64(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 // Configure GraphQL if requested
if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) { if ctx.GlobalIsSet(utils.GraphQLEnabledFlag.Name) {

@ -151,6 +151,7 @@ var (
utils.EVMInterpreterFlag, utils.EVMInterpreterFlag,
utils.MinerNotifyFullFlag, utils.MinerNotifyFullFlag,
configFileFlag, configFileFlag,
utils.CatalystFlag,
} }
rpcFlags = []cli.Flag{ rpcFlags = []cli.Flag{

@ -235,6 +235,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.SnapshotFlag, utils.SnapshotFlag,
utils.BloomFilterSizeFlag, utils.BloomFilterSizeFlag,
cli.HelpFlag, cli.HelpFlag,
utils.CatalystFlag,
}, },
}, },
} }

@ -755,6 +755,11 @@ var (
Usage: "External EVM configuration (default = built-in interpreter)", Usage: "External EVM configuration (default = built-in interpreter)",
Value: "", Value: "",
} }
CatalystFlag = cli.BoolFlag{
Name: "catalyst",
Usage: "Catalyst mode (eth2 integration testing)",
}
) )
// MakeDataDir retrieves the currently requested data directory, terminating // MakeDataDir retrieves the currently requested data directory, terminating
@ -1186,10 +1191,11 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
cfg.NetRestrict = list cfg.NetRestrict = list
} }
if ctx.GlobalBool(DeveloperFlag.Name) { if ctx.GlobalBool(DeveloperFlag.Name) || ctx.GlobalBool(CatalystFlag.Name) {
// --dev mode can't use p2p networking. // --dev mode can't use p2p networking.
cfg.MaxPeers = 0 cfg.MaxPeers = 0
cfg.ListenAddr = ":0" cfg.ListenAddr = ""
cfg.NoDial = true
cfg.NoDiscovery = true cfg.NoDiscovery = true
cfg.DiscoveryV5 = false cfg.DiscoveryV5 = false
} }
@ -1693,14 +1699,16 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
} }
// RegisterEthService adds an Ethereum client to the stack. // 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 { if cfg.SyncMode == downloader.LightSync {
backend, err := les.New(stack, cfg) backend, err := les.New(stack, cfg)
if err != nil { if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err) Fatalf("Failed to register the Ethereum service: %v", err)
} }
stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) stack.RegisterAPIs(tracers.APIs(backend.ApiBackend))
return backend.ApiBackend return backend.ApiBackend, nil
} }
backend, err := eth.New(stack, cfg) backend, err := eth.New(stack, cfg)
if err != nil { if err != nil {
@ -1713,7 +1721,7 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) ethapi.Backend
} }
} }
stack.RegisterAPIs(tracers.APIs(backend.APIBackend)) stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
return backend.APIBackend return backend.APIBackend, backend
} }
// RegisterEthStatsService configures the Ethereum Stats daemon and adds it to // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to

@ -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 { func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
next := new(big.Int).Add(parent.Number, big1) next := new(big.Int).Add(parent.Number, big1)
switch { switch {
case config.IsCatalyst(next):
return big.NewInt(1)
case config.IsMuirGlacier(next): case config.IsMuirGlacier(next):
return calcDifficultyEip2384(time, parent) return calcDifficultyEip2384(time, parent)
case config.IsConstantinople(next): case config.IsConstantinople(next):

@ -1693,6 +1693,22 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
return n, err 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 // insertChain is the internal implementation of InsertChain, which assumes that
// 1) chains are contiguous, and 2) The chain mutex is held. // 1) chains are contiguous, and 2) The chain mutex is held.
// //

@ -223,6 +223,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))

302
eth/catalyst/api.go Normal file

@ -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 <http://www.gnu.org/licenses/>.
// 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
}

229
eth/catalyst/api_test.go Normal file

@ -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 <http://www.gnu.org/licenses/>.
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 := &params.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 := &ethconfig.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
}

70
eth/catalyst/api_types.go Normal file

@ -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 <http://www.gnu.org/licenses/>.
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"`
}

@ -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
}

117
eth/catalyst/gen_ed.go Normal file

@ -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
}

@ -244,16 +244,16 @@ var (
// //
// This configuration is intentionally not using keyed fields to force anyone // This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields. // 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 // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus. // and accepted by the Ethereum core developers into the Clique consensus.
// //
// This configuration is intentionally not using keyed fields to force anyone // This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields. // 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)) 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) 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) 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 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) 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 // Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"` 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) 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 // IsEWASM returns whether num represents a block number after the EWASM fork
func (c *ChainConfig) IsEWASM(num *big.Int) bool { func (c *ChainConfig) IsEWASM(num *big.Int) bool {
return isForked(c.EWASMBlock, num) return isForked(c.EWASMBlock, num)
@ -623,7 +629,7 @@ type Rules struct {
ChainID *big.Int ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin bool IsBerlin, IsCatalyst bool
} }
// Rules ensures c's ChainID is not nil. // Rules ensures c's ChainID is not nil.
@ -643,5 +649,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
IsPetersburg: c.IsPetersburg(num), IsPetersburg: c.IsPetersburg(num),
IsIstanbul: c.IsIstanbul(num), IsIstanbul: c.IsIstanbul(num),
IsBerlin: c.IsBerlin(num), IsBerlin: c.IsBerlin(num),
IsCatalyst: c.IsCatalyst(num),
} }
} }