core, core/state, trie: EIP158, reprice & skip empty account write

This commit implements EIP158 part 1, 2, 3 & 4

1. If an account is empty it's no longer written to the trie. An empty
  account is defined as (balance=0, nonce=0, storage=0, code=0).
2. Delete an empty account if it's touched
3. An empty account is redefined as either non-existent or empty.
4. Zero value calls and zero value suicides no longer consume the 25k
  reation costs.

params: moved core/config to params

Signed-off-by: Jeffrey Wilcke <jeffrey@ethereum.org>
This commit is contained in:
Jeffrey Wilcke
2016-10-20 13:36:29 +02:00
parent 932d973e36
commit 445feaeef5
74 changed files with 729 additions and 573 deletions

View File

@ -23,20 +23,11 @@ import (
"github.com/ethereum/go-ethereum/params"
)
// RuleSet is an interface that defines the current rule set during the
// execution of the EVM instructions (e.g. whether it's homestead)
type RuleSet interface {
IsHomestead(*big.Int) bool
// GasTable returns the gas prices for this phase, which is based on
// block number passed in.
GasTable(*big.Int) params.GasTable
}
// Environment is an EVM requirement and helper which allows access to outside
// information such as states.
type Environment interface {
// The current ruleset
RuleSet() RuleSet
ChainConfig() *params.ChainConfig
// The state database
Db() Database
// Creates a restorable snapshot
@ -115,6 +106,9 @@ type Database interface {
// Exist reports whether the given account exists in state.
// Notably this should also return true for suicided accounts.
Exist(common.Address) bool
// Empty returns whether the given account is empty. Empty
// is defined according to EIP161 (balance = nonce = code = 0).
Empty(common.Address) bool
}
// Account represents a contract or basic ethereum account.

View File

@ -515,7 +515,7 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract
input = memory.Get(offset.Int64(), size.Int64())
gas = new(big.Int).Set(contract.Gas)
)
if env.RuleSet().GasTable(env.BlockNumber()).CreateBySuicide != nil {
if env.ChainConfig().IsEIP150(env.BlockNumber()) {
gas.Div(gas, n64)
gas = gas.Sub(contract.Gas, gas)
}
@ -526,7 +526,7 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract
// homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must
// ignore this error and pretend the operation was successful.
if env.RuleSet().IsHomestead(env.BlockNumber()) && suberr == CodeStoreOutOfGasError {
if env.ChainConfig().IsHomestead(env.BlockNumber()) && suberr == CodeStoreOutOfGasError {
stack.push(new(big.Int))
} else if suberr != nil && suberr != CodeStoreOutOfGasError {
stack.push(new(big.Int))

View File

@ -319,7 +319,7 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *Stack, env
}()
}
homestead := env.RuleSet().IsHomestead(env.BlockNumber())
homestead := env.ChainConfig().IsHomestead(env.BlockNumber())
for pc < uint64(len(program.instructions)) {
instrCount++

View File

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
const maxRun = 1000
@ -172,7 +173,9 @@ func NewEnv(config *Config) *Env {
return env
}
func (self *Env) RuleSet() RuleSet { return ruleSet{new(big.Int)} }
func (self *Env) ChainConfig() *params.ChainConfig {
return &params.ChainConfig{new(big.Int), new(big.Int), true, new(big.Int), common.Hash{}, new(big.Int)}
}
func (self *Env) Vm() Vm { return self.evm }
func (self *Env) Origin() common.Address { return common.Address{} }
func (self *Env) BlockNumber() *big.Int { return big.NewInt(0) }

View File

@ -16,7 +16,11 @@
package vm
import "math/big"
import (
"math/big"
"github.com/ethereum/go-ethereum/params"
)
type jumpPtr struct {
fn instrFn
@ -25,7 +29,7 @@ type jumpPtr struct {
type vmJumpTable [256]jumpPtr
func newJumpTable(ruleset RuleSet, blockNumber *big.Int) vmJumpTable {
func newJumpTable(ruleset *params.ChainConfig, blockNumber *big.Int) vmJumpTable {
var jumpTable vmJumpTable
// when initialising a new VM execution we must first check the homestead

View File

@ -19,16 +19,18 @@ package vm
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/params"
)
func TestInit(t *testing.T) {
jumpTable := newJumpTable(ruleSet{big.NewInt(1)}, big.NewInt(0))
jumpTable := newJumpTable(&params.ChainConfig{HomesteadBlock: big.NewInt(1)}, big.NewInt(0))
if jumpTable[DELEGATECALL].valid {
t.Error("Expected DELEGATECALL not to be present")
}
for _, n := range []int64{1, 2, 100} {
jumpTable := newJumpTable(ruleSet{big.NewInt(1)}, big.NewInt(n))
jumpTable := newJumpTable(&params.ChainConfig{HomesteadBlock: big.NewInt(1)}, big.NewInt(n))
if !jumpTable[DELEGATECALL].valid {
t.Error("Expected DELEGATECALL to be present for block", n)
}

View File

@ -23,13 +23,14 @@ import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
)
// Env is a basic runtime environment required for running the EVM.
type Env struct {
ruleSet vm.RuleSet
depth int
state *state.StateDB
chainConfig *params.ChainConfig
depth int
state *state.StateDB
origin common.Address
coinbase common.Address
@ -47,14 +48,14 @@ type Env struct {
// NewEnv returns a new vm.Environment
func NewEnv(cfg *Config, state *state.StateDB) vm.Environment {
env := &Env{
ruleSet: cfg.RuleSet,
state: state,
origin: cfg.Origin,
coinbase: cfg.Coinbase,
number: cfg.BlockNumber,
time: cfg.Time,
difficulty: cfg.Difficulty,
gasLimit: cfg.GasLimit,
chainConfig: cfg.ChainConfig,
state: state,
origin: cfg.Origin,
coinbase: cfg.Coinbase,
number: cfg.BlockNumber,
time: cfg.Time,
difficulty: cfg.Difficulty,
gasLimit: cfg.GasLimit,
}
env.evm = vm.New(env, vm.Config{
Debug: cfg.Debug,
@ -65,16 +66,16 @@ func NewEnv(cfg *Config, state *state.StateDB) vm.Environment {
return env
}
func (self *Env) RuleSet() vm.RuleSet { return self.ruleSet }
func (self *Env) Vm() vm.Vm { return self.evm }
func (self *Env) Origin() common.Address { return self.origin }
func (self *Env) BlockNumber() *big.Int { return self.number }
func (self *Env) Coinbase() common.Address { return self.coinbase }
func (self *Env) Time() *big.Int { return self.time }
func (self *Env) Difficulty() *big.Int { return self.difficulty }
func (self *Env) Db() vm.Database { return self.state }
func (self *Env) GasLimit() *big.Int { return self.gasLimit }
func (self *Env) VmType() vm.Type { return vm.StdVmTy }
func (self *Env) ChainConfig() *params.ChainConfig { return self.chainConfig }
func (self *Env) Vm() vm.Vm { return self.evm }
func (self *Env) Origin() common.Address { return self.origin }
func (self *Env) BlockNumber() *big.Int { return self.number }
func (self *Env) Coinbase() common.Address { return self.coinbase }
func (self *Env) Time() *big.Int { return self.time }
func (self *Env) Difficulty() *big.Int { return self.difficulty }
func (self *Env) Db() vm.Database { return self.state }
func (self *Env) GasLimit() *big.Int { return self.gasLimit }
func (self *Env) VmType() vm.Type { return vm.StdVmTy }
func (self *Env) GetHash(n uint64) common.Hash {
return self.getHashFn(n)
}

View File

@ -22,7 +22,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
@ -39,7 +38,7 @@ func (ruleSet) GasTable(*big.Int) params.GasTable {
// Config is a basic type specifying certain configuration flags for running
// the EVM.
type Config struct {
RuleSet vm.RuleSet
ChainConfig *params.ChainConfig
Difficulty *big.Int
Origin common.Address
Coinbase common.Address
@ -57,8 +56,8 @@ type Config struct {
// sets defaults on the config
func setDefaults(cfg *Config) {
if cfg.RuleSet == nil {
cfg.RuleSet = ruleSet{}
if cfg.ChainConfig == nil {
cfg.ChainConfig = &params.ChainConfig{new(big.Int), new(big.Int), false, new(big.Int), common.Hash{}, new(big.Int)}
}
if cfg.Difficulty == nil {

View File

@ -51,9 +51,9 @@ type EVM struct {
func New(env Environment, cfg Config) *EVM {
return &EVM{
env: env,
jumpTable: newJumpTable(env.RuleSet(), env.BlockNumber()),
jumpTable: newJumpTable(env.ChainConfig(), env.BlockNumber()),
cfg: cfg,
gasTable: env.RuleSet().GasTable(env.BlockNumber()),
gasTable: env.ChainConfig().GasTable(env.BlockNumber()),
}
}
@ -172,6 +172,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
// Get the memory location of pc
op = contract.GetOp(pc)
//fmt.Printf("OP %d %v\n", op, op)
// calculate the new memory size and gas price for the current executing opcode
newMemSize, cost, err = calculateGasAndSize(evm.gasTable, evm.env, contract, caller, op, statedb, mem, stack)
if err != nil {
@ -254,10 +255,20 @@ func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Co
// stack Check, memory resize & gas phase
switch op {
case SUICIDE:
// if suicide is not nil: homestead gas fork
// EIP150 homestead gas reprice fork:
if gasTable.CreateBySuicide != nil {
gas.Set(gasTable.Suicide)
if !env.Db().Exist(common.BigToAddress(stack.data[len(stack.data)-1])) {
var (
address = common.BigToAddress(stack.data[len(stack.data)-1])
eip158 = env.ChainConfig().IsEIP158(env.BlockNumber())
)
if eip158 {
// if empty and transfers value
if env.Db().Empty(address) && statedb.GetBalance(contract.Address()).BitLen() > 0 {
gas.Add(gas, gasTable.CreateBySuicide)
}
} else if !env.Db().Exist(address) {
gas.Add(gas, gasTable.CreateBySuicide)
}
}
@ -378,12 +389,21 @@ func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Co
case CALL, CALLCODE:
gas.Set(gasTable.Calls)
transfersValue := stack.data[len(stack.data)-3].BitLen() > 0
if op == CALL {
if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) {
var (
address = common.BigToAddress(stack.data[len(stack.data)-2])
eip158 = env.ChainConfig().IsEIP158(env.BlockNumber())
)
if eip158 {
if env.Db().Empty(address) && transfersValue {
gas.Add(gas, params.CallNewAccountGas)
}
} else if !env.Db().Exist(address) {
gas.Add(gas, params.CallNewAccountGas)
}
}
if len(stack.data[stack.len()-3].Bytes()) > 0 {
if transfersValue {
gas.Add(gas, params.CallValueTransferGas)
}
x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7])