diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index c3f41b187c4..38657eda8fd 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -31,7 +31,7 @@ import ( ) const ( - ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0" + ipcAPIs = "admin:1.0 debug:1.0 eth2:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0" httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0" ) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 12a5ae9bfcb..78e65161daf 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 d7f8fd7ab91..980794db735 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 d631b8e332e..90c55aa4f96 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: "", } + // Catalyst + CatalystFlag = cli.BoolFlag{ + Name: "catalyst", + Usage: "Set geth into catalyst mode", + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1670,6 +1675,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) } } + if ctx.GlobalIsSet(CatalystFlag.Name) { + cfg.Catalyst = true + } } // SetDNSDiscoveryDefaults configures DNS discovery with the given URL if diff --git a/core/blockchain.go b/core/blockchain.go index d65ce4f0484..19576f8b33f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1482,7 +1482,7 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error { current := bc.CurrentBlock() if block.ParentHash() != current.Hash() { - if err := bc.reorg(current, block); err != nil { + if err := bc.Reorg(current, block); err != nil { return err } } @@ -1606,7 +1606,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. if reorg { // Reorganise the chain if the parent is not the head block if block.ParentHash() != currentBlock.Hash() { - if err := bc.reorg(currentBlock, block); err != nil { + if err := bc.Reorg(currentBlock, block); err != nil { return NonStatTy, err } } @@ -1693,6 +1693,44 @@ 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(chain types.Blocks) (int, error) { + // Sanity check that we have something meaningful to import + if len(chain) == 0 { + return 0, nil + } + + bc.blockProcFeed.Send(true) + defer bc.blockProcFeed.Send(false) + + // Remove already known canon-blocks + var ( + block, prev *types.Block + ) + // Do a sanity check that the provided chain is actually ordered and linked + for i := 1; i < len(chain); i++ { + block = chain[i] + prev = chain[i-1] + if block.NumberU64() != prev.NumberU64()+1 || block.ParentHash() != prev.Hash() { + // Chain broke ancestry, log a message (programming error) and skip insertion + log.Error("Non contiguous block insert", "number", block.Number(), "hash", block.Hash(), + "parent", block.ParentHash(), "prevnumber", prev.Number(), "prevhash", prev.Hash()) + + return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, prev.NumberU64(), + prev.Hash().Bytes()[:4], i, block.NumberU64(), block.Hash().Bytes()[:4], block.ParentHash().Bytes()[:4]) + } + } + // Pre-checks passed, start the full block imports + bc.wg.Add(1) + bc.chainmu.Lock() + n, err := bc.insertChain(chain, 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. // @@ -2125,7 +2163,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i // reorg takes two blocks, an old chain and a new chain and will reconstruct the // blocks and inserts them to be part of the new canonical chain and accumulates // potential missing transactions and post an event about them. -func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { +func (bc *BlockChain) Reorg(oldBlock, newBlock *types.Block) error { var ( newChain types.Blocks oldChain types.Blocks diff --git a/core/chain_makers.go b/core/chain_makers.go index e058e5a78e5..ced491a0252 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -103,7 +103,7 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { b.SetCoinbase(common.Address{}) } b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs)) - receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) + receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}, nil) if err != nil { panic(err) } diff --git a/core/state_processor.go b/core/state_processor.go index 40a953f0d4f..c996df2fc2f 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -138,7 +138,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { +func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, bcctx *vm.BeaconChainContext) (*types.Receipt, error) { msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) if err != nil { return nil, err diff --git a/core/tx_pool.go b/core/tx_pool.go index 5db1d3df329..d82ea120084 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -153,6 +153,8 @@ type TxPoolConfig struct { GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts Lifetime time.Duration // Maximum amount of time non-executable transaction are queued + + ProcessTxs bool // Whether transaction processing is enabled upon node's launch } // DefaultTxPoolConfig contains the default configurations for the transaction diff --git a/core/vm/evm.go b/core/vm/evm.go index 3f16f33b2d8..7b1027b5a0e 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -76,6 +76,11 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err return nil, errors.New("no compatible interpreter") } +type BeaconChainContext struct { + BeaconRoots []common.Hash // Provides information for BEACONSTATEROOT + RandaoMix common.Hash // Provides information for RANDAOMIX +} + // BlockContext provides the EVM with auxiliary information. Once provided // it shouldn't be modified. type BlockContext struct { @@ -93,6 +98,8 @@ type BlockContext struct { BlockNumber *big.Int // Provides information for NUMBER Time *big.Int // Provides information for TIME Difficulty *big.Int // Provides information for DIFFICULTY + + BeaconCtx *BeaconChainContext // Provides eth2-specific information } // TxContext provides the EVM with information about a transaction. diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 3277674ee82..7cb281b8edb 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -480,6 +480,33 @@ func opGasLimit(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ return nil, nil } +func opBeaconStateRoot(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + num := scope.Stack.peek() + num64, overflow := num.Uint64WithOverflow() + if overflow { + num.Clear() + return nil, nil + } + var upper, lower uint64 + upper = interpreter.evm.Context.BlockNumber.Uint64() + if upper < 257 { + lower = 0 + } else { + lower = upper - 256 + } + if num64 >= lower && num64 < upper { + num.SetBytes(interpreter.evm.Context.BeaconCtx.BeaconRoots[num64-lower].Bytes()) + } else { + num.Clear() + } + return nil, nil +} + +func opRandaoMix(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.Context.BeaconCtx.RandaoMix.Bytes())) + return nil, nil +} + func opPop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { scope.Stack.pop() return nil, nil diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 3b67ad6dea4..9c56199f2eb 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -98,6 +98,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { + case evm.chainRules.IsCatalyst: + jt = catalystInstructionSet case evm.chainRules.IsBerlin: jt = berlinInstructionSet case evm.chainRules.IsIstanbul: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index bb1800ea919..8e794833961 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -57,11 +57,30 @@ var ( constantinopleInstructionSet = newConstantinopleInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet() berlinInstructionSet = newBerlinInstructionSet() + catalystInstructionSet = newCatalystInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]*operation +//nolint:deadcode +func newCatalystInstructionSet() JumpTable { + instructionSet := newBerlinInstructionSet() + instructionSet[BEACONSTATEROOT] = &operation{ + execute: opBeaconStateRoot, + constantGas: GasExtStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + instructionSet[RANDAOMIX] = &operation{ + execute: opRandaoMix, + constantGas: GasExtStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } + return instructionSet +} + // newBerlinInstructionSet returns the frontier, homestead, byzantium, // contantinople, istanbul, petersburg and berlin instructions. func newBerlinInstructionSet() JumpTable { diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index b0adf37d0c2..436b55522c1 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -101,8 +101,10 @@ const ( NUMBER DIFFICULTY GASLIMIT - CHAINID OpCode = 0x46 - SELFBALANCE OpCode = 0x47 + CHAINID OpCode = 0x46 + SELFBALANCE OpCode = 0x47 + BEACONSTATEROOT OpCode = 0x48 + RANDAOMIX OpCode = 0x49 ) // 0x50 range - 'storage' and execution. @@ -272,14 +274,16 @@ var opCodeToString = map[OpCode]string{ EXTCODEHASH: "EXTCODEHASH", // 0x40 range - block operations. - BLOCKHASH: "BLOCKHASH", - COINBASE: "COINBASE", - TIMESTAMP: "TIMESTAMP", - NUMBER: "NUMBER", - DIFFICULTY: "DIFFICULTY", - GASLIMIT: "GASLIMIT", - CHAINID: "CHAINID", - SELFBALANCE: "SELFBALANCE", + BLOCKHASH: "BLOCKHASH", + COINBASE: "COINBASE", + TIMESTAMP: "TIMESTAMP", + NUMBER: "NUMBER", + DIFFICULTY: "DIFFICULTY", + GASLIMIT: "GASLIMIT", + CHAINID: "CHAINID", + SELFBALANCE: "SELFBALANCE", + BEACONSTATEROOT: "BEACONSTATEROOT", + RANDAOMIX: "RANDAOMIX", // 0x50 range - 'storage' and execution. POP: "POP", @@ -396,147 +400,149 @@ func (op OpCode) String() string { } var stringToOp = map[string]OpCode{ - "STOP": STOP, - "ADD": ADD, - "MUL": MUL, - "SUB": SUB, - "DIV": DIV, - "SDIV": SDIV, - "MOD": MOD, - "SMOD": SMOD, - "EXP": EXP, - "NOT": NOT, - "LT": LT, - "GT": GT, - "SLT": SLT, - "SGT": SGT, - "EQ": EQ, - "ISZERO": ISZERO, - "SIGNEXTEND": SIGNEXTEND, - "AND": AND, - "OR": OR, - "XOR": XOR, - "BYTE": BYTE, - "SHL": SHL, - "SHR": SHR, - "SAR": SAR, - "ADDMOD": ADDMOD, - "MULMOD": MULMOD, - "SHA3": SHA3, - "ADDRESS": ADDRESS, - "BALANCE": BALANCE, - "ORIGIN": ORIGIN, - "CALLER": CALLER, - "CALLVALUE": CALLVALUE, - "CALLDATALOAD": CALLDATALOAD, - "CALLDATASIZE": CALLDATASIZE, - "CALLDATACOPY": CALLDATACOPY, - "CHAINID": CHAINID, - "DELEGATECALL": DELEGATECALL, - "STATICCALL": STATICCALL, - "CODESIZE": CODESIZE, - "CODECOPY": CODECOPY, - "GASPRICE": GASPRICE, - "EXTCODESIZE": EXTCODESIZE, - "EXTCODECOPY": EXTCODECOPY, - "RETURNDATASIZE": RETURNDATASIZE, - "RETURNDATACOPY": RETURNDATACOPY, - "EXTCODEHASH": EXTCODEHASH, - "BLOCKHASH": BLOCKHASH, - "COINBASE": COINBASE, - "TIMESTAMP": TIMESTAMP, - "NUMBER": NUMBER, - "DIFFICULTY": DIFFICULTY, - "GASLIMIT": GASLIMIT, - "SELFBALANCE": SELFBALANCE, - "POP": POP, - "MLOAD": MLOAD, - "MSTORE": MSTORE, - "MSTORE8": MSTORE8, - "SLOAD": SLOAD, - "SSTORE": SSTORE, - "JUMP": JUMP, - "JUMPI": JUMPI, - "PC": PC, - "MSIZE": MSIZE, - "GAS": GAS, - "JUMPDEST": JUMPDEST, - "PUSH1": PUSH1, - "PUSH2": PUSH2, - "PUSH3": PUSH3, - "PUSH4": PUSH4, - "PUSH5": PUSH5, - "PUSH6": PUSH6, - "PUSH7": PUSH7, - "PUSH8": PUSH8, - "PUSH9": PUSH9, - "PUSH10": PUSH10, - "PUSH11": PUSH11, - "PUSH12": PUSH12, - "PUSH13": PUSH13, - "PUSH14": PUSH14, - "PUSH15": PUSH15, - "PUSH16": PUSH16, - "PUSH17": PUSH17, - "PUSH18": PUSH18, - "PUSH19": PUSH19, - "PUSH20": PUSH20, - "PUSH21": PUSH21, - "PUSH22": PUSH22, - "PUSH23": PUSH23, - "PUSH24": PUSH24, - "PUSH25": PUSH25, - "PUSH26": PUSH26, - "PUSH27": PUSH27, - "PUSH28": PUSH28, - "PUSH29": PUSH29, - "PUSH30": PUSH30, - "PUSH31": PUSH31, - "PUSH32": PUSH32, - "DUP1": DUP1, - "DUP2": DUP2, - "DUP3": DUP3, - "DUP4": DUP4, - "DUP5": DUP5, - "DUP6": DUP6, - "DUP7": DUP7, - "DUP8": DUP8, - "DUP9": DUP9, - "DUP10": DUP10, - "DUP11": DUP11, - "DUP12": DUP12, - "DUP13": DUP13, - "DUP14": DUP14, - "DUP15": DUP15, - "DUP16": DUP16, - "SWAP1": SWAP1, - "SWAP2": SWAP2, - "SWAP3": SWAP3, - "SWAP4": SWAP4, - "SWAP5": SWAP5, - "SWAP6": SWAP6, - "SWAP7": SWAP7, - "SWAP8": SWAP8, - "SWAP9": SWAP9, - "SWAP10": SWAP10, - "SWAP11": SWAP11, - "SWAP12": SWAP12, - "SWAP13": SWAP13, - "SWAP14": SWAP14, - "SWAP15": SWAP15, - "SWAP16": SWAP16, - "LOG0": LOG0, - "LOG1": LOG1, - "LOG2": LOG2, - "LOG3": LOG3, - "LOG4": LOG4, - "CREATE": CREATE, - "CREATE2": CREATE2, - "CALL": CALL, - "RETURN": RETURN, - "CALLCODE": CALLCODE, - "REVERT": REVERT, - "SELFDESTRUCT": SELFDESTRUCT, + "STOP": STOP, + "ADD": ADD, + "MUL": MUL, + "SUB": SUB, + "DIV": DIV, + "SDIV": SDIV, + "MOD": MOD, + "SMOD": SMOD, + "EXP": EXP, + "NOT": NOT, + "LT": LT, + "GT": GT, + "SLT": SLT, + "SGT": SGT, + "EQ": EQ, + "ISZERO": ISZERO, + "SIGNEXTEND": SIGNEXTEND, + "AND": AND, + "OR": OR, + "XOR": XOR, + "BYTE": BYTE, + "SHL": SHL, + "SHR": SHR, + "SAR": SAR, + "ADDMOD": ADDMOD, + "MULMOD": MULMOD, + "SHA3": SHA3, + "ADDRESS": ADDRESS, + "BALANCE": BALANCE, + "ORIGIN": ORIGIN, + "CALLER": CALLER, + "CALLVALUE": CALLVALUE, + "CALLDATALOAD": CALLDATALOAD, + "CALLDATASIZE": CALLDATASIZE, + "CALLDATACOPY": CALLDATACOPY, + "CHAINID": CHAINID, + "DELEGATECALL": DELEGATECALL, + "STATICCALL": STATICCALL, + "CODESIZE": CODESIZE, + "CODECOPY": CODECOPY, + "GASPRICE": GASPRICE, + "EXTCODESIZE": EXTCODESIZE, + "EXTCODECOPY": EXTCODECOPY, + "RETURNDATASIZE": RETURNDATASIZE, + "RETURNDATACOPY": RETURNDATACOPY, + "EXTCODEHASH": EXTCODEHASH, + "BLOCKHASH": BLOCKHASH, + "COINBASE": COINBASE, + "TIMESTAMP": TIMESTAMP, + "NUMBER": NUMBER, + "DIFFICULTY": DIFFICULTY, + "GASLIMIT": GASLIMIT, + "SELFBALANCE": SELFBALANCE, + "BEACONSTATEROOT": BEACONSTATEROOT, + "RANDAOMIX": RANDAOMIX, + "POP": POP, + "MLOAD": MLOAD, + "MSTORE": MSTORE, + "MSTORE8": MSTORE8, + "SLOAD": SLOAD, + "SSTORE": SSTORE, + "JUMP": JUMP, + "JUMPI": JUMPI, + "PC": PC, + "MSIZE": MSIZE, + "GAS": GAS, + "JUMPDEST": JUMPDEST, + "PUSH1": PUSH1, + "PUSH2": PUSH2, + "PUSH3": PUSH3, + "PUSH4": PUSH4, + "PUSH5": PUSH5, + "PUSH6": PUSH6, + "PUSH7": PUSH7, + "PUSH8": PUSH8, + "PUSH9": PUSH9, + "PUSH10": PUSH10, + "PUSH11": PUSH11, + "PUSH12": PUSH12, + "PUSH13": PUSH13, + "PUSH14": PUSH14, + "PUSH15": PUSH15, + "PUSH16": PUSH16, + "PUSH17": PUSH17, + "PUSH18": PUSH18, + "PUSH19": PUSH19, + "PUSH20": PUSH20, + "PUSH21": PUSH21, + "PUSH22": PUSH22, + "PUSH23": PUSH23, + "PUSH24": PUSH24, + "PUSH25": PUSH25, + "PUSH26": PUSH26, + "PUSH27": PUSH27, + "PUSH28": PUSH28, + "PUSH29": PUSH29, + "PUSH30": PUSH30, + "PUSH31": PUSH31, + "PUSH32": PUSH32, + "DUP1": DUP1, + "DUP2": DUP2, + "DUP3": DUP3, + "DUP4": DUP4, + "DUP5": DUP5, + "DUP6": DUP6, + "DUP7": DUP7, + "DUP8": DUP8, + "DUP9": DUP9, + "DUP10": DUP10, + "DUP11": DUP11, + "DUP12": DUP12, + "DUP13": DUP13, + "DUP14": DUP14, + "DUP15": DUP15, + "DUP16": DUP16, + "SWAP1": SWAP1, + "SWAP2": SWAP2, + "SWAP3": SWAP3, + "SWAP4": SWAP4, + "SWAP5": SWAP5, + "SWAP6": SWAP6, + "SWAP7": SWAP7, + "SWAP8": SWAP8, + "SWAP9": SWAP9, + "SWAP10": SWAP10, + "SWAP11": SWAP11, + "SWAP12": SWAP12, + "SWAP13": SWAP13, + "SWAP14": SWAP14, + "SWAP15": SWAP15, + "SWAP16": SWAP16, + "LOG0": LOG0, + "LOG1": LOG1, + "LOG2": LOG2, + "LOG3": LOG3, + "LOG4": LOG4, + "CREATE": CREATE, + "CREATE2": CREATE2, + "CALL": CALL, + "RETURN": RETURN, + "CALLCODE": CALLCODE, + "REVERT": REVERT, + "SELFDESTRUCT": SELFDESTRUCT, } // StringToOp finds the opcode whose name is stored in `str`. diff --git a/eth/backend.go b/eth/backend.go index 9cf8b856630..704cfaa4e5a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -220,9 +220,14 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { EventMux: eth.eventMux, Checkpoint: checkpoint, Whitelist: config.Whitelist, + Catalyst: config.Catalyst, }); err != nil { return nil, err } + if config.Catalyst { + // Activate transaction processing by default in catalyst mode + eth.handler.acceptTxs = 1 + } eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) @@ -337,6 +342,11 @@ func (s *Ethereum) APIs() []rpc.API { Version: "1.0", Service: s.netRPCService, Public: true, + }, { + Namespace: "eth2", + Version: "1.0", + Service: NewEth2API(s), + Public: true, }, }...) } diff --git a/eth/eth2.go b/eth/eth2.go new file mode 100644 index 00000000000..6927c51fc00 --- /dev/null +++ b/eth/eth2.go @@ -0,0 +1,320 @@ +// 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 eth + +import ( + "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/core/vm" + "github.com/ethereum/go-ethereum/log" + chainParams "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +type Eth2API struct { + eth *Ethereum + env *eth2bpenv + head common.Hash +} + +// NewEth2API creates a new API definition for the eth2 prototype. +func NewEth2API(eth *Ethereum) *Eth2API { + return &Eth2API{eth: eth} +} + +type eth2bpenv struct { + state *state.StateDB + tcount int + gasPool *core.GasPool + + header *types.Header + txs []*types.Transaction + receipts []*types.Receipt +} + +func (api *Eth2API) commitTransaction(tx *types.Transaction, coinbase common.Address, bcParentRoots []common.Hash, randao common.Hash) error { + //snap := eth2rpc.current.state.Snapshot() + + chain := api.eth.BlockChain() + receipt, err := core.ApplyTransaction(chain.Config(), chain, &coinbase, api.env.gasPool, api.env.state, api.env.header, tx, &api.env.header.GasUsed, *chain.GetVMConfig(), &vm.BeaconChainContext{BeaconRoots: bcParentRoots, RandaoMix: randao}) + if err != nil { + //w.current.state.RevertToSnapshot(snap) + return err + } + api.env.txs = append(api.env.txs, tx) + api.env.receipts = append(api.env.receipts, receipt) + + return nil +} + +func (api *Eth2API) makeEnv(parent *types.Block, header *types.Header) error { + state, err := api.eth.BlockChain().StateAt(parent.Root()) + if err != nil { + return err + } + api.env = ð2bpenv{ + state: state, + header: header, + gasPool: new(core.GasPool).AddGas(header.GasLimit), + } + return nil +} + +// Structure described at https://hackmd.io/T9x2mMA4S7us8tJwEB3FDQ +type ProduceBlockParams struct { + ParentHash common.Hash `json:"parent_hash"` + RandaoMix common.Hash `json:"randao_mix"` + Slot uint64 `json:"slot"` + Timestamp uint64 `json:"timestamp"` + RecentBeaconBlockRoots []common.Hash `json:"recent_beacon_block_roots"` +} + +// Structure described at https://ethresear.ch/t/executable-beacon-chain/8271 +type ExecutableData struct { + Coinbase common.Address `json:"coinbase"` + StateRoot common.Hash `json:"state_root"` + GasLimit uint64 `json:"gas_limit"` + GasUsed uint64 `json:"gas_used"` + Transactions []*types.Transaction `json:"transactions"` + ReceiptRoot common.Hash `json:"receipt_root"` + LogsBloom []byte `json:"logs_bloom"` + BlockHash common.Hash `json:"block_hash"` + ParentHash common.Hash `json:"parent_hash"` + Difficulty *big.Int `json:"difficulty"` +} + +// ProduceBlock 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 *Eth2API) ProduceBlock(params ProduceBlockParams) (*ExecutableData, error) { + log.Info("Produce 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) + } + // this will ensure we're not going off too far in the future + 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 + } + + err = api.makeEnv(parent, header) + if err != nil { + return nil, err + } + signer := types.NewEIP155Signer(bc.Config().ChainID) + txs := types.NewTransactionsByPriceAndNonce(signer, pending) + + var transactions []*types.Transaction + + for { + if api.env.gasPool.Gas() < chainParams.TxGas { + log.Trace("Not enough gas for further transactions", "have", api.env.gasPool, "want", chainParams.TxGas) + break + } + + tx := txs.Peek() + if tx == nil { + break + } + + from, _ := types.Sender(signer, tx) + // XXX replay protection check is missing + + // Execute the transaction + api.env.state.Prepare(tx.Hash(), common.Hash{}, api.env.tcount) + err := api.commitTransaction(tx, coinbase, params.RecentBeaconBlockRoots, params.RandaoMix) + 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) + txs.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()) + txs.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()) + txs.Pop() + + case nil: + // Everything ok, collect the logs and shift in the next transaction from the same account + api.env.tcount++ + txs.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) + txs.Shift() + } + } + + block, err := api.eth.Engine().FinalizeAndAssemble(bc, header, api.env.state, transactions, nil /* uncles */, api.env.receipts) + if err != nil { + return nil, err + } + + var logs []*types.Log + var receipts = make(types.Receipts, len(api.env.receipts)) + hash := block.Hash() + for i, receipt := range api.env.receipts { + // add block location fields + receipt.BlockHash = hash + receipt.BlockNumber = block.Number() + receipt.TransactionIndex = uint(i) + + receipts[i] = new(types.Receipt) + *receipts[i] = *receipt + // Update the block hash in all logs since it is now available and not when the + // receipt/log of individual transactions were created. + for _, log := range receipt.Logs { + log.BlockHash = hash + } + logs = append(logs, receipt.Logs...) + } + + block.Header().ReceiptHash = types.DeriveSha(receipts, new(trie.Trie)) + + return &ExecutableData{ + Coinbase: block.Coinbase(), + StateRoot: block.Root(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + Transactions: []*types.Transaction(block.Transactions()), + ReceiptRoot: block.ReceiptHash(), + LogsBloom: block.Bloom().Bytes(), + BlockHash: block.Hash(), + Difficulty: block.Difficulty(), + ParentHash: block.ParentHash(), + }, nil +} + +// Structure described at https://hackmd.io/T9x2mMA4S7us8tJwEB3FDQ +type InsertBlockParams struct { + RandaoMix common.Hash `json:"randao_mix"` + Slot uint64 `json:"slot"` + Timestamp uint64 `json:"timestamp"` + RecentBeaconBlockRoots []common.Hash `json:"recent_beacon_block_roots"` + ExecutableData ExecutableData `json:"executable_data"` +} + +var zeroNonce [8]byte + +func insertBlockParamsToBlock(params InsertBlockParams, number *big.Int) *types.Block { + header := &types.Header{ + ParentHash: params.ExecutableData.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: params.ExecutableData.Coinbase, + Root: params.ExecutableData.StateRoot, + TxHash: types.DeriveSha(types.Transactions(params.ExecutableData.Transactions), trie.NewStackTrie(nil)), + ReceiptHash: params.ExecutableData.ReceiptRoot, + Bloom: types.BytesToBloom(params.ExecutableData.LogsBloom), + Difficulty: params.ExecutableData.Difficulty, + Number: number, + GasLimit: params.ExecutableData.GasLimit, + GasUsed: params.ExecutableData.GasUsed, + Time: params.Timestamp, + Extra: nil, + MixDigest: common.Hash{}, + Nonce: zeroNonce, + } + block := types.NewBlockWithHeader(header).WithBody(params.ExecutableData.Transactions, nil /* uncles */) + + return block +} + +// InsertBlock 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 *Eth2API) InsertBlock(params InsertBlockParams) (bool, error) { + // compute block number as parent.number + 1 + parent := api.eth.BlockChain().GetBlockByHash(params.ExecutableData.ParentHash) + if parent == nil { + return false, fmt.Errorf("could not find parent %x", params.ExecutableData.ParentHash) + } + number := big.NewInt(0) + number.Add(parent.Number(), big.NewInt(1)) + + block := insertBlockParamsToBlock(params, number) + _, err := api.eth.BlockChain().InsertChainWithoutSealVerification(types.Blocks([]*types.Block{block})) + + return (err == nil), err +} + +// Used in tests to add a the list of transactions from a block to the tx pool. +func (api *Eth2API) addBlockTxs(block *types.Block) error { + for _, tx := range block.Transactions() { + api.eth.txPool.AddLocal(tx) + } + + return nil +} + +//func (api *Eth2API) SetHead(newHead common.Hash) error { +//oldBlock := api.eth.BlockChain().CurrentBlock() + +//if oldBlock.Hash() == newHead { +//return nil +//} + +//newBlock := api.eth.BlockChain().GetBlockByHash(newHead) + +//err := api.eth.BlockChain().Reorg(oldBlock, newBlock) +//if err != nil { +//return err +//} +//api.head = newHead +//return nil +//} diff --git a/eth/eth2_test.go b/eth/eth2_test.go new file mode 100644 index 00000000000..72599bc0c3e --- /dev/null +++ b/eth/eth2_test.go @@ -0,0 +1,290 @@ +// 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 eth + +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/node" + "github.com/ethereum/go-ethereum/params" +) + +var ( + 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() + 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")) + } + 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 TestEth2ProduceBlock(t *testing.T) { + genesis, blocks := generateTestChain() + + n, err := node.New(&node.Config{}) + if err != nil { + t.Fatalf("could not get node: %v", err) + } + ethservice, err := New(n, &Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}}) + if err != nil { + t.Fatalf("can't create new ethereum service: %v", err) + } + if err := n.Start(); err != nil { + t.Fatalf("can't start test node: %v", err) + } + if _, err := ethservice.BlockChain().InsertChain(blocks[1:9]); err != nil { + t.Fatalf("can't import test blocks: %v", err) + } + ethservice.SetEtherbase(testAddr) + + api := NewEth2API(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 := ProduceBlockParams{ + ParentHash: blocks[8].ParentHash(), + Slot: blocks[8].NumberU64(), + Timestamp: blocks[8].Time(), + } + execData, err := api.ProduceBlock(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 TestEth2ProduceBlockWithAnotherBlocksTxs(t *testing.T) { + genesis, blocks := generateTestChain() + + n, err := node.New(&node.Config{}) + if err != nil { + t.Fatalf("could not get node: %v", err) + } + ethservice, err := New(n, &Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}}) + if err != nil { + t.Fatalf("can't create new ethereum service: %v", err) + } + if err := n.Start(); err != nil { + t.Fatalf("can't start test node: %v", err) + } + if _, err := ethservice.BlockChain().InsertChain(blocks[1:9]); err != nil { + t.Fatalf("can't import test blocks: %v", err) + } + ethservice.SetEtherbase(testAddr) + + api := NewEth2API(ethservice) + + // Put the 10th block's tx in the pool and produce a new block + api.addBlockTxs(blocks[9]) + blockParams := ProduceBlockParams{ + ParentHash: blocks[9].ParentHash(), + Slot: blocks[9].NumberU64(), + Timestamp: blocks[9].Time(), + } + execData, err := api.ProduceBlock(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 TestEth2InsertBlock(t *testing.T) { + genesis, blocks, forkedBlocks := generateTestChainWithFork(10, 4) + + n, err := node.New(&node.Config{}) + if err != nil { + t.Fatalf("could not get node: %v", err) + } + ethservice, err := New(n, &Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}}) + if err != nil { + t.Fatalf("can't create new ethereum service: %v", err) + } + if err := n.Start(); err != nil { + t.Fatalf("can't start test node: %v", err) + } + if _, err := ethservice.BlockChain().InsertChain(blocks[1:5]); err != nil { + t.Fatalf("can't import test blocks: %v", err) + } + + api := NewEth2API(ethservice) + for i := 5; i < 10; i++ { + p := InsertBlockParams{ + Slot: blocks[i].NumberU64(), + Timestamp: blocks[i].Time(), + ExecutableData: ExecutableData{ + ParentHash: ethservice.BlockChain().CurrentBlock().Hash(), + Coinbase: blocks[i].Coinbase(), + StateRoot: blocks[i].Root(), + Difficulty: blocks[i].Difficulty(), + GasLimit: blocks[i].GasLimit(), + GasUsed: blocks[i].GasUsed(), + Transactions: []*types.Transaction(blocks[i].Transactions()), + ReceiptRoot: blocks[i].ReceiptHash(), + LogsBloom: blocks[i].Bloom().Bytes(), + BlockHash: blocks[i].Hash(), + }, + } + success, err := api.InsertBlock(p) + if err != nil || !success { + t.Fatalf("Failed to insert block: %v", err) + } + } + + // Introduce the fork point + lastBlockNum := blocks[4].Number() + lastBlock := blocks[4] + for i := 0; i < 4; i++ { + lastBlockNum.Add(lastBlockNum, big.NewInt(1)) + p := InsertBlockParams{ + Slot: lastBlockNum.Uint64(), + Timestamp: forkedBlocks[i].Time(), + ExecutableData: ExecutableData{ + ParentHash: lastBlock.Hash(), + Coinbase: forkedBlocks[i].Coinbase(), + StateRoot: forkedBlocks[i].Root(), + Difficulty: forkedBlocks[i].Difficulty(), + GasLimit: forkedBlocks[i].GasLimit(), + GasUsed: forkedBlocks[i].GasUsed(), + Transactions: []*types.Transaction(blocks[i].Transactions()), + ReceiptRoot: forkedBlocks[i].ReceiptHash(), + LogsBloom: forkedBlocks[i].Bloom().Bytes(), + BlockHash: forkedBlocks[i].Hash(), + }, + } + success, err := api.InsertBlock(p) + if err != nil || !success { + t.Fatalf("Failed to insert forked block #%d: %v", i, err) + } + lastBlock = insertBlockParamsToBlock(p, lastBlockNum) + } + + exp := common.HexToHash("526db89301fc787799ef8c272fe512898b97ad96d0b69caee19dc5393b092110") + if ethservice.BlockChain().CurrentBlock().Hash() != exp { + t.Fatalf("Wrong head after inserting fork %x != %x", exp, ethservice.BlockChain().CurrentBlock().Hash()) + } +} + +//func TestEth2SetHead(t *testing.T) { +//genesis, blocks, forkedBlocks := generateTestChainWithFork(10, 5) + +//n, err := node.New(&node.Config{}) +//if err != nil { +//t.Fatalf("could not get node: %v", err) +//} +//ethservice, err := New(n, &Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}}) +//if err != nil { +//t.Fatalf("can't create new ethereum service: %v", err) +//} +//if err := n.Start(); err != nil { +//t.Fatalf("can't start test node: %v", err) +//} +//if _, err := ethservice.BlockChain().InsertChain(blocks[1:5]); err != nil { +//t.Fatalf("can't import test blocks: %v", err) +//} + +//api := NewEth2API(ethservice) +//for i := 5; i < 10; i++ { +//var blockRLP bytes.Buffer +//rlp.Encode(&blockRLP, blocks[i]) +//err := api.InsertBlock(blockRLP.Bytes()) +//if err != nil { +//t.Fatalf("Failed to insert block: %v", err) +//} +//} +//api.head = blocks[9].Hash() + +//if ethservice.BlockChain().CurrentBlock().Hash() != blocks[9].Hash() { +//t.Fatalf("Wrong head") +//} + +//for i := 0; i < 3; i++ { +//var blockRLP bytes.Buffer +//rlp.Encode(&blockRLP, forkedBlocks[i]) +//err := api.InsertBlock(blockRLP.Bytes()) +//if err != nil { +//t.Fatalf("Failed to insert block: %v", err) +//} +//} + +//api.SetHead(forkedBlocks[2].Hash()) + +//if ethservice.BlockChain().CurrentBlock().Hash() == forkedBlocks[2].Hash() { +//t.Fatalf("Wrong head after inserting fork %x != %x", blocks[9].Hash(), ethservice.BlockChain().CurrentBlock().Hash()) +//} +//if api.head != forkedBlocks[2].Hash() { +//t.Fatalf("Registered wrong head") +//} +//} diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 0c6eb0bdd7d..6b21b5b0613 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -201,6 +201,9 @@ type Config struct { // Berlin block override (TODO: remove after the fork) OverrideBerlin *big.Int `toml:",omitempty"` + + // Specify if catalyst mode is enabled + Catalyst bool `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain configuration. diff --git a/eth/handler.go b/eth/handler.go index 11c8565de10..094db818900 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -85,6 +85,7 @@ type handlerConfig struct { EventMux *event.TypeMux // Legacy event mux, deprecate for `feed` Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges Whitelist map[uint64]common.Hash // Hard coded whitelist for sync challenged + Catalyst bool // True iff this is catalyst } type handler struct { @@ -123,6 +124,8 @@ type handler struct { chainSync *chainSyncer wg sync.WaitGroup peerWG sync.WaitGroup + + IsCatalyst bool } // newHandler returns a handler for all Ethereum chain management protocol. @@ -142,6 +145,10 @@ func newHandler(config *handlerConfig) (*handler, error) { whitelist: config.Whitelist, txsyncCh: make(chan *txsync), quitSync: make(chan struct{}), + IsCatalyst: config.Catalyst, + } + if h.IsCatalyst { + h.acceptTxs = 1 } if config.Sync == downloader.FullSync { // The database seems empty as the current block is the genesis. Yet the fast diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 3ff9f2245be..1fe8fb5aba3 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -39,6 +39,7 @@ type ethHandler handler func (h *ethHandler) Chain() *core.BlockChain { return h.chain } func (h *ethHandler) StateBloom() *trie.SyncBloom { return h.stateBloom } func (h *ethHandler) TxPool() eth.TxPool { return h.txpool } +func (h *ethHandler) Catalyst() bool { return h.IsCatalyst } // RunPeer is invoked when a peer joins on the `eth` protocol. func (h *ethHandler) RunPeer(peer *eth.Peer, hand eth.Handler) error { diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 5f5d4e9e82d..a29e8a4d303 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -54,6 +54,7 @@ func (h *testEthHandler) TxPool() eth.TxPool { panic("no backi func (h *testEthHandler) AcceptTxs() bool { return true } func (h *testEthHandler) RunPeer(*eth.Peer, eth.Handler) error { panic("not used in tests") } func (h *testEthHandler) PeerInfo(enode.ID) interface{} { panic("not used in tests") } +func (h *testEthHandler) Catalyst() bool { return false } func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error { switch packet := packet.(type) { diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 0dc3de98982..5815c9459e9 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -92,6 +92,9 @@ type Backend interface { // the remote peer. Only packets not consumed by the protocol handler will // be forwarded to the backend. Handle(peer *Peer, packet Packet) error + + // Catalist returns true if the backend in running in catalyst mode + Catalyst() bool } // TxPool defines the methods needed by the protocol handler to serve transactions. diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 30beae931b6..7daca0bb60c 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -108,6 +108,9 @@ func (b *testBackend) AcceptTxs() bool { func (b *testBackend) Handle(*Peer, Packet) error { panic("data processing tests should be done in the handler package") } +func (b *testBackend) Catalyst() bool { + return false +} // Tests that block headers can be retrieved from a remote chain based on user queries. func TestGetBlockHeaders64(t *testing.T) { testGetBlockHeaders(t, 64) } diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 8433fa343a7..8be01613ed3 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -273,6 +273,10 @@ func answerGetReceiptsQuery(backend Backend, query GetReceiptsPacket, peer *Peer } func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error { + if backend.Catalyst() { + return nil + } + // A batch of new block announcements just arrived ann := new(NewBlockHashesPacket) if err := msg.Decode(ann); err != nil { @@ -287,6 +291,10 @@ func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error { } func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { + if backend.Catalyst() { + return nil + } + // Retrieve and decode the propagated block ann := new(NewBlockPacket) if err := msg.Decode(ann); err != nil { diff --git a/miner/worker.go b/miner/worker.go index 2cee6af0c32..d484690ede7 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -736,7 +736,7 @@ func (w *worker) updateSnapshot() { func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { snap := w.current.state.Snapshot() - receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig()) + receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig(), nil) if err != nil { w.current.state.RevertToSnapshot(snap) return nil, err diff --git a/params/config.go b/params/config.go index 143e2e2a369..f6b19f28b6a 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), } }