From 4c0e2f32f9fd84ccc2322141630f07772a957134 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Sat, 14 May 2016 17:08:38 +0200 Subject: [PATCH 1/9] common/math, core/vm, core/state: EVM gas calculations to 64bit This changes the internals of the EVM-JIT by dropping support for blocks (and therefor also transactions) that can exceed a 64^2-1 gas-limit. This means that the EVM-JIT can drop support for abritrary precision (*big.Int) and move to 64bit based gas calculations. For obvious reasons integer overflows have added to make sure that gas calculations such as gas for memory doesn't overflow 64bit. If the overflow check fails it throws an out-of-gas error and will halt the execution of the code. --- cmd/evm/main.go | 33 +++--- common/big.go | 6 +- common/math/integer.go | 22 ++++ common/math/integer_test.go | 50 ++++++++ core/execution.go | 36 +++--- core/state/state_object.go | 2 +- core/state_transition.go | 4 +- core/vm/common.go | 36 ------ core/vm/contract.go | 37 +++--- core/vm/environment.go | 12 +- core/vm/gas.go | 36 ++++++ core/vm/instructions.go | 24 ++-- core/vm/jit.go | 224 ++++++++++++++++++++++++------------ core/vm/jit_optimiser.go | 20 ++-- core/vm/jit_test.go | 39 +++---- core/vm/logger_test.go | 12 +- core/vm/memory.go | 3 +- core/vm/runtime/env.go | 19 +-- core/vm/runtime/runtime.go | 2 - core/vm/segments.go | 4 +- core/vm/uint64.go | 175 ++++++++++++++++++++++++++++ core/vm/vm.go | 23 ++-- core/vm_env.go | 17 +-- tests/state_test_util.go | 7 +- tests/util.go | 38 +++--- tests/vm_test_util.go | 7 +- 26 files changed, 604 insertions(+), 284 deletions(-) create mode 100644 common/math/integer.go create mode 100644 common/math/integer_test.go create mode 100644 core/vm/uint64.go diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 22707c1cc3..8d3c8f2c5a 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -121,8 +121,7 @@ func run(ctx *cli.Context) error { sender := statedb.CreateAccount(common.StringToAddress("sender")) logger := vm.NewStructLogger(nil) - - vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)), vm.Config{ + vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)), common.Big(ctx.GlobalString(PriceFlag.Name)), vm.Config{ Debug: ctx.GlobalBool(DebugFlag.Name), ForceJit: ctx.GlobalBool(ForceJitFlag.Name), EnableJit: !ctx.GlobalBool(DisableJitFlag.Name), @@ -142,7 +141,6 @@ func run(ctx *cli.Context) error { sender, input, common.Big(ctx.GlobalString(GasFlag.Name)), - common.Big(ctx.GlobalString(PriceFlag.Name)), common.Big(ctx.GlobalString(ValueFlag.Name)), ) } else { @@ -155,7 +153,6 @@ func run(ctx *cli.Context) error { receiver.Address(), common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), common.Big(ctx.GlobalString(GasFlag.Name)), - common.Big(ctx.GlobalString(PriceFlag.Name)), common.Big(ctx.GlobalString(ValueFlag.Name)), ) } @@ -202,19 +199,20 @@ type VMEnv struct { transactor *common.Address value *big.Int - depth int - Gas *big.Int - time *big.Int - logs []vm.StructLog + depth int + Gas, price *big.Int + time *big.Int + logs []vm.StructLog evm *vm.EVM } -func NewEnv(state *state.StateDB, transactor common.Address, value *big.Int, cfg vm.Config) *VMEnv { +func NewEnv(state *state.StateDB, transactor common.Address, value, price *big.Int, cfg vm.Config) *VMEnv { env := &VMEnv{ state: state, transactor: &transactor, value: value, + price: price, time: big.NewInt(time.Now().Unix()), } @@ -240,6 +238,7 @@ func (self *VMEnv) Difficulty() *big.Int { return common.Big1 } func (self *VMEnv) BlockHash() []byte { return make([]byte, 32) } func (self *VMEnv) Value() *big.Int { return self.value } func (self *VMEnv) GasLimit() *big.Int { return big.NewInt(1000000000) } +func (self *VMEnv) GasPrice() *big.Int { return big.NewInt(1000000000) } func (self *VMEnv) VmType() vm.Type { return vm.StdVmTy } func (self *VMEnv) Depth() int { return 0 } func (self *VMEnv) SetDepth(i int) { self.depth = i } @@ -259,19 +258,19 @@ func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) { core.Transfer(from, to, amount) } -func (self *VMEnv) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { +func (self *VMEnv) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { self.Gas = gas - return core.Call(self, caller, addr, data, gas, price, value) + return core.Call(self, caller, addr, data, gas, value) } -func (self *VMEnv) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { - return core.CallCode(self, caller, addr, data, gas, price, value) +func (self *VMEnv) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + return core.CallCode(self, caller, addr, data, gas, value) } -func (self *VMEnv) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { - return core.DelegateCall(self, caller, addr, data, gas, price) +func (self *VMEnv) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { + return core.DelegateCall(self, caller, addr, data, gas) } -func (self *VMEnv) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { - return core.Create(self, caller, data, gas, price, value) +func (self *VMEnv) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { + return core.Create(self, caller, data, gas, value) } diff --git a/common/big.go b/common/big.go index 4ce87ee0c6..0282db68cf 100644 --- a/common/big.go +++ b/common/big.go @@ -16,7 +16,10 @@ package common -import "math/big" +import ( + "math" + "math/big" +) // Common big integers often used var ( @@ -33,6 +36,7 @@ var ( Big256 = big.NewInt(0xff) Big257 = big.NewInt(257) MaxBig = String2Big("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + Max64Bit = new(big.Int).SetUint64(math.MaxUint64) ) // Big pow diff --git a/common/math/integer.go b/common/math/integer.go new file mode 100644 index 0000000000..041cf3c700 --- /dev/null +++ b/common/math/integer.go @@ -0,0 +1,22 @@ +package math + +import gmath "math" + +/* + * NOTE: The following methods need to be optimised using either bit checking or asm + */ + +// IsMinSafe returns whether the subtraction is safe and does not overflow. +func IsSubSafe(x, y uint64) bool { + return x >= y +} + +// IsAddSafe returns whether the addition is safe and does not overflow. +func IsAddSafe(x, y uint64) bool { + return y <= gmath.MaxUint64-x +} + +// IsAddSafe returns whether the multiplication is safe and does not overflow. +func IsMulSafe(x, y uint64) bool { + return x == 0 || y == 0 || y <= gmath.MaxUint64/x +} diff --git a/common/math/integer_test.go b/common/math/integer_test.go new file mode 100644 index 0000000000..4e61c8a140 --- /dev/null +++ b/common/math/integer_test.go @@ -0,0 +1,50 @@ +package math + +import ( + gmath "math" + "testing" +) + +type operation byte + +const ( + sub operation = iota + add + mul +) + +func TestIsAddSafe(t *testing.T) { + for i, test := range []struct { + x uint64 + y uint64 + safe bool + op operation + }{ + // add operations + {gmath.MaxUint64, 1, false, add}, + {gmath.MaxUint64 - 1, 1, true, add}, + + // sub operations + {0, 1, false, sub}, + {0, 0, true, sub}, + + // mul operations + {10, 10, true, mul}, + {gmath.MaxUint64, 2, false, mul}, + {gmath.MaxUint64, 1, true, mul}, + } { + var isSafe bool + switch test.op { + case sub: + isSafe = IsSubSafe(test.x, test.y) + case add: + isSafe = IsAddSafe(test.x, test.y) + case mul: + isSafe = IsMulSafe(test.x, test.y) + } + + if test.safe != isSafe { + t.Errorf("%d failed. Expected test to be %v, got %v", i, test.safe, isSafe) + } + } +} diff --git a/core/execution.go b/core/execution.go index 1cb507ee78..8f519b24c7 100644 --- a/core/execution.go +++ b/core/execution.go @@ -26,30 +26,30 @@ import ( ) // Call executes within the given contract -func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) { - ret, _, err = exec(env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value) +func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, value *big.Int) (ret []byte, err error) { + ret, _, err = exec(env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, value) return ret, err } // CallCode executes the given address' code as the given contract address -func CallCode(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) { +func CallCode(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, value *big.Int) (ret []byte, err error) { callerAddr := caller.Address() - ret, _, err = exec(env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, value) + ret, _, err = exec(env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, value) return ret, err } // DelegateCall is equivalent to CallCode except that sender and value propagates from parent scope to child scope -func DelegateCall(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice *big.Int) (ret []byte, err error) { +func DelegateCall(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas *big.Int) (ret []byte, err error) { callerAddr := caller.Address() originAddr := env.Origin() callerValue := caller.Value() - ret, _, err = execDelegateCall(env, caller, &originAddr, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, gasPrice, callerValue) + ret, _, err = execDelegateCall(env, caller, &originAddr, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, callerValue) return ret, err } // Create creates a new contract with the given code -func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPrice, value *big.Int) (ret []byte, address common.Address, err error) { - ret, address, err = exec(env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, gasPrice, value) +func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, value *big.Int) (ret []byte, address common.Address, err error) { + ret, address, err = exec(env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, value) // Here we get an error if we run into maximum stack depth, // See: https://github.com/ethereum/yellowpaper/pull/131 // and YP definitions for CREATE instruction @@ -59,18 +59,18 @@ func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPric return ret, address, err } -func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) { +func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, value *big.Int) (ret []byte, addr common.Address, err error) { evm := env.Vm() // Depth check execution. Fail if we're trying to execute above the // limit. if env.Depth() > int(params.CallCreateDepth.Int64()) { - caller.ReturnGas(gas, gasPrice) + caller.ReturnGas(gas.Uint64()) return nil, common.Address{}, vm.DepthError } if !env.CanTransfer(caller.Address(), value) { - caller.ReturnGas(gas, gasPrice) + caller.ReturnGas(gas.Uint64()) return nil, common.Address{}, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address())) } @@ -104,7 +104,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A // initialise a new contract and set the code that is to be used by the // EVM. The contract is a scoped environment for this execution context // only. - contract := vm.NewContract(caller, to, value, gas, gasPrice) + contract := vm.NewContract(caller, to, value, gas) contract.SetCallCode(codeAddr, codeHash, code) defer contract.Finalise() @@ -116,7 +116,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A if err == nil && createAccount { dataGas := big.NewInt(int64(len(ret))) dataGas.Mul(dataGas, params.CreateDataGas) - if contract.UseGas(dataGas) { + if contract.UseGas(dataGas.Uint64()) { env.Db().SetCode(*address, ret) } else { err = vm.CodeStoreOutOfGasError @@ -127,7 +127,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in homestead this also counts for code storage gas errors. if err != nil && (env.RuleSet().IsHomestead(env.BlockNumber()) || err != vm.CodeStoreOutOfGasError) { - contract.UseGas(contract.Gas) + contract.UseGas(contract.Gas()) env.RevertToSnapshot(snapshotPreTransfer) } @@ -135,12 +135,12 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A return ret, addr, err } -func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) { +func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, value *big.Int) (ret []byte, addr common.Address, err error) { evm := env.Vm() // Depth check execution. Fail if we're trying to execute above the // limit. if env.Depth() > int(params.CallCreateDepth.Int64()) { - caller.ReturnGas(gas, gasPrice) + caller.ReturnGas(gas.Uint64()) return nil, common.Address{}, vm.DepthError } @@ -154,13 +154,13 @@ func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toA } // Iinitialise a new contract and make initialise the delegate values - contract := vm.NewContract(caller, to, value, gas, gasPrice).AsDelegate() + contract := vm.NewContract(caller, to, value, gas).AsDelegate() contract.SetCallCode(codeAddr, codeHash, code) defer contract.Finalise() ret, err = evm.Run(contract, input) if err != nil { - contract.UseGas(contract.Gas) + contract.UseGas(contract.Gas()) env.RevertToSnapshot(snapshot) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 6eab27d9e7..c4fd89afdc 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -260,7 +260,7 @@ func (self *StateObject) setBalance(amount *big.Int) { } // Return the gas back to the origin. Used by the Virtual machine or Closures -func (c *StateObject) ReturnGas(gas, price *big.Int) {} +func (c *StateObject) ReturnGas(gas uint64) {} func (self *StateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *StateObject { stateObject := newObject(db, self.address, self.data, onDirty) diff --git a/core/state_transition.go b/core/state_transition.go index 9e6b2f567e..70f1844838 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -244,7 +244,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b vmenv := self.env //var addr common.Address if contractCreation { - ret, _, err = vmenv.Create(sender, self.data, self.gas, self.gasPrice, self.value) + ret, _, err = vmenv.Create(sender, self.data, self.gas, self.value) if homestead && err == vm.CodeStoreOutOfGasError { self.gas = Big0 } @@ -256,7 +256,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b } else { // Increment the nonce for the next transaction self.state.SetNonce(sender.Address(), self.state.GetNonce(sender.Address())+1) - ret, err = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.gasPrice, self.value) + ret, err = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.value) if err != nil { glog.V(logger.Core).Infoln("VM call err:", err) } diff --git a/core/vm/common.go b/core/vm/common.go index 2878b92d2a..a17a6496b5 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -21,7 +21,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params" ) // Type is the VM type accepted by **NewVm** @@ -45,41 +44,6 @@ var ( max = big.NewInt(math.MaxInt64) // Maximum 64 bit integer ) -// calculates the memory size required for a step -func calcMemSize(off, l *big.Int) *big.Int { - if l.Cmp(common.Big0) == 0 { - return common.Big0 - } - - return new(big.Int).Add(off, l) -} - -// calculates the quadratic gas -func quadMemGas(mem *Memory, newMemSize, gas *big.Int) { - if newMemSize.Cmp(common.Big0) > 0 { - newMemSizeWords := toWordSize(newMemSize) - newMemSize.Mul(newMemSizeWords, u256(32)) - - if newMemSize.Cmp(u256(int64(mem.Len()))) > 0 { - // be careful reusing variables here when changing. - // The order has been optimised to reduce allocation - oldSize := toWordSize(big.NewInt(int64(mem.Len()))) - pow := new(big.Int).Exp(oldSize, common.Big2, Zero) - linCoef := oldSize.Mul(oldSize, params.MemoryGas) - quadCoef := new(big.Int).Div(pow, params.QuadCoeffDiv) - oldTotalFee := new(big.Int).Add(linCoef, quadCoef) - - pow.Exp(newMemSizeWords, common.Big2, Zero) - linCoef = linCoef.Mul(newMemSizeWords, params.MemoryGas) - quadCoef = quadCoef.Div(pow, params.QuadCoeffDiv) - newTotalFee := linCoef.Add(linCoef, quadCoef) - - fee := newTotalFee.Sub(newTotalFee, oldTotalFee) - gas.Add(gas, fee) - } - } -} - // Simple helper func u256(n int64) *big.Int { return big.NewInt(n) diff --git a/core/vm/contract.go b/core/vm/contract.go index 70455a4c23..34c713642d 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -24,7 +24,7 @@ import ( // ContractRef is a reference to the contract's backing object type ContractRef interface { - ReturnGas(*big.Int, *big.Int) + ReturnGas(uint64) Address() common.Address Value() *big.Int SetCode(common.Hash, []byte) @@ -48,7 +48,8 @@ type Contract struct { CodeAddr *common.Address Input []byte - value, Gas, UsedGas, Price *big.Int + value, gas *big.Int + gas64 uint64 Args []byte @@ -56,7 +57,7 @@ type Contract struct { } // NewContract returns a new contract environment for the execution of EVM. -func NewContract(caller ContractRef, object ContractRef, value, gas, price *big.Int) *Contract { +func NewContract(caller ContractRef, object ContractRef, value, gas *big.Int) *Contract { c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object, Args: nil} if parent, ok := caller.(*Contract); ok { @@ -68,12 +69,9 @@ func NewContract(caller ContractRef, object ContractRef, value, gas, price *big. // Gas should be a pointer so it can safely be reduced through the run // This pointer will be off the state transition - c.Gas = gas //new(big.Int).Set(gas) + c.gas = gas //new(big.Int).Set(gas) + c.gas64 = gas.Uint64() c.value = new(big.Int).Set(value) - // In most cases price and value are pointers to transaction objects - // and we don't want the transaction's values to change. - c.Price = new(big.Int).Set(price) - c.UsedGas = new(big.Int) return c } @@ -102,6 +100,11 @@ func (c *Contract) GetByte(n uint64) byte { return 0 } +// Gas returns the current gas left +func (c *Contract) Gas() uint64 { + return c.gas64 +} + // Caller returns the caller of the contract. // // Caller will recursively call caller when the contract is a delegate @@ -113,24 +116,24 @@ func (c *Contract) Caller() common.Address { // Finalise finalises the contract and returning any remaining gas to the original // caller. func (c *Contract) Finalise() { + c.gas.SetUint64(c.gas64) // Return the remaining gas to the caller - c.caller.ReturnGas(c.Gas, c.Price) + c.caller.ReturnGas(c.gas64) } // UseGas attempts the use gas and subtracts it and returns true on success -func (c *Contract) UseGas(gas *big.Int) (ok bool) { - ok = useGas(c.Gas, gas) - if ok { - c.UsedGas.Add(c.UsedGas, gas) +func (c *Contract) UseGas(gas uint64) (ok bool) { + if c.gas64 < gas { + return false } - return + c.gas64 -= gas + return true } // ReturnGas adds the given gas back to itself. -func (c *Contract) ReturnGas(gas, price *big.Int) { +func (c *Contract) ReturnGas(gas uint64) { // Return the gas to the context - c.Gas.Add(c.Gas, gas) - c.UsedGas.Sub(c.UsedGas, gas) + c.gas64 += gas } // Address returns the contracts address diff --git a/core/vm/environment.go b/core/vm/environment.go index a4b2ac1966..4d55daaa12 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -53,6 +53,8 @@ type Environment interface { Difficulty() *big.Int // The gas limit of the block GasLimit() *big.Int + // The gas price of the current call + GasPrice() *big.Int // Determines whether it's possible to transact CanTransfer(from common.Address, balance *big.Int) bool // Transfers amount from one account to the other @@ -66,13 +68,13 @@ type Environment interface { // Set the current calling depth SetDepth(i int) // Call another contract - Call(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) + Call(me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) // Take another's contract code and execute within our own context - CallCode(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) + CallCode(me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) // Same as CallCode except sender and value is propagated from parent to child scope - DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) + DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) // Create a new contract - Create(me ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) + Create(me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) } // Vm is the basic interface for an implementation of the EVM. @@ -121,7 +123,7 @@ type Account interface { SetNonce(uint64) Balance() *big.Int Address() common.Address - ReturnGas(*big.Int, *big.Int) + ReturnGas(uint64) SetCode(common.Hash, []byte) ForEachStorage(cb func(key, value common.Hash) bool) Value() *big.Int diff --git a/core/vm/gas.go b/core/vm/gas.go index eb2c163463..440ee864d7 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -20,6 +20,7 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" ) @@ -72,6 +73,41 @@ func toWordSize(size *big.Int) *big.Int { return tmp } +// calculates the memory size required for a step +func calcMemSize(off, l *big.Int) *big.Int { + if l.Cmp(common.Big0) == 0 { + return common.Big0 + } + + return new(big.Int).Add(off, l) +} + +// calculates the quadratic gas +func quadMemGas(mem *Memory, newMemSize, gas *big.Int) { + if newMemSize.Cmp(common.Big0) > 0 { + newMemSizeWords := toWordSize(newMemSize) + newMemSize.Mul(newMemSizeWords, u256(32)) + + if newMemSize.Cmp(u256(int64(mem.Len()))) > 0 { + // be careful reusing variables here when changing. + // The order has been optimised to reduce allocation + oldSize := toWordSize(big.NewInt(int64(mem.Len()))) + pow := new(big.Int).Exp(oldSize, common.Big2, Zero) + linCoef := oldSize.Mul(oldSize, params.MemoryGas) + quadCoef := new(big.Int).Div(pow, params.QuadCoeffDiv) + oldTotalFee := new(big.Int).Add(linCoef, quadCoef) + + pow.Exp(newMemSizeWords, common.Big2, Zero) + linCoef = linCoef.Mul(newMemSizeWords, params.MemoryGas) + quadCoef = quadCoef.Div(pow, params.QuadCoeffDiv) + newTotalFee := linCoef.Add(linCoef, quadCoef) + + fee := newTotalFee.Sub(newTotalFee, oldTotalFee) + gas.Add(gas, fee) + } + } +} + type req struct { stackPop int gas *big.Int diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 79aee60d28..e16e07dbeb 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -42,7 +42,7 @@ type instruction struct { fn instrFn data *big.Int - gas *big.Int + gas uint64 spop int spush int @@ -71,7 +71,7 @@ func (instr instruction) do(program *Program, pc *uint64, env Environment, contr return nil, OutOfGasError } // Resize the memory calculated previously - memory.Resize(newMemSize.Uint64()) + memory.Resize(newMemSize) // These opcodes return an argument and are therefor handled // differently from the rest of the opcodes @@ -316,7 +316,7 @@ func opMulmod(instr instruction, pc *uint64, env Environment, contract *Contract func opSha3(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { offset, size := stack.pop(), stack.pop() - hash := crypto.Keccak256(memory.Get(offset.Int64(), size.Int64())) + hash := crypto.Keccak256(memory.GetPtr(offset.Int64(), size.Int64())) stack.push(common.BytesToBig(hash)) } @@ -396,7 +396,7 @@ func opExtCodeCopy(instr instruction, pc *uint64, env Environment, contract *Con } func opGasprice(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { - stack.push(new(big.Int).Set(contract.Price)) + stack.push(new(big.Int).Set(env.GasPrice())) } func opBlockhash(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { @@ -461,7 +461,7 @@ func opLog(instr instruction, pc *uint64, env Environment, contract *Contract, m func opMload(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { offset := stack.pop() - val := common.BigD(memory.Get(offset.Int64(), 32)) + val := common.BigD(memory.GetPtr(offset.Int64(), 32)) stack.push(val) } @@ -504,7 +504,7 @@ func opMsize(instr instruction, pc *uint64, env Environment, contract *Contract, } func opGas(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { - stack.push(new(big.Int).Set(contract.Gas)) + stack.push(new(big.Int).SetUint64(contract.gas64)) } func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { @@ -512,10 +512,10 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract value = stack.pop() offset, size = stack.pop(), stack.pop() input = memory.Get(offset.Int64(), size.Int64()) - gas = new(big.Int).Set(contract.Gas) + gas = new(big.Int).SetUint64(contract.gas64) ) - contract.UseGas(contract.Gas) - _, addr, suberr := env.Create(contract, input, gas, contract.Price, value) + contract.UseGas(contract.gas64) + _, addr, suberr := env.Create(contract, input, gas, value) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must @@ -548,7 +548,7 @@ func opCall(instr instruction, pc *uint64, env Environment, contract *Contract, gas.Add(gas, params.CallStipend) } - ret, err := env.Call(contract, address, args, gas, contract.Price, value) + ret, err := env.Call(contract, address, args, gas, value) if err != nil { stack.push(new(big.Int)) @@ -579,7 +579,7 @@ func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contra gas.Add(gas, params.CallStipend) } - ret, err := env.CallCode(contract, address, args, gas, contract.Price, value) + ret, err := env.CallCode(contract, address, args, gas, value) if err != nil { stack.push(new(big.Int)) @@ -596,7 +596,7 @@ func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Co toAddr := common.BigToAddress(to) args := memory.Get(inOffset.Int64(), inSize.Int64()) - ret, err := env.DelegateCall(contract, toAddr, args, gas, contract.Price) + ret, err := env.DelegateCall(contract, toAddr, args, gas) if err != nil { stack.push(new(big.Int)) } else { diff --git a/core/vm/jit.go b/core/vm/jit.go index 55d2e04775..39635034cb 100644 --- a/core/vm/jit.go +++ b/core/vm/jit.go @@ -18,11 +18,13 @@ package vm import ( "fmt" + gmath "math" "math/big" "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -114,7 +116,7 @@ func (p *Program) addInstr(op OpCode, pc uint64, fn instrFn, data *big.Int) { if op >= DUP1 && op <= DUP16 { baseOp = DUP1 } - base := _baseCheck[baseOp] + base := _baseCheck64[baseOp] returns := op == RETURN || op == SUICIDE || op == STOP instr := instruction{op, pc, fn, data, base.gas, base.stackPop, base.stackPush, returns} @@ -357,14 +359,15 @@ func validDest(dests map[uint64]struct{}, dest *big.Int) bool { // jitCalculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for // the operation. This does not reduce gas or resizes the memory. -func jitCalculateGasAndSize(env Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) { +func jitCalculateGasAndSize(env Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *Stack) (uint64, uint64, error) { var ( - gas = new(big.Int) - newMemSize *big.Int = new(big.Int) + newMemSize uint64 + sizeFault bool ) - err := jitBaseCheck(instr, stack, gas) + + gas, err := jitBaseCalc(instr, stack) if err != nil { - return nil, nil, err + return 0, 0, err } // stack Check, memory resize & gas phase @@ -373,40 +376,57 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi n := int(op - SWAP1 + 2) err := stack.require(n) if err != nil { - return nil, nil, err + return 0, 0, err } - gas.Set(GasFastestStep) + gas = GasFastestStep64 case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: n := int(op - DUP1 + 1) err := stack.require(n) if err != nil { - return nil, nil, err + return 0, 0, err } - gas.Set(GasFastestStep) + gas = GasFastestStep64 case LOG0, LOG1, LOG2, LOG3, LOG4: n := int(op - LOG0) err := stack.require(n + 2) if err != nil { - return nil, nil, err + return 0, 0, err } mSize, mStart := stack.data[stack.len()-2], stack.data[stack.len()-1] + if mSize.BitLen() > 64 { + return 0, 0, OutOfGasError + } + msize64 := mSize.Uint64() - add := new(big.Int) - gas.Add(gas, params.LogGas) - gas.Add(gas, add.Mul(big.NewInt(int64(n)), params.LogTopicGas)) - gas.Add(gas, add.Mul(mSize, params.LogDataGas)) + gas = (gas + LogGas64) + (uint64(n) * LogTopicGas64) + if !math.IsMulSafe(msize64, LogDataGas64) { + return 0, 0, OutOfGasError + } + gasLogData := msize64 * LogDataGas64 - newMemSize = calcMemSize(mStart, mSize) + if !math.IsAddSafe(gas, gasLogData) { + return 0, 0, OutOfGasError + } + gas += gasLogData + + newMemSize, sizeFault = calcMemSize64(mStart, mSize) case EXP: - gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(len(stack.data[stack.len()-2].Bytes()))), params.ExpByteGas)) + x := uint64(len(stack.data[stack.len()-2].Bytes())) + if !math.IsMulSafe(x, ExpByteGas64) { + return 0, 0, OutOfGasError + } + x *= ExpByteGas64 + if !math.IsAddSafe(gas, x) { + return 0, 0, OutOfGasError + } + gas += x case SSTORE: err := stack.require(2) if err != nil { - return nil, nil, err + return 0, 0, err } - var g *big.Int y, x := stack.data[stack.len()-2], stack.data[stack.len()-1] val := statedb.GetState(contract.Address(), common.BigToHash(x)) @@ -415,98 +435,152 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi // 2. From a non-zero value address to a zero-value address (DELETE) // 3. From a non-zero to a non-zero (CHANGE) if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) { - g = params.SstoreSetGas + gas = SstoreSetGas64 } else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) { statedb.AddRefund(params.SstoreRefundGas) - g = params.SstoreClearGas + gas = SstoreClearGas64 } else { - g = params.SstoreResetGas + gas = SstoreResetGas64 } - gas.Set(g) case SUICIDE: if !statedb.HasSuicided(contract.Address()) { statedb.AddRefund(params.SuicideRefundGas) } case MLOAD: - newMemSize = calcMemSize(stack.peek(), u256(32)) + newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(32)) case MSTORE8: - newMemSize = calcMemSize(stack.peek(), u256(1)) + newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(1)) case MSTORE: - newMemSize = calcMemSize(stack.peek(), u256(32)) + newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(32)) case RETURN: - newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) + newMemSize, sizeFault = calcMemSize64(stack.peek(), stack.data[stack.len()-2]) case SHA3: - newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) + newMemSize, sizeFault = calcMemSize64(stack.peek(), stack.data[stack.len()-2]) + if sizeFault { + return 0, 0, OutOfGasError + } - words := toWordSize(stack.data[stack.len()-2]) - gas.Add(gas, words.Mul(words, params.Sha3WordGas)) + if stack.data[stack.len()-2].BitLen() > 64 { + return 0, 0, OutOfGasError + } + words := toWordSize64(stack.data[stack.len()-2].Uint64()) + if !math.IsMulSafe(words, KeccakWordGas64) { + return 0, 0, OutOfGasError + } + wordsGas := words * KeccakWordGas64 + if !math.IsAddSafe(gas, wordsGas) { + return 0, 0, OutOfGasError + } + gas += wordsGas case CALLDATACOPY: - newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3]) + newMemSize, sizeFault = calcMemSize64(stack.peek(), stack.data[stack.len()-3]) + if sizeFault { + return 0, 0, OutOfGasError + } - words := toWordSize(stack.data[stack.len()-3]) - gas.Add(gas, words.Mul(words, params.CopyGas)) + words := toWordSize64(stack.data[stack.len()-3].Uint64()) + if !math.IsMulSafe(words, CopyGas64) { + return 0, 0, OutOfGasError + } + wordsGas := words * CopyGas64 + if !math.IsAddSafe(gas, wordsGas) { + return 0, 0, OutOfGasError + } + gas += wordsGas case CODECOPY: - newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3]) + newMemSize, sizeFault = calcMemSize64(stack.peek(), stack.data[stack.len()-3]) + if sizeFault { + return 0, 0, OutOfGasError + } - words := toWordSize(stack.data[stack.len()-3]) - gas.Add(gas, words.Mul(words, params.CopyGas)) + words := toWordSize64(stack.data[stack.len()-3].Uint64()) + if !math.IsMulSafe(words, CopyGas64) { + return 0, 0, OutOfGasError + } + wordsGas := words * CopyGas64 + if !math.IsAddSafe(gas, wordsGas) { + return 0, 0, OutOfGasError + } + gas += wordsGas case EXTCODECOPY: - newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-4]) - - words := toWordSize(stack.data[stack.len()-4]) - gas.Add(gas, words.Mul(words, params.CopyGas)) + newMemSize, sizeFault = calcMemSize64(stack.data[stack.len()-2], stack.data[stack.len()-4]) + if sizeFault { + return 0, 0, OutOfGasError + } + words := toWordSize64(stack.data[stack.len()-4].Uint64()) + if !math.IsMulSafe(words, CopyGas64) { + return 0, 0, OutOfGasError + } + wordsGas := words * CopyGas64 + if !math.IsAddSafe(gas, wordsGas) { + return 0, 0, OutOfGasError + } + gas += wordsGas case CREATE: - newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-3]) + newMemSize, sizeFault = calcMemSize64(stack.data[stack.len()-2], stack.data[stack.len()-3]) case CALL, CALLCODE: - gas.Add(gas, stack.data[stack.len()-1]) + callGas := stack.data[stack.len()-1] + if callGas.BitLen() > 64 { + return 0, 0, OutOfGasError + } + gas += callGas.Uint64() if op == CALL { if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) { - gas.Add(gas, params.CallNewAccountGas) + if !math.IsAddSafe(gas, CallNewAccountGas64) { + return 0, 0, OutOfGasError + } + gas += CallNewAccountGas64 } } if len(stack.data[stack.len()-3].Bytes()) > 0 { - gas.Add(gas, params.CallValueTransferGas) + if !math.IsAddSafe(gas, CallValueTransferGas64) { + return 0, 0, OutOfGasError + } + gas += CallValueTransferGas64 } - x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7]) - y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5]) + x, xSizeFault := calcMemSize64(stack.data[stack.len()-6], stack.data[stack.len()-7]) + if xSizeFault { + return 0, 0, OutOfGasError + } + y, ySizeFault := calcMemSize64(stack.data[stack.len()-4], stack.data[stack.len()-5]) + if ySizeFault { + return 0, 0, OutOfGasError + } - newMemSize = common.BigMax(x, y) + newMemSize = x + if y > newMemSize { + newMemSize = y + } case DELEGATECALL: - gas.Add(gas, stack.data[stack.len()-1]) + callGas := stack.data[stack.len()-1] + if callGas.BitLen() > 64 { + return 0, 0, OutOfGasError + } + gas += callGas.Uint64() - x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6]) - y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4]) + x, xSizeFault := calcMemSize64(stack.data[stack.len()-5], stack.data[stack.len()-6]) + if xSizeFault { + return 0, 0, OutOfGasError + } + y, ySizeFault := calcMemSize64(stack.data[stack.len()-3], stack.data[stack.len()-4]) + if ySizeFault { + return 0, 0, OutOfGasError + } - newMemSize = common.BigMax(x, y) + newMemSize = uint64(gmath.Max(float64(x), float64(y))) + } + if sizeFault { + return 0, 0, OutOfGasError } - quadMemGas(mem, newMemSize, gas) - return newMemSize, gas, nil -} - -// jitBaseCheck is the same as baseCheck except it doesn't do the look up in the -// gas table. This is done during compilation instead. -func jitBaseCheck(instr instruction, stack *Stack, gas *big.Int) error { - err := stack.require(instr.spop) - if err != nil { - return err - } - - if instr.spush > 0 && stack.len()-instr.spop+instr.spush > int(params.StackLimit.Int64()) { - return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit.Int64()) - } - - // nil on gas means no base calculation - if instr.gas == nil { - return nil - } - - gas.Add(gas, instr.gas) - - return nil + memGas, _ := calcQuadMemGas64(mem, newMemSize) + if !math.IsAddSafe(gas, memGas) { + return 0, 0, OutOfGasError + } + return toWordSize64(newMemSize) * 32, gas + memGas, nil } diff --git a/core/vm/jit_optimiser.go b/core/vm/jit_optimiser.go index 31ad0c2e2b..831af1ed10 100644 --- a/core/vm/jit_optimiser.go +++ b/core/vm/jit_optimiser.go @@ -30,15 +30,16 @@ func optimiseProgram(program *Program) { var load []instruction var ( - statsJump = 0 - statsPush = 0 + statsJump = 0 + statsOldPush = 0 + statsNewPush = 0 ) if glog.V(logger.Debug) { glog.Infof("optimising %x\n", program.Id[:4]) tstart := time.Now() defer func() { - glog.Infof("optimised %x done in %v with JMP: %d PSH: %d\n", program.Id[:4], time.Since(tstart), statsJump, statsPush) + glog.Infof("optimised %x done in %v with JMP: %d PSH: %d/%d\n", program.Id[:4], time.Since(tstart), statsJump, statsNewPush, statsOldPush) }() } @@ -65,6 +66,7 @@ func optimiseProgram(program *Program) { switch { case instr.op.IsPush(): load = append(load, instr) + statsOldPush++ case instr.op.IsStaticJump(): if len(load) == 0 { continue @@ -74,7 +76,7 @@ func optimiseProgram(program *Program) { if len(load) > 2 { seg, size := makePushSeg(load[:len(load)-1]) program.instructions[i-size-1] = seg - statsPush++ + statsNewPush++ } // create a segment consisting of a pre determined // jump, destination and validity. @@ -88,7 +90,7 @@ func optimiseProgram(program *Program) { if len(load) > 1 { seg, size := makePushSeg(load) program.instructions[i-size] = seg - statsPush++ + statsNewPush++ } load = nil } @@ -99,12 +101,12 @@ func optimiseProgram(program *Program) { func makePushSeg(instrs []instruction) (pushSeg, int) { var ( data []*big.Int - gas = new(big.Int) + gas uint64 ) for _, instr := range instrs { data = append(data, instr.data) - gas.Add(gas, instr.gas) + gas += instr.gas } return pushSeg{data, gas}, len(instrs) @@ -113,9 +115,7 @@ func makePushSeg(instrs []instruction) (pushSeg, int) { // makeStaticJumpSeg creates a new static jump segment from a predefined // destination (PUSH, JUMP). func makeStaticJumpSeg(to *big.Int, program *Program) jumpSeg { - gas := new(big.Int) - gas.Add(gas, _baseCheck[PUSH1].gas) - gas.Add(gas, _baseCheck[JUMP].gas) + gas := _baseCheck64[PUSH1].gas + _baseCheck64[JUMP].gas contract := &Contract{Code: program.code} pos, err := jump(program.mapping, program.destinations, contract, to) diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go index a6de710e13..b387dc90f1 100644 --- a/core/vm/jit_test.go +++ b/core/vm/jit_test.go @@ -29,10 +29,7 @@ const maxRun = 1000 func TestSegmenting(t *testing.T) { prog := NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, 0x0}) - err := CompileProgram(prog) - if err != nil { - t.Fatal(err) - } + CompileProgram(prog) if instr, ok := prog.instructions[0].(pushSeg); ok { if len(instr.data) != 2 { @@ -43,20 +40,16 @@ func TestSegmenting(t *testing.T) { } prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)}) - err = CompileProgram(prog) - if err != nil { - t.Fatal(err) - } + CompileProgram(prog) + if _, ok := prog.instructions[1].(jumpSeg); ok { } else { t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1]) } prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)}) - err = CompileProgram(prog) - if err != nil { - t.Fatal(err) - } + CompileProgram(prog) + if instr, ok := prog.instructions[0].(pushSeg); ok { if len(instr.data) != 2 { t.Error("expected 2 element width pushSegment, got", len(instr.data)) @@ -72,10 +65,7 @@ func TestSegmenting(t *testing.T) { func TestCompiling(t *testing.T) { prog := NewProgram([]byte{0x60, 0x10}) - err := CompileProgram(prog) - if err != nil { - t.Error("didn't expect compile error") - } + CompileProgram(prog) if len(prog.instructions) != 1 { t.Error("expected 1 compiled instruction, got", len(prog.instructions)) @@ -85,8 +75,8 @@ func TestCompiling(t *testing.T) { func TestResetInput(t *testing.T) { var sender account - env := NewEnv(&Config{EnableJit: true, ForceJit: true}) - contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0)) + env := NewEnv(true, true) + contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000)) contract.CodeAddr = &common.Address{} program := NewProgram([]byte{}) @@ -134,7 +124,7 @@ func (account) SetBalance(*big.Int) {} func (account) SetNonce(uint64) {} func (account) Balance() *big.Int { return nil } func (account) Address() common.Address { return common.Address{} } -func (account) ReturnGas(*big.Int, *big.Int) {} +func (account) ReturnGas(uint64) {} func (account) SetCode(common.Hash, []byte) {} func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} @@ -149,7 +139,7 @@ func runVmBench(test vmBench, b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0)) + context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000)) context.Code = test.code context.CodeAddr = &common.Address{} _, err := env.Vm().Run(context, test.input) @@ -174,6 +164,7 @@ func NewEnv(config *Config) *Env { func (self *Env) RuleSet() RuleSet { return ruleSet{new(big.Int)} } func (self *Env) Vm() Vm { return self.evm } +func (self *Env) GasPrice() *big.Int { return new(big.Int) } func (self *Env) Origin() common.Address { return common.Address{} } func (self *Env) BlockNumber() *big.Int { return big.NewInt(0) } @@ -197,15 +188,15 @@ func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool { return true } func (self *Env) Transfer(from, to Account, amount *big.Int) {} -func (self *Env) Call(caller ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { +func (self *Env) Call(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { return nil, nil } -func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { +func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { return nil, nil } -func (self *Env) Create(caller ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { +func (self *Env) Create(caller ContractRef, data []byte, gas, price *big.Int) ([]byte, common.Address, error) { return nil, common.Address{}, nil } -func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { +func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { return nil, nil } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index d4d164eb6f..a7aa0cfa0e 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -27,10 +27,10 @@ type dummyContractRef struct { calledForEach bool } -func (dummyContractRef) ReturnGas(*big.Int, *big.Int) {} -func (dummyContractRef) Address() common.Address { return common.Address{} } -func (dummyContractRef) Value() *big.Int { return new(big.Int) } -func (dummyContractRef) SetCode(common.Hash, []byte) {} +func (dummyContractRef) ReturnGas(uint64) {} +func (dummyContractRef) Address() common.Address { return common.Address{} } +func (dummyContractRef) Value() *big.Int { return new(big.Int) } +func (dummyContractRef) SetCode(common.Hash, []byte) {} func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) { d.calledForEach = true } @@ -61,7 +61,7 @@ func TestStoreCapture(t *testing.T) { logger = NewStructLogger(nil) mem = NewMemory() stack = newstack() - contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), new(big.Int), new(big.Int)) + contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), new(big.Int)) ) stack.push(big.NewInt(1)) stack.push(big.NewInt(0)) @@ -83,7 +83,7 @@ func TestStorageCapture(t *testing.T) { t.Skip("implementing this function is difficult. it requires all sort of interfaces to be implemented which isn't trivial. The value (the actual test) isn't worth it") var ( ref = &dummyContractRef{} - contract = NewContract(ref, ref, new(big.Int), new(big.Int), new(big.Int)) + contract = NewContract(ref, ref, new(big.Int), new(big.Int)) env = newDummyEnv(ref) logger = NewStructLogger(nil) mem = NewMemory() diff --git a/core/vm/memory.go b/core/vm/memory.go index d011884173..ddf3b605eb 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -21,10 +21,11 @@ import "fmt" // Memory implements a simple memory model for the ethereum virtual machine. type Memory struct { store []byte + cost uint64 } func NewMemory() *Memory { - return &Memory{nil} + return &Memory{} } // Set sets offset + size to value diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 59fbaa792a..0e0247f2f8 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -38,6 +38,7 @@ type Env struct { time *big.Int difficulty *big.Int gasLimit *big.Int + gasPrice *big.Int getHashFn func(uint64) common.Hash @@ -55,6 +56,7 @@ func NewEnv(cfg *Config, state *state.StateDB) vm.Environment { time: cfg.Time, difficulty: cfg.Difficulty, gasLimit: cfg.GasLimit, + gasPrice: cfg.GasPrice, } env.evm = vm.New(env, vm.Config{ Debug: cfg.Debug, @@ -66,6 +68,7 @@ func NewEnv(cfg *Config, state *state.StateDB) vm.Environment { } func (self *Env) RuleSet() vm.RuleSet { return self.ruleSet } +func (self *Env) GasPrice() *big.Int { return self.gasPrice } 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 } @@ -97,17 +100,17 @@ func (self *Env) Transfer(from, to vm.Account, amount *big.Int) { core.Transfer(from, to, amount) } -func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { - return core.Call(self, caller, addr, data, gas, price, value) +func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + return core.Call(self, caller, addr, data, gas, value) } -func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { - return core.CallCode(self, caller, addr, data, gas, price, value) +func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + return core.CallCode(self, caller, addr, data, gas, value) } -func (self *Env) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { - return core.DelegateCall(self, me, addr, data, gas, price) +func (self *Env) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { + return core.DelegateCall(self, me, addr, data, gas) } -func (self *Env) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { - return core.Create(self, caller, data, gas, price, value) +func (self *Env) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { + return core.Create(self, caller, data, gas, value) } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 6d980cb327..3c7e10c79d 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -112,7 +112,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { receiver.Address(), input, cfg.GasLimit, - cfg.GasPrice, cfg.Value, ) @@ -136,7 +135,6 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) { address, input, cfg.GasLimit, - cfg.GasPrice, cfg.Value, ) diff --git a/core/vm/segments.go b/core/vm/segments.go index 648d8a04a1..61d87367c6 100644 --- a/core/vm/segments.go +++ b/core/vm/segments.go @@ -21,7 +21,7 @@ import "math/big" type jumpSeg struct { pos uint64 err error - gas *big.Int + gas uint64 } func (j jumpSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { @@ -39,7 +39,7 @@ func (s jumpSeg) Op() OpCode { return 0 } type pushSeg struct { data []*big.Int - gas *big.Int + gas uint64 } func (s pushSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { diff --git a/core/vm/uint64.go b/core/vm/uint64.go new file mode 100644 index 0000000000..96d1e76364 --- /dev/null +++ b/core/vm/uint64.go @@ -0,0 +1,175 @@ +package vm + +import ( + "fmt" + gmath "math" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" +) + +var ( + maxInt63 = new(big.Int).Exp(big.NewInt(2), big.NewInt(63), big.NewInt(0)) + maxIntCap = new(big.Int).Sub(maxInt63, big.NewInt(1)) +) + +var ( + StackLimit64 = params.StackLimit.Uint64() + GasQuickStep64 uint64 = 2 + GasFastestStep64 uint64 = 3 + GasFastStep64 uint64 = 5 + GasMidStep64 uint64 = 8 + GasSlowStep64 uint64 = 10 + GasExtStep64 uint64 = 20 + + GasReturn64 uint64 = 0 + GasStop64 uint64 = 0 + + GasContractByte64 uint64 = 200 + LogGas64 = params.LogGas.Uint64() + LogTopicGas64 = params.LogTopicGas.Uint64() + LogDataGas64 = params.LogDataGas.Uint64() + ExpByteGas64 = params.ExpByteGas.Uint64() + SstoreSetGas64 = params.SstoreSetGas.Uint64() + SstoreClearGas64 = params.SstoreClearGas.Uint64() + SstoreResetGas64 = params.SstoreResetGas.Uint64() + KeccakWordGas64 = params.Sha3WordGas.Uint64() + CopyGas64 = params.CopyGas.Uint64() + CallNewAccountGas64 = params.CallNewAccountGas.Uint64() + CallValueTransferGas64 = params.CallValueTransferGas.Uint64() + MemoryGas64 = params.MemoryGas.Uint64() + QuadCoeffDiv64 = params.QuadCoeffDiv.Uint64() +) + +// casts a arbitrary number to the amount of words (sets of 32 bytes) +func toWordSize64(size uint64) uint64 { + return (size + 31) / 32 +} + +// calculates the memory size required for a step +func calcMemSize64(off, l *big.Int) (uint64, bool) { + if l.Cmp(common.Big0) == 0 { + return 0, false + } + size := new(big.Int).Add(off, l) + if size.Cmp(maxIntCap) > 0 { + return 0, true + } + return size.Uint64(), false +} + +// calculates the quadratic gas +// TODO this function requires guarding of overflows +func calcQuadMemGas64(mem *Memory, newMemSize uint64) (uint64, bool) { + oldTotalFee := mem.cost + if newMemSize > 0 { + newMemSizeWords := toWordSize64(newMemSize) + newMemSize = newMemSizeWords * 32 + + if newMemSize > uint64(mem.Len()) { + pow := uint64(gmath.Pow(float64(newMemSizeWords), 2)) + linCoef := newMemSizeWords * MemoryGas64 + quadCoef := pow / QuadCoeffDiv64 + newTotalFee := linCoef + quadCoef + + fee := newTotalFee - oldTotalFee + mem.cost = newTotalFee + + return fee, false + } + } + return 0, false +} + +// jitBaseCalc is the same as baseCheck except it doesn't do the look up in the +// gas table. This is done during compilation instead. +func jitBaseCalc(instr instruction, stack *Stack) (uint64, error) { + err := stack.require(instr.spop) + if err != nil { + return 0, err + } + + if instr.spush > 0 && stack.len()-instr.spop+instr.spush > int(StackLimit64) { + return 0, fmt.Errorf("stack limit reached %d (%d)", stack.len(), StackLimit64) + } + + // 0 on gas means no base calculation + if instr.gas == 0 { + return 0, nil + } + + return instr.gas, nil +} + +type req64 struct { + stackPop int + gas uint64 + stackPush int +} + +var _baseCheck64 = map[OpCode]req64{ + // opcode | stack pop | gas price | stack push + ADD: {2, GasFastestStep64, 1}, + LT: {2, GasFastestStep64, 1}, + GT: {2, GasFastestStep64, 1}, + SLT: {2, GasFastestStep64, 1}, + SGT: {2, GasFastestStep64, 1}, + EQ: {2, GasFastestStep64, 1}, + ISZERO: {1, GasFastestStep64, 1}, + SUB: {2, GasFastestStep64, 1}, + AND: {2, GasFastestStep64, 1}, + OR: {2, GasFastestStep64, 1}, + XOR: {2, GasFastestStep64, 1}, + NOT: {1, GasFastestStep64, 1}, + BYTE: {2, GasFastestStep64, 1}, + CALLDATALOAD: {1, GasFastestStep64, 1}, + CALLDATACOPY: {3, GasFastestStep64, 1}, + MLOAD: {1, GasFastestStep64, 1}, + MSTORE: {2, GasFastestStep64, 0}, + MSTORE8: {2, GasFastestStep64, 0}, + CODECOPY: {3, GasFastestStep64, 0}, + MUL: {2, GasFastStep64, 1}, + DIV: {2, GasFastStep64, 1}, + SDIV: {2, GasFastStep64, 1}, + MOD: {2, GasFastStep64, 1}, + SMOD: {2, GasFastStep64, 1}, + SIGNEXTEND: {2, GasFastStep64, 1}, + ADDMOD: {3, GasMidStep64, 1}, + MULMOD: {3, GasMidStep64, 1}, + JUMP: {1, GasMidStep64, 0}, + JUMPI: {2, GasSlowStep64, 0}, + EXP: {2, GasSlowStep64, 1}, + ADDRESS: {0, GasQuickStep64, 1}, + ORIGIN: {0, GasQuickStep64, 1}, + CALLER: {0, GasQuickStep64, 1}, + CALLVALUE: {0, GasQuickStep64, 1}, + CODESIZE: {0, GasQuickStep64, 1}, + GASPRICE: {0, GasQuickStep64, 1}, + COINBASE: {0, GasQuickStep64, 1}, + TIMESTAMP: {0, GasQuickStep64, 1}, + NUMBER: {0, GasQuickStep64, 1}, + CALLDATASIZE: {0, GasQuickStep64, 1}, + DIFFICULTY: {0, GasQuickStep64, 1}, + GASLIMIT: {0, GasQuickStep64, 1}, + POP: {1, GasQuickStep64, 0}, + PC: {0, GasQuickStep64, 1}, + MSIZE: {0, GasQuickStep64, 1}, + GAS: {0, GasQuickStep64, 1}, + BLOCKHASH: {1, GasExtStep64, 1}, + BALANCE: {1, GasExtStep64, 1}, + EXTCODESIZE: {1, GasExtStep64, 1}, + EXTCODECOPY: {4, GasExtStep64, 0}, + SLOAD: {1, params.SloadGas.Uint64(), 1}, + SSTORE: {2, 0, 0}, + SHA3: {2, params.Sha3Gas.Uint64(), 1}, + CREATE: {3, params.CreateGas.Uint64(), 1}, + CALL: {7, params.CallGas.Uint64(), 1}, + CALLCODE: {7, params.CallGas.Uint64(), 1}, + DELEGATECALL: {6, params.CallGas.Uint64(), 1}, + JUMPDEST: {0, params.JumpdestGas.Uint64(), 0}, + SUICIDE: {1, 0, 0}, + RETURN: {2, 0, 0}, + PUSH1: {0, GasFastestStep64, 1}, + DUP1: {0, 0, 1}, +} diff --git a/core/vm/vm.go b/core/vm/vm.go index 033ada21c5..7e81f856ad 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -142,7 +142,8 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { // User defer pattern to check for an error and, based on the error being nil or not, use all gas and return. defer func() { if err != nil && evm.cfg.Debug { - evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth(), err) + gas := new(big.Int).SetUint64(contract.gas64) + evm.cfg.Tracer.CaptureState(evm.env, pc, op, gas, cost, mem, stack, contract, evm.env.Depth(), err) } }() @@ -150,22 +151,11 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { glog.Infof("running byte VM %x\n", codehash[:4]) tstart := time.Now() defer func() { - glog.Infof("byte VM %x done. time: %v instrc: %v\n", codehash[:4], time.Since(tstart), instrCount) + glog.Infof("VM %x done. time: %v instrc: %v\n", codehash[:4], time.Since(tstart), instrCount) }() } for ; ; instrCount++ { - /* - if EnableJit && it%100 == 0 { - if program != nil && progStatus(atomic.LoadInt32(&program.status)) == progReady { - // move execution - fmt.Println("moved", it) - glog.V(logger.Info).Infoln("Moved execution to JIT") - return runProgram(program, pc, mem, stack, evm.env, contract, input) - } - } - */ - // Get the memory location of pc op = contract.GetOp(pc) // calculate the new memory size and gas price for the current executing opcode @@ -176,7 +166,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { // Use the calculated gas. When insufficient gas is present, use all gas and return an // Out Of Gas error - if !contract.UseGas(cost) { + if !contract.UseGas(cost.Uint64()) { return nil, OutOfGasError } @@ -184,7 +174,8 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { mem.Resize(newMemSize.Uint64()) // Add a log message if evm.cfg.Debug { - evm.cfg.Tracer.CaptureState(evm.env, pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth(), nil) + gas := new(big.Int).SetUint64(contract.gas64) + evm.cfg.Tracer.CaptureState(evm.env, pc, op, gas, cost, mem, stack, contract, evm.env.Depth(), nil) } if opPtr := evm.jumpTable[op]; opPtr.valid { @@ -370,7 +361,7 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef // RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go func (evm *EVM) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) { gas := p.Gas(len(input)) - if contract.UseGas(gas) { + if contract.UseGas(gas.Uint64()) { ret = p.Call(input) return ret, nil diff --git a/core/vm_env.go b/core/vm_env.go index d62eebbd9e..a95165f17c 100644 --- a/core/vm_env.go +++ b/core/vm_env.go @@ -74,6 +74,7 @@ func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase } func (self *VMEnv) Time() *big.Int { return self.header.Time } func (self *VMEnv) Difficulty() *big.Int { return self.header.Difficulty } func (self *VMEnv) GasLimit() *big.Int { return self.header.GasLimit } +func (self *VMEnv) GasPrice() *big.Int { return self.msg.GasPrice() } func (self *VMEnv) Value() *big.Int { return self.msg.Value() } func (self *VMEnv) Db() vm.Database { return self.state } func (self *VMEnv) Depth() int { return self.depth } @@ -101,17 +102,17 @@ func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) { Transfer(from, to, amount) } -func (self *VMEnv) Call(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { - return Call(self, me, addr, data, gas, price, value) +func (self *VMEnv) Call(me vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + return Call(self, me, addr, data, gas, value) } -func (self *VMEnv) CallCode(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { - return CallCode(self, me, addr, data, gas, price, value) +func (self *VMEnv) CallCode(me vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + return CallCode(self, me, addr, data, gas, value) } -func (self *VMEnv) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { - return DelegateCall(self, me, addr, data, gas, price) +func (self *VMEnv) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { + return DelegateCall(self, me, addr, data, gas) } -func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { - return Create(self, me, data, gas, price, value) +func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { + return Create(self, me, data, gas, value) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 9852715006..1aa00ef075 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -21,6 +21,7 @@ import ( "encoding/hex" "fmt" "io" + "math" "math/big" "strconv" "strings" @@ -109,7 +110,7 @@ func runStateTests(ruleSet RuleSet, tests map[string]VmTest, skipTests []string) } for name, test := range tests { - if skipTest[name] /*|| name != "callcodecallcode_11" */ { + if skipTest[name] /*|| name != "TestInputLimitsLight"*/ { glog.Infoln("Skipping state test", name) continue } @@ -149,6 +150,10 @@ func runStateTest(ruleSet RuleSet, test VmTest) error { // err error logs vm.Logs ) + if common.Big(test.Transaction["gasLimit"]).Cmp(new(big.Int).SetUint64(math.MaxUint64)) > 0 { + fmt.Println("skipping a test because of unsupported high gas") + return nil + } ret, logs, _, _ = RunState(ruleSet, statedb, env, test.Transaction) diff --git a/tests/util.go b/tests/util.go index 8a9d09213e..e51820cd9c 100644 --- a/tests/util.go +++ b/tests/util.go @@ -158,12 +158,12 @@ func (r RuleSet) IsHomestead(n *big.Int) bool { } type Env struct { - ruleSet RuleSet - depth int - state *state.StateDB - skipTransfer bool - initial bool - Gas *big.Int + ruleSet RuleSet + depth int + state *state.StateDB + skipTransfer bool + initial bool + Gas, gasPrice *big.Int origin common.Address parent common.Hash @@ -198,6 +198,7 @@ func NewEnvFromMap(ruleSet RuleSet, state *state.StateDB, envValues map[string]s env.difficulty = common.Big(envValues["currentDifficulty"]) env.gasLimit = common.Big(envValues["currentGasLimit"]) env.Gas = new(big.Int) + env.gasPrice = common.Big(exeValues["gasPrice"]) env.evm = vm.New(env, vm.Config{ EnableJit: EnableJit, @@ -216,6 +217,7 @@ 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) GasPrice() *big.Int { return self.gasPrice } func (self *Env) VmType() vm.Type { return vm.StdVmTy } func (self *Env) GetHash(n uint64) common.Hash { return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String()))) @@ -249,46 +251,46 @@ func (self *Env) Transfer(from, to vm.Account, amount *big.Int) { core.Transfer(from, to, amount) } -func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { +func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { if self.vmTest && self.depth > 0 { - caller.ReturnGas(gas, price) + caller.ReturnGas(gas.Uint64()) return nil, nil } - ret, err := core.Call(self, caller, addr, data, gas, price, value) + ret, err := core.Call(self, caller, addr, data, gas, value) self.Gas = gas return ret, err } -func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) { +func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { if self.vmTest && self.depth > 0 { - caller.ReturnGas(gas, price) + caller.ReturnGas(gas.Uint64()) return nil, nil } - return core.CallCode(self, caller, addr, data, gas, price, value) + return core.CallCode(self, caller, addr, data, gas, value) } -func (self *Env) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { +func (self *Env) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { if self.vmTest && self.depth > 0 { - caller.ReturnGas(gas, price) + caller.ReturnGas(gas.Uint64()) return nil, nil } - return core.DelegateCall(self, caller, addr, data, gas, price) + return core.DelegateCall(self, caller, addr, data, gas) } -func (self *Env) Create(caller vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { +func (self *Env) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { if self.vmTest { - caller.ReturnGas(gas, price) + caller.ReturnGas(gas.Uint64()) nonce := self.state.GetNonce(caller.Address()) obj := self.state.GetOrNewStateObject(crypto.CreateAddress(caller.Address(), nonce)) return nil, obj.Address(), nil } else { - return core.Create(self, caller, data, gas, price, value) + return core.Create(self, caller, data, gas, value) } } diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index c269f21e09..abcdb5d437 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -128,9 +128,9 @@ func runVmTests(tests map[string]VmTest, skipTests []string) error { } for name, test := range tests { - if skipTest[name] { + if skipTest[name] /*|| name != "57a9a2fbfe21a848e8d83379562a8513417c73d127495cf4abc5a44a5fe08e92"*/ { glog.Infoln("Skipping VM test", name) - return nil + continue } if err := runVmTest(test); err != nil { @@ -217,7 +217,6 @@ func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, from = common.HexToAddress(exec["caller"]) data = common.FromHex(exec["data"]) gas = common.Big(exec["gas"]) - price = common.Big(exec["gasPrice"]) value = common.Big(exec["value"]) ) // Reset the pre-compiled contracts for VM tests. @@ -229,7 +228,7 @@ func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, vmenv.vmTest = true vmenv.skipTransfer = true vmenv.initial = true - ret, err := vmenv.Call(caller, to, data, gas, price, value) + ret, err := vmenv.Call(caller, to, data, gas, value) return ret, vmenv.state.Logs(), vmenv.Gas, err } From 8085f3694656fc2c747451258f28402ae5df4303 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 15 Jun 2016 11:28:33 +0200 Subject: [PATCH 2/9] core/vm: removed arbitrary precision VM + multithreaded JIT status Removed the arbitrary precision VM in favour of the optimised JIT VM. This enables the new VM for all calls and creates and removed the need for the --jitvm and --forcejit flags. This fixes a minor issue when the jit is used in a multithreaded environment it would ignore the complitation status of the JIT and default to the byte-code VM. Instead it now waits for the compilation to finish and run the JIT if the execution was successful. --- cmd/geth/main.go | 4 +- cmd/geth/usage.go | 4 +- cmd/utils/flags.go | 33 +- core/vm/contracts.go | 12 + core/vm/doc.go | 10 +- core/vm/gas.go | 274 +++++++------- core/vm/{jit.go => program.go} | 115 +++--- ...{jit_optimiser.go => program_optimiser.go} | 2 +- core/vm/{jit_test.go => program_test.go} | 0 core/vm/{jit_util.go => program_util.go} | 0 ...{jit_util_test.go => program_util_test.go} | 0 core/vm/uint64.go | 175 --------- core/vm/vm.go | 334 +++--------------- core/vm_env.go | 10 +- tests/util.go | 2 +- 15 files changed, 254 insertions(+), 721 deletions(-) rename core/vm/{jit.go => program.go} (83%) rename core/vm/{jit_optimiser.go => program_optimiser.go} (98%) rename core/vm/{jit_test.go => program_test.go} (100%) rename core/vm/{jit_util.go => program_util.go} (100%) rename core/vm/{jit_util_test.go => program_util_test.go} (100%) delete mode 100644 core/vm/uint64.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 65311ca418..e2e85f2965 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -171,9 +171,7 @@ participating. utils.WhisperEnabledFlag, utils.DevModeFlag, utils.TestNetFlag, - utils.VMForceJitFlag, - utils.VMJitCacheFlag, - utils.VMEnableJitFlag, + utils.VMCacheFlag, utils.NetworkIdFlag, utils.RPCCORSDomainFlag, utils.MetricsEnabledFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index dc1788aad2..a95f310ec7 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -144,9 +144,7 @@ var AppHelpFlagGroups = []flagGroup{ { Name: "VIRTUAL MACHINE", Flags: []cli.Flag{ - utils.VMEnableJitFlag, - utils.VMForceJitFlag, - utils.VMJitCacheFlag, + utils.VMCacheFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0be499c5bc..a94f34484d 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -22,13 +22,11 @@ import ( "io/ioutil" "math" "math/big" - "math/rand" "os" "path/filepath" "runtime" "strconv" "strings" - "time" "github.com/ethereum/ethash" "github.com/ethereum/go-ethereum/accounts" @@ -213,18 +211,10 @@ var ( Value: "", } - VMForceJitFlag = cli.BoolFlag{ - Name: "forcejit", - Usage: "Force the JIT VM to take precedence", - } - VMJitCacheFlag = cli.IntFlag{ - Name: "jitcache", - Usage: "Amount of cached JIT VM programs", - Value: 64, - } - VMEnableJitFlag = cli.BoolFlag{ - Name: "jitvm", - Usage: "Enable the JIT VM", + VMCacheFlag = cli.IntFlag{ + Name: "vmcache", + Usage: "Amount of cached VM programs", + Value: 1024, } // logging and debug settings @@ -454,9 +444,6 @@ func makeNodeUserIdent(ctx *cli.Context) string { if identity := ctx.GlobalString(IdentityFlag.Name); len(identity) > 0 { comps = append(comps, identity) } - if ctx.GlobalBool(VMEnableJitFlag.Name) { - comps = append(comps, "JIT") - } return strings.Join(comps, "/") } @@ -665,16 +652,6 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) { Fatalf("The %v flags are mutually exclusive", netFlags) } - // initialise new random number generator - rand := rand.New(rand.NewSource(time.Now().UnixNano())) - // get enabled jit flag - jitEnabled := ctx.GlobalBool(VMEnableJitFlag.Name) - // if the jit is not enabled enable it for 10 pct of the people - if !jitEnabled && rand.Float64() < 0.1 { - jitEnabled = true - glog.V(logger.Info).Infoln("You're one of the lucky few that will try out the JIT VM (random). If you get a consensus failure please be so kind to report this incident with the block hash that failed. You can switch to the regular VM by setting --jitvm=false") - } - ethConf := ð.Config{ Etherbase: MakeEtherbase(stack.AccountManager(), ctx), ChainConfig: MakeChainConfig(ctx, stack), @@ -686,8 +663,6 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) { ExtraData: MakeMinerExtra(extra, ctx), NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name), DocRoot: ctx.GlobalString(DocRootFlag.Name), - EnableJit: jitEnabled, - ForceJit: ctx.GlobalBool(VMForceJitFlag.Name), GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)), GpoMinGasPrice: common.String2Big(ctx.GlobalString(GpoMinGasPriceFlag.Name)), GpoMaxGasPrice: common.String2Big(ctx.GlobalString(GpoMaxGasPriceFlag.Name)), diff --git a/core/vm/contracts.go b/core/vm/contracts.go index b45f14724b..63ddd8ab78 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -40,6 +40,18 @@ func (self PrecompiledAccount) Call(in []byte) []byte { // Precompiled contains the default set of ethereum contracts var Precompiled = PrecompiledContracts() +// RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go +func RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) { + gas := p.Gas(len(input)) + if contract.UseGas(gas.Uint64()) { + ret = p.Call(input) + + return ret, nil + } else { + return nil, OutOfGasError + } +} + // PrecompiledContracts returns the default set of precompiled ethereum // contracts defined by the ethereum yellow paper. func PrecompiledContracts() map[string]*PrecompiledAccount { diff --git a/core/vm/doc.go b/core/vm/doc.go index 239be2cfec..2021416729 100644 --- a/core/vm/doc.go +++ b/core/vm/doc.go @@ -17,16 +17,10 @@ /* Package vm implements the Ethereum Virtual Machine. -The vm package implements two EVMs, a byte code VM and a JIT VM. The BC -(Byte Code) VM loops over a set of bytes and executes them according to the set -of rules defined in the Ethereum yellow paper. When the BC VM is invoked it -invokes the JIT VM in a separate goroutine and compiles the byte code in JIT -instructions. - -The JIT VM, when invoked, loops around a set of pre-defined instructions until +The VM, when invoked, loops around a set of pre-defined instructions until it either runs of gas, causes an internal error, returns or stops. -The JIT optimiser attempts to pre-compile instructions in to chunks or segments +The optimiser attempts to pre-compile instructions in to chunks or segments such as multiple PUSH operations and static JUMPs. It does this by analysing the opcodes and attempts to match certain regions to known sets. Whenever the optimiser finds said segments it creates a new instruction and replaces the diff --git a/core/vm/gas.go b/core/vm/gas.go index 440ee864d7..77585c681f 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -1,23 +1,8 @@ -// Copyright 2015 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 vm import ( "fmt" + gmath "math" "math/big" "github.com/ethereum/go-ethereum/common" @@ -25,157 +10,166 @@ import ( ) var ( - GasQuickStep = big.NewInt(2) - GasFastestStep = big.NewInt(3) - GasFastStep = big.NewInt(5) - GasMidStep = big.NewInt(8) - GasSlowStep = big.NewInt(10) - GasExtStep = big.NewInt(20) - - GasReturn = big.NewInt(0) - GasStop = big.NewInt(0) - - GasContractByte = big.NewInt(200) + maxInt63 = new(big.Int).Exp(big.NewInt(2), big.NewInt(63), big.NewInt(0)) + maxIntCap = new(big.Int).Sub(maxInt63, big.NewInt(1)) ) -// baseCheck checks for any stack error underflows -func baseCheck(op OpCode, stack *Stack, gas *big.Int) error { - // PUSH and DUP are a bit special. They all cost the same but we do want to have checking on stack push limit - // PUSH is also allowed to calculate the same price for all PUSHes - // DUP requirements are handled elsewhere (except for the stack limit check) - if op >= PUSH1 && op <= PUSH32 { - op = PUSH1 - } - if op >= DUP1 && op <= DUP16 { - op = DUP1 - } +var ( + StackLimit64 = params.StackLimit.Uint64() + GasQuickStep64 uint64 = 2 + GasFastestStep64 uint64 = 3 + GasFastStep64 uint64 = 5 + GasMidStep64 uint64 = 8 + GasSlowStep64 uint64 = 10 + GasExtStep64 uint64 = 20 - if r, ok := _baseCheck[op]; ok { - err := stack.require(r.stackPop) - if err != nil { - return err - } + GasReturn64 uint64 = 0 + GasStop64 uint64 = 0 - if r.stackPush > 0 && stack.len()-r.stackPop+r.stackPush > int(params.StackLimit.Int64()) { - return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit.Int64()) - } - - gas.Add(gas, r.gas) - } - return nil -} + GasContractByte64 uint64 = 200 + LogGas64 = params.LogGas.Uint64() + LogTopicGas64 = params.LogTopicGas.Uint64() + LogDataGas64 = params.LogDataGas.Uint64() + ExpByteGas64 = params.ExpByteGas.Uint64() + SstoreSetGas64 = params.SstoreSetGas.Uint64() + SstoreClearGas64 = params.SstoreClearGas.Uint64() + SstoreResetGas64 = params.SstoreResetGas.Uint64() + KeccakWordGas64 = params.Sha3WordGas.Uint64() + CopyGas64 = params.CopyGas.Uint64() + CallNewAccountGas64 = params.CallNewAccountGas.Uint64() + CallValueTransferGas64 = params.CallValueTransferGas.Uint64() + MemoryGas64 = params.MemoryGas.Uint64() + QuadCoeffDiv64 = params.QuadCoeffDiv.Uint64() +) // casts a arbitrary number to the amount of words (sets of 32 bytes) -func toWordSize(size *big.Int) *big.Int { - tmp := new(big.Int) - tmp.Add(size, u256(31)) - tmp.Div(tmp, u256(32)) - return tmp +func toWordSize(size uint64) uint64 { + return (size + 31) / 32 } // calculates the memory size required for a step -func calcMemSize(off, l *big.Int) *big.Int { +func calcMemSize(off, l *big.Int) (uint64, bool) { if l.Cmp(common.Big0) == 0 { - return common.Big0 + return 0, false } - - return new(big.Int).Add(off, l) + size := new(big.Int).Add(off, l) + if size.Cmp(maxIntCap) > 0 { + return 0, true + } + return size.Uint64(), false } // calculates the quadratic gas -func quadMemGas(mem *Memory, newMemSize, gas *big.Int) { - if newMemSize.Cmp(common.Big0) > 0 { +// TODO this function requires guarding of overflows +func calcQuadMemGas(mem *Memory, newMemSize uint64) (uint64, bool) { + oldTotalFee := mem.cost + if newMemSize > 0 { newMemSizeWords := toWordSize(newMemSize) - newMemSize.Mul(newMemSizeWords, u256(32)) + newMemSize = newMemSizeWords * 32 - if newMemSize.Cmp(u256(int64(mem.Len()))) > 0 { - // be careful reusing variables here when changing. - // The order has been optimised to reduce allocation - oldSize := toWordSize(big.NewInt(int64(mem.Len()))) - pow := new(big.Int).Exp(oldSize, common.Big2, Zero) - linCoef := oldSize.Mul(oldSize, params.MemoryGas) - quadCoef := new(big.Int).Div(pow, params.QuadCoeffDiv) - oldTotalFee := new(big.Int).Add(linCoef, quadCoef) + if newMemSize > uint64(mem.Len()) { + pow := uint64(gmath.Pow(float64(newMemSizeWords), 2)) + linCoef := newMemSizeWords * MemoryGas64 + quadCoef := pow / QuadCoeffDiv64 + newTotalFee := linCoef + quadCoef - pow.Exp(newMemSizeWords, common.Big2, Zero) - linCoef = linCoef.Mul(newMemSizeWords, params.MemoryGas) - quadCoef = quadCoef.Div(pow, params.QuadCoeffDiv) - newTotalFee := linCoef.Add(linCoef, quadCoef) + fee := newTotalFee - oldTotalFee + mem.cost = newTotalFee - fee := newTotalFee.Sub(newTotalFee, oldTotalFee) - gas.Add(gas, fee) + return fee, false } } + return 0, false +} + +// jitBaseCalc is the same as baseCheck except it doesn't do the look up in the +// gas table. This is done during compilation instead. +func jitBaseCalc(instr instruction, stack *Stack) (uint64, error) { + err := stack.require(instr.spop) + if err != nil { + return 0, err + } + + if instr.spush > 0 && stack.len()-instr.spop+instr.spush > int(StackLimit64) { + return 0, fmt.Errorf("stack limit reached %d (%d)", stack.len(), StackLimit64) + } + + // 0 on gas means no base calculation + if instr.gas == 0 { + return 0, nil + } + + return instr.gas, nil } type req struct { stackPop int - gas *big.Int + gas uint64 stackPush int } var _baseCheck = map[OpCode]req{ // opcode | stack pop | gas price | stack push - ADD: {2, GasFastestStep, 1}, - LT: {2, GasFastestStep, 1}, - GT: {2, GasFastestStep, 1}, - SLT: {2, GasFastestStep, 1}, - SGT: {2, GasFastestStep, 1}, - EQ: {2, GasFastestStep, 1}, - ISZERO: {1, GasFastestStep, 1}, - SUB: {2, GasFastestStep, 1}, - AND: {2, GasFastestStep, 1}, - OR: {2, GasFastestStep, 1}, - XOR: {2, GasFastestStep, 1}, - NOT: {1, GasFastestStep, 1}, - BYTE: {2, GasFastestStep, 1}, - CALLDATALOAD: {1, GasFastestStep, 1}, - CALLDATACOPY: {3, GasFastestStep, 1}, - MLOAD: {1, GasFastestStep, 1}, - MSTORE: {2, GasFastestStep, 0}, - MSTORE8: {2, GasFastestStep, 0}, - CODECOPY: {3, GasFastestStep, 0}, - MUL: {2, GasFastStep, 1}, - DIV: {2, GasFastStep, 1}, - SDIV: {2, GasFastStep, 1}, - MOD: {2, GasFastStep, 1}, - SMOD: {2, GasFastStep, 1}, - SIGNEXTEND: {2, GasFastStep, 1}, - ADDMOD: {3, GasMidStep, 1}, - MULMOD: {3, GasMidStep, 1}, - JUMP: {1, GasMidStep, 0}, - JUMPI: {2, GasSlowStep, 0}, - EXP: {2, GasSlowStep, 1}, - ADDRESS: {0, GasQuickStep, 1}, - ORIGIN: {0, GasQuickStep, 1}, - CALLER: {0, GasQuickStep, 1}, - CALLVALUE: {0, GasQuickStep, 1}, - CODESIZE: {0, GasQuickStep, 1}, - GASPRICE: {0, GasQuickStep, 1}, - COINBASE: {0, GasQuickStep, 1}, - TIMESTAMP: {0, GasQuickStep, 1}, - NUMBER: {0, GasQuickStep, 1}, - CALLDATASIZE: {0, GasQuickStep, 1}, - DIFFICULTY: {0, GasQuickStep, 1}, - GASLIMIT: {0, GasQuickStep, 1}, - POP: {1, GasQuickStep, 0}, - PC: {0, GasQuickStep, 1}, - MSIZE: {0, GasQuickStep, 1}, - GAS: {0, GasQuickStep, 1}, - BLOCKHASH: {1, GasExtStep, 1}, - BALANCE: {1, GasExtStep, 1}, - EXTCODESIZE: {1, GasExtStep, 1}, - EXTCODECOPY: {4, GasExtStep, 0}, - SLOAD: {1, params.SloadGas, 1}, - SSTORE: {2, Zero, 0}, - SHA3: {2, params.Sha3Gas, 1}, - CREATE: {3, params.CreateGas, 1}, - CALL: {7, params.CallGas, 1}, - CALLCODE: {7, params.CallGas, 1}, - DELEGATECALL: {6, params.CallGas, 1}, - JUMPDEST: {0, params.JumpdestGas, 0}, - SUICIDE: {1, Zero, 0}, - RETURN: {2, Zero, 0}, - PUSH1: {0, GasFastestStep, 1}, - DUP1: {0, Zero, 1}, + ADD: {2, GasFastestStep64, 1}, + LT: {2, GasFastestStep64, 1}, + GT: {2, GasFastestStep64, 1}, + SLT: {2, GasFastestStep64, 1}, + SGT: {2, GasFastestStep64, 1}, + EQ: {2, GasFastestStep64, 1}, + ISZERO: {1, GasFastestStep64, 1}, + SUB: {2, GasFastestStep64, 1}, + AND: {2, GasFastestStep64, 1}, + OR: {2, GasFastestStep64, 1}, + XOR: {2, GasFastestStep64, 1}, + NOT: {1, GasFastestStep64, 1}, + BYTE: {2, GasFastestStep64, 1}, + CALLDATALOAD: {1, GasFastestStep64, 1}, + CALLDATACOPY: {3, GasFastestStep64, 1}, + MLOAD: {1, GasFastestStep64, 1}, + MSTORE: {2, GasFastestStep64, 0}, + MSTORE8: {2, GasFastestStep64, 0}, + CODECOPY: {3, GasFastestStep64, 0}, + MUL: {2, GasFastStep64, 1}, + DIV: {2, GasFastStep64, 1}, + SDIV: {2, GasFastStep64, 1}, + MOD: {2, GasFastStep64, 1}, + SMOD: {2, GasFastStep64, 1}, + SIGNEXTEND: {2, GasFastStep64, 1}, + ADDMOD: {3, GasMidStep64, 1}, + MULMOD: {3, GasMidStep64, 1}, + JUMP: {1, GasMidStep64, 0}, + JUMPI: {2, GasSlowStep64, 0}, + EXP: {2, GasSlowStep64, 1}, + ADDRESS: {0, GasQuickStep64, 1}, + ORIGIN: {0, GasQuickStep64, 1}, + CALLER: {0, GasQuickStep64, 1}, + CALLVALUE: {0, GasQuickStep64, 1}, + CODESIZE: {0, GasQuickStep64, 1}, + GASPRICE: {0, GasQuickStep64, 1}, + COINBASE: {0, GasQuickStep64, 1}, + TIMESTAMP: {0, GasQuickStep64, 1}, + NUMBER: {0, GasQuickStep64, 1}, + CALLDATASIZE: {0, GasQuickStep64, 1}, + DIFFICULTY: {0, GasQuickStep64, 1}, + GASLIMIT: {0, GasQuickStep64, 1}, + POP: {1, GasQuickStep64, 0}, + PC: {0, GasQuickStep64, 1}, + MSIZE: {0, GasQuickStep64, 1}, + GAS: {0, GasQuickStep64, 1}, + BLOCKHASH: {1, GasExtStep64, 1}, + BALANCE: {1, GasExtStep64, 1}, + EXTCODESIZE: {1, GasExtStep64, 1}, + EXTCODECOPY: {4, GasExtStep64, 0}, + SLOAD: {1, params.SloadGas.Uint64(), 1}, + SSTORE: {2, 0, 0}, + SHA3: {2, params.Sha3Gas.Uint64(), 1}, + CREATE: {3, params.CreateGas.Uint64(), 1}, + CALL: {7, params.CallGas.Uint64(), 1}, + CALLCODE: {7, params.CallGas.Uint64(), 1}, + DELEGATECALL: {6, params.CallGas.Uint64(), 1}, + JUMPDEST: {0, params.JumpdestGas.Uint64(), 0}, + SUICIDE: {1, 0, 0}, + RETURN: {2, 0, 0}, + PUSH1: {0, GasFastestStep64, 1}, + DUP1: {0, 0, 1}, } diff --git a/core/vm/jit.go b/core/vm/program.go similarity index 83% rename from core/vm/jit.go rename to core/vm/program.go index 39635034cb..85e8fb9d98 100644 --- a/core/vm/jit.go +++ b/core/vm/program.go @@ -17,7 +17,6 @@ package vm import ( - "fmt" gmath "math" "math/big" "sync/atomic" @@ -41,7 +40,7 @@ const ( progReady // ready for use status progError // error status (usually caused during compilation) - defaultJitMaxCache int = 64 // maximum amount of jit cached programs + defaultJitMaxCache int = 1024 // maximum amount of jit cached programs ) var MaxProgSize int // Max cache size for JIT programs @@ -116,7 +115,7 @@ func (p *Program) addInstr(op OpCode, pc uint64, fn instrFn, data *big.Int) { if op >= DUP1 && op <= DUP16 { baseOp = DUP1 } - base := _baseCheck64[baseOp] + base := _baseCheck[baseOp] returns := op == RETURN || op == SUICIDE || op == STOP instr := instruction{op, pc, fn, data, base.gas, base.stackPop, base.stackPush, returns} @@ -126,17 +125,13 @@ func (p *Program) addInstr(op OpCode, pc uint64, fn instrFn, data *big.Int) { } // CompileProgram compiles the given program and return an error when it fails -func CompileProgram(program *Program) (err error) { +func CompileProgram(program *Program) { if progStatus(atomic.LoadInt32(&program.status)) == progCompile { - return nil + return } atomic.StoreInt32(&program.status, int32(progCompile)) defer func() { - if err != nil { - atomic.StoreInt32(&program.status, int32(progError)) - } else { - atomic.StoreInt32(&program.status, int32(progReady)) - } + atomic.StoreInt32(&program.status, int32(progReady)) }() if glog.V(logger.Debug) { glog.Infof("compiling %x\n", program.Id[:4]) @@ -295,54 +290,10 @@ func CompileProgram(program *Program) (err error) { } optimiseProgram(program) - - return nil } -// RunProgram runs the program given the environment and contract and returns an -// error if the execution failed (non-consensus) func RunProgram(program *Program, env Environment, contract *Contract, input []byte) ([]byte, error) { - return runProgram(program, 0, NewMemory(), newstack(), env, contract, input) -} - -func runProgram(program *Program, pcstart uint64, mem *Memory, stack *Stack, env Environment, contract *Contract, input []byte) ([]byte, error) { - contract.Input = input - - var ( - pc uint64 = program.mapping[pcstart] - instrCount = 0 - ) - - if glog.V(logger.Debug) { - glog.Infof("running JIT program %x\n", program.Id[:4]) - tstart := time.Now() - defer func() { - glog.Infof("JIT program %x done. time: %v instrc: %v\n", program.Id[:4], time.Since(tstart), instrCount) - }() - } - - homestead := env.RuleSet().IsHomestead(env.BlockNumber()) - for pc < uint64(len(program.instructions)) { - instrCount++ - - instr := program.instructions[pc] - if instr.Op() == DELEGATECALL && !homestead { - return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op()) - } - - ret, err := instr.do(program, &pc, env, contract, mem, stack) - if err != nil { - return nil, err - } - - if instr.halts() { - return ret, nil - } - } - - contract.Input = nil - - return nil, nil + return New(env, Config{}).Run(contract, input) } // validDest checks if the given destination is a valid one given the @@ -410,7 +361,7 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi } gas += gasLogData - newMemSize, sizeFault = calcMemSize64(mStart, mSize) + newMemSize, sizeFault = calcMemSize(mStart, mSize) case EXP: x := uint64(len(stack.data[stack.len()-2].Bytes())) if !math.IsMulSafe(x, ExpByteGas64) { @@ -448,15 +399,15 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi statedb.AddRefund(params.SuicideRefundGas) } case MLOAD: - newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(32)) + newMemSize, sizeFault = calcMemSize(stack.peek(), u256(32)) case MSTORE8: - newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(1)) + newMemSize, sizeFault = calcMemSize(stack.peek(), u256(1)) case MSTORE: - newMemSize, sizeFault = calcMemSize64(stack.peek(), u256(32)) + newMemSize, sizeFault = calcMemSize(stack.peek(), u256(32)) case RETURN: - newMemSize, sizeFault = calcMemSize64(stack.peek(), stack.data[stack.len()-2]) + newMemSize, sizeFault = calcMemSize(stack.peek(), stack.data[stack.len()-2]) case SHA3: - newMemSize, sizeFault = calcMemSize64(stack.peek(), stack.data[stack.len()-2]) + newMemSize, sizeFault = calcMemSize(stack.peek(), stack.data[stack.len()-2]) if sizeFault { return 0, 0, OutOfGasError } @@ -464,7 +415,7 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi if stack.data[stack.len()-2].BitLen() > 64 { return 0, 0, OutOfGasError } - words := toWordSize64(stack.data[stack.len()-2].Uint64()) + words := toWordSize(stack.data[stack.len()-2].Uint64()) if !math.IsMulSafe(words, KeccakWordGas64) { return 0, 0, OutOfGasError } @@ -474,12 +425,12 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi } gas += wordsGas case CALLDATACOPY: - newMemSize, sizeFault = calcMemSize64(stack.peek(), stack.data[stack.len()-3]) + newMemSize, sizeFault = calcMemSize(stack.peek(), stack.data[stack.len()-3]) if sizeFault { return 0, 0, OutOfGasError } - words := toWordSize64(stack.data[stack.len()-3].Uint64()) + words := toWordSize(stack.data[stack.len()-3].Uint64()) if !math.IsMulSafe(words, CopyGas64) { return 0, 0, OutOfGasError } @@ -489,12 +440,12 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi } gas += wordsGas case CODECOPY: - newMemSize, sizeFault = calcMemSize64(stack.peek(), stack.data[stack.len()-3]) + newMemSize, sizeFault = calcMemSize(stack.peek(), stack.data[stack.len()-3]) if sizeFault { return 0, 0, OutOfGasError } - words := toWordSize64(stack.data[stack.len()-3].Uint64()) + words := toWordSize(stack.data[stack.len()-3].Uint64()) if !math.IsMulSafe(words, CopyGas64) { return 0, 0, OutOfGasError } @@ -504,12 +455,12 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi } gas += wordsGas case EXTCODECOPY: - newMemSize, sizeFault = calcMemSize64(stack.data[stack.len()-2], stack.data[stack.len()-4]) + newMemSize, sizeFault = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-4]) if sizeFault { return 0, 0, OutOfGasError } - words := toWordSize64(stack.data[stack.len()-4].Uint64()) + words := toWordSize(stack.data[stack.len()-4].Uint64()) if !math.IsMulSafe(words, CopyGas64) { return 0, 0, OutOfGasError } @@ -519,7 +470,7 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi } gas += wordsGas case CREATE: - newMemSize, sizeFault = calcMemSize64(stack.data[stack.len()-2], stack.data[stack.len()-3]) + newMemSize, sizeFault = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-3]) case CALL, CALLCODE: callGas := stack.data[stack.len()-1] if callGas.BitLen() > 64 { @@ -543,11 +494,11 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi gas += CallValueTransferGas64 } - x, xSizeFault := calcMemSize64(stack.data[stack.len()-6], stack.data[stack.len()-7]) + x, xSizeFault := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7]) if xSizeFault { return 0, 0, OutOfGasError } - y, ySizeFault := calcMemSize64(stack.data[stack.len()-4], stack.data[stack.len()-5]) + y, ySizeFault := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5]) if ySizeFault { return 0, 0, OutOfGasError } @@ -563,11 +514,11 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi } gas += callGas.Uint64() - x, xSizeFault := calcMemSize64(stack.data[stack.len()-5], stack.data[stack.len()-6]) + x, xSizeFault := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6]) if xSizeFault { return 0, 0, OutOfGasError } - y, ySizeFault := calcMemSize64(stack.data[stack.len()-3], stack.data[stack.len()-4]) + y, ySizeFault := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4]) if ySizeFault { return 0, 0, OutOfGasError } @@ -578,9 +529,23 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi return 0, 0, OutOfGasError } - memGas, _ := calcQuadMemGas64(mem, newMemSize) + memGas, _ := calcQuadMemGas(mem, newMemSize) if !math.IsAddSafe(gas, memGas) { return 0, 0, OutOfGasError } - return toWordSize64(newMemSize) * 32, gas + memGas, nil + return toWordSize(newMemSize) * 32, gas + memGas, nil +} + +// waitCompile returns a new channel to broadcast the new result after +// a compilation has started. +func WaitCompile(id common.Hash) chan progStatus { + ch := make(chan progStatus) + go func() { + defer close(ch) + for GetProgramStatus(id) == progCompile { + time.Sleep(time.Microsecond * 10) + } + ch <- GetProgramStatus(id) + }() + return ch } diff --git a/core/vm/jit_optimiser.go b/core/vm/program_optimiser.go similarity index 98% rename from core/vm/jit_optimiser.go rename to core/vm/program_optimiser.go index 831af1ed10..6d1ac93264 100644 --- a/core/vm/jit_optimiser.go +++ b/core/vm/program_optimiser.go @@ -115,7 +115,7 @@ func makePushSeg(instrs []instruction) (pushSeg, int) { // makeStaticJumpSeg creates a new static jump segment from a predefined // destination (PUSH, JUMP). func makeStaticJumpSeg(to *big.Int, program *Program) jumpSeg { - gas := _baseCheck64[PUSH1].gas + _baseCheck64[JUMP].gas + gas := _baseCheck[PUSH1].gas + _baseCheck[JUMP].gas contract := &Contract{Code: program.code} pos, err := jump(program.mapping, program.destinations, contract, to) diff --git a/core/vm/jit_test.go b/core/vm/program_test.go similarity index 100% rename from core/vm/jit_test.go rename to core/vm/program_test.go diff --git a/core/vm/jit_util.go b/core/vm/program_util.go similarity index 100% rename from core/vm/jit_util.go rename to core/vm/program_util.go diff --git a/core/vm/jit_util_test.go b/core/vm/program_util_test.go similarity index 100% rename from core/vm/jit_util_test.go rename to core/vm/program_util_test.go diff --git a/core/vm/uint64.go b/core/vm/uint64.go deleted file mode 100644 index 96d1e76364..0000000000 --- a/core/vm/uint64.go +++ /dev/null @@ -1,175 +0,0 @@ -package vm - -import ( - "fmt" - gmath "math" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params" -) - -var ( - maxInt63 = new(big.Int).Exp(big.NewInt(2), big.NewInt(63), big.NewInt(0)) - maxIntCap = new(big.Int).Sub(maxInt63, big.NewInt(1)) -) - -var ( - StackLimit64 = params.StackLimit.Uint64() - GasQuickStep64 uint64 = 2 - GasFastestStep64 uint64 = 3 - GasFastStep64 uint64 = 5 - GasMidStep64 uint64 = 8 - GasSlowStep64 uint64 = 10 - GasExtStep64 uint64 = 20 - - GasReturn64 uint64 = 0 - GasStop64 uint64 = 0 - - GasContractByte64 uint64 = 200 - LogGas64 = params.LogGas.Uint64() - LogTopicGas64 = params.LogTopicGas.Uint64() - LogDataGas64 = params.LogDataGas.Uint64() - ExpByteGas64 = params.ExpByteGas.Uint64() - SstoreSetGas64 = params.SstoreSetGas.Uint64() - SstoreClearGas64 = params.SstoreClearGas.Uint64() - SstoreResetGas64 = params.SstoreResetGas.Uint64() - KeccakWordGas64 = params.Sha3WordGas.Uint64() - CopyGas64 = params.CopyGas.Uint64() - CallNewAccountGas64 = params.CallNewAccountGas.Uint64() - CallValueTransferGas64 = params.CallValueTransferGas.Uint64() - MemoryGas64 = params.MemoryGas.Uint64() - QuadCoeffDiv64 = params.QuadCoeffDiv.Uint64() -) - -// casts a arbitrary number to the amount of words (sets of 32 bytes) -func toWordSize64(size uint64) uint64 { - return (size + 31) / 32 -} - -// calculates the memory size required for a step -func calcMemSize64(off, l *big.Int) (uint64, bool) { - if l.Cmp(common.Big0) == 0 { - return 0, false - } - size := new(big.Int).Add(off, l) - if size.Cmp(maxIntCap) > 0 { - return 0, true - } - return size.Uint64(), false -} - -// calculates the quadratic gas -// TODO this function requires guarding of overflows -func calcQuadMemGas64(mem *Memory, newMemSize uint64) (uint64, bool) { - oldTotalFee := mem.cost - if newMemSize > 0 { - newMemSizeWords := toWordSize64(newMemSize) - newMemSize = newMemSizeWords * 32 - - if newMemSize > uint64(mem.Len()) { - pow := uint64(gmath.Pow(float64(newMemSizeWords), 2)) - linCoef := newMemSizeWords * MemoryGas64 - quadCoef := pow / QuadCoeffDiv64 - newTotalFee := linCoef + quadCoef - - fee := newTotalFee - oldTotalFee - mem.cost = newTotalFee - - return fee, false - } - } - return 0, false -} - -// jitBaseCalc is the same as baseCheck except it doesn't do the look up in the -// gas table. This is done during compilation instead. -func jitBaseCalc(instr instruction, stack *Stack) (uint64, error) { - err := stack.require(instr.spop) - if err != nil { - return 0, err - } - - if instr.spush > 0 && stack.len()-instr.spop+instr.spush > int(StackLimit64) { - return 0, fmt.Errorf("stack limit reached %d (%d)", stack.len(), StackLimit64) - } - - // 0 on gas means no base calculation - if instr.gas == 0 { - return 0, nil - } - - return instr.gas, nil -} - -type req64 struct { - stackPop int - gas uint64 - stackPush int -} - -var _baseCheck64 = map[OpCode]req64{ - // opcode | stack pop | gas price | stack push - ADD: {2, GasFastestStep64, 1}, - LT: {2, GasFastestStep64, 1}, - GT: {2, GasFastestStep64, 1}, - SLT: {2, GasFastestStep64, 1}, - SGT: {2, GasFastestStep64, 1}, - EQ: {2, GasFastestStep64, 1}, - ISZERO: {1, GasFastestStep64, 1}, - SUB: {2, GasFastestStep64, 1}, - AND: {2, GasFastestStep64, 1}, - OR: {2, GasFastestStep64, 1}, - XOR: {2, GasFastestStep64, 1}, - NOT: {1, GasFastestStep64, 1}, - BYTE: {2, GasFastestStep64, 1}, - CALLDATALOAD: {1, GasFastestStep64, 1}, - CALLDATACOPY: {3, GasFastestStep64, 1}, - MLOAD: {1, GasFastestStep64, 1}, - MSTORE: {2, GasFastestStep64, 0}, - MSTORE8: {2, GasFastestStep64, 0}, - CODECOPY: {3, GasFastestStep64, 0}, - MUL: {2, GasFastStep64, 1}, - DIV: {2, GasFastStep64, 1}, - SDIV: {2, GasFastStep64, 1}, - MOD: {2, GasFastStep64, 1}, - SMOD: {2, GasFastStep64, 1}, - SIGNEXTEND: {2, GasFastStep64, 1}, - ADDMOD: {3, GasMidStep64, 1}, - MULMOD: {3, GasMidStep64, 1}, - JUMP: {1, GasMidStep64, 0}, - JUMPI: {2, GasSlowStep64, 0}, - EXP: {2, GasSlowStep64, 1}, - ADDRESS: {0, GasQuickStep64, 1}, - ORIGIN: {0, GasQuickStep64, 1}, - CALLER: {0, GasQuickStep64, 1}, - CALLVALUE: {0, GasQuickStep64, 1}, - CODESIZE: {0, GasQuickStep64, 1}, - GASPRICE: {0, GasQuickStep64, 1}, - COINBASE: {0, GasQuickStep64, 1}, - TIMESTAMP: {0, GasQuickStep64, 1}, - NUMBER: {0, GasQuickStep64, 1}, - CALLDATASIZE: {0, GasQuickStep64, 1}, - DIFFICULTY: {0, GasQuickStep64, 1}, - GASLIMIT: {0, GasQuickStep64, 1}, - POP: {1, GasQuickStep64, 0}, - PC: {0, GasQuickStep64, 1}, - MSIZE: {0, GasQuickStep64, 1}, - GAS: {0, GasQuickStep64, 1}, - BLOCKHASH: {1, GasExtStep64, 1}, - BALANCE: {1, GasExtStep64, 1}, - EXTCODESIZE: {1, GasExtStep64, 1}, - EXTCODECOPY: {4, GasExtStep64, 0}, - SLOAD: {1, params.SloadGas.Uint64(), 1}, - SSTORE: {2, 0, 0}, - SHA3: {2, params.Sha3Gas.Uint64(), 1}, - CREATE: {3, params.CreateGas.Uint64(), 1}, - CALL: {7, params.CallGas.Uint64(), 1}, - CALLCODE: {7, params.CallGas.Uint64(), 1}, - DELEGATECALL: {6, params.CallGas.Uint64(), 1}, - JUMPDEST: {0, params.JumpdestGas.Uint64(), 0}, - SUICIDE: {1, 0, 0}, - RETURN: {2, 0, 0}, - PUSH1: {0, GasFastestStep64, 1}, - DUP1: {0, 0, 1}, -} diff --git a/core/vm/vm.go b/core/vm/vm.go index 7e81f856ad..908f4721f8 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -18,14 +18,12 @@ package vm import ( "fmt" - "math/big" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/params" ) // Config are the configuration options for the EVM @@ -56,13 +54,13 @@ func New(env Environment, cfg Config) *EVM { } // Run loops and evaluates the contract's code with the given input data -func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { +func (evm *EVM) Run(contract *Contract, input []byte) ([]byte, error) { evm.env.SetDepth(evm.env.Depth() + 1) defer evm.env.SetDepth(evm.env.Depth() - 1) if contract.CodeAddr != nil { if p := Precompiled[contract.CodeAddr.Str()]; p != nil { - return evm.RunPrecompiled(p, input, contract) + return RunPrecompiled(p, input, contract) } } @@ -71,301 +69,75 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { return nil, nil } - codehash := contract.CodeHash // codehash is used when doing jump dest caching + codehash := contract.CodeHash // codehash is used as an identifier for the programs if codehash == (common.Hash{}) { codehash = crypto.Keccak256Hash(contract.Code) } - var program *Program - if evm.cfg.EnableJit { - // If the JIT is enabled check the status of the JIT program, - // if it doesn't exist compile a new program in a separate - // goroutine or wait for compilation to finish if the JIT is - // forced. - switch GetProgramStatus(codehash) { - case progReady: - return RunProgram(GetProgram(codehash), evm.env, contract, input) - case progUnknown: - if evm.cfg.ForceJit { - // Create and compile program - program = NewProgram(contract.Code) - perr := CompileProgram(program) - if perr == nil { - return RunProgram(program, evm.env, contract, input) - } - glog.V(logger.Info).Infoln("error compiling program", err) - } else { - // create and compile the program. Compilation - // is done in a separate goroutine - program = NewProgram(contract.Code) - go func() { - err := CompileProgram(program) - if err != nil { - glog.V(logger.Info).Infoln("error compiling program", err) - return - } - }() - } - } + // If the JIT is enabled check the status of the JIT program, + // if it doesn't exist compile a new program in a separate + // goroutine or wait for compilation to finish if the JIT is + // forced. + switch GetProgramStatus(codehash) { + case progReady: + return evm.runProgram(GetProgram(codehash), contract, input) + case progUnknown: + // Create and compile program + program := NewProgram(contract.Code) + CompileProgram(program) + + return evm.runProgram(program, contract, input) + case progCompile: + // if the program is already compling wait for the compilation to finish + // and use the program instead of defaulting to the regular byte code vm. + <-WaitCompile(codehash) + + return evm.runProgram(GetProgram(codehash), contract, input) } + return nil, fmt.Errorf("Unexpected return using program %x", codehash) +} - var ( - caller = contract.caller - code = contract.Code - instrCount = 0 - - op OpCode // current opcode - mem = NewMemory() // bound memory - stack = newstack() // local stack - statedb = evm.env.Db() // current state - // For optimisation reason we're using uint64 as the program counter. - // It's theoretically possible to go above 2^64. The YP defines the PC to be uint256. Practically much less so feasible. - pc = uint64(0) // program counter - - // jump evaluates and checks whether the given jump destination is a valid one - // if valid move the `pc` otherwise return an error. - jump = func(from uint64, to *big.Int) error { - if !contract.jumpdests.has(codehash, code, to) { - nop := contract.GetOp(to.Uint64()) - return fmt.Errorf("invalid jump destination (%v) %v", nop, to) - } - - pc = to.Uint64() - - return nil - } - - newMemSize *big.Int - cost *big.Int - ) +func (evm *EVM) runProgram(program *Program, contract *Contract, input []byte) ([]byte, error) { contract.Input = input - // User defer pattern to check for an error and, based on the error being nil or not, use all gas and return. - defer func() { - if err != nil && evm.cfg.Debug { - gas := new(big.Int).SetUint64(contract.gas64) - evm.cfg.Tracer.CaptureState(evm.env, pc, op, gas, cost, mem, stack, contract, evm.env.Depth(), err) - } - }() + var ( + pc uint64 = 0 //program.mapping[pcstart] + instrCount uint64 = 0 + mem = NewMemory() + stack = newstack() + env = evm.env + ) if glog.V(logger.Debug) { - glog.Infof("running byte VM %x\n", codehash[:4]) + glog.Infof("running JIT program %x\n", program.Id[:4]) tstart := time.Now() defer func() { - glog.Infof("VM %x done. time: %v instrc: %v\n", codehash[:4], time.Since(tstart), instrCount) + glog.Infof("JIT program %x done. time: %v instrc: %v\n", program.Id[:4], time.Since(tstart), instrCount) }() } - for ; ; instrCount++ { - // Get the memory location of pc - op = contract.GetOp(pc) - // calculate the new memory size and gas price for the current executing opcode - newMemSize, cost, err = calculateGasAndSize(evm.env, contract, caller, op, statedb, mem, stack) + homestead := env.RuleSet().IsHomestead(env.BlockNumber()) + for pc < uint64(len(program.instructions)) { + instrCount++ + + instr := program.instructions[pc] + if instr.Op() == DELEGATECALL && !homestead { + return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op()) + } + + ret, err := instr.do(program, &pc, env, contract, mem, stack) if err != nil { + //gas := new(big.Int).SetUint64(contract.gas64) + //evm.cfg.Tracer.CaptureState(evm.env, pc, instr.Op(), gas, cost, mem, stack, contract, evm.env.Depth(), err) + return nil, err } - // Use the calculated gas. When insufficient gas is present, use all gas and return an - // Out Of Gas error - if !contract.UseGas(cost.Uint64()) { - return nil, OutOfGasError + if instr.halts() { + return ret, nil } - - // Resize the memory calculated previously - mem.Resize(newMemSize.Uint64()) - // Add a log message - if evm.cfg.Debug { - gas := new(big.Int).SetUint64(contract.gas64) - evm.cfg.Tracer.CaptureState(evm.env, pc, op, gas, cost, mem, stack, contract, evm.env.Depth(), nil) - } - - if opPtr := evm.jumpTable[op]; opPtr.valid { - if opPtr.fn != nil { - opPtr.fn(instruction{}, &pc, evm.env, contract, mem, stack) - } else { - switch op { - case PC: - opPc(instruction{data: new(big.Int).SetUint64(pc)}, &pc, evm.env, contract, mem, stack) - case JUMP: - if err := jump(pc, stack.pop()); err != nil { - return nil, err - } - - continue - case JUMPI: - pos, cond := stack.pop(), stack.pop() - - if cond.Cmp(common.BigTrue) >= 0 { - if err := jump(pc, pos); err != nil { - return nil, err - } - - continue - } - case RETURN: - offset, size := stack.pop(), stack.pop() - ret := mem.GetPtr(offset.Int64(), size.Int64()) - - return ret, nil - case SUICIDE: - opSuicide(instruction{}, nil, evm.env, contract, mem, stack) - - fallthrough - case STOP: // Stop the contract - return nil, nil - } - } - } else { - return nil, fmt.Errorf("Invalid opcode %x", op) - } - - pc++ - - } -} - -// calculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for -// the operation. This does not reduce gas or resizes the memory. -func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef, op OpCode, statedb Database, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) { - var ( - gas = new(big.Int) - newMemSize *big.Int = new(big.Int) - ) - err := baseCheck(op, stack, gas) - if err != nil { - return nil, nil, err - } - - // stack Check, memory resize & gas phase - switch op { - case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: - n := int(op - SWAP1 + 2) - err := stack.require(n) - if err != nil { - return nil, nil, err - } - gas.Set(GasFastestStep) - case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: - n := int(op - DUP1 + 1) - err := stack.require(n) - if err != nil { - return nil, nil, err - } - gas.Set(GasFastestStep) - case LOG0, LOG1, LOG2, LOG3, LOG4: - n := int(op - LOG0) - err := stack.require(n + 2) - if err != nil { - return nil, nil, err - } - - mSize, mStart := stack.data[stack.len()-2], stack.data[stack.len()-1] - - gas.Add(gas, params.LogGas) - gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(n)), params.LogTopicGas)) - gas.Add(gas, new(big.Int).Mul(mSize, params.LogDataGas)) - - newMemSize = calcMemSize(mStart, mSize) - case EXP: - gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(len(stack.data[stack.len()-2].Bytes()))), params.ExpByteGas)) - case SSTORE: - err := stack.require(2) - if err != nil { - return nil, nil, err - } - - var g *big.Int - y, x := stack.data[stack.len()-2], stack.data[stack.len()-1] - val := statedb.GetState(contract.Address(), common.BigToHash(x)) - - // This checks for 3 scenario's and calculates gas accordingly - // 1. From a zero-value address to a non-zero value (NEW VALUE) - // 2. From a non-zero value address to a zero-value address (DELETE) - // 3. From a non-zero to a non-zero (CHANGE) - if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) { - // 0 => non 0 - g = params.SstoreSetGas - } else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) { - statedb.AddRefund(params.SstoreRefundGas) - - g = params.SstoreClearGas - } else { - // non 0 => non 0 (or 0 => 0) - g = params.SstoreResetGas - } - gas.Set(g) - case SUICIDE: - if !statedb.HasSuicided(contract.Address()) { - statedb.AddRefund(params.SuicideRefundGas) - } - case MLOAD: - newMemSize = calcMemSize(stack.peek(), u256(32)) - case MSTORE8: - newMemSize = calcMemSize(stack.peek(), u256(1)) - case MSTORE: - newMemSize = calcMemSize(stack.peek(), u256(32)) - case RETURN: - newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) - case SHA3: - newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2]) - - words := toWordSize(stack.data[stack.len()-2]) - gas.Add(gas, words.Mul(words, params.Sha3WordGas)) - case CALLDATACOPY: - newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3]) - - words := toWordSize(stack.data[stack.len()-3]) - gas.Add(gas, words.Mul(words, params.CopyGas)) - case CODECOPY: - newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3]) - - words := toWordSize(stack.data[stack.len()-3]) - gas.Add(gas, words.Mul(words, params.CopyGas)) - case EXTCODECOPY: - newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-4]) - - words := toWordSize(stack.data[stack.len()-4]) - gas.Add(gas, words.Mul(words, params.CopyGas)) - - case CREATE: - newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-3]) - case CALL, CALLCODE: - gas.Add(gas, stack.data[stack.len()-1]) - - if op == CALL { - if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) { - gas.Add(gas, params.CallNewAccountGas) - } - } - - if len(stack.data[stack.len()-3].Bytes()) > 0 { - gas.Add(gas, params.CallValueTransferGas) - } - - x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7]) - y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5]) - - newMemSize = common.BigMax(x, y) - case DELEGATECALL: - gas.Add(gas, stack.data[stack.len()-1]) - - x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6]) - y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4]) - - newMemSize = common.BigMax(x, y) - } - quadMemGas(mem, newMemSize, gas) - - return newMemSize, gas, nil -} - -// RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go -func (evm *EVM) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) { - gas := p.Gas(len(input)) - if contract.UseGas(gas.Uint64()) { - ret = p.Call(input) - - return ret, nil - } else { - return nil, OutOfGasError } + + contract.Input = nil + + return nil, nil } diff --git a/core/vm_env.go b/core/vm_env.go index a95165f17c..f46a04330f 100644 --- a/core/vm_env.go +++ b/core/vm_env.go @@ -41,11 +41,11 @@ func GetHashFn(ref common.Hash, chain *BlockChain) func(n uint64) common.Hash { } type VMEnv struct { - chainConfig *ChainConfig // Chain configuration - state *state.StateDB // State to use for executing - evm *vm.EVM // The Ethereum Virtual Machine - depth int // Current execution depth - msg Message // Message appliod + chainConfig *ChainConfig // Chain configuration + state *state.StateDB // State to use for executing + evm vm.VirtualMachine // The Ethereum Virtual Machine + depth int // Current execution depth + msg Message // Message appliod header *types.Header // Header information chain *BlockChain // Blockchain handle diff --git a/tests/util.go b/tests/util.go index e51820cd9c..e0ee7a7d55 100644 --- a/tests/util.go +++ b/tests/util.go @@ -176,7 +176,7 @@ type Env struct { vmTest bool - evm *vm.EVM + evm vm.VirtualMachine } func NewEnv(ruleSet RuleSet, state *state.StateDB) *Env { From a41adb9bda6d53e2a4708f27ae936b7831766724 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Fri, 29 Jul 2016 12:41:39 +0200 Subject: [PATCH 3/9] core/vm, tests: refactored native contracts Refactored native contract such that native contract can be easily extended using a struct that implements the vm.NativeContract interface. --- core/vm/contracts.go | 110 ++++++++++++++++++++------------------- core/vm/program_test.go | 2 +- core/vm/vm.go | 2 +- tests/state_test_util.go | 1 - tests/vm_test_util.go | 2 +- 5 files changed, 59 insertions(+), 58 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 63ddd8ab78..4335403df3 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -26,25 +26,27 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// PrecompiledAccount represents a native ethereum contract -type PrecompiledAccount struct { - Gas func(l int) *big.Int - fn func(in []byte) []byte -} - -// Call calls the native function -func (self PrecompiledAccount) Call(in []byte) []byte { - return self.fn(in) +// Precompiled contract is the basic interface for native Go contracts. The implementation +// requires a deterministic gas count based on the input size of the Run method of the +// contract. +type PrecompiledContract interface { + RequiredGas(inputSize int) *big.Int // RequiredPrice calculates the contract gas use + Run(input []byte) []byte // Run runs the precompiled contract } // Precompiled contains the default set of ethereum contracts -var Precompiled = PrecompiledContracts() +var PrecompiledContracts = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256{}, + common.BytesToAddress([]byte{3}): &ripemd160{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, +} // RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go -func RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) { - gas := p.Gas(len(input)) +func RunPrecompiled(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { + gas := p.RequiredGas(len(input)) if contract.UseGas(gas.Uint64()) { - ret = p.Call(input) + ret = p.Run(input) return ret, nil } else { @@ -52,50 +54,17 @@ func RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (re } } -// PrecompiledContracts returns the default set of precompiled ethereum -// contracts defined by the ethereum yellow paper. -func PrecompiledContracts() map[string]*PrecompiledAccount { - return map[string]*PrecompiledAccount{ - // ECRECOVER - string(common.LeftPadBytes([]byte{1}, 20)): &PrecompiledAccount{func(l int) *big.Int { - return params.EcrecoverGas - }, ecrecoverFunc}, +// ECRECOVER implemented as a native contract +type ecrecover struct{} - // SHA256 - string(common.LeftPadBytes([]byte{2}, 20)): &PrecompiledAccount{func(l int) *big.Int { - n := big.NewInt(int64(l+31) / 32) - n.Mul(n, params.Sha256WordGas) - return n.Add(n, params.Sha256Gas) - }, sha256Func}, - - // RIPEMD160 - string(common.LeftPadBytes([]byte{3}, 20)): &PrecompiledAccount{func(l int) *big.Int { - n := big.NewInt(int64(l+31) / 32) - n.Mul(n, params.Ripemd160WordGas) - return n.Add(n, params.Ripemd160Gas) - }, ripemd160Func}, - - string(common.LeftPadBytes([]byte{4}, 20)): &PrecompiledAccount{func(l int) *big.Int { - n := big.NewInt(int64(l+31) / 32) - n.Mul(n, params.IdentityWordGas) - - return n.Add(n, params.IdentityGas) - }, memCpy}, - } +func (c *ecrecover) RequiredGas(inputSize int) *big.Int { + return params.EcrecoverGas } -func sha256Func(in []byte) []byte { - return crypto.Sha256(in) -} +func (c *ecrecover) Run(in []byte) []byte { + const ecRecoverInputLength = 128 -func ripemd160Func(in []byte) []byte { - return common.LeftPadBytes(crypto.Ripemd160(in), 32) -} - -const ecRecoverInputLength = 128 - -func ecrecoverFunc(in []byte) []byte { - in = common.RightPadBytes(in, 128) + in = common.RightPadBytes(in, ecRecoverInputLength) // "in" is (hash, v, r, s), each 32 bytes // but for ecrecover we want (r, s, v) @@ -126,6 +95,39 @@ func ecrecoverFunc(in []byte) []byte { return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32) } -func memCpy(in []byte) []byte { +// SHA256 implemented as a native contract +type sha256 struct{} + +func (c *sha256) RequiredGas(inputSize int) *big.Int { + n := big.NewInt(int64(inputSize+31) / 32) + n.Mul(n, params.Sha256WordGas) + return n.Add(n, params.Sha256Gas) +} +func (c *sha256) Run(in []byte) []byte { + return crypto.Sha256(in) +} + +// RIPMED160 implemented as a native contract +type ripemd160 struct{} + +func (c *ripemd160) RequiredGas(inputSize int) *big.Int { + n := big.NewInt(int64(inputSize+31) / 32) + n.Mul(n, params.Ripemd160WordGas) + return n.Add(n, params.Ripemd160Gas) +} +func (c *ripemd160) Run(in []byte) []byte { + return common.LeftPadBytes(crypto.Ripemd160(in), 32) +} + +// data copy implemented as a native contract +type dataCopy struct{} + +func (c *dataCopy) RequiredGas(inputSize int) *big.Int { + n := big.NewInt(int64(inputSize+31) / 32) + n.Mul(n, params.IdentityWordGas) + + return n.Add(n, params.IdentityGas) +} +func (c *dataCopy) Run(in []byte) []byte { return in } diff --git a/core/vm/program_test.go b/core/vm/program_test.go index b387dc90f1..df689afc40 100644 --- a/core/vm/program_test.go +++ b/core/vm/program_test.go @@ -75,7 +75,7 @@ func TestCompiling(t *testing.T) { func TestResetInput(t *testing.T) { var sender account - env := NewEnv(true, true) + env := NewEnv(&Config{}) contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000)) contract.CodeAddr = &common.Address{} diff --git a/core/vm/vm.go b/core/vm/vm.go index 908f4721f8..8f4fafc1df 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -59,7 +59,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) ([]byte, error) { defer evm.env.SetDepth(evm.env.Depth() - 1) if contract.CodeAddr != nil { - if p := Precompiled[contract.CodeAddr.Str()]; p != nil { + if p, exist := PrecompiledContracts[*contract.CodeAddr]; exist { return RunPrecompiled(p, input, contract) } } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 1aa00ef075..00ddf232cc 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -224,7 +224,6 @@ func RunState(ruleSet RuleSet, statedb *state.StateDB, env, tx map[string]string to = &t } // Set pre compiled contracts - vm.Precompiled = vm.PrecompiledContracts() snapshot := statedb.Snapshot() gaspool := new(core.GasPool).AddGas(common.Big(env["currentGasLimit"])) diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index abcdb5d437..d28f053c04 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -220,7 +220,7 @@ func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, value = common.Big(exec["value"]) ) // Reset the pre-compiled contracts for VM tests. - vm.Precompiled = make(map[string]*vm.PrecompiledAccount) + vm.PrecompiledContracts = make(map[common.Address]vm.PrecompiledContract) caller := state.GetOrNewStateObject(from) From 4e25441a8f208379eb5426904ef8520ffd46d09b Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 28 Sep 2016 15:29:50 +0200 Subject: [PATCH 4/9] core/vm: removed jit prefix from functions --- core/vm/gas.go | 4 ++-- core/vm/instructions.go | 2 +- core/vm/program.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/vm/gas.go b/core/vm/gas.go index 77585c681f..b5b916dc24 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -82,9 +82,9 @@ func calcQuadMemGas(mem *Memory, newMemSize uint64) (uint64, bool) { return 0, false } -// jitBaseCalc is the same as baseCheck except it doesn't do the look up in the +// baseCalc is the same as baseCheck except it doesn't do the look up in the // gas table. This is done during compilation instead. -func jitBaseCalc(instr instruction, stack *Stack) (uint64, error) { +func baseCalc(instr instruction, stack *Stack) (uint64, error) { err := stack.require(instr.spop) if err != nil { return 0, err diff --git a/core/vm/instructions.go b/core/vm/instructions.go index e16e07dbeb..757a75d4fb 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -60,7 +60,7 @@ func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract func (instr instruction) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { // calculate the new memory size and gas price for the current executing opcode - newMemSize, cost, err := jitCalculateGasAndSize(env, contract, instr, env.Db(), memory, stack) + newMemSize, cost, err := calculateGasAndSize(env, contract, instr, env.Db(), memory, stack) if err != nil { return nil, err } diff --git a/core/vm/program.go b/core/vm/program.go index 85e8fb9d98..04fb4fe09c 100644 --- a/core/vm/program.go +++ b/core/vm/program.go @@ -308,15 +308,15 @@ func validDest(dests map[uint64]struct{}, dest *big.Int) bool { return ok } -// jitCalculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for +// calculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for // the operation. This does not reduce gas or resizes the memory. -func jitCalculateGasAndSize(env Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *Stack) (uint64, uint64, error) { +func calculateGasAndSize(env Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *Stack) (uint64, uint64, error) { var ( newMemSize uint64 sizeFault bool ) - gas, err := jitBaseCalc(instr, stack) + gas, err := baseCalc(instr, stack) if err != nil { return 0, 0, err } From 1f8e72382105623c4ca352bed896b1072f8f519f Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Thu, 29 Sep 2016 13:50:39 +0200 Subject: [PATCH 5/9] core, tests, miner, cmd: improved EVM environment Reduced the overhead caused by all the diffirent implementations of the vm.Environment interface. Where previously the Environment was an interface it has now become an implementation it self using several new easy to implement interfaces such as the Backend that takes care of the state and CallContext which takes care of the EVM calling conventions. Most of these interfaces have been implemented in the core package with the exception of the test suit which uses a combination of the core package implementations and a mix of its own. This will reduce the amount of extra overhead when changing an interface, such as adding a new method or changing a method signature. --- accounts/abi/bind/backends/simulated.go | 9 +- cmd/evm/main.go | 184 +++++++++------------- common/registrar/ethreg/api.go | 11 +- core/evm.go | 96 ++++++++++++ core/execution.go | 51 +++--- core/state/dump.go | 18 +++ core/state/statedb.go | 7 + core/state_processor.go | 15 +- core/state_transition.go | 12 +- core/vm/environment.go | 130 ---------------- core/vm/instructions.go | 175 ++++++++++----------- core/vm/interface.go | 199 ++++++++++++++++++++++++ core/vm/logger.go | 6 +- core/vm/logger_test.go | 16 +- core/vm/program.go | 6 +- core/vm/program_optimiser.go | 2 +- core/vm/program_test.go | 123 +-------------- core/vm/runtime/env.go | 116 -------------- core/vm/runtime/runtime.go | 50 +++++- core/vm/runtime/runtime_test.go | 4 +- core/vm/segments.go | 4 +- core/vm/vm.go | 25 +-- core/vm/vm_jit_fake.go | 2 +- core/vm_env.go | 118 -------------- eth/api.go | 13 +- eth/api_backend.go | 11 +- eth/backend.go | 5 - internal/ethapi/backend.go | 2 +- internal/ethapi/tracer.go | 46 +++--- internal/ethapi/tracer_test.go | 29 +++- miner/worker.go | 8 +- tests/state_test_util.go | 10 +- tests/util.go | 166 +++++--------------- tests/vm_test_util.go | 11 +- 34 files changed, 746 insertions(+), 934 deletions(-) create mode 100644 core/evm.go delete mode 100644 core/vm/environment.go create mode 100644 core/vm/interface.go delete mode 100644 core/vm/runtime/env.go delete mode 100644 core/vm_env.go diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 74203a468d..c7b67b3afb 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -222,7 +222,14 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM from.SetBalance(common.MaxBig) // Execute the call. msg := callmsg{call} - vmenv := core.NewEnv(statedb, chainConfig, b.blockchain, msg, block.Header(), vm.Config{}) + + backend := &core.EVMBackend{ + GetHashFn: core.GetHashFn(block.ParentHash(), b.blockchain), + State: statedb, + } + context := core.ToEVMContext(chainConfig, msg, block.Header()) + vmenv := vm.NewEnvironment(context, backend, chainConfig, chainConfig.VmConfig) + gaspool := new(core.GasPool).AddGas(common.MaxBig) ret, gasUsed, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() return ret, gasUsed, err diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 8d3c8f2c5a..67caa2daf5 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -28,7 +28,6 @@ import ( "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/crypto" "github.com/ethereum/go-ethereum/ethdb" @@ -45,33 +44,55 @@ var ( Name: "debug", Usage: "output full trace logs", } - ForceJitFlag = cli.BoolFlag{ - Name: "forcejit", - Usage: "forces jit compilation", - } - DisableJitFlag = cli.BoolFlag{ - Name: "nojit", - Usage: "disabled jit compilation", - } CodeFlag = cli.StringFlag{ Name: "code", Usage: "EVM code", } GasFlag = cli.StringFlag{ Name: "gas", - Usage: "gas limit for the evm", + Usage: "set the gas limit for the EVM execution", Value: "10000000000", } PriceFlag = cli.StringFlag{ Name: "price", - Usage: "price set for the evm", + Usage: "set the price for the EVM execution", Value: "0", } ValueFlag = cli.StringFlag{ Name: "value", - Usage: "value set for the evm", + Usage: "set the value for the EVM execution", Value: "0", } + SenderFlag = cli.StringFlag{ + Name: "origin", + Usage: "set the origin for the EVM execution", + Value: "0x", + } + CoinbaseFlag = cli.StringFlag{ + Name: "coinbase", + Usage: "coinbase set for the evm", + Value: "0x", + } + BlockNumberFlag = cli.StringFlag{ + Name: "blocknumber", + Usage: "set the block number for the EVM execution", + Value: "1", + } + BlockTimeFlag = cli.StringFlag{ + Name: "blocktime", + Usage: "set the block time for the EVM execution", + Value: "1", + } + BlockDifficultyFlag = cli.StringFlag{ + Name: "difficulty", + Usage: "set the block difficulty for the EVM execution", + Value: "1", + } + GasLimitFlag = cli.StringFlag{ + Name: "gaslimit", + Usage: "sets the gas limit for the EVM execution", + Value: "1", + } DumpFlag = cli.BoolFlag{ Name: "dump", Usage: "dumps the state after the run", @@ -99,8 +120,6 @@ func init() { CreateFlag, DebugFlag, VerbosityFlag, - ForceJitFlag, - DisableJitFlag, SysStatFlag, CodeFlag, GasFlag, @@ -108,6 +127,11 @@ func init() { ValueFlag, DumpFlag, InputFlag, + GasLimitFlag, + CoinbaseFlag, + SenderFlag, + BlockNumberFlag, + BlockTimeFlag, } app.Action = run } @@ -117,22 +141,48 @@ func run(ctx *cli.Context) error { glog.SetV(ctx.GlobalInt(VerbosityFlag.Name)) db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, db) - sender := statedb.CreateAccount(common.StringToAddress("sender")) logger := vm.NewStructLogger(nil) - vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)), common.Big(ctx.GlobalString(PriceFlag.Name)), vm.Config{ - Debug: ctx.GlobalBool(DebugFlag.Name), - ForceJit: ctx.GlobalBool(ForceJitFlag.Name), - EnableJit: !ctx.GlobalBool(DisableJitFlag.Name), - Tracer: logger, - }) - tstart := time.Now() + db, err := ethdb.NewMemDatabase() + if err != nil { + panic(err) + } + st, err := state.New(common.Hash{}, db) + if err != nil { + panic(err) + } + backend := &core.EVMBackend{ + GetHashFn: func(n uint64) common.Hash { + return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String()))) + }, + State: st, + } + context := vm.Context{ + CallContext: core.EVMCallContext{core.CanTransfer, core.Transfer}, + Origin: common.StringToAddress(ctx.GlobalString(SenderFlag.Name)), + Coinbase: common.StringToAddress(ctx.GlobalString(CoinbaseFlag.Name)), + BlockNumber: common.String2Big(ctx.GlobalString(BlockNumberFlag.Name)), + Time: common.String2Big(ctx.GlobalString(BlockTimeFlag.Name)), + Difficulty: common.String2Big(ctx.GlobalString(BlockDifficultyFlag.Name)), + GasLimit: common.String2Big(ctx.GlobalString(GasLimitFlag.Name)), + GasPrice: common.String2Big(ctx.GlobalString(PriceFlag.Name)), + } + + vmenv := vm.NewEnvironment( + context, + backend, + ruleSet{}, + vm.Config{ + Debug: ctx.GlobalBool(DebugFlag.Name), + Tracer: logger, + }, + ) var ( - ret []byte - err error + ret []byte + tstart = time.Now() + sender = st.CreateAccount(context.Origin) ) if ctx.GlobalBool(CreateFlag.Name) { @@ -144,8 +194,7 @@ func run(ctx *cli.Context) error { common.Big(ctx.GlobalString(ValueFlag.Name)), ) } else { - receiver := statedb.CreateAccount(common.StringToAddress("receiver")) - + receiver := st.CreateAccount(common.StringToAddress("receiver")) code := common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name)) receiver.SetCode(crypto.Keccak256Hash(code), code) ret, err = vmenv.Call( @@ -159,8 +208,7 @@ func run(ctx *cli.Context) error { vmdone := time.Since(tstart) if ctx.GlobalBool(DumpFlag.Name) { - statedb.Commit() - fmt.Println(string(statedb.Dump())) + fmt.Println(string(st.Dump())) } vm.StdErrFormat(logger.StructLogs()) @@ -192,85 +240,7 @@ func main() { } } -type VMEnv struct { - state *state.StateDB - block *types.Block - - transactor *common.Address - value *big.Int - - depth int - Gas, price *big.Int - time *big.Int - logs []vm.StructLog - - evm *vm.EVM -} - -func NewEnv(state *state.StateDB, transactor common.Address, value, price *big.Int, cfg vm.Config) *VMEnv { - env := &VMEnv{ - state: state, - transactor: &transactor, - value: value, - price: price, - time: big.NewInt(time.Now().Unix()), - } - - env.evm = vm.New(env, cfg) - return env -} - // ruleSet implements vm.RuleSet and will always default to the homestead rule set. type ruleSet struct{} func (ruleSet) IsHomestead(*big.Int) bool { return true } - -func (self *VMEnv) RuleSet() vm.RuleSet { return ruleSet{} } -func (self *VMEnv) Vm() vm.Vm { return self.evm } -func (self *VMEnv) Db() vm.Database { return self.state } -func (self *VMEnv) SnapshotDatabase() int { return self.state.Snapshot() } -func (self *VMEnv) RevertToSnapshot(snap int) { self.state.RevertToSnapshot(snap) } -func (self *VMEnv) Origin() common.Address { return *self.transactor } -func (self *VMEnv) BlockNumber() *big.Int { return common.Big0 } -func (self *VMEnv) Coinbase() common.Address { return *self.transactor } -func (self *VMEnv) Time() *big.Int { return self.time } -func (self *VMEnv) Difficulty() *big.Int { return common.Big1 } -func (self *VMEnv) BlockHash() []byte { return make([]byte, 32) } -func (self *VMEnv) Value() *big.Int { return self.value } -func (self *VMEnv) GasLimit() *big.Int { return big.NewInt(1000000000) } -func (self *VMEnv) GasPrice() *big.Int { return big.NewInt(1000000000) } -func (self *VMEnv) VmType() vm.Type { return vm.StdVmTy } -func (self *VMEnv) Depth() int { return 0 } -func (self *VMEnv) SetDepth(i int) { self.depth = i } -func (self *VMEnv) GetHash(n uint64) common.Hash { - if self.block.Number().Cmp(big.NewInt(int64(n))) == 0 { - return self.block.Hash() - } - return common.Hash{} -} -func (self *VMEnv) AddLog(log *vm.Log) { - self.state.AddLog(log) -} -func (self *VMEnv) CanTransfer(from common.Address, balance *big.Int) bool { - return self.state.GetBalance(from).Cmp(balance) >= 0 -} -func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) { - core.Transfer(from, to, amount) -} - -func (self *VMEnv) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - self.Gas = gas - return core.Call(self, caller, addr, data, gas, value) -} - -func (self *VMEnv) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - return core.CallCode(self, caller, addr, data, gas, value) -} - -func (self *VMEnv) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { - return core.DelegateCall(self, caller, addr, data, gas) -} - -func (self *VMEnv) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { - return core.Create(self, caller, data, gas, value) -} diff --git a/common/registrar/ethreg/api.go b/common/registrar/ethreg/api.go index 6dd0ef46f3..32fef80528 100644 --- a/common/registrar/ethreg/api.go +++ b/common/registrar/ethreg/api.go @@ -194,7 +194,16 @@ func (be *registryAPIBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr } header := be.bc.CurrentBlock().Header() - vmenv := core.NewEnv(statedb, be.config, be.bc, msg, header, vm.Config{}) + + backend := &core.EVMBackend{ + GetHashFn: core.GetHashFn(header.ParentHash, be.bc), + State: statedb, + } + + context := core.ToEVMContext(be.config, msg, header) + vmenv := vm.NewEnvironment(context, backend, be.config, vm.Config{}) + + //vmenv := vm.NewEnvironment(false, statedb, be.config, be.bc, msg, header, vm.Config{}) gp := new(core.GasPool).AddGas(common.MaxBig) res, gas, err := core.ApplyMessage(vmenv, msg, gp) diff --git a/core/evm.go b/core/evm.go new file mode 100644 index 0000000000..2ca48ca662 --- /dev/null +++ b/core/evm.go @@ -0,0 +1,96 @@ +// Copyright 2014 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 core + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +// ToEVMContext creates a new context for use in the EVM. +func ToEVMContext(config *ChainConfig, msg Message, header *types.Header) vm.Context { + var from common.Address + if config.IsHomestead(header.Number) { + from, _ = msg.From() + } else { + from, _ = msg.FromFrontier() + } + + return vm.Context{ + CallContext: EVMCallContext{CanTransfer, Transfer}, + Origin: from, + Coinbase: header.Coinbase, + BlockNumber: new(big.Int).Set(header.Number), + Time: new(big.Int).Set(header.Time), + Difficulty: new(big.Int).Set(header.Difficulty), + GasLimit: new(big.Int).Set(header.GasLimit), + GasPrice: new(big.Int).Set(msg.GasPrice()), + } +} + +// GetHashFn returns a function for which the VM env can query block hashes through +// up to the limit defined by the Yellow Paper and uses the given block chain +// to query for information. +func GetHashFn(ref common.Hash, chain *BlockChain) func(n uint64) common.Hash { + return func(n uint64) common.Hash { + for block := chain.GetBlockByHash(ref); block != nil; block = chain.GetBlock(block.ParentHash(), block.NumberU64()-1) { + if block.NumberU64() == n { + return block.Hash() + } + } + + return common.Hash{} + } +} + +// CanTransfer checks wether there are enough funds in the address' account to make a transfer. +func CanTransfer(db vm.Database, addr common.Address, amount *big.Int) bool { + return db.GetBalance(addr).Cmp(amount) >= 0 +} + +// Transfer subtracts amount from sender and adds amount to recipient using the given Db +func Transfer(db vm.Database, sender, recipient common.Address, amount *big.Int) { + db.SubBalance(sender, amount) + db.AddBalance(recipient, amount) +} + +// EVMBackend implements vm.Backend and provides an interface to keep track of the current +// state. +type EVMBackend struct { + GetHashFn func(uint64) common.Hash // getHashFn callback is used to retrieve block hashes + State *state.StateDB +} + +// MakeSnapshot returns a copy of the state. +func (b *EVMBackend) SnapshotDatabase() int { + return b.State.Snapshot() +} + +// Get returns the state +func (b *EVMBackend) Get() vm.Database { return b.State } + +// Set sets the current state +func (b *EVMBackend) RevertToSnapshot(revision int) { b.State.RevertToSnapshot(revision) } + +// GetHash returns the canonical hash referenced by the depth +func (b *EVMBackend) GetHash(n uint64) common.Hash { + return b.GetHashFn(n) +} diff --git a/core/execution.go b/core/execution.go index 8f519b24c7..f23c6d4d49 100644 --- a/core/execution.go +++ b/core/execution.go @@ -25,31 +25,36 @@ import ( "github.com/ethereum/go-ethereum/params" ) +type EVMCallContext struct { + CanTransfer func(db vm.Database, addr common.Address, amount *big.Int) bool + Transfer func(db vm.Database, sender, recipient common.Address, amount *big.Int) +} + // Call executes within the given contract -func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, value *big.Int) (ret []byte, err error) { - ret, _, err = exec(env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, value) +func (c EVMCallContext) Call(env *vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, value *big.Int) (ret []byte, err error) { + ret, _, err = c.exec(env, caller, &addr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, value) return ret, err } // CallCode executes the given address' code as the given contract address -func CallCode(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, value *big.Int) (ret []byte, err error) { +func (c EVMCallContext) CallCode(env *vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, value *big.Int) (ret []byte, err error) { callerAddr := caller.Address() - ret, _, err = exec(env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, value) + ret, _, err = c.exec(env, caller, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, value) return ret, err } // DelegateCall is equivalent to CallCode except that sender and value propagates from parent scope to child scope -func DelegateCall(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas *big.Int) (ret []byte, err error) { +func (c EVMCallContext) DelegateCall(env *vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas *big.Int) (ret []byte, err error) { callerAddr := caller.Address() - originAddr := env.Origin() + originAddr := env.Origin callerValue := caller.Value() - ret, _, err = execDelegateCall(env, caller, &originAddr, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, callerValue) + ret, _, err = c.execDelegateCall(env, caller, &originAddr, &callerAddr, &addr, env.Db().GetCodeHash(addr), input, env.Db().GetCode(addr), gas, callerValue) return ret, err } // Create creates a new contract with the given code -func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, value *big.Int) (ret []byte, address common.Address, err error) { - ret, address, err = exec(env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, value) +func (c EVMCallContext) Create(env *vm.Environment, caller vm.ContractRef, code []byte, gas, value *big.Int) (ret []byte, address common.Address, err error) { + ret, address, err = c.exec(env, caller, nil, nil, crypto.Keccak256Hash(code), nil, code, gas, value) // Here we get an error if we run into maximum stack depth, // See: https://github.com/ethereum/yellowpaper/pull/131 // and YP definitions for CREATE instruction @@ -59,17 +64,17 @@ func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, value * return ret, address, err } -func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, value *big.Int) (ret []byte, addr common.Address, err error) { +func (c EVMCallContext) exec(env *vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, value *big.Int) (ret []byte, addr common.Address, err error) { evm := env.Vm() // Depth check execution. Fail if we're trying to execute above the // limit. - if env.Depth() > int(params.CallCreateDepth.Int64()) { + if env.Depth > int(params.CallCreateDepth.Int64()) { caller.ReturnGas(gas.Uint64()) return nil, common.Address{}, vm.DepthError } - if !env.CanTransfer(caller.Address(), value) { + if !c.CanTransfer(env.Db(), caller.Address(), value) { caller.ReturnGas(gas.Uint64()) return nil, common.Address{}, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address())) @@ -85,7 +90,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A createAccount = true } - snapshotPreTransfer := env.SnapshotDatabase() + snapshotPreTransfer := env.Backend.SnapshotDatabase() var ( from = env.Db().GetAccount(caller.Address()) to vm.Account @@ -99,7 +104,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A to = env.Db().GetAccount(*address) } } - env.Transfer(from, to, value) + c.Transfer(env.Db(), from.Address(), to.Address(), value) // initialise a new contract and set the code that is to be used by the // EVM. The contract is a scoped environment for this execution context @@ -126,25 +131,25 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in homestead this also counts for code storage gas errors. - if err != nil && (env.RuleSet().IsHomestead(env.BlockNumber()) || err != vm.CodeStoreOutOfGasError) { + if err != nil && (env.RuleSet().IsHomestead(env.BlockNumber) || err != vm.CodeStoreOutOfGasError) { contract.UseGas(contract.Gas()) - env.RevertToSnapshot(snapshotPreTransfer) + env.Backend.RevertToSnapshot(snapshotPreTransfer) } return ret, addr, err } -func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, value *big.Int) (ret []byte, addr common.Address, err error) { +func (c EVMCallContext) execDelegateCall(env *vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, codeHash common.Hash, input, code []byte, gas, value *big.Int) (ret []byte, addr common.Address, err error) { evm := env.Vm() // Depth check execution. Fail if we're trying to execute above the // limit. - if env.Depth() > int(params.CallCreateDepth.Int64()) { + if env.Depth > int(params.CallCreateDepth.Int64()) { caller.ReturnGas(gas.Uint64()) return nil, common.Address{}, vm.DepthError } - snapshot := env.SnapshotDatabase() + snapshot := env.Backend.SnapshotDatabase() var to vm.Account if !env.Db().Exist(*toAddr) { @@ -162,14 +167,8 @@ func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toA if err != nil { contract.UseGas(contract.Gas()) - env.RevertToSnapshot(snapshot) + env.Backend.RevertToSnapshot(snapshot) } return ret, addr, err } - -// generic transfer method -func Transfer(from, to vm.Account, amount *big.Int) { - from.SubBalance(amount) - to.AddBalance(amount) -} diff --git a/core/state/dump.go b/core/state/dump.go index 8294d61b9f..aa79f96726 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -67,6 +67,24 @@ func (self *StateDB) RawDump() Dump { } dump.Accounts[common.Bytes2Hex(addr)] = account } + for addr, stateObject := range self.stateObjects { + account := DumpAccount{ + Balance: stateObject.Balance().String(), + Nonce: stateObject.Nonce(), + Root: common.Bytes2Hex(stateObject.data.Root[:]), + CodeHash: common.Bytes2Hex(stateObject.CodeHash()), + Code: common.Bytes2Hex(stateObject.Code(self.db)), + Storage: make(map[string]string), + } + storageIt := stateObject.getTrie(self.db).Iterator() + for storageIt.Next() { + account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) + } + for storageAddr, value := range stateObject.cachedStorage { + account.Storage[common.Bytes2Hex(storageAddr[:])] = value.Hex() + } + dump.Accounts[common.Bytes2Hex(addr[:])] = account + } return dump } diff --git a/core/state/statedb.go b/core/state/statedb.go index ec9e9392f0..f46f2f4427 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -294,6 +294,13 @@ func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) { } } +func (self *StateDB) SubBalance(addr common.Address, amount *big.Int) { + stateObject := self.GetOrNewStateObject(addr) + if stateObject != nil { + stateObject.SubBalance(amount) + } +} + func (self *StateDB) SetBalance(addr common.Address, amount *big.Int) { stateObject := self.GetOrNewStateObject(addr) if stateObject != nil { diff --git a/core/state_processor.go b/core/state_processor.go index fd8e9762e9..558d3326ff 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -90,7 +90,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // ApplyTransactions returns the generated receipts and vm logs during the // execution of the state transition phase. func ApplyTransaction(config *ChainConfig, bc *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config) (*types.Receipt, vm.Logs, *big.Int, error) { - _, gas, err := ApplyMessage(NewEnv(statedb, config, bc, tx, header, cfg), tx, gp) + backend := &EVMBackend{ + GetHashFn: GetHashFn(header.ParentHash, bc), + State: statedb, + } + context := ToEVMContext(config, tx, header) + + env := vm.NewEnvironment(context, backend, config, cfg) + + _, gas, err := ApplyMessage(env, tx, gp) if err != nil { return nil, nil, nil, err } @@ -105,13 +113,12 @@ func ApplyTransaction(config *ChainConfig, bc *BlockChain, gp *GasPool, statedb receipt.ContractAddress = crypto.CreateAddress(from, tx.Nonce()) } - logs := statedb.GetLogs(tx.Hash()) - receipt.Logs = logs + receipt.Logs = statedb.GetLogs(tx.Hash()) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) glog.V(logger.Debug).Infoln(receipt) - return receipt, logs, gas, err + return receipt, receipt.Logs, gas, err } // AccumulateRewards credits the coinbase of the given block with the diff --git a/core/state_transition.go b/core/state_transition.go index 70f1844838..f6366414bb 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -57,7 +57,7 @@ type StateTransition struct { data []byte state vm.Database - env vm.Environment + env *vm.Environment } // Message represents a message sent to a contract. @@ -106,7 +106,7 @@ func IntrinsicGas(data []byte, contractCreation, homestead bool) *big.Int { } // NewStateTransition initialises and returns a new state transition object. -func NewStateTransition(env vm.Environment, msg Message, gp *GasPool) *StateTransition { +func NewStateTransition(env *vm.Environment, msg Message, gp *GasPool) *StateTransition { return &StateTransition{ gp: gp, env: env, @@ -127,7 +127,7 @@ func NewStateTransition(env vm.Environment, msg Message, gp *GasPool) *StateTran // the gas used (which includes gas refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. -func ApplyMessage(env vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) { +func ApplyMessage(env *vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) { st := NewStateTransition(env, msg, gp) ret, _, gasUsed, err := st.TransitionDb() @@ -139,7 +139,7 @@ func (self *StateTransition) from() (vm.Account, error) { f common.Address err error ) - if self.env.RuleSet().IsHomestead(self.env.BlockNumber()) { + if self.env.RuleSet().IsHomestead(self.env.BlockNumber) { f, err = self.msg.From() } else { f, err = self.msg.FromFrontier() @@ -234,7 +234,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b msg := self.msg sender, _ := self.from() // err checked in preCheck - homestead := self.env.RuleSet().IsHomestead(self.env.BlockNumber()) + homestead := self.env.RuleSet().IsHomestead(self.env.BlockNumber) contractCreation := MessageCreatesContract(msg) // Pay intrinsic gas if err = self.useGas(IntrinsicGas(self.data, contractCreation, homestead)); err != nil { @@ -274,7 +274,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b requiredGas = new(big.Int).Set(self.gasUsed()) self.refundGas() - self.state.AddBalance(self.env.Coinbase(), new(big.Int).Mul(self.gasUsed(), self.gasPrice)) + self.env.Db().AddBalance(self.env.Coinbase, new(big.Int).Mul(self.gasUsed(), self.gasPrice)) return ret, requiredGas, self.gasUsed(), err } diff --git a/core/vm/environment.go b/core/vm/environment.go deleted file mode 100644 index 4d55daaa12..0000000000 --- a/core/vm/environment.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2014 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 vm - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -// 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 -} - -// Environment is an EVM requirement and helper which allows access to outside -// information such as states. -type Environment interface { - // The current ruleset - RuleSet() RuleSet - // The state database - Db() Database - // Creates a restorable snapshot - SnapshotDatabase() int - // Set database to previous snapshot - RevertToSnapshot(int) - // Address of the original invoker (first occurrence of the VM invoker) - Origin() common.Address - // The block number this VM is invoked on - BlockNumber() *big.Int - // The n'th hash ago from this block number - GetHash(uint64) common.Hash - // The handler's address - Coinbase() common.Address - // The current time (block time) - Time() *big.Int - // Difficulty set on the current block - Difficulty() *big.Int - // The gas limit of the block - GasLimit() *big.Int - // The gas price of the current call - GasPrice() *big.Int - // Determines whether it's possible to transact - CanTransfer(from common.Address, balance *big.Int) bool - // Transfers amount from one account to the other - Transfer(from, to Account, amount *big.Int) - // Adds a LOG to the state - AddLog(*Log) - // Type of the VM - Vm() Vm - // Get the curret calling depth - Depth() int - // Set the current calling depth - SetDepth(i int) - // Call another contract - Call(me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) - // Take another's contract code and execute within our own context - CallCode(me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) - // Same as CallCode except sender and value is propagated from parent to child scope - DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) - // Create a new contract - Create(me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) -} - -// Vm is the basic interface for an implementation of the EVM. -type Vm interface { - // Run should execute the given contract with the input given in in - // and return the contract execution return bytes or an error if it - // failed. - Run(c *Contract, in []byte) ([]byte, error) -} - -// Database is a EVM database for full state querying. -type Database interface { - GetAccount(common.Address) Account - CreateAccount(common.Address) Account - - AddBalance(common.Address, *big.Int) - GetBalance(common.Address) *big.Int - - GetNonce(common.Address) uint64 - SetNonce(common.Address, uint64) - - GetCodeHash(common.Address) common.Hash - GetCodeSize(common.Address) int - GetCode(common.Address) []byte - SetCode(common.Address, []byte) - - AddRefund(*big.Int) - GetRefund() *big.Int - - GetState(common.Address, common.Hash) common.Hash - SetState(common.Address, common.Hash, common.Hash) - - Suicide(common.Address) bool - HasSuicided(common.Address) bool - - // Exist reports whether the given account exists in state. - // Notably this should also return true for suicided accounts. - Exist(common.Address) bool -} - -// Account represents a contract or basic ethereum account. -type Account interface { - SubBalance(amount *big.Int) - AddBalance(amount *big.Int) - SetBalance(*big.Int) - SetNonce(uint64) - Balance() *big.Int - Address() common.Address - ReturnGas(uint64) - SetCode(common.Hash, []byte) - ForEachStorage(cb func(key, value common.Hash) bool) - Value() *big.Int -} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 757a75d4fb..7b20dbcde0 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -27,14 +27,14 @@ import ( type programInstruction interface { // executes the program instruction and allows the instruction to modify the state of the program - do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) + do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) // returns whether the program instruction halts the execution of the JIT halts() bool // Returns the current op code (debugging purposes) Op() OpCode } -type instrFn func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) +type instrFn func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) type instruction struct { op OpCode @@ -58,12 +58,13 @@ func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract return mapping[to.Uint64()], nil } -func (instr instruction) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func (instr instruction) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { // calculate the new memory size and gas price for the current executing opcode newMemSize, cost, err := calculateGasAndSize(env, contract, instr, env.Db(), memory, stack) if err != nil { return nil, err } + //fmt.Printf("%04d: %-8v %v\n", *pc, instr.op, cost) // Use the calculated gas. When insufficient gas is present, use all gas and return an // Out Of Gas error @@ -114,26 +115,26 @@ func (instr instruction) Op() OpCode { return instr.op } -func opStaticJump(instr instruction, pc *uint64, ret *big.Int, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opStaticJump(instr instruction, pc *uint64, ret *big.Int, env *Environment, contract *Contract, memory *Memory, stack *Stack) { ret.Set(instr.data) } -func opAdd(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opAdd(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() stack.push(U256(x.Add(x, y))) } -func opSub(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSub(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() stack.push(U256(x.Sub(x, y))) } -func opMul(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opMul(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() stack.push(U256(x.Mul(x, y))) } -func opDiv(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opDiv(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() if y.Cmp(common.Big0) != 0 { stack.push(U256(x.Div(x, y))) @@ -142,7 +143,7 @@ func opDiv(instr instruction, pc *uint64, env Environment, contract *Contract, m } } -func opSdiv(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSdiv(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := S256(stack.pop()), S256(stack.pop()) if y.Cmp(common.Big0) == 0 { stack.push(new(big.Int)) @@ -162,7 +163,7 @@ func opSdiv(instr instruction, pc *uint64, env Environment, contract *Contract, } } -func opMod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opMod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() if y.Cmp(common.Big0) == 0 { stack.push(new(big.Int)) @@ -171,7 +172,7 @@ func opMod(instr instruction, pc *uint64, env Environment, contract *Contract, m } } -func opSmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSmod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := S256(stack.pop()), S256(stack.pop()) if y.Cmp(common.Big0) == 0 { @@ -191,12 +192,12 @@ func opSmod(instr instruction, pc *uint64, env Environment, contract *Contract, } } -func opExp(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opExp(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() stack.push(U256(x.Exp(x, y, Pow256))) } -func opSignExtend(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSignExtend(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { back := stack.pop() if back.Cmp(big.NewInt(31)) < 0 { bit := uint(back.Uint64()*8 + 7) @@ -213,12 +214,12 @@ func opSignExtend(instr instruction, pc *uint64, env Environment, contract *Cont } } -func opNot(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opNot(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x := stack.pop() stack.push(U256(x.Not(x))) } -func opLt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opLt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() if x.Cmp(y) < 0 { stack.push(big.NewInt(1)) @@ -227,7 +228,7 @@ func opLt(instr instruction, pc *uint64, env Environment, contract *Contract, me } } -func opGt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opGt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() if x.Cmp(y) > 0 { stack.push(big.NewInt(1)) @@ -236,7 +237,7 @@ func opGt(instr instruction, pc *uint64, env Environment, contract *Contract, me } } -func opSlt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSlt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := S256(stack.pop()), S256(stack.pop()) if x.Cmp(S256(y)) < 0 { stack.push(big.NewInt(1)) @@ -245,7 +246,7 @@ func opSlt(instr instruction, pc *uint64, env Environment, contract *Contract, m } } -func opSgt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSgt(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := S256(stack.pop()), S256(stack.pop()) if x.Cmp(y) > 0 { stack.push(big.NewInt(1)) @@ -254,7 +255,7 @@ func opSgt(instr instruction, pc *uint64, env Environment, contract *Contract, m } } -func opEq(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opEq(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() if x.Cmp(y) == 0 { stack.push(big.NewInt(1)) @@ -263,7 +264,7 @@ func opEq(instr instruction, pc *uint64, env Environment, contract *Contract, me } } -func opIszero(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opIszero(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x := stack.pop() if x.Cmp(common.Big0) > 0 { stack.push(new(big.Int)) @@ -272,19 +273,19 @@ func opIszero(instr instruction, pc *uint64, env Environment, contract *Contract } } -func opAnd(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opAnd(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() stack.push(x.And(x, y)) } -func opOr(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opOr(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() stack.push(x.Or(x, y)) } -func opXor(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opXor(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y := stack.pop(), stack.pop() stack.push(x.Xor(x, y)) } -func opByte(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opByte(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { th, val := stack.pop(), stack.pop() if th.Cmp(big.NewInt(32)) < 0 { byte := big.NewInt(int64(common.LeftPadBytes(val.Bytes(), 32)[th.Int64()])) @@ -293,7 +294,7 @@ func opByte(instr instruction, pc *uint64, env Environment, contract *Contract, stack.push(new(big.Int)) } } -func opAddmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opAddmod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y, z := stack.pop(), stack.pop(), stack.pop() if z.Cmp(Zero) > 0 { add := x.Add(x, y) @@ -303,7 +304,7 @@ func opAddmod(instr instruction, pc *uint64, env Environment, contract *Contract stack.push(new(big.Int)) } } -func opMulmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opMulmod(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { x, y, z := stack.pop(), stack.pop(), stack.pop() if z.Cmp(Zero) > 0 { mul := x.Mul(x, y) @@ -314,45 +315,45 @@ func opMulmod(instr instruction, pc *uint64, env Environment, contract *Contract } } -func opSha3(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSha3(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { offset, size := stack.pop(), stack.pop() hash := crypto.Keccak256(memory.GetPtr(offset.Int64(), size.Int64())) stack.push(common.BytesToBig(hash)) } -func opAddress(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opAddress(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.push(common.Bytes2Big(contract.Address().Bytes())) } -func opBalance(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opBalance(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { addr := common.BigToAddress(stack.pop()) balance := env.Db().GetBalance(addr) stack.push(new(big.Int).Set(balance)) } -func opOrigin(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { - stack.push(env.Origin().Big()) +func opOrigin(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { + stack.push(env.Origin.Big()) } -func opCaller(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opCaller(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.push(contract.Caller().Big()) } -func opCallValue(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opCallValue(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.push(new(big.Int).Set(contract.value)) } -func opCalldataLoad(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opCalldataLoad(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.push(common.Bytes2Big(getData(contract.Input, stack.pop(), common.Big32))) } -func opCalldataSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opCalldataSize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.push(big.NewInt(int64(len(contract.Input)))) } -func opCalldataCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opCalldataCopy(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { var ( mOff = stack.pop() cOff = stack.pop() @@ -361,18 +362,18 @@ func opCalldataCopy(instr instruction, pc *uint64, env Environment, contract *Co memory.Set(mOff.Uint64(), l.Uint64(), getData(contract.Input, cOff, l)) } -func opExtCodeSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opExtCodeSize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { addr := common.BigToAddress(stack.pop()) l := big.NewInt(int64(env.Db().GetCodeSize(addr))) stack.push(l) } -func opCodeSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opCodeSize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { l := big.NewInt(int64(len(contract.Code))) stack.push(l) } -func opCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opCodeCopy(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { var ( mOff = stack.pop() cOff = stack.pop() @@ -383,7 +384,7 @@ func opCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contra memory.Set(mOff.Uint64(), l.Uint64(), codeCopy) } -func opExtCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opExtCodeCopy(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { var ( addr = common.BigToAddress(stack.pop()) mOff = stack.pop() @@ -395,58 +396,58 @@ func opExtCodeCopy(instr instruction, pc *uint64, env Environment, contract *Con memory.Set(mOff.Uint64(), l.Uint64(), codeCopy) } -func opGasprice(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { - stack.push(new(big.Int).Set(env.GasPrice())) +func opGasprice(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { + stack.push(new(big.Int).Set(env.GasPrice)) } -func opBlockhash(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opBlockhash(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { num := stack.pop() - n := new(big.Int).Sub(env.BlockNumber(), common.Big257) - if num.Cmp(n) > 0 && num.Cmp(env.BlockNumber()) < 0 { + n := new(big.Int).Sub(env.BlockNumber, common.Big257) + if num.Cmp(n) > 0 && num.Cmp(env.BlockNumber) < 0 { stack.push(env.GetHash(num.Uint64()).Big()) } else { stack.push(new(big.Int)) } } -func opCoinbase(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { - stack.push(env.Coinbase().Big()) +func opCoinbase(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { + stack.push(env.Coinbase.Big()) } -func opTimestamp(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { - stack.push(U256(new(big.Int).Set(env.Time()))) +func opTimestamp(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { + stack.push(U256(new(big.Int).Set(env.Time))) } -func opNumber(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { - stack.push(U256(new(big.Int).Set(env.BlockNumber()))) +func opNumber(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { + stack.push(U256(new(big.Int).Set(env.BlockNumber))) } -func opDifficulty(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { - stack.push(U256(new(big.Int).Set(env.Difficulty()))) +func opDifficulty(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { + stack.push(U256(new(big.Int).Set(env.Difficulty))) } -func opGasLimit(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { - stack.push(U256(new(big.Int).Set(env.GasLimit()))) +func opGasLimit(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { + stack.push(U256(new(big.Int).Set(env.GasLimit))) } -func opPop(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opPop(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.pop() } -func opPush(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opPush(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.push(new(big.Int).Set(instr.data)) } -func opDup(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opDup(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.dup(int(instr.data.Int64())) } -func opSwap(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSwap(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.swap(int(instr.data.Int64())) } -func opLog(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opLog(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { n := int(instr.data.Int64()) topics := make([]common.Hash, n) mStart, mSize := stack.pop(), stack.pop() @@ -455,59 +456,59 @@ func opLog(instr instruction, pc *uint64, env Environment, contract *Contract, m } d := memory.Get(mStart.Int64(), mSize.Int64()) - log := NewLog(contract.Address(), topics, d, env.BlockNumber().Uint64()) - env.AddLog(log) + log := NewLog(contract.Address(), topics, d, env.BlockNumber.Uint64()) + env.Db().AddLog(log) } -func opMload(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opMload(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { offset := stack.pop() val := common.BigD(memory.GetPtr(offset.Int64(), 32)) stack.push(val) } -func opMstore(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opMstore(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { // pop value of the stack mStart, val := stack.pop(), stack.pop() memory.Set(mStart.Uint64(), 32, common.BigToBytes(val, 256)) } -func opMstore8(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opMstore8(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { off, val := stack.pop().Int64(), stack.pop().Int64() memory.store[off] = byte(val & 0xff) } -func opSload(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSload(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { loc := common.BigToHash(stack.pop()) val := env.Db().GetState(contract.Address(), loc).Big() stack.push(val) } -func opSstore(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSstore(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { loc := common.BigToHash(stack.pop()) val := stack.pop() env.Db().SetState(contract.Address(), loc, common.BigToHash(val)) } -func opJump(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opJump(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { } -func opJumpi(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opJumpi(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { } -func opJumpdest(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opJumpdest(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { } -func opPc(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opPc(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.push(new(big.Int).Set(instr.data)) } -func opMsize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opMsize(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.push(big.NewInt(int64(memory.Len()))) } -func opGas(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opGas(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.push(new(big.Int).SetUint64(contract.gas64)) } -func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opCreate(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { var ( value = stack.pop() offset, size = stack.pop(), stack.pop() @@ -520,7 +521,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.RuleSet().IsHomestead(env.BlockNumber) && suberr == CodeStoreOutOfGasError { stack.push(new(big.Int)) } else if suberr != nil && suberr != CodeStoreOutOfGasError { stack.push(new(big.Int)) @@ -529,7 +530,7 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract } } -func opCall(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opCall(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { gas := stack.pop() // pop gas and value of the stack. addr, value := stack.pop(), stack.pop() @@ -560,7 +561,7 @@ func opCall(instr instruction, pc *uint64, env Environment, contract *Contract, } } -func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opCallCode(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { gas := stack.pop() // pop gas and value of the stack. addr, value := stack.pop(), stack.pop() @@ -591,7 +592,7 @@ func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contra } } -func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opDelegateCall(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { gas, to, inOffset, inSize, outOffset, outSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() toAddr := common.BigToAddress(to) @@ -605,12 +606,12 @@ func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Co } } -func opReturn(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opReturn(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { } -func opStop(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opStop(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { } -func opSuicide(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { +func opSuicide(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { balance := env.Db().GetBalance(contract.Address()) env.Db().AddBalance(common.BigToAddress(stack.pop()), balance) @@ -621,7 +622,7 @@ func opSuicide(instr instruction, pc *uint64, env Environment, contract *Contrac // make log instruction function func makeLog(size int) instrFn { - return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { + return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { topics := make([]common.Hash, size) mStart, mSize := stack.pop(), stack.pop() for i := 0; i < size; i++ { @@ -629,14 +630,14 @@ func makeLog(size int) instrFn { } d := memory.Get(mStart.Int64(), mSize.Int64()) - log := NewLog(contract.Address(), topics, d, env.BlockNumber().Uint64()) - env.AddLog(log) + log := NewLog(contract.Address(), topics, d, env.BlockNumber.Uint64()) + env.Db().AddLog(log) } } // make push instruction function func makePush(size uint64, bsize *big.Int) instrFn { - return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { + return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { byts := getData(contract.Code, new(big.Int).SetUint64(*pc+1), bsize) stack.push(common.Bytes2Big(byts)) *pc += size @@ -645,7 +646,7 @@ func makePush(size uint64, bsize *big.Int) instrFn { // make push instruction function func makeDup(size int64) instrFn { - return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { + return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.dup(int(size)) } } @@ -654,7 +655,7 @@ func makeDup(size int64) instrFn { func makeSwap(size int64) instrFn { // switch n + 1 otherwise n would be swapped with n size += 1 - return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) { + return func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) { stack.swap(int(size)) } } diff --git a/core/vm/interface.go b/core/vm/interface.go new file mode 100644 index 0000000000..914a511ae0 --- /dev/null +++ b/core/vm/interface.go @@ -0,0 +1,199 @@ +// Copyright 2014 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 vm + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// Vm is the basic interface for an implementation of the EVM. +type Vm interface { + // Run should execute the given contract with the input given in in + // and return the contract execution return bytes or an error if it + // failed. + Run(c *Contract, in []byte) ([]byte, error) +} + +// 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 +} + +// Database is a EVM database for full state querying. +type Database interface { + GetAccount(common.Address) Account + CreateAccount(common.Address) Account + + SubBalance(common.Address, *big.Int) + AddBalance(common.Address, *big.Int) + GetBalance(common.Address) *big.Int + + GetNonce(common.Address) uint64 + SetNonce(common.Address, uint64) + + GetCodeHash(common.Address) common.Hash + GetCode(common.Address) []byte + SetCode(common.Address, []byte) + GetCodeSize(common.Address) int + + AddRefund(*big.Int) + GetRefund() *big.Int + + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + + Suicide(common.Address) bool + HasSuicided(common.Address) bool + + // Exist reports whether the given account exists in state. + // Notably this should also return true for suicided accounts. + Exist(common.Address) bool + + AddLog(*Log) +} + +// Account represents a contract or basic ethereum account. +type Account interface { + SubBalance(amount *big.Int) + AddBalance(amount *big.Int) + SetBalance(*big.Int) + SetNonce(uint64) + Balance() *big.Int + Address() common.Address + ReturnGas(uint64) + SetCode(common.Hash, []byte) + ForEachStorage(cb func(key, value common.Hash) bool) + Value() *big.Int +} + +// Context provides the EVM with auxilary information. Once provided it shouldn't be modified. +type Context struct { + CallContext + + // Message information + Origin common.Address // Provides information for ORIGIN + Coinbase common.Address // Provides information for COINBASE + GasPrice *big.Int // Provides information for GASPRICE + + // Block information + GasLimit *big.Int // Provides information for GASLIMIT + BlockNumber *big.Int // Provides information for NUMBER + Time *big.Int // Provides information for TIME + Difficulty *big.Int // Provides information for DIFFICULTY +} + +// CallContext provides a basic interface for the EVM calling conventions. The EVM Environment +// depends on this context being implemented for doing subcalls and initialising new EVM contracts. +type CallContext interface { + // Call another contract + Call(env *Environment, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) + // Take another's contract code and execute within our own context + CallCode(env *Environment, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) + // Same as CallCode except sender and value is propagated from parent to child scope + DelegateCall(env *Environment, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) + // Create a new contract + Create(env *Environment, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) +} + +// Backend is the basic interface for keeping track of state and taking care of +// returning ancestory data. +type Backend interface { + // GetHash returns the hash corresponding to n + GetHash(n uint64) common.Hash + // Creates a restorable snapshot + SnapshotDatabase() int + // Set database to previous snapshot + RevertToSnapshot(int) + // Get returns the database + Get() Database +} + +type Environment struct { + Context + + Backend Backend + + ruleSet RuleSet + vmConfig Config + + evm Vm + + Depth int +} + +func NewEnvironment(context Context, backend Backend, ruleSet RuleSet, vmCfg Config) *Environment { + env := &Environment{ + Context: context, + Backend: backend, + vmConfig: vmCfg, + ruleSet: ruleSet, + } + env.evm = New(env, vmCfg) + return env +} + +func (env *Environment) Call(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + if env.vmConfig.Test && env.Depth > 0 { + caller.ReturnGas(gas.Uint64()) + + return nil, nil + } + + return env.CallContext.Call(env, caller, addr, data, gas, value) +} + +// Take another's contract code and execute within our own context +func (env *Environment) CallCode(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { + if env.vmConfig.Test && env.Depth > 0 { + caller.ReturnGas(gas.Uint64()) + + return nil, nil + } + + return env.CallContext.CallCode(env, caller, addr, data, gas, value) +} + +// Same as CallCode except sender and value is propagated from parent to child scope +func (env *Environment) DelegateCall(caller ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { + if env.vmConfig.Test && env.Depth > 0 { + caller.ReturnGas(gas.Uint64()) + + return nil, nil + } + + return env.CallContext.DelegateCall(env, caller, addr, data, gas) +} + +// Create a new contract +func (env *Environment) Create(caller ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { + if env.vmConfig.Test && env.Depth > 0 { + caller.ReturnGas(gas.Uint64()) + + return nil, common.Address{}, nil + } + + return env.CallContext.Create(env, caller, data, gas, value) +} +func (env *Environment) RuleSet() RuleSet { return env.ruleSet } +func (env *Environment) Vm() Vm { return env.evm } +func (env *Environment) Db() Database { return env.Backend.Get() } +func (env *Environment) GetHash(n uint64) common.Hash { + return env.Backend.GetHash(n) +} diff --git a/core/vm/logger.go b/core/vm/logger.go index ae62b6b573..e6816d0912 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -64,7 +64,7 @@ type StructLog struct { // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. type Tracer interface { - CaptureState(env Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) + CaptureState(env *Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) } // StructLogger is an EVM state logger and implements Tracer. @@ -93,7 +93,7 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { // captureState logs a new structured log message and pushes it out to the environment // // captureState also tracks SSTORE ops to track dirty values. -func (l *StructLogger) CaptureState(env Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) { +func (l *StructLogger) CaptureState(env *Environment, pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *Stack, contract *Contract, depth int, err error) { // initialise new changed values storage container for this contract // if not present. if l.changedValues[contract.Address()] == nil { @@ -149,7 +149,7 @@ func (l *StructLogger) CaptureState(env Environment, pc uint64, op OpCode, gas, } } // create a new snaptshot of the EVM. - log := StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, env.Depth(), err} + log := StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, env.Depth, err} l.logs = append(l.logs, log) } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index a7aa0cfa0e..2995e81db7 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -23,6 +23,10 @@ import ( "github.com/ethereum/go-ethereum/common" ) +type ruleset struct{} + +func (ruleset) IsHomestead(*big.Int) bool { return true } + type dummyContractRef struct { calledForEach bool } @@ -41,14 +45,14 @@ func (d *dummyContractRef) SetNonce(uint64) {} func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } type dummyEnv struct { - *Env + *Environment ref *dummyContractRef } func newDummyEnv(ref *dummyContractRef) *dummyEnv { return &dummyEnv{ - Env: NewEnv(&Config{EnableJit: false, ForceJit: false}), - ref: ref, + Environment: NewEnvironment(Context{}, nil, ruleset{}, Config{}), + ref: ref, } } func (d dummyEnv) GetAccount(common.Address) Account { @@ -57,7 +61,7 @@ func (d dummyEnv) GetAccount(common.Address) Account { func TestStoreCapture(t *testing.T) { var ( - env = NewEnv(&Config{EnableJit: false, ForceJit: false}) + env = NewEnvironment(Context{}, nil, ruleset{}, Config{}) logger = NewStructLogger(nil) mem = NewMemory() stack = newstack() @@ -90,13 +94,13 @@ func TestStorageCapture(t *testing.T) { stack = newstack() ) - logger.CaptureState(env, 0, STOP, new(big.Int), new(big.Int), mem, stack, contract, 0, nil) + logger.CaptureState(env.Environment, 0, STOP, new(big.Int), new(big.Int), mem, stack, contract, 0, nil) if ref.calledForEach { t.Error("didn't expect for each to be called") } logger = NewStructLogger(&LogConfig{FullStorage: true}) - logger.CaptureState(env, 0, STOP, new(big.Int), new(big.Int), mem, stack, contract, 0, nil) + logger.CaptureState(env.Environment, 0, STOP, new(big.Int), new(big.Int), mem, stack, contract, 0, nil) if !ref.calledForEach { t.Error("expected for each to be called") } diff --git a/core/vm/program.go b/core/vm/program.go index 04fb4fe09c..0fadfe15c4 100644 --- a/core/vm/program.go +++ b/core/vm/program.go @@ -288,11 +288,9 @@ func CompileProgram(program *Program) { program.addInstr(op, pc, nil, nil) } } - - optimiseProgram(program) } -func RunProgram(program *Program, env Environment, contract *Contract, input []byte) ([]byte, error) { +func RunProgram(program *Program, env *Environment, contract *Contract, input []byte) ([]byte, error) { return New(env, Config{}).Run(contract, input) } @@ -310,7 +308,7 @@ func validDest(dests map[uint64]struct{}, dest *big.Int) bool { // calculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for // the operation. This does not reduce gas or resizes the memory. -func calculateGasAndSize(env Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *Stack) (uint64, uint64, error) { +func calculateGasAndSize(env *Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *Stack) (uint64, uint64, error) { var ( newMemSize uint64 sizeFault bool diff --git a/core/vm/program_optimiser.go b/core/vm/program_optimiser.go index 6d1ac93264..79c91df3e6 100644 --- a/core/vm/program_optimiser.go +++ b/core/vm/program_optimiser.go @@ -26,7 +26,7 @@ import ( // optimeProgram optimises a JIT program creating segments out of program // instructions. Currently covered are multi-pushes and static jumps -func optimiseProgram(program *Program) { +func OptimiseProgram(program *Program) { var load []instruction var ( diff --git a/core/vm/program_test.go b/core/vm/program_test.go index df689afc40..e4694d8ae3 100644 --- a/core/vm/program_test.go +++ b/core/vm/program_test.go @@ -16,20 +16,14 @@ package vm -import ( - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) +import "testing" const maxRun = 1000 func TestSegmenting(t *testing.T) { prog := NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, 0x0}) CompileProgram(prog) + OptimiseProgram(prog) if instr, ok := prog.instructions[0].(pushSeg); ok { if len(instr.data) != 2 { @@ -41,6 +35,7 @@ func TestSegmenting(t *testing.T) { prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)}) CompileProgram(prog) + OptimiseProgram(prog) if _, ok := prog.instructions[1].(jumpSeg); ok { } else { @@ -49,6 +44,7 @@ func TestSegmenting(t *testing.T) { prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)}) CompileProgram(prog) + OptimiseProgram(prog) if instr, ok := prog.instructions[0].(pushSeg); ok { if len(instr.data) != 2 { @@ -72,8 +68,9 @@ func TestCompiling(t *testing.T) { } } +/* func TestResetInput(t *testing.T) { - var sender account + var sender Account env := NewEnv(&Config{}) contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000)) @@ -85,6 +82,7 @@ func TestResetInput(t *testing.T) { t.Errorf("expected input to be nil, got %x", contract.Input) } } +*/ func TestPcMappingToInstruction(t *testing.T) { program := NewProgram([]byte{byte(PUSH2), 0xbe, 0xef, byte(ADD)}) @@ -93,110 +91,3 @@ func TestPcMappingToInstruction(t *testing.T) { t.Error("expected mapping PC 4 to me instr no. 2, got", program.mapping[4]) } } - -var benchmarks = map[string]vmBench{ - "pushes": vmBench{ - false, false, false, - common.Hex2Bytes("600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01"), nil, - }, -} - -func BenchmarkPushes(b *testing.B) { - runVmBench(benchmarks["pushes"], b) -} - -type vmBench struct { - precompile bool // compile prior to executing - nojit bool // ignore jit (sets DisbaleJit = true - forcejit bool // forces the jit, precompile is ignored - - code []byte - input []byte -} - -type account struct{} - -func (account) SubBalance(amount *big.Int) {} -func (account) AddBalance(amount *big.Int) {} -func (account) SetAddress(common.Address) {} -func (account) Value() *big.Int { return nil } -func (account) SetBalance(*big.Int) {} -func (account) SetNonce(uint64) {} -func (account) Balance() *big.Int { return nil } -func (account) Address() common.Address { return common.Address{} } -func (account) ReturnGas(uint64) {} -func (account) SetCode(common.Hash, []byte) {} -func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} - -func runVmBench(test vmBench, b *testing.B) { - var sender account - - if test.precompile && !test.forcejit { - NewProgram(test.code) - } - env := NewEnv(&Config{EnableJit: !test.nojit, ForceJit: test.forcejit}) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000)) - context.Code = test.code - context.CodeAddr = &common.Address{} - _, err := env.Vm().Run(context, test.input) - if err != nil { - b.Error(err) - b.FailNow() - } - } -} - -type Env struct { - gasLimit *big.Int - depth int - evm *EVM -} - -func NewEnv(config *Config) *Env { - env := &Env{gasLimit: big.NewInt(10000), depth: 0} - env.evm = New(env, *config) - return env -} - -func (self *Env) RuleSet() RuleSet { return ruleSet{new(big.Int)} } -func (self *Env) Vm() Vm { return self.evm } -func (self *Env) GasPrice() *big.Int { return new(big.Int) } -func (self *Env) Origin() common.Address { return common.Address{} } -func (self *Env) BlockNumber() *big.Int { return big.NewInt(0) } - -//func (self *Env) PrevHash() []byte { return self.parent } -func (self *Env) Coinbase() common.Address { return common.Address{} } -func (self *Env) SnapshotDatabase() int { return 0 } -func (self *Env) RevertToSnapshot(int) {} -func (self *Env) Time() *big.Int { return big.NewInt(time.Now().Unix()) } -func (self *Env) Difficulty() *big.Int { return big.NewInt(0) } -func (self *Env) Db() Database { return nil } -func (self *Env) GasLimit() *big.Int { return self.gasLimit } -func (self *Env) VmType() Type { return StdVmTy } -func (self *Env) GetHash(n uint64) common.Hash { - return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String()))) -} -func (self *Env) AddLog(log *Log) { -} -func (self *Env) Depth() int { return self.depth } -func (self *Env) SetDepth(i int) { self.depth = i } -func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool { - return true -} -func (self *Env) Transfer(from, to Account, amount *big.Int) {} -func (self *Env) Call(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - return nil, nil -} -func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - return nil, nil -} -func (self *Env) Create(caller ContractRef, data []byte, gas, price *big.Int) ([]byte, common.Address, error) { - return nil, common.Address{}, nil -} -func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { - return nil, nil -} diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go deleted file mode 100644 index 0e0247f2f8..0000000000 --- a/core/vm/runtime/env.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2015 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 runtime - -import ( - "math/big" - - "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/vm" -) - -// Env is a basic runtime environment required for running the EVM. -type Env struct { - ruleSet vm.RuleSet - depth int - state *state.StateDB - - origin common.Address - coinbase common.Address - - number *big.Int - time *big.Int - difficulty *big.Int - gasLimit *big.Int - gasPrice *big.Int - - getHashFn func(uint64) common.Hash - - evm *vm.EVM -} - -// 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, - gasPrice: cfg.GasPrice, - } - env.evm = vm.New(env, vm.Config{ - Debug: cfg.Debug, - EnableJit: !cfg.DisableJit, - ForceJit: !cfg.DisableJit, - }) - - return env -} - -func (self *Env) RuleSet() vm.RuleSet { return self.ruleSet } -func (self *Env) GasPrice() *big.Int { return self.gasPrice } -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) -} -func (self *Env) AddLog(log *vm.Log) { - self.state.AddLog(log) -} -func (self *Env) Depth() int { return self.depth } -func (self *Env) SetDepth(i int) { self.depth = i } -func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool { - return self.state.GetBalance(from).Cmp(balance) >= 0 -} -func (self *Env) SnapshotDatabase() int { - return self.state.Snapshot() -} -func (self *Env) RevertToSnapshot(snapshot int) { - self.state.RevertToSnapshot(snapshot) -} - -func (self *Env) Transfer(from, to vm.Account, amount *big.Int) { - core.Transfer(from, to, amount) -} - -func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - return core.Call(self, caller, addr, data, gas, value) -} -func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - return core.CallCode(self, caller, addr, data, gas, value) -} - -func (self *Env) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { - return core.DelegateCall(self, me, addr, data, gas) -} - -func (self *Env) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { - return core.Create(self, caller, data, gas, value) -} diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 3c7e10c79d..d87331f268 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -17,10 +17,12 @@ package runtime import ( + "math" "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/vm" "github.com/ethereum/go-ethereum/crypto" @@ -41,7 +43,7 @@ type Config struct { Coinbase common.Address BlockNumber *big.Int Time *big.Int - GasLimit *big.Int + GasLimit uint64 GasPrice *big.Int Value *big.Int DisableJit bool // "disable" so it's enabled by default @@ -63,8 +65,8 @@ func setDefaults(cfg *Config) { if cfg.Time == nil { cfg.Time = big.NewInt(time.Now().Unix()) } - if cfg.GasLimit == nil { - cfg.GasLimit = new(big.Int).Set(common.MaxBig) + if cfg.GasLimit == 0 { + cfg.GasLimit = math.MaxUint64 } if cfg.GasPrice == nil { cfg.GasPrice = new(big.Int) @@ -98,8 +100,26 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { db, _ := ethdb.NewMemDatabase() cfg.State, _ = state.New(common.Hash{}, db) } + + var chainConfig = &core.ChainConfig{} + backend := &core.EVMBackend{ + GetHashFn: cfg.GetHashFn, + State: cfg.State, + } + + context := vm.Context{ + CallContext: core.EVMCallContext{core.CanTransfer, core.Transfer}, + Origin: cfg.Origin, + Coinbase: cfg.Coinbase, + BlockNumber: cfg.BlockNumber, + Time: cfg.Time, + Difficulty: cfg.Difficulty, + GasLimit: new(big.Int).SetUint64(cfg.GasLimit), + GasPrice: cfg.GasPrice, + } + vmenv := vm.NewEnvironment(context, backend, chainConfig, chainConfig.VmConfig) + var ( - vmenv = NewEnv(cfg, cfg.State) sender = cfg.State.CreateAccount(cfg.Origin) receiver = cfg.State.CreateAccount(common.StringToAddress("contract")) ) @@ -111,7 +131,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { sender, receiver.Address(), input, - cfg.GasLimit, + new(big.Int).SetUint64(cfg.GasLimit), cfg.Value, ) @@ -126,7 +146,23 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) { setDefaults(cfg) - vmenv := NewEnv(cfg, cfg.State) + var chainConfig = &core.ChainConfig{} + backend := &core.EVMBackend{ + GetHashFn: cfg.GetHashFn, + State: cfg.State, + } + + context := vm.Context{ + CallContext: core.EVMCallContext{core.CanTransfer, core.Transfer}, + Origin: cfg.Origin, + Coinbase: cfg.Coinbase, + BlockNumber: cfg.BlockNumber, + Time: cfg.Time, + Difficulty: cfg.Difficulty, + GasLimit: new(big.Int).SetUint64(cfg.GasLimit), + GasPrice: cfg.GasPrice, + } + vmenv := vm.NewEnvironment(context, backend, chainConfig, chainConfig.VmConfig) sender := cfg.State.GetOrNewStateObject(cfg.Origin) // Call the code with the given configuration. @@ -134,7 +170,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) { sender, address, input, - cfg.GasLimit, + new(big.Int).SetUint64(cfg.GasLimit), cfg.Value, ) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 88c76c7319..725dda9a0d 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -39,8 +39,8 @@ func TestDefaults(t *testing.T) { if cfg.Time == nil { t.Error("expected time to be non nil") } - if cfg.GasLimit == nil { - t.Error("expected time to be non nil") + if cfg.GasLimit == 0 { + t.Error("expected time to be non 0") } if cfg.GasPrice == nil { t.Error("expected time to be non nil") diff --git a/core/vm/segments.go b/core/vm/segments.go index 61d87367c6..ed223d82f2 100644 --- a/core/vm/segments.go +++ b/core/vm/segments.go @@ -24,7 +24,7 @@ type jumpSeg struct { gas uint64 } -func (j jumpSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func (j jumpSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { if !contract.UseGas(j.gas) { return nil, OutOfGasError } @@ -42,7 +42,7 @@ type pushSeg struct { gas uint64 } -func (s pushSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func (s pushSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { // Use the calculated gas. When insufficient gas is present, use all gas and return an // Out Of Gas error if !contract.UseGas(s.gas) { diff --git a/core/vm/vm.go b/core/vm/vm.go index 8f4fafc1df..5b85211438 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -28,10 +28,12 @@ import ( // Config are the configuration options for the EVM type Config struct { - Debug bool - EnableJit bool - ForceJit bool - Tracer Tracer + Test bool // boolean to indicate this is a VM Test + + ForceCompile bool + NoOptimise bool // skip optimising the program if it hasn't been compiled + Debug bool + Tracer Tracer } // EVM is used to run Ethereum based contracts and will utilise the @@ -39,24 +41,24 @@ type Config struct { // The EVM will run the byte code VM or JIT VM based on the passed // configuration. type EVM struct { - env Environment + env *Environment jumpTable vmJumpTable cfg Config } // New returns a new instance of the EVM. -func New(env Environment, cfg Config) *EVM { +func New(env *Environment, cfg Config) *EVM { return &EVM{ env: env, - jumpTable: newJumpTable(env.RuleSet(), env.BlockNumber()), + jumpTable: newJumpTable(env.RuleSet(), env.BlockNumber), cfg: cfg, } } // Run loops and evaluates the contract's code with the given input data func (evm *EVM) Run(contract *Contract, input []byte) ([]byte, error) { - evm.env.SetDepth(evm.env.Depth() + 1) - defer evm.env.SetDepth(evm.env.Depth() - 1) + evm.env.Depth++ + defer func() { evm.env.Depth-- }() if contract.CodeAddr != nil { if p, exist := PrecompiledContracts[*contract.CodeAddr]; exist { @@ -84,6 +86,9 @@ func (evm *EVM) Run(contract *Contract, input []byte) ([]byte, error) { // Create and compile program program := NewProgram(contract.Code) CompileProgram(program) + if !evm.cfg.NoOptimise { + OptimiseProgram(program) + } return evm.runProgram(program, contract, input) case progCompile: @@ -115,7 +120,7 @@ func (evm *EVM) runProgram(program *Program, contract *Contract, input []byte) ( }() } - homestead := env.RuleSet().IsHomestead(env.BlockNumber()) + homestead := env.RuleSet().IsHomestead(env.BlockNumber) for pc < uint64(len(program.instructions)) { instrCount++ diff --git a/core/vm/vm_jit_fake.go b/core/vm/vm_jit_fake.go index 4fa98ccd9c..4d6acf37fc 100644 --- a/core/vm/vm_jit_fake.go +++ b/core/vm/vm_jit_fake.go @@ -20,7 +20,7 @@ package vm import "fmt" -func NewJitVm(env Environment) VirtualMachine { +func NewJitVm(env *Environment) VirtualMachine { fmt.Printf("Warning! EVM JIT not enabled.\n") return New(env, Config{}) } diff --git a/core/vm_env.go b/core/vm_env.go deleted file mode 100644 index f46a04330f..0000000000 --- a/core/vm_env.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2014 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 core - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" -) - -// GetHashFn returns a function for which the VM env can query block hashes through -// up to the limit defined by the Yellow Paper and uses the given block chain -// to query for information. -func GetHashFn(ref common.Hash, chain *BlockChain) func(n uint64) common.Hash { - return func(n uint64) common.Hash { - for block := chain.GetBlockByHash(ref); block != nil; block = chain.GetBlock(block.ParentHash(), block.NumberU64()-1) { - if block.NumberU64() == n { - return block.Hash() - } - } - - return common.Hash{} - } -} - -type VMEnv struct { - chainConfig *ChainConfig // Chain configuration - state *state.StateDB // State to use for executing - evm vm.VirtualMachine // The Ethereum Virtual Machine - depth int // Current execution depth - msg Message // Message appliod - - header *types.Header // Header information - chain *BlockChain // Blockchain handle - getHashFn func(uint64) common.Hash // getHashFn callback is used to retrieve block hashes -} - -func NewEnv(state *state.StateDB, chainConfig *ChainConfig, chain *BlockChain, msg Message, header *types.Header, cfg vm.Config) *VMEnv { - env := &VMEnv{ - chainConfig: chainConfig, - chain: chain, - state: state, - header: header, - msg: msg, - getHashFn: GetHashFn(header.ParentHash, chain), - } - - env.evm = vm.New(env, cfg) - return env -} - -func (self *VMEnv) RuleSet() vm.RuleSet { return self.chainConfig } -func (self *VMEnv) Vm() vm.Vm { return self.evm } -func (self *VMEnv) Origin() common.Address { f, _ := self.msg.From(); return f } -func (self *VMEnv) BlockNumber() *big.Int { return self.header.Number } -func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase } -func (self *VMEnv) Time() *big.Int { return self.header.Time } -func (self *VMEnv) Difficulty() *big.Int { return self.header.Difficulty } -func (self *VMEnv) GasLimit() *big.Int { return self.header.GasLimit } -func (self *VMEnv) GasPrice() *big.Int { return self.msg.GasPrice() } -func (self *VMEnv) Value() *big.Int { return self.msg.Value() } -func (self *VMEnv) Db() vm.Database { return self.state } -func (self *VMEnv) Depth() int { return self.depth } -func (self *VMEnv) SetDepth(i int) { self.depth = i } -func (self *VMEnv) GetHash(n uint64) common.Hash { - return self.getHashFn(n) -} - -func (self *VMEnv) AddLog(log *vm.Log) { - self.state.AddLog(log) -} -func (self *VMEnv) CanTransfer(from common.Address, balance *big.Int) bool { - return self.state.GetBalance(from).Cmp(balance) >= 0 -} - -func (self *VMEnv) SnapshotDatabase() int { - return self.state.Snapshot() -} - -func (self *VMEnv) RevertToSnapshot(snapshot int) { - self.state.RevertToSnapshot(snapshot) -} - -func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) { - Transfer(from, to, amount) -} - -func (self *VMEnv) Call(me vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - return Call(self, me, addr, data, gas, value) -} -func (self *VMEnv) CallCode(me vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - return CallCode(self, me, addr, data, gas, value) -} - -func (self *VMEnv) DelegateCall(me vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { - return DelegateCall(self, me, addr, data, gas) -} - -func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { - return Create(self, me, data, gas, value) -} diff --git a/eth/api.go b/eth/api.go index c2fdbe99c2..fcb3882446 100644 --- a/eth/api.go +++ b/eth/api.go @@ -520,9 +520,17 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common. value: tx.Value(), data: tx.Data(), } + + backend := &core.EVMBackend{ + GetHashFn: core.GetHashFn(block.ParentHash(), api.eth.BlockChain()), + State: stateDb, + } + context := core.ToEVMContext(api.config, msg, block.Header()) + // Mutate the state if we haven't reached the tracing transaction yet if uint64(idx) < txIndex { - vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{}) + vmenv := vm.NewEnvironment(context, backend, api.config, vm.Config{}) + //vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{}) _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) if err != nil { return nil, fmt.Errorf("mutation failed: %v", err) @@ -531,7 +539,8 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common. continue } // Otherwise trace the transaction and return - vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{Debug: true, Tracer: tracer}) + vmenv := vm.NewEnvironment(context, backend, api.config, vm.Config{Debug: true, Tracer: tracer}) + //vmenv := core.NewEnv(stateDb, api.config, api.eth.BlockChain(), msg, block.Header(), vm.Config{Debug: true, Tracer: tracer}) ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) diff --git a/eth/api_backend.go b/eth/api_backend.go index 42b84bf9ba..6cbaae953c 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -97,13 +97,20 @@ func (b *EthApiBackend) GetTd(blockHash common.Hash) *big.Int { return b.eth.blockchain.GetTdByHash(blockHash) } -func (b *EthApiBackend) GetVMEnv(ctx context.Context, msg core.Message, state ethapi.State, header *types.Header) (vm.Environment, func() error, error) { +func (b *EthApiBackend) GetVMEnv(ctx context.Context, msg core.Message, state ethapi.State, header *types.Header) (*vm.Environment, func() error, error) { statedb := state.(EthApiState).state addr, _ := msg.From() from := statedb.GetOrNewStateObject(addr) from.SetBalance(common.MaxBig) vmError := func() error { return nil } - return core.NewEnv(statedb, b.eth.chainConfig, b.eth.blockchain, msg, header, b.eth.chainConfig.VmConfig), vmError, nil + + backend := &core.EVMBackend{ + GetHashFn: core.GetHashFn(header.ParentHash, b.eth.blockchain), + State: statedb, + } + context := core.ToEVMContext(b.eth.chainConfig, msg, header) + + return vm.NewEnvironment(context, backend, b.eth.chainConfig, b.eth.chainConfig.VmConfig), vmError, nil } func (b *EthApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { diff --git a/eth/backend.go b/eth/backend.go index c4a883c9e9..a9e1630d33 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -35,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/common/registrar/ethreg" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/gasprice" @@ -202,10 +201,6 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { core.WriteChainConfig(chainDb, genesis.Hash(), config.ChainConfig) eth.chainConfig = config.ChainConfig - eth.chainConfig.VmConfig = vm.Config{ - EnableJit: config.EnableJit, - ForceJit: config.ForceJit, - } eth.blockchain, err = core.NewBlockChain(chainDb, eth.chainConfig, eth.pow, eth.EventMux()) if err != nil { diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 0aa3da18d0..3b8ae77e54 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -50,7 +50,7 @@ type Backend interface { GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) GetTd(blockHash common.Hash) *big.Int - GetVMEnv(ctx context.Context, msg core.Message, state State, header *types.Header) (vm.Environment, func() error, error) + GetVMEnv(ctx context.Context, msg core.Message, state State, header *types.Header) (*vm.Environment, func() error, error) // TxPool API SendTx(ctx context.Context, signedTx *types.Transaction) error RemoveTx(txHash common.Hash) diff --git a/internal/ethapi/tracer.go b/internal/ethapi/tracer.go index 16ec6ebf0a..7ba6fc694a 100644 --- a/internal/ethapi/tracer.go +++ b/internal/ethapi/tracer.go @@ -167,17 +167,17 @@ func (dw *dbWrapper) toValue(vm *otto.Otto) otto.Value { // JavascriptTracer provides an implementation of Tracer that evaluates a // Javascript function for each VM execution step. type JavascriptTracer struct { - vm *otto.Otto // Javascript VM instance - traceobj *otto.Object // User-supplied object to call - log map[string]interface{} // (Reusable) map for the `log` arg to `step` - logvalue otto.Value // JS view of `log` - memory *memoryWrapper // Wrapper around the VM memory - memvalue otto.Value // JS view of `memory` - stack *stackWrapper // Wrapper around the VM stack - stackvalue otto.Value // JS view of `stack` - db *dbWrapper // Wrapper around the VM environment - dbvalue otto.Value // JS view of `db` - err error // Error, if one has occurred + vm *otto.Otto // Javascript VM instance + traceobj *otto.Object // User-supplied object to call + log map[string]interface{} // (Reusable) map for the `log` arg to `step` + logvalue otto.Value // JS view of `log` + memory *memoryWrapper // Wrapper around the VM memory + memvalue otto.Value // JS view of `memory` + stack *stackWrapper // Wrapper around the VM stack + stackvalue otto.Value // JS view of `stack` + db *dbWrapper // Wrapper around the VM environment + dbvalue otto.Value // JS view of `db` + err error // Error, if one has occurred } // NewJavascriptTracer instantiates a new JavascriptTracer instance. @@ -222,17 +222,17 @@ func NewJavascriptTracer(code string) (*JavascriptTracer, error) { db := &dbWrapper{} return &JavascriptTracer{ - vm: vm, - traceobj: jstracer, - log: log, - logvalue: logvalue, - memory: mem, - memvalue: mem.toValue(vm), - stack: stack, - stackvalue: stack.toValue(vm), - db: db, - dbvalue: db.toValue(vm), - err: nil, + vm: vm, + traceobj: jstracer, + log: log, + logvalue: logvalue, + memory: mem, + memvalue: mem.toValue(vm), + stack: stack, + stackvalue: stack.toValue(vm), + db: db, + dbvalue: db.toValue(vm), + err: nil, }, nil } @@ -278,7 +278,7 @@ func wrapError(context string, err error) error { } // CaptureState implements the Tracer interface to trace a single step of VM execution -func (jst *JavascriptTracer) CaptureState(env vm.Environment, pc uint64, op vm.OpCode, gas, cost *big.Int, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) { +func (jst *JavascriptTracer) CaptureState(env *vm.Environment, pc uint64, op vm.OpCode, gas, cost *big.Int, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) { if jst.err == nil { jst.memory.memory = memory jst.stack.stack = stack diff --git a/internal/ethapi/tracer_test.go b/internal/ethapi/tracer_test.go index 127af32a84..0119e07053 100644 --- a/internal/ethapi/tracer_test.go +++ b/internal/ethapi/tracer_test.go @@ -24,8 +24,11 @@ import ( "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/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" ) type ruleSet struct{} @@ -40,7 +43,7 @@ type Env struct { func NewEnv(config *vm.Config) *Env { env := &Env{gasLimit: big.NewInt(10000), depth: 0} - env.evm = vm.New(env, *config) + //env.evm = vm.New(env, *config) return env } @@ -92,14 +95,20 @@ func (account) SetBalance(*big.Int) {} func (account) SetNonce(uint64) {} func (account) Balance() *big.Int { return nil } func (account) Address() common.Address { return common.Address{} } -func (account) ReturnGas(*big.Int, *big.Int) {} +func (account) ReturnGas(uint64) {} func (account) SetCode(common.Hash, []byte) {} func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} func runTrace(tracer *JavascriptTracer) (interface{}, error) { - env := NewEnv(&vm.Config{Debug: true, Tracer: tracer}) + db, _ := ethdb.NewMemDatabase() + st, _ := state.New(common.Hash{}, db) + backend := &core.EVMBackend{ + GetHashFn: nil, + State: st, + } + env := vm.NewEnvironment(vm.Context{GasLimit: big.NewInt(1000000)}, backend, &ruleSet{}, vm.Config{Debug: true, Tracer: tracer}) - contract := vm.NewContract(account{}, account{}, big.NewInt(0), env.GasLimit(), big.NewInt(1)) + contract := vm.NewContract(account{}, account{}, big.NewInt(0), env.GasLimit) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} _, err := env.Vm().Run(contract, []byte{}) @@ -176,7 +185,7 @@ func TestHalt(t *testing.T) { tracer.Stop(timeout) }() - if _, err = runTrace(tracer); err.Error() != "stahp in server-side tracer function 'step'" { + if _, err = runTrace(tracer); err == nil || (err != nil && err.Error() != "stahp in server-side tracer function 'step'") { t.Errorf("Expected timeout error, got %v", err) } } @@ -187,8 +196,14 @@ func TestHaltBetweenSteps(t *testing.T) { t.Fatal(err) } - env := NewEnv(&vm.Config{Debug: true, Tracer: tracer}) - contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), big.NewInt(0), big.NewInt(0)) + db, _ := ethdb.NewMemDatabase() + st, _ := state.New(common.Hash{}, db) + backend := &core.EVMBackend{ + GetHashFn: nil, + State: st, + } + env := vm.NewEnvironment(vm.Context{GasLimit: big.NewInt(1000000)}, backend, &ruleSet{}, vm.Config{Debug: true, Tracer: tracer}) + contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), big.NewInt(0)) tracer.CaptureState(env, 0, 0, big.NewInt(0), big.NewInt(0), nil, nil, contract, 0, nil) timeout := errors.New("stahp") diff --git a/miner/worker.go b/miner/worker.go index e5348cef42..16b94b363c 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -621,13 +621,7 @@ func (env *Work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, g snap := env.state.Snapshot() // this is a bit of a hack to force jit for the miners - config := env.config.VmConfig - if !(config.EnableJit && config.ForceJit) { - config.EnableJit = false - } - config.ForceJit = false // disable forcing jit - - receipt, logs, _, err := core.ApplyTransaction(env.config, bc, gp, env.state, env.header, tx, env.header.GasUsed, config) + receipt, logs, _, err := core.ApplyTransaction(env.config, bc, gp, env.state, env.header, tx, env.header.GasUsed, env.config.VmConfig) if err != nil { env.state.RevertToSnapshot(snap) return err, nil diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 00ddf232cc..be0ba707c1 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -104,13 +104,16 @@ func benchStateTest(ruleSet RuleSet, test VmTest, env map[string]string, b *test } func runStateTests(ruleSet RuleSet, tests map[string]VmTest, skipTests []string) error { + //glog.SetToStderr(true) + //glog.SetV(6) + skipTest := make(map[string]bool, len(skipTests)) for _, name := range skipTests { skipTest[name] = true } for name, test := range tests { - if skipTest[name] /*|| name != "TestInputLimitsLight"*/ { + if skipTest[name] /*|| name != "gasPrice0"*/ { glog.Infoln("Skipping state test", name) continue } @@ -230,13 +233,12 @@ func RunState(ruleSet RuleSet, statedb *state.StateDB, env, tx map[string]string key, _ := hex.DecodeString(tx["secretKey"]) addr := crypto.PubkeyToAddress(crypto.ToECDSA(key).PublicKey) message := NewMessage(addr, to, data, value, gas, price, nonce) - vmenv := NewEnvFromMap(ruleSet, statedb, env, tx) - vmenv.origin = addr + vmenv := NewEVMEnvironment(false, ruleSet, statedb, env, tx) ret, _, err := core.ApplyMessage(vmenv, message, gaspool) if core.IsNonceErr(err) || core.IsInvalidTxErr(err) || core.IsGasLimitErr(err) { statedb.RevertToSnapshot(snapshot) } statedb.Commit() - return ret, vmenv.state.Logs(), vmenv.Gas, err + return ret, statedb.Logs(), gas, err } diff --git a/tests/util.go b/tests/util.go index e0ee7a7d55..75cea35554 100644 --- a/tests/util.go +++ b/tests/util.go @@ -18,6 +18,7 @@ package tests import ( "bytes" + "encoding/hex" "fmt" "math/big" "os" @@ -157,141 +158,50 @@ func (r RuleSet) IsHomestead(n *big.Int) bool { return n.Cmp(r.HomesteadBlock) >= 0 } -type Env struct { - ruleSet RuleSet - depth int - state *state.StateDB - skipTransfer bool - initial bool - Gas, gasPrice *big.Int - - origin common.Address - parent common.Hash - coinbase common.Address - - number *big.Int - time *big.Int - difficulty *big.Int - gasLimit *big.Int - - vmTest bool - - evm vm.VirtualMachine -} - -func NewEnv(ruleSet RuleSet, state *state.StateDB) *Env { - env := &Env{ - ruleSet: ruleSet, - state: state, +func NewEVMEnvironment(vmTest bool, ruleSet RuleSet, state *state.StateDB, envValues map[string]string, exeValues map[string]string) *vm.Environment { + origin := common.HexToAddress(exeValues["caller"]) + if len(exeValues["secretKey"]) > 0 { + key, _ := hex.DecodeString(exeValues["secretKey"]) + origin = crypto.PubkeyToAddress(crypto.ToECDSA(key).PublicKey) } - return env -} -func NewEnvFromMap(ruleSet RuleSet, state *state.StateDB, envValues map[string]string, exeValues map[string]string) *Env { - env := NewEnv(ruleSet, state) + context := vm.Context{ + Origin: origin, + Coinbase: common.HexToAddress(envValues["currentCoinbase"]), + BlockNumber: common.Big(envValues["currentNumber"]), + Time: common.Big(envValues["currentTimestamp"]), + Difficulty: common.Big(envValues["currentDifficulty"]), + GasLimit: common.Big(envValues["currentGasLimit"]), + GasPrice: common.Big(exeValues["gasPrice"]), + } - env.origin = common.HexToAddress(exeValues["caller"]) - env.parent = common.HexToHash(envValues["previousHash"]) - env.coinbase = common.HexToAddress(envValues["currentCoinbase"]) - env.number = common.Big(envValues["currentNumber"]) - env.time = common.Big(envValues["currentTimestamp"]) - env.difficulty = common.Big(envValues["currentDifficulty"]) - env.gasLimit = common.Big(envValues["currentGasLimit"]) - env.Gas = new(big.Int) - env.gasPrice = common.Big(exeValues["gasPrice"]) - - env.evm = vm.New(env, vm.Config{ - EnableJit: EnableJit, - ForceJit: ForceJit, - }) - - 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) GasPrice() *big.Int { return self.gasPrice } -func (self *Env) VmType() vm.Type { return vm.StdVmTy } -func (self *Env) GetHash(n uint64) common.Hash { - return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String()))) -} -func (self *Env) AddLog(log *vm.Log) { - self.state.AddLog(log) -} -func (self *Env) Depth() int { return self.depth } -func (self *Env) SetDepth(i int) { self.depth = i } -func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool { - if self.skipTransfer { - if self.initial { - self.initial = false - return true + //var initialCall bool + backend := &core.EVMBackend{ + GetHashFn: func(n uint64) common.Hash { + return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String()))) + }, + State: state, + } + initialCall := true + canTransfer := func(db vm.Database, address common.Address, amount *big.Int) bool { + if vmTest { + if initialCall { + initialCall = false + return true + } } + return db.GetBalance(address).Cmp(amount) >= 0 } - - return self.state.GetBalance(from).Cmp(balance) >= 0 -} -func (self *Env) SnapshotDatabase() int { - return self.state.Snapshot() -} -func (self *Env) RevertToSnapshot(snapshot int) { - self.state.RevertToSnapshot(snapshot) -} - -func (self *Env) Transfer(from, to vm.Account, amount *big.Int) { - if self.skipTransfer { - return + transfer := func(db vm.Database, sender, recipient common.Address, amount *big.Int) { + if vmTest { + return + } + core.Transfer(db, sender, recipient, amount) } - core.Transfer(from, to, amount) -} + context.CallContext = core.EVMCallContext{canTransfer, transfer} -func (self *Env) Call(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - if self.vmTest && self.depth > 0 { - caller.ReturnGas(gas.Uint64()) - - return nil, nil - } - ret, err := core.Call(self, caller, addr, data, gas, value) - self.Gas = gas - - return ret, err - -} -func (self *Env) CallCode(caller vm.ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { - if self.vmTest && self.depth > 0 { - caller.ReturnGas(gas.Uint64()) - - return nil, nil - } - return core.CallCode(self, caller, addr, data, gas, value) -} - -func (self *Env) DelegateCall(caller vm.ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { - if self.vmTest && self.depth > 0 { - caller.ReturnGas(gas.Uint64()) - - return nil, nil - } - return core.DelegateCall(self, caller, addr, data, gas) -} - -func (self *Env) Create(caller vm.ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { - if self.vmTest { - caller.ReturnGas(gas.Uint64()) - - nonce := self.state.GetNonce(caller.Address()) - obj := self.state.GetOrNewStateObject(crypto.CreateAddress(caller.Address(), nonce)) - - return nil, obj.Address(), nil - } else { - return core.Create(self, caller, data, gas, value) - } + env := vm.NewEnvironment(context, backend, ruleSet, vm.Config{Test: vmTest}) + return env } type Message struct { diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index d28f053c04..daba619635 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -211,7 +211,7 @@ func runVmTest(test VmTest) error { return nil } -func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, *big.Int, error) { +func RunVm(statedb *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, *big.Int, error) { var ( to = common.HexToAddress(exec["address"]) from = common.HexToAddress(exec["caller"]) @@ -222,13 +222,10 @@ func RunVm(state *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs, // Reset the pre-compiled contracts for VM tests. vm.PrecompiledContracts = make(map[common.Address]vm.PrecompiledContract) - caller := state.GetOrNewStateObject(from) + caller := statedb.GetOrNewStateObject(from) - vmenv := NewEnvFromMap(RuleSet{params.MainNetHomesteadBlock, params.MainNetDAOForkBlock, true}, state, env, exec) - vmenv.vmTest = true - vmenv.skipTransfer = true - vmenv.initial = true + vmenv := NewEVMEnvironment(true, RuleSet{params.MainNetHomesteadBlock, params.MainNetDAOForkBlock, true}, statedb, env, exec) ret, err := vmenv.Call(caller, to, data, gas, value) - return ret, vmenv.state.Logs(), vmenv.Gas, err + return ret, statedb.Logs(), gas, err } From aeb272ef5e3e84621e64a2efce724cd77b14d9bb Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Fri, 30 Sep 2016 15:09:23 +0200 Subject: [PATCH 6/9] fuzz/evm: added EVM fuzzer (go-fuzz) --- fuzz/evm/main.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 fuzz/evm/main.go diff --git a/fuzz/evm/main.go b/fuzz/evm/main.go new file mode 100644 index 0000000000..aab814ba60 --- /dev/null +++ b/fuzz/evm/main.go @@ -0,0 +1,11 @@ +package evm + +import "github.com/ethereum/go-ethereum/core/vm/runtime" + +func Fuzz(data []byte) int { + ret, _, err := runtime.Execute(data, data, &runtime.Config{GasLimit: 5000000}) + if err != nil && ret != nil { + panic("ret != nil on error") + } + return 1 +} From 926d81e34e76377fd0ec6193bb5e29a486f5ec46 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Sat, 1 Oct 2016 22:05:22 +0200 Subject: [PATCH 7/9] core/state, core/vm: improve code hashing in the EVM --- core/state/statedb.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/state/statedb.go b/core/state/statedb.go index f46f2f4427..4a50db0522 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -259,6 +259,7 @@ func (self *StateDB) GetCodeSize(addr common.Address) int { return size } +// GetCodeHash returns the hash of the code associated with the address. func (self *StateDB) GetCodeHash(addr common.Address) common.Hash { stateObject := self.GetStateObject(addr) if stateObject == nil { From 0169435782144cbc5680908f722ca154f6587ee0 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Sun, 9 Oct 2016 13:57:08 +0200 Subject: [PATCH 8/9] tmp slow block --- cmd/utils/flags.go | 3 +++ core/blockchain.go | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a94f34484d..c0d541ce80 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -33,6 +33,7 @@ import ( "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/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" @@ -623,6 +624,8 @@ func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node { WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name), WSModules: MakeRPCModules(ctx.GlobalString(WSApiFlag.Name)), } + vm.SetJITCacheSize(ctx.GlobalInt(VMCacheFlag.Name)) + if ctx.GlobalBool(DevModeFlag.Name) { if !ctx.GlobalIsSet(DataDirFlag.Name) { config.DataDir = filepath.Join(os.TempDir(), "/ethereum_dev_mode") diff --git a/core/blockchain.go b/core/blockchain.go index 01aa987ddc..a05e8d104a 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -974,6 +974,12 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { } stats.processed++ + if time.Since(bstart) > 1200*time.Millisecond { + glog.Infof("#### inserted slow block ####") + glog.Infof("[%v] inserted block #%d (%d TXs %v G %d UNCs) (%x...). Took %v\n", time.Now().UnixNano(), block.Number(), len(block.Transactions()), block.GasUsed(), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart)) + glog.Infof("#############################") + } + if glog.V(logger.Info) { stats.report(chain, i) } From 062bac6b8b27e295479bd05b36e065a18bfef703 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 11 Oct 2016 17:58:41 +0200 Subject: [PATCH 9/9] core/vm: working on peephole optimisation --- core/vm/instructions.go | 9 +++++++ core/vm/program.go | 4 +-- core/vm/program_optimiser.go | 48 ++++++++++++++++++++++++------------ core/vm/program_util.go | 23 ++++++++++------- core/vm/segments.go | 40 +++++++++++++++++++++++++++++- core/vm/vm.go | 11 +++++++++ 6 files changed, 107 insertions(+), 28 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 7b20dbcde0..1204c8d39a 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -34,6 +34,8 @@ type programInstruction interface { Op() OpCode } +//[static memory], if calldata goto jump table + type instrFn func(instr instruction, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) type instruction struct { @@ -49,6 +51,13 @@ type instruction struct { returns bool } +func (instr instruction) String() string { + if instr.op == PUSH1 { + return fmt.Sprintf("PUSH %v", instr.data) + } + return fmt.Sprintf("%v", instr.op) +} + func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract *Contract, to *big.Int) (uint64, error) { if !validDest(destinations, to) { nop := contract.GetOp(to.Uint64()) diff --git a/core/vm/program.go b/core/vm/program.go index 0fadfe15c4..b3cf3e3306 100644 --- a/core/vm/program.go +++ b/core/vm/program.go @@ -82,8 +82,6 @@ type Program struct { Id common.Hash // Id of the program status int32 // status should be accessed atomically - contract *Contract - instructions []programInstruction // instruction set mapping map[uint64]uint64 // real PC mapping to array indices destinations map[uint64]struct{} // cached jump destinations @@ -288,6 +286,8 @@ func CompileProgram(program *Program) { program.addInstr(op, pc, nil, nil) } } + // reset the program code. It's no longer required by the program itself. + //program.code = nil } func RunProgram(program *Program, env *Environment, contract *Contract, input []byte) ([]byte, error) { diff --git a/core/vm/program_optimiser.go b/core/vm/program_optimiser.go index 79c91df3e6..197e77a39d 100644 --- a/core/vm/program_optimiser.go +++ b/core/vm/program_optimiser.go @@ -17,13 +17,17 @@ package vm import ( + "fmt" "math/big" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" ) +type fnId string + // optimeProgram optimises a JIT program creating segments out of program // instructions. Currently covered are multi-pushes and static jumps func OptimiseProgram(program *Program) { @@ -43,22 +47,34 @@ func OptimiseProgram(program *Program) { }() } - /* - code := Parse(program.code) - for _, test := range [][]OpCode{ - []OpCode{PUSH, PUSH, ADD}, - []OpCode{PUSH, PUSH, SUB}, - []OpCode{PUSH, PUSH, MUL}, - []OpCode{PUSH, PUSH, DIV}, - } { - matchCount := 0 - MatchFn(code, test, func(i int) bool { - matchCount++ - return true - }) - fmt.Printf("found %d match count on: %v\n", matchCount, test) - } - */ + code := Parse(program.code) + for _, test := range [][]OpCode{ + []OpCode{PUSH, DUP, EQ, PUSH, JUMPI}, + } { + matchCount := 0 + fmt.Printf("found %d match count on: %v\n", matchCount, test) + } + + MatchFn(code, []OpCode{PUSH, PUSH, EXP}, func(i int) bool { + // TODO optimise this instruction + return true + }) + + funcTable := make(map[fnId]uint64) + MatchFn(code, []OpCode{DUP, PUSH, EQ, PUSH, JUMPI}, func(i int) bool { + pushOp := code[i+1] + size := int64(program.code[pushOp.pc]) - int64(PUSH1) + 1 + funcId := fnId(getData([]byte(program.code), big.NewInt(int64(pushOp.pc+1)), big.NewInt(size))) + + pushOp = code[i+3] + size = int64(program.code[pushOp.pc]) - int64(PUSH1) + 1 + position := common.Bytes2Big(getData([]byte(program.code), big.NewInt(int64(pushOp.pc+1)), big.NewInt(size))).Uint64() + glog.Infof("jumpTable entry: %x => %d\n", funcId, position) + + funcTable[funcId] = position + + return true + }) for i := 0; i < len(program.instructions); i++ { instr := program.instructions[i].(instruction) diff --git a/core/vm/program_util.go b/core/vm/program_util.go index 947dae88f4..067061abe9 100644 --- a/core/vm/program_util.go +++ b/core/vm/program_util.go @@ -16,23 +16,28 @@ package vm +type parsedOp struct { + op OpCode + pc uint64 +} + // Parse parses all opcodes from the given code byte slice. This function // performs no error checking and may return non-existing opcodes. -func Parse(code []byte) (opcodes []OpCode) { +func Parse(code []byte) (opcodes []parsedOp) { for pc := uint64(0); pc < uint64(len(code)); pc++ { op := OpCode(code[pc]) switch op { case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: a := uint64(op) - uint64(PUSH1) + 1 + opcodes = append(opcodes, parsedOp{PUSH, pc}) pc += a - opcodes = append(opcodes, PUSH) case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: - opcodes = append(opcodes, DUP) + opcodes = append(opcodes, parsedOp{DUP, pc}) case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: - opcodes = append(opcodes, SWAP) + opcodes = append(opcodes, parsedOp{SWAP, pc}) default: - opcodes = append(opcodes, op) + opcodes = append(opcodes, parsedOp{op, pc}) } } @@ -43,7 +48,7 @@ func Parse(code []byte) (opcodes []OpCode) { // an appropriate match. matcherFn yields the starting position in the input. // MatchFn will continue to search for a match until it reaches the end of the // buffer or if matcherFn return false. -func MatchFn(input, match []OpCode, matcherFn func(int) bool) { +func MatchFn(input []parsedOp, match []OpCode, matcherFn func(int) bool) { // short circuit if either input or match is empty or if the match is // greater than the input if len(input) == 0 || len(match) == 0 || len(match) > len(input) { @@ -51,11 +56,11 @@ func MatchFn(input, match []OpCode, matcherFn func(int) bool) { } main: - for i, op := range input[:len(input)+1-len(match)] { + for i, parsedOp := range input[:len(input)+1-len(match)] { // match first opcode and continue search - if op == match[0] { + if parsedOp.op == match[0] { for j := 1; j < len(match); j++ { - if input[i+j] != match[j] { + if input[i+j].op != match[j] { continue main } } diff --git a/core/vm/segments.go b/core/vm/segments.go index ed223d82f2..feb71fafe3 100644 --- a/core/vm/segments.go +++ b/core/vm/segments.go @@ -16,7 +16,10 @@ package vm -import "math/big" +import ( + "fmt" + "math/big" +) type jumpSeg struct { pos uint64 @@ -24,6 +27,10 @@ type jumpSeg struct { gas uint64 } +func (js jumpSeg) String() string { + return fmt.Sprintf("[JUMP SEG: %d]", js.pos) +} + func (j jumpSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { if !contract.UseGas(j.gas) { return nil, OutOfGasError @@ -42,6 +49,14 @@ type pushSeg struct { gas uint64 } +func (ps pushSeg) String() string { + var ret string + for _, num := range ps.data { + ret += fmt.Sprintf("PUSH %v\n", num) + } + return ret +} + func (s pushSeg) do(program *Program, pc *uint64, env *Environment, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { // Use the calculated gas. When insufficient gas is present, use all gas and return an // Out Of Gas error @@ -58,3 +73,26 @@ func (s pushSeg) do(program *Program, pc *uint64, env *Environment, contract *Co func (s pushSeg) halts() bool { return false } func (s pushSeg) Op() OpCode { return 0 } + +//CALLDATASIZE, ISZERO, PUSH2, JUMPI +//if len(calldata) > 0 { +// *pc = T.pos +//} + +//PUSH 224, PUSH 2, EXP, PUSH 0, CALLDATALOAD, DIV +//calldata[:4] + +//PUSH4, DUP2, EQ, PUSH2, JUMP +/* +if calldata[:4] == (PUSH4) +else if calldata[:4] == (PUSH2) ???? +else if calldata[:4] == (PUSH2) ???? + +type programJumpTable map[funcId]dest + +if len(calldata) > 0 { + if ppc, exist := programJumpTable[string(calldata[:4])]; exit { + *pc = ppc + } +} +*/ diff --git a/core/vm/vm.go b/core/vm/vm.go index 5b85211438..b13b2ecc4b 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -18,6 +18,7 @@ package vm import ( "fmt" + "os" "time" "github.com/ethereum/go-ethereum/common" @@ -120,11 +121,21 @@ func (evm *EVM) runProgram(program *Program, contract *Contract, input []byte) ( }() } + var ops []OpCode + defer func() { + if len(ops) > 0 { + fmt.Printf("%x\n", input) + fmt.Printf("%d\n", ops) + os.Exit(1) + } + }() homestead := env.RuleSet().IsHomestead(env.BlockNumber) for pc < uint64(len(program.instructions)) { instrCount++ instr := program.instructions[pc] + //fmt.Println(instr) + ops = append(ops, instr.Op()) if instr.Op() == DELEGATECALL && !homestead { return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op()) }