mirror of https://github.com/ethereum/go-ethereum
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>pull/22697/head
parent
4f3ba6742f
commit
f79cce5de9
@ -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 |
||||||
|
} |
@ -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 := ¶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 |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -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 |
||||||
|
} |
Loading…
Reference in new issue