Skip to content

Commit

Permalink
cmd,consensus,core,eth,params,tests: articulate EIP features
Browse files Browse the repository at this point in the history
Refactors chain configuration and respective feature
implementations to use `IsEIP<NUMBER>` definitions and methods,
instead of `Is<HardForkName>`, whenever possible. Doing so
attempts to address problems of ambiguity and complexity in chain
configuration and feature implementation.

Signed-off-by: Isaac Ardis (isaac.ardis@gmail.com)
  • Loading branch information
whilei committed Jan 4, 2019
1 parent 4997526 commit 124d692
Show file tree
Hide file tree
Showing 19 changed files with 923 additions and 233 deletions.
4 changes: 2 additions & 2 deletions cmd/puppeth/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ func (spec *parityChainSpec) setPrecompile(address byte, data *parityChainSpecBu
}

func (spec *parityChainSpec) setByzantium(num *big.Int) {
spec.Engine.Ethash.Params.BlockReward[hexutil.EncodeBig(num)] = hexutil.EncodeBig(ethash.ByzantiumBlockReward)
spec.Engine.Ethash.Params.BlockReward[hexutil.EncodeBig(num)] = hexutil.EncodeBig(ethash.EIP649FBlockReward)
spec.Engine.Ethash.Params.DifficultyBombDelays[hexutil.EncodeBig(num)] = hexutil.EncodeUint64(3000000)
n := hexutil.Uint64(num.Uint64())
spec.Engine.Ethash.Params.EIP100bTransition = n
Expand All @@ -428,7 +428,7 @@ func (spec *parityChainSpec) setByzantium(num *big.Int) {
}

func (spec *parityChainSpec) setConstantinople(num *big.Int) {
spec.Engine.Ethash.Params.BlockReward[hexutil.EncodeBig(num)] = hexutil.EncodeBig(ethash.ConstantinopleBlockReward)
spec.Engine.Ethash.Params.BlockReward[hexutil.EncodeBig(num)] = hexutil.EncodeBig(ethash.EIP1234FBlockReward)
spec.Engine.Ethash.Params.DifficultyBombDelays[hexutil.EncodeBig(num)] = hexutil.EncodeUint64(2000000)
n := hexutil.Uint64(num.Uint64())
spec.Params.EIP145Transition = n
Expand Down
2 changes: 1 addition & 1 deletion consensus/clique/clique.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
// rewards given, and returns the final block.
func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
// No block rewards in PoA, so the state remains as is and uncles are dropped
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
header.Root = state.IntermediateRoot(chain.Config().IsEIP161F(header.Number))
header.UncleHash = types.CalcUncleHash(nil)

// Assemble and return the final block for sealing
Expand Down
40 changes: 24 additions & 16 deletions consensus/ethash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,25 @@ import (

// Ethash proof-of-work protocol constants.
var (
FrontierBlockReward = big.NewInt(5e+18) // Block reward in wei for successfully mining a block
ByzantiumBlockReward = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium
ConstantinopleBlockReward = big.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople
maxUncles = 2 // Maximum number of uncles allowed in a single block
allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks
FrontierBlockReward = big.NewInt(5e+18) // Block reward in wei for successfully mining a block
EIP649FBlockReward = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium
EIP1234FBlockReward = big.NewInt(2e+18) // Block reward in wei for successfully mining a block upward from Constantinople
maxUncles = 2 // Maximum number of uncles allowed in a single block
allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks

// calcDifficultyConstantinople is the difficulty adjustment algorithm for Constantinople.
// calcDifficultyEIP1234 is the difficulty adjustment algorithm for Constantinople.
// It returns the difficulty that a new block should have when created at time given the
// parent block's time and difficulty. The calculation uses the Byzantium rules, but with
// bomb offset 5M.
// Specification EIP-1234: https://eips.ethereum.org/EIPS/eip-1234
calcDifficultyConstantinople = makeDifficultyCalculator(big.NewInt(5000000))
calcDifficultyEIP1234 = makeDifficultyCalculator(big.NewInt(5000000))

// calcDifficultyByzantium is the difficulty adjustment algorithm. It returns
// calcDifficultyByzantium is the difficulty adjustment algorithm for Byzantium. It returns
// the difficulty that a new block should have when created at time given the
// parent block's time and difficulty. The calculation uses the Byzantium rules.
// Specification EIP-649: https://eips.ethereum.org/EIPS/eip-649
// Related meta-ish EIP-669: https://github.com/ethereum/EIPs/pull/669
// Note that this calculator also includes the change from EIP100.
calcDifficultyByzantium = makeDifficultyCalculator(big.NewInt(3000000))
)

Expand Down Expand Up @@ -313,10 +315,16 @@ func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, p
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
next := new(big.Int).Add(parent.Number, big1)
switch {
case config.IsConstantinople(next):
return calcDifficultyConstantinople(time, parent)
case config.IsByzantium(next):
case config.IsEIP1234F(next):
return calcDifficultyEIP1234(time, parent)
case config.IsByzantium(next) || (config.IsEIP649F(next) && config.IsEIP100F(next)):
return calcDifficultyByzantium(time, parent)
case config.IsEIP649F(next):
// TODO: calculator for only EIP649:difficulty bomb delay (without EIP100:mean time adjustment)
panic("not implemented")
case config.IsEIP100F(next):
// TODO: calculator for only EIP100:mean time adjustment (without EIP649:difficulty bomb delay)
panic("not implemented")
case config.IsHomestead(next):
return calcDifficultyHomestead(time, parent)
default:
Expand Down Expand Up @@ -567,7 +575,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header)
func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
// Accumulate any block and uncle rewards and commit the final state root
accumulateRewards(chain.Config(), state, header, uncles)
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
header.Root = state.IntermediateRoot(chain.Config().IsEIP161F(header.Number))

// Header seems complete, assemble into a block and return
return types.NewBlock(header, txs, uncles, receipts), nil
Expand Down Expand Up @@ -608,11 +616,11 @@ var (
func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
// Select the correct block reward based on chain progression
blockReward := FrontierBlockReward
if config.IsByzantium(header.Number) {
blockReward = ByzantiumBlockReward
if config.IsEIP649F(header.Number) {
blockReward = EIP649FBlockReward
}
if config.IsConstantinople(header.Number) {
blockReward = ConstantinopleBlockReward
if config.IsEIP1234F(header.Number) {
blockReward = EIP1234FBlockReward
}
// Accumulate the rewards for the miner and any included uncles
reward := new(big.Int).Set(blockReward)
Expand Down
2 changes: 1 addition & 1 deletion core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat
}
// Validate the state root against the received state root and throw
// an error if they don't match.
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
if root := statedb.IntermediateRoot(v.config.IsEIP161F(header.Number)); header.Root != root {
return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root)
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/hashicorp/golang-lru"
lru "github.com/hashicorp/golang-lru"
)

var (
Expand Down Expand Up @@ -946,7 +946,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
}
rawdb.WriteBlock(bc.db, block)

root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number()))
root, err := state.Commit(bc.chainConfig.IsEIP161F(block.Number()))
if err != nil {
return NonStatTy, err
}
Expand Down
4 changes: 2 additions & 2 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
block, _ := b.engine.Finalize(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts)

// Write state changes to db
root, err := statedb.Commit(config.IsEIP158(b.header.Number))
root, err := statedb.Commit(config.IsEIP161F(b.header.Number))
if err != nil {
panic(fmt.Sprintf("state write error: %v", err))
}
Expand Down Expand Up @@ -233,7 +233,7 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
}

return &types.Header{
Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())),
Root: state.IntermediateRoot(chain.Config().IsEIP161F(parent.Number())),
ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(),
Difficulty: engine.CalcDifficulty(chain, time.Uint64(), &types.Header{
Expand Down
4 changes: 2 additions & 2 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
}
// Update the state with pending changes
var root []byte
if config.IsByzantium(header.Number) {
if config.IsEIP658F(header.Number) {
statedb.Finalise(true)
} else {
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
root = statedb.IntermediateRoot(config.IsEIP161F(header.Number)).Bytes()
}
*usedGas += gas

Expand Down
35 changes: 23 additions & 12 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,8 @@ type PrecompiledContract interface {
Run(input []byte) ([]byte, error) // Run runs the precompiled contract
}

// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum
// contracts used in the Frontier and Homestead releases.
var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
}

// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum
// contracts used in the Byzantium release.
var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
// AllPrecompiledContracts returns all possible precompiled contracts.
var AllPrecompiledContracts = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
Expand All @@ -59,6 +49,27 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{8}): &bn256Pairing{},
}

// IsPrecompiledContractEnabled checks whether a given precompiled contract is enabled for a chain config at a given block.
func IsPrecompiledContractEnabled(config *params.ChainConfig, num *big.Int, codeAddr common.Address) bool {
switch codeAddr {
case common.BytesToAddress([]byte{1}),
common.BytesToAddress([]byte{2}),
common.BytesToAddress([]byte{3}),
common.BytesToAddress([]byte{4}):
return true
case common.BytesToAddress([]byte{5}):
return config.IsEIP198F(num)
case common.BytesToAddress([]byte{6}):
return config.IsEIP213F(num)
case common.BytesToAddress([]byte{7}):
return config.IsEIP213F(num)
case common.BytesToAddress([]byte{8}):
return config.IsEIP212F(num)
default:
return false
}
}

// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.RequiredGas(input)
Expand Down
96 changes: 94 additions & 2 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"math/big"
"testing"

"github.com/ethereum/go-ethereum/params"

"github.com/ethereum/go-ethereum/common"
)

Expand Down Expand Up @@ -337,9 +339,10 @@ var bn256PairingTests = []precompiledTest{
}

func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
p := PrecompiledContractsByzantium[common.HexToAddress(addr)]
p := AllPrecompiledContracts[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.input)
contract := NewContract(AccountRef(common.HexToAddress("1337")),

nil, new(big.Int), p.RequiredGas(in))
t.Run(fmt.Sprintf("%s-Gas=%d", test.name, contract.Gas), func(t *testing.T) {
if res, err := RunPrecompiledContract(p, in, contract); err != nil {
Expand All @@ -350,11 +353,100 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
})
}

func TestIsPrecompiledContractEnabled(t *testing.T) {
var homeCts = []common.Address{
common.BytesToAddress([]byte{1}),
common.BytesToAddress([]byte{2}),
common.BytesToAddress([]byte{3}),
common.BytesToAddress([]byte{4}),
}
var byzUniqCts = []common.Address{
common.BytesToAddress([]byte{5}),
common.BytesToAddress([]byte{6}),
common.BytesToAddress([]byte{7}),
common.BytesToAddress([]byte{8}),
}
var byzCts = append(homeCts, byzUniqCts...)
var nonCts = []common.Address{
common.Address{},
common.BytesToAddress([]byte{42}),
common.HexToAddress("0xdeadbeef"),
}
type c struct {
addr common.Address
config *params.ChainConfig
blockNum *big.Int
want bool
}
cases := []c{}
addCaseWhere := func(config *params.ChainConfig, addr common.Address, bn *big.Int, want bool) {
cases = append(cases, c{
addr: addr,
config: config,
blockNum: bn,
want: want,
})
}
for _, a := range homeCts {
addCaseWhere(params.AllEthashProtocolChanges, a, big.NewInt(0), true)
addCaseWhere(params.MainnetChainConfig, a, big.NewInt(0), true)
addCaseWhere(params.MainnetChainConfig, a, new(big.Int).Sub(params.MainnetChainConfig.ByzantiumBlock, common.Big1), true)
addCaseWhere(params.MainnetChainConfig, a, params.MainnetChainConfig.ByzantiumBlock, true)
addCaseWhere(params.MainnetChainConfig, a, new(big.Int).Add(params.MainnetChainConfig.ByzantiumBlock, common.Big1), true)
}
for _, a := range byzUniqCts {
addCaseWhere(params.MainnetChainConfig, a, new(big.Int).Sub(params.MainnetChainConfig.ByzantiumBlock, common.Big1), false)
addCaseWhere(params.MainnetChainConfig, a, params.MainnetChainConfig.ByzantiumBlock, true)
addCaseWhere(params.MainnetChainConfig, a, new(big.Int).Add(params.MainnetChainConfig.ByzantiumBlock, common.Big1), true)
}
for _, a := range byzCts {
addCaseWhere(params.AllEthashProtocolChanges, a, big.NewInt(0), true)
addCaseWhere(params.MainnetChainConfig, a, new(big.Int).Add(params.MainnetChainConfig.ByzantiumBlock, common.Big1), true)
}
for _, a := range nonCts {
addCaseWhere(params.AllEthashProtocolChanges, a, big.NewInt(0), false)
addCaseWhere(params.MainnetChainConfig, a, new(big.Int).Sub(params.MainnetChainConfig.ByzantiumBlock, common.Big1), false)
addCaseWhere(params.MainnetChainConfig, a, new(big.Int).Add(params.MainnetChainConfig.ByzantiumBlock, common.Big1), false)
}

for i, c := range cases {
got := IsPrecompiledContractEnabled(c.config, c.blockNum, c.addr)
if c.want != got {
t.Errorf("test: %d, address: %x, want: %v, got: %v", i, c.addr, c.want, got)
}

// test 1:1 with pre-existing hard-fork implementation style in *evm#Call
precomps := map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
}
if c.config.IsByzantium(c.blockNum) {
precomps = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{},
common.BytesToAddress([]byte{6}): &bn256Add{},
common.BytesToAddress([]byte{7}): &bn256ScalarMul{},
common.BytesToAddress([]byte{8}): &bn256Pairing{},
}
}
expect := precomps[c.addr] == nil
got = !IsPrecompiledContractEnabled(c.config, c.blockNum, c.addr)
if got != expect {
t.Errorf("addr: %x, bn: %v, want: %v, got: %v", c.addr, c.blockNum, c.want, got)
}
}
}

func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
if test.noBenchmark {
return
}
p := PrecompiledContractsByzantium[common.HexToAddress(addr)]
p := AllPrecompiledContracts[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.input)
reqGas := p.RequiredGas(in)
contract := NewContract(AccountRef(common.HexToAddress("1337")),
Expand Down
18 changes: 5 additions & 13 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,8 @@ type (
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
if contract.CodeAddr != nil {
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
precompiles = PrecompiledContractsByzantium
}
if p := precompiles[*contract.CodeAddr]; p != nil {
return RunPrecompiledContract(p, input, contract)
if IsPrecompiledContractEnabled(evm.ChainConfig(), evm.BlockNumber, *contract.CodeAddr) {
return RunPrecompiledContract(AllPrecompiledContracts[*contract.CodeAddr], input, contract)
}
}
for _, interpreter := range evm.interpreters {
Expand Down Expand Up @@ -197,11 +193,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
snapshot = evm.StateDB.Snapshot()
)
if !evm.StateDB.Exist(addr) {
precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
precompiles = PrecompiledContractsByzantium
}
if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
if !IsPrecompiledContractEnabled(evm.ChainConfig(), evm.BlockNumber, addr) && evm.ChainConfig().IsEIP161F(evm.BlockNumber) && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
Expand Down Expand Up @@ -391,7 +383,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// Create a new account on the state
snapshot := evm.StateDB.Snapshot()
evm.StateDB.CreateAccount(address)
if evm.ChainConfig().IsEIP158(evm.BlockNumber) {
if evm.ChainConfig().IsEIP161F(evm.BlockNumber) {
evm.StateDB.SetNonce(address, 1)
}
evm.Transfer(evm.StateDB, caller.Address(), address, value)
Expand All @@ -414,7 +406,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
ret, err := run(evm, contract, nil, false)

// check whether the max code size has been exceeded
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
maxCodeSizeExceeded := evm.ChainConfig().IsEIP170F(evm.BlockNumber) && len(ret) > params.MaxCodeSize
// if the contract creation ran successfully and no errors were returned
// calculate the gas required to store the code. If the code could not
// be stored due to not enough gas set an error and let it be handled
Expand Down
Loading

0 comments on commit 124d692

Please sign in to comment.