From 445feaeef58bd89a113743dccf6fd5df55cde6fa Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Thu, 20 Oct 2016 13:36:29 +0200 Subject: 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 --- core/vm/environment.go | 14 ++++---------- core/vm/instructions.go | 4 ++-- core/vm/jit.go | 2 +- core/vm/jit_test.go | 5 ++++- core/vm/jump_table.go | 8 ++++++-- core/vm/jump_table_test.go | 6 ++++-- core/vm/runtime/env.go | 43 ++++++++++++++++++++++--------------------- core/vm/runtime/runtime.go | 7 +++---- core/vm/vm.go | 32 ++++++++++++++++++++++++++------ 9 files changed, 72 insertions(+), 49 deletions(-) (limited to 'core/vm') diff --git a/core/vm/environment.go b/core/vm/environment.go index f8996e648..e97c1e58c 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -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. diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 94a8c6348..4f98953b5 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -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)) diff --git a/core/vm/jit.go b/core/vm/jit.go index 55d2e0477..b75558d39 100644 --- a/core/vm/jit.go +++ b/core/vm/jit.go @@ -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++ diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go index a6de710e1..f9c0e8fa2 100644 --- a/core/vm/jit_test.go +++ b/core/vm/jit_test.go @@ -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 ¶ms.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) } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 255c4f189..4e997f636 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -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 diff --git a/core/vm/jump_table_test.go b/core/vm/jump_table_test.go index 4fcb4a220..6a6bb5cf4 100644 --- a/core/vm/jump_table_test.go +++ b/core/vm/jump_table_test.go @@ -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(¶ms.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(¶ms.ChainConfig{HomesteadBlock: big.NewInt(1)}, big.NewInt(n)) if !jumpTable[DELEGATECALL].valid { t.Error("Expected DELEGATECALL to be present for block", n) } diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 59fbaa792..f1a2b60d3 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -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) } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 343aee514..f2d86a324 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -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 = ¶ms.ChainConfig{new(big.Int), new(big.Int), false, new(big.Int), common.Hash{}, new(big.Int)} } if cfg.Difficulty == nil { diff --git a/core/vm/vm.go b/core/vm/vm.go index 09cddc2f8..6e316acda 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -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]) -- cgit