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() {