From cb7cd07ed09da1ac2a2b10961cbcb5773eb20175 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 23 Jun 2015 19:04:05 +0200 Subject: [PATCH 1/5] cmd/vm: code segmenting --- cmd/evm/main.go | 134 ++++++++++++++++++++++++++++--------- core/state/state_object.go | 2 +- core/vm/context.go | 21 ++++-- core/vm/gas.go | 4 +- core/vm/opcodes.go | 12 ++++ core/vm/stack.go | 16 ++--- core/vm/vm.go | 113 +++++++++++++++++++++++-------- crypto/crypto.go | 10 +++ tests/state_test_util.go | 6 +- 9 files changed, 237 insertions(+), 81 deletions(-) diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 7c9d27fac9..f93645781a 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -22,79 +22,153 @@ package main import ( - "flag" "fmt" - "log" "math/big" "os" "runtime" "time" + "github.com/codegangsta/cli" + "github.com/ethereum/go-ethereum/cmd/utils" "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/ethdb" - "github.com/ethereum/go-ethereum/logger" ) var ( - code = flag.String("code", "", "evm code") - loglevel = flag.Int("log", 4, "log level") - gas = flag.String("gas", "1000000000", "gas amount") - price = flag.String("price", "0", "gas price") - value = flag.String("value", "0", "tx value") - dump = flag.Bool("dump", false, "dump state after run") - data = flag.String("data", "", "data") + app *cli.App + DebugFlag = cli.BoolFlag{ + Name: "debug", + Usage: "output full trace logs", + } + CodeFlag = cli.StringFlag{ + Name: "code", + Usage: "EVM code", + } + GasFlag = cli.StringFlag{ + Name: "gas", + Usage: "gas limit for the evm", + Value: "10000000000", + } + PriceFlag = cli.StringFlag{ + Name: "price", + Usage: "price set for the evm", + Value: "0", + } + ValueFlag = cli.StringFlag{ + Name: "value", + Usage: "value set for the evm", + Value: "0", + } + DumpFlag = cli.BoolFlag{ + Name: "dump", + Usage: "dumps the state after the run", + } + InputFlag = cli.StringFlag{ + Name: "input", + Usage: "input for the EVM", + } + SysStatFlag = cli.BoolFlag{ + Name: "sysstat", + Usage: "display system stats", + } ) -func perr(v ...interface{}) { - fmt.Println(v...) - //os.Exit(1) +func init() { + app = utils.NewApp("0.2", "the evm command line interface") + app.Flags = []cli.Flag{ + DebugFlag, + SysStatFlag, + CodeFlag, + GasFlag, + PriceFlag, + ValueFlag, + DumpFlag, + InputFlag, + } + app.Action = run } -func main() { - flag.Parse() +func run(ctx *cli.Context) { + vm.Debug = ctx.GlobalBool(DebugFlag.Name) - logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(*loglevel))) - - vm.Debug = true db, _ := ethdb.NewMemDatabase() statedb := state.New(common.Hash{}, db) sender := statedb.CreateAccount(common.StringToAddress("sender")) receiver := statedb.CreateAccount(common.StringToAddress("receiver")) - receiver.SetCode(common.Hex2Bytes(*code)) + receiver.SetCode(common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name))) - vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(*value)) + vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name))) tstart := time.Now() - ret, e := vmenv.Call(sender, receiver.Address(), common.Hex2Bytes(*data), common.Big(*gas), common.Big(*price), common.Big(*value)) + vmstart := time.Now() + ret, e := vmenv.Call( + sender, + 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)), + ) - logger.Flush() if e != nil { - perr(e) + fmt.Println(e) + os.Exit(1) } + fmt.Println("no analysis", time.Since(vmstart)) - if *dump { + vmstart = time.Now() + ret, e = vmenv.Call( + sender, + 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)), + ) + + if e != nil { + fmt.Println(e) + os.Exit(1) + } + fmt.Println("with analysis", time.Since(vmstart)) + + if ctx.GlobalBool(DumpFlag.Name) { fmt.Println(string(statedb.Dump())) } - vm.StdErrFormat(vmenv.StructLogs()) - var mem runtime.MemStats - runtime.ReadMemStats(&mem) - fmt.Printf("vm took %v\n", time.Since(tstart)) - fmt.Printf(`alloc: %d + if ctx.GlobalBool(SysStatFlag.Name) { + var mem runtime.MemStats + runtime.ReadMemStats(&mem) + fmt.Printf("vm took %v\n", time.Since(tstart)) + fmt.Printf(`alloc: %d tot alloc: %d no. malloc: %d heap alloc: %d heap objs: %d num gc: %d `, mem.Alloc, mem.TotalAlloc, mem.Mallocs, mem.HeapAlloc, mem.HeapObjects, mem.NumGC) + } - fmt.Printf("%x\n", ret) + fmt.Printf("OUT: 0x%x\n", ret) + + for codehash, chunks := range vm.Chunks { + for _, chunk := range chunks { + fmt.Printf("%x => %v\n", codehash, chunk) + } + } +} + +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } } type VMEnv struct { diff --git a/core/state/state_object.go b/core/state/state_object.go index a31c182d20..bad276491f 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -243,7 +243,7 @@ func (self *StateObject) AddGas(gas, price *big.Int) { } func (self *StateObject) Copy() *StateObject { - stateObject := NewStateObject(self.Address(), self.db) + stateObject := &StateObject{db: self.db, address: self.address, balance: new(big.Int), gasPool: new(big.Int), dirty: true} stateObject.balance.Set(self.balance) stateObject.codeHash = common.CopyBytes(self.codeHash) stateObject.nonce = self.nonce diff --git a/core/vm/context.go b/core/vm/context.go index e33324b535..3db45a6067 100644 --- a/core/vm/context.go +++ b/core/vm/context.go @@ -26,16 +26,25 @@ type Context struct { Args []byte } +var dests destinations + +func init() { + dests = make(destinations) +} + // Create a new context for the given data items. func NewContext(caller ContextRef, object ContextRef, value, gas, price *big.Int) *Context { c := &Context{caller: caller, self: object, Args: nil} - if parent, ok := caller.(*Context); ok { - // Reuse JUMPDEST analysis from parent context if available. - c.jumpdests = parent.jumpdests - } else { - c.jumpdests = make(destinations) - } + c.jumpdests = dests + /* + if parent, ok := caller.(*Context); ok { + // Reuse JUMPDEST analysis from parent context if available. + c.jumpdests = parent.jumpdests + } else { + c.jumpdests = make(destinations) + } + */ // Gas should be a pointer so it can safely be reduced through the run // This pointer will be off the state transition diff --git a/core/vm/gas.go b/core/vm/gas.go index 32f5fec04d..0caf4a1920 100644 --- a/core/vm/gas.go +++ b/core/vm/gas.go @@ -38,8 +38,8 @@ func baseCheck(op OpCode, stack *stack, gas *big.Int) error { return err } - if r.stackPush > 0 && len(stack.data)-r.stackPop+r.stackPush > int(params.StackLimit.Int64())+1 { - return fmt.Errorf("stack limit reached %d (%d)", len(stack.data), params.StackLimit.Int64()) + 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) diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 1ea80a212d..4bd3abce7a 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -332,3 +332,15 @@ func (o OpCode) String() string { return str } + +func isDynamic(op OpCode) bool { + switch op { + case CREATE, CALL, CALLCODE, JUMP, JUMPI, SUICIDE, STOP, RETURN, EXTCODECOPY, CODECOPY, MSTORE, MSTORE8: + return true + } + if _, ok := opCodeToString[op]; !ok { + return true + } + + return false +} diff --git a/core/vm/stack.go b/core/vm/stack.go index 2be5c3dbe6..99c1b62b86 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -11,32 +11,26 @@ func newstack() *stack { type stack struct { data []*big.Int - ptr int } func (st *stack) Data() []*big.Int { - return st.data[:st.ptr] + return st.data } func (st *stack) push(d *big.Int) { // NOTE push limit (1024) is checked in baseCheck stackItem := new(big.Int).Set(d) - if len(st.data) > st.ptr { - st.data[st.ptr] = stackItem - } else { - st.data = append(st.data, stackItem) - } - st.ptr++ + st.data = append(st.data, stackItem) } func (st *stack) pop() (ret *big.Int) { - st.ptr-- - ret = st.data[st.ptr] + ret = st.data[len(st.data)-1] + st.data = st.data[:len(st.data)-1] return } func (st *stack) len() int { - return st.ptr + return len(st.data) } func (st *stack) swap(n int) { diff --git a/core/vm/vm.go b/core/vm/vm.go index 9e092300de..5c23105349 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -8,8 +8,27 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/hashicorp/golang-lru" ) +type segment struct { + cstart, cend uint64 + msize uint64 + gas *big.Int +} + +func (c *segment) String() string { + return fmt.Sprintf("{%d %d %d %v}", c.cstart, c.cend, c.msize, c.gas) +} + +type codeSegments map[uint64]*segment + +var segments *lru.Cache + +func init() { + segments, _ = lru.New(256) +} + // Vm implements VirtualMachine type Vm struct { env Environment @@ -44,14 +63,19 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { value = context.value price = context.Price - op OpCode // current opcode - codehash = crypto.Sha3Hash(code) // codehash is used when doing jump dest caching - mem = NewMemory() // bound memory - stack = newstack() // local stack - statedb = self.env.State() // current state + op OpCode // current opcode + codehash = crypto.FnvHash(code) // codehash is used when doing jump dest caching + mem = NewMemory() // bound memory + stack = newstack() // local stack + statedb = self.env.State() // 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. Pratically much less so feasible. - pc = uint64(0) // program counter + pc = uint64(0) // program counter + ppc = uint64(0) // previous program counter + csegment *segment // current code segment + csegments codeSegments // program segments + nopay uint64 = 0 + disableOptim bool // jump evaluates and checks whether the given jump destination is a valid one // if valid move the `pc` otherwise return an error. @@ -69,13 +93,15 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { newMemSize *big.Int cost *big.Int ) + if c, ok := segments.Get(codehash); ok { + csegments = c.(codeSegments) + } else { + csegments = make(codeSegments) + segments.Add(codehash, csegments) + } // 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 self.After != nil { - self.After(context, err) - } - if err != nil { self.log(pc, op, context.Gas, cost, mem, stack, context, err) @@ -84,6 +110,13 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { ret = context.Return(nil) } + + if csegment != nil && err != nil && csegment.cend == 0 { + // delete this tracking segment. We couldn't fully determine the stats + delete(csegments, csegment.cstart) + } else if csegment != nil && err == nil && csegment.cend == 0 { + csegment.cend = pc + } }() if context.CodeAddr != nil { @@ -98,31 +131,57 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { } for { - // The base for all big integer arithmetic - base := new(big.Int) - - // Get the memory location of pc + // Get the current operation location of pc op = context.GetOp(pc) + if isDynamic(op) && csegment != nil { + csegment.cend = ppc + csegment = nil + nopay = 0 + } else if csegment == nil && pc != 0 { + // allocate a new segment for tracking information + csegment = &segment{pc, 0, 0, new(big.Int)} + csegments[pc] = csegment + } else { + if segment, ok := csegments[pc]; ok && !disableOptim { + nopay = segment.cend + if !context.UseGas(segment.gas) { + context.UseGas(context.Gas) - // calculate the new memory size and gas price for the current executing opcode - newMemSize, cost, err = self.calculateGasAndSize(context, caller, op, statedb, mem, stack) - if err != nil { - return nil, err + return context.Return(nil), OutOfGasError{} + } + } } - // Use the calculated gas. When insufficient gas is present, use all gas and return an - // Out Of Gas error - if !context.UseGas(cost) { + if pc > nopay || pc == 0 || disableOptim { + // calculate the new memory size and gas price for the current executing opcode + newMemSize, cost, err = self.calculateGasAndSize(context, caller, op, statedb, mem, stack) + if err != nil { + return nil, err + } - context.UseGas(context.Gas) + // Use the calculated gas. When insufficient gas is present, use all gas and return an + // Out Of Gas error + if !context.UseGas(cost) { + context.UseGas(context.Gas) - return context.Return(nil), OutOfGasError{} + return context.Return(nil), OutOfGasError{} + } + + // Resize the memory calculated previously + mem.Resize(newMemSize.Uint64()) + + if csegment != nil { + csegment.msize = uint64(len(mem.store)) + csegment.gas.Add(csegment.gas, cost) + } } - // Resize the memory calculated previously - mem.Resize(newMemSize.Uint64()) // Add a log message self.log(pc, op, context.Gas, cost, mem, stack, context, nil) + ppc = pc + // The base for all big integer arithmetic + base := new(big.Int) + switch op { case ADD: x, y := stack.pop(), stack.pop() @@ -348,7 +407,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { case SHA3: offset, size := stack.pop(), stack.pop() - data := crypto.Sha3(mem.Get(offset.Int64(), size.Int64())) + data := crypto.Sha3(mem.GetPtr(offset.Int64(), size.Int64())) stack.push(common.BigD(data)) @@ -491,7 +550,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { case MLOAD: offset := stack.pop() - val := common.BigD(mem.Get(offset.Int64(), 32)) + val := common.BigD(mem.GetPtr(offset.Int64(), 32)) stack.push(val) case MSTORE: diff --git a/crypto/crypto.go b/crypto/crypto.go index 153bbbc5d4..7192837566 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -8,6 +8,7 @@ import ( "crypto/rand" "crypto/sha256" "fmt" + "hash/fnv" "io" "io/ioutil" "math/big" @@ -35,6 +36,15 @@ func init() { secp256k1n = common.String2Big("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") } +func FnvHash(data ...[]byte) (h common.Hash) { + fnvHash := fnv.New32() + for _, b := range data { + fnvHash.Write(b) + } + fnvHash.Sum(h[:0]) + return h +} + func Sha3(data ...[]byte) []byte { d := sha3.NewKeccak256() for _, b := range data { diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 2f3d497bef..2003fda5af 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -91,9 +91,7 @@ func runStateTest(test VmTest) error { } var ( - ret []byte - // gas *big.Int - // err error + ret []byte logs state.Logs ) @@ -113,7 +111,7 @@ func runStateTest(test VmTest) error { } if obj.Balance().Cmp(common.Big(account.Balance)) != 0 { - return fmt.Errorf("(%x) balance failed. Expected %v, got %v => %v\n", obj.Address().Bytes()[:4], account.Balance, obj.Balance(), new(big.Int).Sub(common.Big(account.Balance), obj.Balance())) + return fmt.Errorf("(%x) balance failed. Expected %v, got %v => %v\n", obj.Address().Bytes()[:4], common.Big(account.Balance), obj.Balance(), new(big.Int).Sub(common.Big(account.Balance), obj.Balance())) } if obj.Nonce() != common.String2Big(account.Nonce).Uint64() { From e92fce8a03c8accea897f15bf350c785082e0b7b Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 23 Jun 2015 21:35:25 +0200 Subject: [PATCH 2/5] core/vm: add SSTORE to "dynamic" --- core/vm/opcodes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 4bd3abce7a..4535ab6b02 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -335,7 +335,7 @@ func (o OpCode) String() string { func isDynamic(op OpCode) bool { switch op { - case CREATE, CALL, CALLCODE, JUMP, JUMPI, SUICIDE, STOP, RETURN, EXTCODECOPY, CODECOPY, MSTORE, MSTORE8: + case CREATE, CALL, CALLCODE, JUMP, JUMPI, SUICIDE, STOP, RETURN, EXTCODECOPY, CODECOPY, MSTORE, MSTORE8, SSTORE: return true } if _, ok := opCodeToString[op]; !ok { From 27bf7b6c6573f5d54e468d9986e91553e626ffee Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Thu, 25 Jun 2015 11:01:22 +0200 Subject: [PATCH 3/5] core/vm: improved segments by adding native calls When segments are build native functions call will be used next time round instead of the large switch loop. --- cmd/evm/main.go | 18 ++++---- core/vm/vm.go | 106 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 94 insertions(+), 30 deletions(-) diff --git a/cmd/evm/main.go b/cmd/evm/main.go index f93645781a..1b54cb0a69 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -44,6 +44,10 @@ var ( Name: "debug", Usage: "output full trace logs", } + SegmentationFlag = cli.BoolFlag{ + Name: "nosegmentation", + Usage: "Disabled VM segmentation", + } CodeFlag = cli.StringFlag{ Name: "code", Usage: "EVM code", @@ -81,6 +85,7 @@ func init() { app = utils.NewApp("0.2", "the evm command line interface") app.Flags = []cli.Flag{ DebugFlag, + SegmentationFlag, SysStatFlag, CodeFlag, GasFlag, @@ -94,6 +99,7 @@ func init() { func run(ctx *cli.Context) { vm.Debug = ctx.GlobalBool(DebugFlag.Name) + vm.DisableSegmentation = ctx.GlobalBool(SegmentationFlag.Name) db, _ := ethdb.NewMemDatabase() statedb := state.New(common.Hash{}, db) @@ -119,7 +125,9 @@ func run(ctx *cli.Context) { fmt.Println(e) os.Exit(1) } - fmt.Println("no analysis", time.Since(vmstart)) + fmt.Println("no segmentation", time.Since(vmstart)) + + fmt.Println() vmstart = time.Now() ret, e = vmenv.Call( @@ -135,7 +143,7 @@ func run(ctx *cli.Context) { fmt.Println(e) os.Exit(1) } - fmt.Println("with analysis", time.Since(vmstart)) + fmt.Println("with segmentation", time.Since(vmstart)) if ctx.GlobalBool(DumpFlag.Name) { fmt.Println(string(statedb.Dump())) @@ -156,12 +164,6 @@ num gc: %d } fmt.Printf("OUT: 0x%x\n", ret) - - for codehash, chunks := range vm.Chunks { - for _, chunk := range chunks { - fmt.Printf("%x => %v\n", codehash, chunk) - } - } } func main() { diff --git a/core/vm/vm.go b/core/vm/vm.go index 5c23105349..8ad3cd6569 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -2,6 +2,7 @@ package vm import ( "fmt" + "math" "math/big" "github.com/ethereum/go-ethereum/common" @@ -11,14 +12,17 @@ import ( "github.com/hashicorp/golang-lru" ) -type segment struct { - cstart, cend uint64 - msize uint64 - gas *big.Int +type operation struct { + fn func(*stack, []byte) + data []byte } -func (c *segment) String() string { - return fmt.Sprintf("{%d %d %d %v}", c.cstart, c.cend, c.msize, c.gas) +func Add(s *stack, data []byte) { + s.push(new(big.Int).Add(s.pop(), s.pop())) +} + +func Push(s *stack, data []byte) { + s.push(common.BytesToBig(data)) } type codeSegments map[uint64]*segment @@ -29,6 +33,22 @@ func init() { segments, _ = lru.New(256) } +var DisableSegmentation bool + +type segment struct { + cstart, cend, next uint64 + msize uint64 + gas *big.Int + + ssize int + + ops []operation +} + +func (c *segment) String() string { + return fmt.Sprintf("{%d %d %d %v}", c.cstart, c.cend, c.msize, c.gas) +} + // Vm implements VirtualMachine type Vm struct { env Environment @@ -70,12 +90,17 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { statedb = self.env.State() // 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. Pratically much less so feasible. - pc = uint64(0) // program counter - ppc = uint64(0) // previous program counter - csegment *segment // current code segment - csegments codeSegments // program segments - nopay uint64 = 0 - disableOptim bool + pc = uint64(0) // program counter + ppc = uint64(0) // previous program counter + csegment *segment // current code segment + csegments codeSegments // program segments + nopay uint64 = 0 + + addop = func(op operation) { + if csegment != nil && !DisableSegmentation { + csegment.ops = append(csegment.ops, op) + } + } // jump evaluates and checks whether the given jump destination is a valid one // if valid move the `pc` otherwise return an error. @@ -116,6 +141,12 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { delete(csegments, csegment.cstart) } else if csegment != nil && err == nil && csegment.cend == 0 { csegment.cend = pc + csegment.ssize = int(math.Abs(math.Min(float64(stack.len()-csegment.ssize), 0))) + } + + t, _ := segments.Get(codehash) + for _, segment := range t.(codeSegments) { + fmt.Println(segment) } }() @@ -133,26 +164,52 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { for { // Get the current operation location of pc op = context.GetOp(pc) - if isDynamic(op) && csegment != nil { + if isDynamic(op) && csegment != nil && !DisableSegmentation { + // end of segment bound csegment.cend = ppc + csegment.next = pc + // write out the required required elements on the stack. i.e. the amount of items + // required outside of the segment itself. + // 0: P, 1 + // 1: J, 2 + // 2: D -+ + // 3: P, 2 |- M,0 = M,0 pushes 1. Requires 2. 1 outside bounds of M,0 + // 4: ADD -+ + // 5: J, 2 + csegment.ssize = int(math.Abs(math.Min(float64(stack.len()-csegment.ssize), 0))) csegment = nil nopay = 0 - } else if csegment == nil && pc != 0 { - // allocate a new segment for tracking information - csegment = &segment{pc, 0, 0, new(big.Int)} - csegments[pc] = csegment - } else { - if segment, ok := csegments[pc]; ok && !disableOptim { - nopay = segment.cend - if !context.UseGas(segment.gas) { + } else if csegment == nil && pc != 0 && !DisableSegmentation { + // check if a segment is available to us and set it. + if seg, ok := csegments[pc]; ok { + nopay = seg.cend + + // make sure there's enough stack items to complete the segment + if err := stack.require(seg.ssize); err != nil { + return context.Return(nil), err + } + + // use required gas for this segment + if !context.UseGas(seg.gas) { context.UseGas(context.Gas) return context.Return(nil), OutOfGasError{} } + + for _, operation := range seg.ops { + operation.fn(stack, operation.data) + } + + pc = seg.next + continue + } else { + // allocate a new segment for tracking information + csegment = &segment{pc, 0, 0, 0, new(big.Int), stack.len(), nil} + csegments[pc] = csegment } } - if pc > nopay || pc == 0 || disableOptim { + if pc > nopay || pc == 0 || DisableSegmentation { // calculate the new memory size and gas price for the current executing opcode newMemSize, cost, err = self.calculateGasAndSize(context, caller, op, statedb, mem, stack) if err != nil { @@ -190,6 +247,8 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { U256(base) + addop(operation{Add, nil}) + // pop result back on the stack stack.push(base) case SUB: @@ -522,6 +581,9 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { 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: size := uint64(op - PUSH1 + 1) byts := getData(code, new(big.Int).SetUint64(pc+1), new(big.Int).SetUint64(size)) + + addop(operation{Push, byts}) + // push value to stack stack.push(common.Bytes2Big(byts)) pc += size From f17ae6b25644f0184c225622ab238087d1085ecc Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Thu, 25 Jun 2015 12:47:37 +0200 Subject: [PATCH 4/5] core/vm: native functions --- cmd/evm/main.go | 28 ++--- core/vm/native.go | 283 ++++++++++++++++++++++++++++++++++++++++++++++ core/vm/vm.go | 126 ++++++++------------- 3 files changed, 344 insertions(+), 93 deletions(-) create mode 100644 core/vm/native.go diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 1b54cb0a69..ef7caf83c6 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -129,21 +129,23 @@ func run(ctx *cli.Context) { fmt.Println() - vmstart = time.Now() - ret, e = vmenv.Call( - sender, - 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)), - ) + if !vm.DisableSegmentation { + vmstart = time.Now() + ret, e = vmenv.Call( + sender, + 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)), + ) - if e != nil { - fmt.Println(e) - os.Exit(1) + if e != nil { + fmt.Println(e) + os.Exit(1) + } + fmt.Println("with segmentation", time.Since(vmstart)) } - fmt.Println("with segmentation", time.Since(vmstart)) if ctx.GlobalBool(DumpFlag.Name) { fmt.Println(string(statedb.Dump())) diff --git a/core/vm/native.go b/core/vm/native.go new file mode 100644 index 0000000000..96f149af78 --- /dev/null +++ b/core/vm/native.go @@ -0,0 +1,283 @@ +package vm + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/hashicorp/golang-lru" +) + +var ( + segments *lru.Cache + DisableSegmentation bool +) + +func init() { + segments, _ = lru.New(256) +} + +type codeSegments map[uint64]*segment + +// operation is a "native" implementation of an OpCode +type operation struct { + fn func(*stack, []byte) + data []byte +} + +type segment struct { + cstart, cend, next uint64 + msize uint64 + gas *big.Int + + ssize int + + ops []operation +} + +func (c *segment) String() string { + return fmt.Sprintf("{%d %d %d %v}", c.cstart, c.cend, c.msize, c.gas) +} + +// native operations + +func opPush(s *stack, data []byte) { + s.push(common.BytesToBig(data)) +} + +func opAdd(s *stack, data []byte) { + s.push(U256(new(big.Int).Add(s.pop(), s.pop()))) +} + +func opSub(s *stack, data []byte) { + s.push(U256(new(big.Int).Sub(s.pop(), s.pop()))) +} + +func opMul(s *stack, data []byte) { + s.push(U256(new(big.Int).Mul(s.pop(), s.pop()))) +} + +func opDiv(s *stack, data []byte) { + x, y := s.pop(), s.pop() + + if y.Cmp(common.Big0) != 0 { + new(big.Int).Div(x, y) + } + + U256(new(big.Int)) + + // pop result back on the s + s.push(new(big.Int)) +} + +func opSdiv(s *stack, data []byte) { + x, y := S256(s.pop()), S256(s.pop()) + + if y.Cmp(common.Big0) == 0 { + new(big.Int).Set(common.Big0) + } else { + n := new(big.Int) + if new(big.Int).Mul(x, y).Cmp(common.Big0) < 0 { + n.SetInt64(-1) + } else { + n.SetInt64(1) + } + + new(big.Int).Div(x.Abs(x), y.Abs(y)).Mul(new(big.Int), n) + + U256(new(big.Int)) + } + + s.push(new(big.Int)) +} + +func opMod(s *stack, data []byte) { + x, y := s.pop(), s.pop() + + if y.Cmp(common.Big0) == 0 { + new(big.Int).Set(common.Big0) + } else { + new(big.Int).Mod(x, y) + } + + U256(new(big.Int)) + + s.push(new(big.Int)) +} + +func opSmod(s *stack, data []byte) { + x, y := S256(s.pop()), S256(s.pop()) + + if y.Cmp(common.Big0) == 0 { + new(big.Int).Set(common.Big0) + } else { + n := new(big.Int) + if x.Cmp(common.Big0) < 0 { + n.SetInt64(-1) + } else { + n.SetInt64(1) + } + + new(big.Int).Mod(x.Abs(x), y.Abs(y)).Mul(new(big.Int), n) + + U256(new(big.Int)) + } + + s.push(new(big.Int)) + +} + +func opExp(s *stack, data []byte) { + x, y := s.pop(), s.pop() + + new(big.Int).Exp(x, y, Pow256) + + U256(new(big.Int)) + + s.push(new(big.Int)) +} + +func opSignextend(s *stack, data []byte) { + back := s.pop() + if back.Cmp(big.NewInt(31)) < 0 { + bit := uint(back.Uint64()*8 + 7) + num := s.pop() + mask := new(big.Int).Lsh(common.Big1, bit) + mask.Sub(mask, common.Big1) + if common.BitTest(num, int(bit)) { + num.Or(num, mask.Not(mask)) + } else { + num.And(num, mask) + } + + num = U256(num) + + s.push(num) + } +} + +func opNot(s *stack, data []byte) { + s.push(U256(new(big.Int).Not(s.pop()))) +} + +func opLt(s *stack, data []byte) { + x, y := s.pop(), s.pop() + + // x < y + if x.Cmp(y) < 0 { + s.push(common.BigTrue) + } else { + s.push(common.BigFalse) + } +} + +func opGt(s *stack, data []byte) { + x, y := s.pop(), s.pop() + + // x > y + if x.Cmp(y) > 0 { + s.push(common.BigTrue) + } else { + s.push(common.BigFalse) + } +} + +func opSlt(s *stack, data []byte) { + x, y := S256(s.pop()), S256(s.pop()) + + // x < y + if x.Cmp(S256(y)) < 0 { + s.push(common.BigTrue) + } else { + s.push(common.BigFalse) + } +} + +func opSgt(s *stack, data []byte) { + x, y := S256(s.pop()), S256(s.pop()) + + // x > y + if x.Cmp(y) > 0 { + s.push(common.BigTrue) + } else { + s.push(common.BigFalse) + } +} + +func opEq(s *stack, data []byte) { + x, y := s.pop(), s.pop() + + // x == y + if x.Cmp(y) == 0 { + s.push(common.BigTrue) + } else { + s.push(common.BigFalse) + } +} + +func opIszero(s *stack, data []byte) { + x := s.pop() + if x.Cmp(common.BigFalse) > 0 { + s.push(common.BigFalse) + } else { + s.push(common.BigTrue) + } +} + +func opAnd(s *stack, data []byte) { + s.push(new(big.Int).And(s.pop(), s.pop())) +} + +func opOr(s *stack, data []byte) { + s.push(new(big.Int).Or(s.pop(), s.pop())) +} + +func opXor(s *stack, data []byte) { + s.push(new(big.Int).Xor(s.pop(), s.pop())) +} + +func opByte(s *stack, data []byte) { + th, val := s.pop(), s.pop() + + res := new(big.Int) + if th.Cmp(big.NewInt(32)) < 0 { + byt := big.NewInt(int64(common.LeftPadBytes(val.Bytes(), 32)[th.Int64()])) + + res.Set(byt) + } else { + res.Set(common.BigFalse) + } + + s.push(res) +} + +func opAddmod(s *stack, data []byte) { + x := s.pop() + y := s.pop() + z := s.pop() + + res := new(big.Int) + if z.Cmp(Zero) > 0 { + add := new(big.Int).Add(x, y) + res.Mod(add, z) + res = U256(res) + } + + s.push(res) +} + +func opMulmod(s *stack, data []byte) { + x := s.pop() + y := s.pop() + z := s.pop() + + res := new(big.Int) + if z.Cmp(Zero) > 0 { + res.Mul(x, y) + res.Mod(res, z) + + res = U256(res) + } + + s.push(res) +} diff --git a/core/vm/vm.go b/core/vm/vm.go index 8ad3cd6569..9872f75437 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -9,46 +9,8 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" - "github.com/hashicorp/golang-lru" ) -type operation struct { - fn func(*stack, []byte) - data []byte -} - -func Add(s *stack, data []byte) { - s.push(new(big.Int).Add(s.pop(), s.pop())) -} - -func Push(s *stack, data []byte) { - s.push(common.BytesToBig(data)) -} - -type codeSegments map[uint64]*segment - -var segments *lru.Cache - -func init() { - segments, _ = lru.New(256) -} - -var DisableSegmentation bool - -type segment struct { - cstart, cend, next uint64 - msize uint64 - gas *big.Int - - ssize int - - ops []operation -} - -func (c *segment) String() string { - return fmt.Sprintf("{%d %d %d %v}", c.cstart, c.cend, c.msize, c.gas) -} - // Vm implements VirtualMachine type Vm struct { env Environment @@ -164,48 +126,50 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { for { // Get the current operation location of pc op = context.GetOp(pc) - if isDynamic(op) && csegment != nil && !DisableSegmentation { - // end of segment bound - csegment.cend = ppc - csegment.next = pc - // write out the required required elements on the stack. i.e. the amount of items - // required outside of the segment itself. - // 0: P, 1 - // 1: J, 2 - // 2: D -+ - // 3: P, 2 |- M,0 = M,0 pushes 1. Requires 2. 1 outside bounds of M,0 - // 4: ADD -+ - // 5: J, 2 - csegment.ssize = int(math.Abs(math.Min(float64(stack.len()-csegment.ssize), 0))) - csegment = nil - nopay = 0 - } else if csegment == nil && pc != 0 && !DisableSegmentation { - // check if a segment is available to us and set it. - if seg, ok := csegments[pc]; ok { - nopay = seg.cend + if !DisableSegmentation { + if isDynamic(op) && csegment != nil { + // end of segment bound + csegment.cend = ppc + csegment.next = pc + // write out the required required elements on the stack. i.e. the amount of items + // required outside of the segment itself. + // 0: P, 1 + // 1: J, 2 + // 2: D -+ + // 3: P, 2 |- M,0 = M,0 pushes 1. Requires 2. 1 outside bounds of M,0 + // 4: ADD -+ + // 5: J, 2 + csegment.ssize = int(math.Abs(math.Min(float64(stack.len()-csegment.ssize), 0))) + csegment = nil + nopay = 0 + } else if csegment == nil && pc != 0 { + // check if a segment is available to us and set it. + if seg, ok := csegments[pc]; ok { + nopay = seg.cend - // make sure there's enough stack items to complete the segment - if err := stack.require(seg.ssize); err != nil { - return context.Return(nil), err + // make sure there's enough stack items to complete the segment + if err := stack.require(seg.ssize); err != nil { + return context.Return(nil), err + } + + // use required gas for this segment + if !context.UseGas(seg.gas) { + context.UseGas(context.Gas) + + return context.Return(nil), OutOfGasError{} + } + + for _, operation := range seg.ops { + operation.fn(stack, operation.data) + } + + pc = seg.next + continue + } else { + // allocate a new segment for tracking information + csegment = &segment{pc, 0, 0, 0, new(big.Int), stack.len(), nil} + csegments[pc] = csegment } - - // use required gas for this segment - if !context.UseGas(seg.gas) { - context.UseGas(context.Gas) - - return context.Return(nil), OutOfGasError{} - } - - for _, operation := range seg.ops { - operation.fn(stack, operation.data) - } - - pc = seg.next - continue - } else { - // allocate a new segment for tracking information - csegment = &segment{pc, 0, 0, 0, new(big.Int), stack.len(), nil} - csegments[pc] = csegment } } @@ -247,7 +211,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { U256(base) - addop(operation{Add, nil}) + addop(operation{opAdd, nil}) // pop result back on the stack stack.push(base) @@ -339,6 +303,8 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { U256(base) stack.push(base) + + addop(operation{opExp, nil}) case SIGNEXTEND: back := stack.pop() if back.Cmp(big.NewInt(31)) < 0 { @@ -582,7 +548,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { size := uint64(op - PUSH1 + 1) byts := getData(code, new(big.Int).SetUint64(pc+1), new(big.Int).SetUint64(size)) - addop(operation{Push, byts}) + addop(operation{opPush, byts}) // push value to stack stack.push(common.Bytes2Big(byts)) From 5027822bdebd9e0fd23d231109596eebbe04c759 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Thu, 25 Jun 2015 13:27:24 +0200 Subject: [PATCH 5/5] wip --- cmd/evm/main.go | 26 +--------- core/vm/logger.go | 1 - core/vm/native.go | 2 +- core/vm/opcodes.go | 2 +- core/vm/vm.go | 105 ++++++++++++++++++++++++--------------- tests/state_test_util.go | 7 +++ 6 files changed, 77 insertions(+), 66 deletions(-) diff --git a/cmd/evm/main.go b/cmd/evm/main.go index ef7caf83c6..232978d4b7 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -110,8 +110,6 @@ func run(ctx *cli.Context) { vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name))) tstart := time.Now() - - vmstart := time.Now() ret, e := vmenv.Call( sender, receiver.Address(), @@ -120,32 +118,12 @@ func run(ctx *cli.Context) { common.Big(ctx.GlobalString(PriceFlag.Name)), common.Big(ctx.GlobalString(ValueFlag.Name)), ) + vmdone := time.Since(tstart) if e != nil { fmt.Println(e) os.Exit(1) } - fmt.Println("no segmentation", time.Since(vmstart)) - - fmt.Println() - - if !vm.DisableSegmentation { - vmstart = time.Now() - ret, e = vmenv.Call( - sender, - 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)), - ) - - if e != nil { - fmt.Println(e) - os.Exit(1) - } - fmt.Println("with segmentation", time.Since(vmstart)) - } if ctx.GlobalBool(DumpFlag.Name) { fmt.Println(string(statedb.Dump())) @@ -155,7 +133,7 @@ func run(ctx *cli.Context) { if ctx.GlobalBool(SysStatFlag.Name) { var mem runtime.MemStats runtime.ReadMemStats(&mem) - fmt.Printf("vm took %v\n", time.Since(tstart)) + fmt.Printf("vm took %v\n", vmdone) fmt.Printf(`alloc: %d tot alloc: %d no. malloc: %d diff --git a/core/vm/logger.go b/core/vm/logger.go index 0e2a417ae4..5a3900280c 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -9,7 +9,6 @@ import ( ) func StdErrFormat(logs []StructLog) { - fmt.Fprintf(os.Stderr, "VM STAT %d OPs\n", len(logs)) for _, log := range logs { fmt.Fprintf(os.Stderr, "PC %08d: %s GAS: %v COST: %v", log.Pc, log.Op, log.Gas, log.GasCost) if log.Err != nil { diff --git a/core/vm/native.go b/core/vm/native.go index 96f149af78..adb19dc527 100644 --- a/core/vm/native.go +++ b/core/vm/native.go @@ -36,7 +36,7 @@ type segment struct { } func (c *segment) String() string { - return fmt.Sprintf("{%d %d %d %v}", c.cstart, c.cend, c.msize, c.gas) + return fmt.Sprintf("{start: %d end: %d ssize: %d gas: %v}", c.cstart, c.cend, c.ssize, c.gas) } // native operations diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 4535ab6b02..a3c7a92639 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -335,7 +335,7 @@ func (o OpCode) String() string { func isDynamic(op OpCode) bool { switch op { - case CREATE, CALL, CALLCODE, JUMP, JUMPI, SUICIDE, STOP, RETURN, EXTCODECOPY, CODECOPY, MSTORE, MSTORE8, SSTORE: + case CREATE, CALL, CALLCODE, CALLDATACOPY, GAS, MLOAD, JUMP, JUMPI, SUICIDE, STOP, RETURN, EXTCODECOPY, CODECOPY, MSTORE, MSTORE8, SSTORE: return true } if _, ok := opCodeToString[op]; !ok { diff --git a/core/vm/vm.go b/core/vm/vm.go index 9872f75437..1e92554807 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -11,6 +11,8 @@ import ( "github.com/ethereum/go-ethereum/params" ) +var t int + // Vm implements VirtualMachine type Vm struct { env Environment @@ -38,6 +40,13 @@ func New(env Environment) *Vm { func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { self.env.SetDepth(self.env.Depth() + 1) defer self.env.SetDepth(self.env.Depth() - 1) + defer func() { + if r := recover(); r != nil { + StdErrFormat(self.env.StructLogs()) + ret = nil + err = fmt.Errorf("%v", r) + } + }() var ( caller = context.caller @@ -98,17 +107,14 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { ret = context.Return(nil) } - if csegment != nil && err != nil && csegment.cend == 0 { - // delete this tracking segment. We couldn't fully determine the stats - delete(csegments, csegment.cstart) - } else if csegment != nil && err == nil && csegment.cend == 0 { - csegment.cend = pc - csegment.ssize = int(math.Abs(math.Min(float64(stack.len()-csegment.ssize), 0))) - } - - t, _ := segments.Get(codehash) - for _, segment := range t.(codeSegments) { - fmt.Println(segment) + if !DisableSegmentation && csegment != nil && csegment.cend == 0 { + if err != nil { + // delete this tracking segment. We couldn't fully determine the stats + delete(csegments, csegment.cstart) + } else { + csegment.cend = pc + csegment.ssize = int(math.Abs(math.Min(float64(stack.len()-csegment.ssize), 0))) + } } }() @@ -124,27 +130,34 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { } for { + cost = new(big.Int) // Get the current operation location of pc op = context.GetOp(pc) if !DisableSegmentation { - if isDynamic(op) && csegment != nil { - // end of segment bound - csegment.cend = ppc - csegment.next = pc - // write out the required required elements on the stack. i.e. the amount of items - // required outside of the segment itself. - // 0: P, 1 - // 1: J, 2 - // 2: D -+ - // 3: P, 2 |- M,0 = M,0 pushes 1. Requires 2. 1 outside bounds of M,0 - // 4: ADD -+ - // 5: J, 2 - csegment.ssize = int(math.Abs(math.Min(float64(stack.len()-csegment.ssize), 0))) + if isDynamic(op) { + //fmt.Println("encountered dyn") + if csegment != nil && csegment.cend == 0 { + //fmt.Println("closing segment with", op, "@", ppc, csegment.gas) + // mark end of segment bound + csegment.cend = ppc + // set the next op for continuation + csegment.next = pc + // write out the required required elements on the stack. i.e. the amount of items + // required outside of the segment itself. + // 0: P, 1 + // 1: J, 2 + // 2: D -+ + // 3: P, 2 |- M,0 = M,0 pushes 1. Requires 2. 1 outside bounds of M,0 + // 4: ADD -+ + // 5: J, 2 + csegment.ssize = int(math.Abs(math.Min(float64(stack.len()-csegment.ssize), 0))) + } csegment = nil nopay = 0 } else if csegment == nil && pc != 0 { // check if a segment is available to us and set it. if seg, ok := csegments[pc]; ok { + //fmt.Println(pc, "using seg", seg) nopay = seg.cend // make sure there's enough stack items to complete the segment @@ -152,27 +165,36 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { return context.Return(nil), err } + cost.Set(seg.gas) // use required gas for this segment if !context.UseGas(seg.gas) { context.UseGas(context.Gas) return context.Return(nil), OutOfGasError{} } + csegment = seg - for _, operation := range seg.ops { - operation.fn(stack, operation.data) - } + /* + for _, operation := range seg.ops { + operation.fn(stack, operation.data) + } - pc = seg.next - continue + pc = seg.next + continue + */ } else { + //fmt.Println("creating", pc) // allocate a new segment for tracking information csegment = &segment{pc, 0, 0, 0, new(big.Int), stack.len(), nil} csegments[pc] = csegment + nopay = 0 } } } + // set previous program counter + ppc = pc + //fmt.Println(pc, nopay, pc > nopay, pc == 0) if pc > nopay || pc == 0 || DisableSegmentation { // calculate the new memory size and gas price for the current executing opcode newMemSize, cost, err = self.calculateGasAndSize(context, caller, op, statedb, mem, stack) @@ -180,6 +202,10 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { return nil, err } + if csegment != nil { + csegment.gas.Add(csegment.gas, cost) + } + // Use the calculated gas. When insufficient gas is present, use all gas and return an // Out Of Gas error if !context.UseGas(cost) { @@ -191,15 +217,10 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { // Resize the memory calculated previously mem.Resize(newMemSize.Uint64()) - if csegment != nil { - csegment.msize = uint64(len(mem.store)) - csegment.gas.Add(csegment.gas, cost) - } } // Add a log message self.log(pc, op, context.Gas, cost, mem, stack, context, nil) - ppc = pc // The base for all big integer arithmetic base := new(big.Int) @@ -227,12 +248,12 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { case MUL: x, y := stack.pop(), stack.pop() - base.Mul(x, y) + res := new(big.Int).Mul(x, y) - U256(base) + U256(res) // pop result back on the stack - stack.push(base) + stack.push(res) case DIV: x, y := stack.pop(), stack.pop() @@ -625,6 +646,10 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) { case MSIZE: stack.push(big.NewInt(int64(mem.Len()))) case GAS: + if t > 1 { + //return context.Return(nil), nil + } + t++ stack.push(context.Gas) case CREATE: @@ -887,7 +912,9 @@ func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, st mem := make([]byte, len(memory.Data())) copy(mem, memory.Data()) stck := make([]*big.Int, len(stack.Data())) - copy(stck, stack.Data()) + for i, item := range stack.Data() { + stck[i] = new(big.Int).Set(item) + } object := context.self.(*state.StateObject) storage := make(map[common.Hash][]byte) @@ -895,7 +922,7 @@ func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, st storage[common.BytesToHash(k)] = v }) - self.env.AddStructLog(StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, err}) + self.env.AddStructLog(StructLog{pc, op, new(big.Int).Set(gas), new(big.Int).Set(cost), mem, stck, storage, err}) } } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 2003fda5af..ccd8c008af 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -50,6 +50,13 @@ func runStateTests(tests map[string]VmTest, skipTests []string) error { } for name, test := range tests { + //vm.Debug = true + //vm.DisableSegmentation = true + /* + if name != "Call10" { + continue + } + */ if skipTest[name] { glog.Infoln("Skipping state test", name) return nil