From c437b70c97579387092c42afd33f1125bb5f5337 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 15 May 2017 22:40:18 +0200 Subject: [PATCH 1/7] cmd/evm,logger: added jsonlogger which does not accumulate logs, added prestate --- cmd/evm/main.go | 10 ++++++++ cmd/evm/runner.go | 62 +++++++++++++++++++++++++++++++++++++++++------ core/vm/logger.go | 27 ++++++++++++++++++--- 3 files changed, 88 insertions(+), 11 deletions(-) diff --git a/cmd/evm/main.go b/cmd/evm/main.go index e85d31d03d18..db6b671557fb 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -90,6 +90,14 @@ var ( Name: "nogasmetering", Usage: "disable gas metering", } + GenesisFlag = cli.StringFlag{ + Name: "prestate", + Usage: "JSON file with prestate (genesis) config", + } + MachineFlag = cli.BoolFlag{ + Name: "json", + Usage: "output trace logs in machine readable format (json)", + } ) func init() { @@ -108,6 +116,8 @@ func init() { MemProfileFlag, CPUProfileFlag, StatDumpFlag, + GenesisFlag, + MachineFlag, } app.Commands = []cli.Command{ compileCommand, diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 22538d7b13af..33dd58b5d347 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -18,6 +18,7 @@ package main import ( "bytes" + "encoding/json" "fmt" "io/ioutil" "os" @@ -29,11 +30,13 @@ import ( "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler" "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/vm" "github.com/ethereum/go-ethereum/core/vm/runtime" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" cli "gopkg.in/urfave/cli.v1" ) @@ -45,17 +48,55 @@ var runCommand = cli.Command{ Description: `The run command runs arbitrary EVM code.`, } +// readGenesis will read the given JSON format genesis file and return +// the initialized Genesis structure +func readGenesis(genesisPath string) *core.Genesis { + // Make sure we have a valid genesis JSON + //genesisPath := ctx.Args().First() + if len(genesisPath) == 0 { + utils.Fatalf("Must supply path to genesis JSON file") + } + file, err := os.Open(genesisPath) + if err != nil { + utils.Fatalf("Failed to read genesis file: %v", err) + } + defer file.Close() + + genesis := new(core.Genesis) + if err := json.NewDecoder(file).Decode(genesis); err != nil { + utils.Fatalf("invalid genesis file: %v", err) + } + return genesis +} + func runCmd(ctx *cli.Context) error { glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name))) log.Root().SetHandler(glogger) var ( - db, _ = ethdb.NewMemDatabase() - statedb, _ = state.New(common.Hash{}, db) - sender = common.StringToAddress("sender") - logger = vm.NewStructLogger(nil) + tracer vm.Tracer + debugLogger *vm.StructLogger + statedb *state.StateDB + chainConfig *params.ChainConfig + sender = common.StringToAddress("sender") ) + if ctx.GlobalBool(MachineFlag.Name) { + tracer = vm.NewJSONLogger(os.Stdout) + } else if ctx.GlobalBool(DebugFlag.Name) { + debugLogger = vm.NewStructLogger(nil) + tracer = debugLogger + } + + if ctx.GlobalString(GenesisFlag.Name) != "" { + gen := readGenesis(ctx.GlobalString(GenesisFlag.Name)) + _, statedb = gen.ToBlock() + chainConfig = gen.Config + } else { + var db, _ = ethdb.NewMemDatabase() + statedb, _ = state.New(common.Hash{}, db) + } + statedb.CreateAccount(sender) var ( @@ -103,8 +144,8 @@ func runCmd(ctx *cli.Context) error { GasPrice: utils.GlobalBig(ctx, PriceFlag.Name), Value: utils.GlobalBig(ctx, ValueFlag.Name), EVMConfig: vm.Config{ - Tracer: logger, - Debug: ctx.GlobalBool(DebugFlag.Name), + Tracer: tracer, + Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name), DisableGasMetering: ctx.GlobalBool(DisableGasMeteringFlag.Name), }, } @@ -122,6 +163,9 @@ func runCmd(ctx *cli.Context) error { defer pprof.StopCPUProfile() } + if chainConfig != nil { + runtimeConfig.ChainConfig = chainConfig + } tstart := time.Now() if ctx.GlobalBool(CreateFlag.Name) { input := append(code, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name))...) @@ -153,8 +197,10 @@ func runCmd(ctx *cli.Context) error { } if ctx.GlobalBool(DebugFlag.Name) { - fmt.Fprintln(os.Stderr, "#### TRACE ####") - vm.WriteTrace(os.Stderr, logger.StructLogs()) + if debugLogger != nil { + fmt.Fprintln(os.Stderr, "#### TRACE ####") + vm.WriteTrace(os.Stderr, debugLogger.StructLogs()) + } fmt.Fprintln(os.Stderr, "#### LOGS ####") vm.WriteLogs(os.Stderr, statedb.Logs()) } diff --git a/core/vm/logger.go b/core/vm/logger.go index 825025b05f5f..0f17840bf5e8 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -18,6 +18,7 @@ package vm import ( "encoding/hex" + "encoding/json" "fmt" "io" "math/big" @@ -82,7 +83,13 @@ type StructLogger struct { changedValues map[common.Address]Storage } -// NewLogger returns a new logger +// JSONLogger merely contains a writer, and immediately outputs to that channel, +// instead of collecting logs +type JSONLogger struct { + encoder *json.Encoder +} + +// NewStructLogger returns a new logger func NewStructLogger(cfg *LogConfig) *StructLogger { logger := &StructLogger{ changedValues: make(map[common.Address]Storage), @@ -93,9 +100,23 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { return logger } -// captureState logs a new structured log message and pushes it out to the environment +// NewJSONLogger returns a new JSON logger +func NewJSONLogger(writer io.Writer) *JSONLogger { + logger := &JSONLogger{ + encoder: json.NewEncoder(writer), + } + return logger +} + +// CaptureState outputs state information on the logger +func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { + log := StructLog{pc, op, gas, cost, memory.Data(), stack.Data(), nil, env.depth, err} + return l.encoder.Encode(log) +} + +// CaptureState logs a new structured log message and pushes it out to the environment // -// captureState also tracks SSTORE ops to track dirty values. +// CaptureState also tracks SSTORE ops to track dirty values. func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { // check if already accumulated the specified number of logs if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { From 567f69539e489f072f6814e9469a920f7fc5c3d4 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 2 Jun 2017 10:53:55 +0200 Subject: [PATCH 2/7] cmd/evm: better json output, add result to json --- cmd/evm/runner.go | 12 +++++--- core/vm/logger.go | 63 ++++++++++++++++++++++++++++++++++++++- internal/ethapi/tracer.go | 7 +++++ 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 33dd58b5d347..a9e032b1a9c5 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -86,8 +86,9 @@ func runCmd(ctx *cli.Context) error { } else if ctx.GlobalBool(DebugFlag.Name) { debugLogger = vm.NewStructLogger(nil) tracer = debugLogger + } else { + debugLogger = vm.NewStructLogger(nil) } - if ctx.GlobalString(GenesisFlag.Name) != "" { gen := readGenesis(ctx.GlobalString(GenesisFlag.Name)) _, statedb = gen.ToBlock() @@ -216,11 +217,14 @@ GC calls: %d `, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC) } + if tracer != nil { + tracer.CaptureEnd(ret, 0, execTime) + } else { + fmt.Printf("0x%x\n", ret) + } - fmt.Printf("0x%x", ret) if err != nil { - fmt.Printf(" error: %v", err) + fmt.Printf(" error: %v\n", err) } - fmt.Println() return nil } diff --git a/core/vm/logger.go b/core/vm/logger.go index 0f17840bf5e8..d8f550b374fe 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "math/big" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -61,6 +62,50 @@ type StructLog struct { Depth int Err error } +type StructLogJsonOut struct { + Pc uint64 `json:"pc"` + Op OpCode `json:"op"` + OpName string `json:"opName"` + Gas math.HexOrDecimal64 `json:"gas"` + GasCost math.HexOrDecimal64 `json:"gasCost"` + Memory string `json:"memory"` + Stack hexArray `json:"stack"` + //Storage map[common.Hash]common.Hash `json:"storage"` + Depth int `json:"depth"` + //Err error +} +type hexArray []*big.Int + +// MarshalJSON encodes the memory as 32-byte hex strings instead of bigints +func (a hexArray) MarshalJSON() ([]byte, error) { + + if items := ([]*big.Int)(a); len(items) > 0 { + hexitems := make([]*math.HexOrDecimal256, len(items)) + for i, item := range items { + x := math.HexOrDecimal256(*item) + hexitems[i] = &x + } + return json.Marshal(hexitems) + } + return []byte("[]"), nil +} + +// MarshalJSON encodes StructLog for json output +func (s StructLog) MarshalJSON() ([]byte, error) { + var enc StructLogJsonOut + enc.Pc = s.Pc + enc.Op = s.Op + enc.OpName = s.Op.String() + enc.Gas = math.HexOrDecimal64(s.Gas) + enc.GasCost = math.HexOrDecimal64(s.GasCost) + //enc.Memory = fmt.Sprintf("0x%v", len(common.Bytes2Hex(s.Memory))/2) + enc.Memory = fmt.Sprintf("%v bytes", len(s.Memory)) + + enc.Stack = s.Stack + //enc.Storage = s.Storage + enc.Depth = s.Depth + return json.Marshal(&enc) +} // Tracer is used to collect execution traces from an EVM transaction // execution. CaptureState is called for each step of the VM with the @@ -69,6 +114,7 @@ type StructLog struct { // if you need to retain them beyond the current call. type Tracer interface { CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error + CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error } // StructLogger is an EVM state logger and implements Tracer. @@ -110,8 +156,19 @@ func NewJSONLogger(writer io.Writer) *JSONLogger { // CaptureState outputs state information on the logger func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { - log := StructLog{pc, op, gas, cost, memory.Data(), stack.Data(), nil, env.depth, err} + log := StructLog{pc, op, gas + cost, cost, memory.Data(), stack.Data(), nil, env.depth, err} + return l.encoder.Encode(log) +} +func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error { + type endLog struct { + Output string `json:"output"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + Time time.Duration `json:"time"` + } + + log := endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t} return l.encoder.Encode(log) + } // CaptureState logs a new structured log message and pushes it out to the environment @@ -184,6 +241,10 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui l.logs = append(l.logs, log) return nil } +func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error { + fmt.Printf("0x%x", output) + return nil +} // StructLogs returns a list of captured log entries func (l *StructLogger) StructLogs() []StructLog { diff --git a/internal/ethapi/tracer.go b/internal/ethapi/tracer.go index d343635641f7..fc66839ea56f 100644 --- a/internal/ethapi/tracer.go +++ b/internal/ethapi/tracer.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -344,6 +345,12 @@ func (jst *JavascriptTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, return nil } +// CaptureEnd is called after the call finishes +func (jst *JavascriptTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error { + //TODO! @Arachnid please figure out of there's anything we can use this method for + return nil +} + // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error func (jst *JavascriptTracer) GetResult() (result interface{}, err error) { if jst.err != nil { From 21ac4dd1091b3c11e9344ae8f607f19788667e92 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sun, 4 Jun 2017 20:46:29 +0200 Subject: [PATCH 3/7] core/vm, cmd/evm: Add ability for 'evm' binary to report gas used --- cmd/evm/runner.go | 14 ++++++++------ core/vm/runtime/runtime.go | 12 ++++++------ core/vm/runtime/runtime_test.go | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index a9e032b1a9c5..c1e731e23d76 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -137,11 +137,11 @@ func runCmd(ctx *cli.Context) error { } code = common.Hex2Bytes(string(bytes.TrimRight(hexcode, "\n"))) } - + initialGas := ctx.GlobalUint64(GasFlag.Name) runtimeConfig := runtime.Config{ Origin: sender, State: statedb, - GasLimit: ctx.GlobalUint64(GasFlag.Name), + GasLimit: initialGas, GasPrice: utils.GlobalBig(ctx, PriceFlag.Name), Value: utils.GlobalBig(ctx, ValueFlag.Name), EVMConfig: vm.Config{ @@ -168,14 +168,15 @@ func runCmd(ctx *cli.Context) error { runtimeConfig.ChainConfig = chainConfig } tstart := time.Now() + var leftOverGas uint64 if ctx.GlobalBool(CreateFlag.Name) { input := append(code, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name))...) - ret, _, err = runtime.Create(input, &runtimeConfig) + ret, _, leftOverGas, err = runtime.Create(input, &runtimeConfig) } else { receiver := common.StringToAddress("receiver") statedb.SetCode(receiver, code) - ret, err = runtime.Call(receiver, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), &runtimeConfig) + ret, leftOverGas, err = runtime.Call(receiver, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), &runtimeConfig) } execTime := time.Since(tstart) @@ -214,11 +215,12 @@ heap objects: %d allocations: %d total allocations: %d GC calls: %d +Gas used: %d -`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC) +`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas) } if tracer != nil { - tracer.CaptureEnd(ret, 0, execTime) + tracer.CaptureEnd(ret, initialGas-leftOverGas, execTime) } else { fmt.Printf("0x%x\n", ret) } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 94265626f200..aa386a995ee2 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -125,7 +125,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { } // Create executes the code using the EVM create method -func Create(input []byte, cfg *Config) ([]byte, common.Address, error) { +func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { if cfg == nil { cfg = new(Config) } @@ -141,13 +141,13 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, error) { ) // Call the code with the given configuration. - code, address, _, err := vmenv.Create( + code, address, leftOverGas, err := vmenv.Create( sender, input, cfg.GasLimit, cfg.Value, ) - return code, address, err + return code, address, leftOverGas, err } // Call executes the code given by the contract's address. It will return the @@ -155,14 +155,14 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, error) { // // Call, unlike Execute, requires a config and also requires the State field to // be set. -func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) { +func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, error) { setDefaults(cfg) vmenv := NewEnv(cfg, cfg.State) sender := cfg.State.GetOrNewStateObject(cfg.Origin) // Call the code with the given configuration. - ret, _, err := vmenv.Call( + ret, leftOverGas, err := vmenv.Call( sender, address, input, @@ -170,5 +170,5 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) { cfg.Value, ) - return ret, err + return ret, leftOverGas, err } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index fe39e97a017f..7f40770d21f9 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -106,7 +106,7 @@ func TestCall(t *testing.T) { byte(vm.RETURN), }) - ret, err := Call(address, nil, &Config{State: state}) + ret, _, err := Call(address, nil, &Config{State: state}) if err != nil { t.Fatal("didn't expect error", err) } From 581dd37fa90660baab01509243df5e965fedcf0c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 7 Jun 2017 10:48:00 +0200 Subject: [PATCH 4/7] cmd/evm: Added 'sender'-flag --- cmd/evm/main.go | 5 +++++ cmd/evm/runner.go | 3 +++ 2 files changed, 8 insertions(+) diff --git a/cmd/evm/main.go b/cmd/evm/main.go index db6b671557fb..48a1b92cbb73 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -98,6 +98,10 @@ var ( Name: "json", Usage: "output trace logs in machine readable format (json)", } + SenderFlag = cli.StringFlag{ + Name: "sender", + Usage: "The transaction origin", + } ) func init() { @@ -118,6 +122,7 @@ func init() { StatDumpFlag, GenesisFlag, MachineFlag, + SenderFlag, } app.Commands = []cli.Command{ compileCommand, diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index c1e731e23d76..90ed42897d21 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -97,6 +97,9 @@ func runCmd(ctx *cli.Context) error { var db, _ = ethdb.NewMemDatabase() statedb, _ = state.New(common.Hash{}, db) } + if ctx.GlobalString(SenderFlag.Name) != "" { + sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name)) + } statedb.CreateAccount(sender) From 373f587d8a5951f74e45570a833e2193d9944eb4 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 7 Jun 2017 11:14:20 +0200 Subject: [PATCH 5/7] core/vm: use json codec to generate structlog json --- cmd/evm/json_logger.go | 69 ++++++++++++++++++++++++++ cmd/evm/runner.go | 2 +- core/vm/gen_structlog.go | 94 ++++++++++++++++++++++++++++++++++++ core/vm/logger.go | 101 ++++++++------------------------------- 4 files changed, 185 insertions(+), 81 deletions(-) create mode 100644 cmd/evm/json_logger.go create mode 100644 core/vm/gen_structlog.go diff --git a/cmd/evm/json_logger.go b/cmd/evm/json_logger.go new file mode 100644 index 000000000000..420b51676362 --- /dev/null +++ b/cmd/evm/json_logger.go @@ -0,0 +1,69 @@ +// Copyright 2017 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 main + +import ( + "encoding/json" + "io" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/vm" +) + +// JSONLogger merely contains a writer, and immediately outputs to that channel, +// instead of collecting logs +type JSONLogger struct { + encoder *json.Encoder +} + +// NewJSONLogger returns a new JSON logger +func NewJSONLogger(writer io.Writer) *JSONLogger { + logger := &JSONLogger{ + encoder: json.NewEncoder(writer), + } + return logger +} + +// CaptureState outputs state information on the logger +func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { + log := vm.StructLog{ + Pc: pc, + Op: op, + Gas: gas + cost, + GasCost: cost, + Memory: memory.Data(), + Stack: stack.Data(), + Storage: nil, + Depth: depth, + Err: err} + return l.encoder.Encode(log) +} + +// CaptureEnd is triggered at end of execution +func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error { + type endLog struct { + Output string `json:"output"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + Time time.Duration `json:"time"` + } + + log := endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t} + return l.encoder.Encode(log) + +} diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 90ed42897d21..b1fb8998f11b 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -82,7 +82,7 @@ func runCmd(ctx *cli.Context) error { sender = common.StringToAddress("sender") ) if ctx.GlobalBool(MachineFlag.Name) { - tracer = vm.NewJSONLogger(os.Stdout) + tracer = NewJSONLogger(os.Stdout) } else if ctx.GlobalBool(DebugFlag.Name) { debugLogger = vm.NewStructLogger(nil) tracer = debugLogger diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go new file mode 100644 index 000000000000..fec0e3619690 --- /dev/null +++ b/core/vm/gen_structlog.go @@ -0,0 +1,94 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package vm + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +func (s StructLog) MarshalJSON() ([]byte, error) { + type StructLog struct { + Pc uint64 `json:"pc"` + Op OpCode `json:"op"` + Gas math.HexOrDecimal64 `json:"gas"` + GasCost math.HexOrDecimal64 `json:"gasCost"` + Memory []byte `json:"memory"` + Stack []*math.HexOrDecimal256 `json:"stack"` + Storage map[common.Hash]common.Hash `json:-` + Depth int `json:"depth"` + Err error `json:"error"` + OpName string `json:"opName"` + MemorySize string `json:"memSize"` + } + var enc StructLog + enc.Pc = s.Pc + enc.Op = s.Op + enc.Gas = math.HexOrDecimal64(s.Gas) + enc.GasCost = math.HexOrDecimal64(s.GasCost) + enc.Memory = s.Memory + if s.Stack != nil { + enc.Stack = make([]*math.HexOrDecimal256, len(s.Stack)) + for k, v := range s.Stack { + enc.Stack[k] = (*math.HexOrDecimal256)(v) + } + } + enc.Storage = s.Storage + enc.Depth = s.Depth + enc.Err = s.Err + enc.OpName = s.OpName() + enc.MemorySize = s.MemorySize() + return json.Marshal(&enc) +} + +func (s *StructLog) UnmarshalJSON(input []byte) error { + type StructLog struct { + Pc *uint64 `json:"pc"` + Op *OpCode `json:"op"` + Gas *math.HexOrDecimal64 `json:"gas"` + GasCost *math.HexOrDecimal64 `json:"gasCost"` + Memory []byte `json:"memory"` + Stack []*math.HexOrDecimal256 `json:"stack"` + Storage map[common.Hash]common.Hash `json:-` + Depth *int `json:"depth"` + Err *error `json:"error"` + } + var dec StructLog + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Pc != nil { + s.Pc = *dec.Pc + } + if dec.Op != nil { + s.Op = *dec.Op + } + if dec.Gas != nil { + s.Gas = uint64(*dec.Gas) + } + if dec.GasCost != nil { + s.GasCost = uint64(*dec.GasCost) + } + if dec.Memory != nil { + s.Memory = dec.Memory + } + if dec.Stack != nil { + s.Stack = make([]*big.Int, len(dec.Stack)) + for k, v := range dec.Stack { + s.Stack[k] = (*big.Int)(v) + } + } + if dec.Storage != nil { + s.Storage = dec.Storage + } + if dec.Depth != nil { + s.Depth = *dec.Depth + } + if dec.Err != nil { + s.Err = *dec.Err + } + return nil +} diff --git a/core/vm/logger.go b/core/vm/logger.go index d8f550b374fe..df9e3a514efe 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -18,7 +18,6 @@ package vm import ( "encoding/hex" - "encoding/json" "fmt" "io" "math/big" @@ -51,60 +50,33 @@ type LogConfig struct { // StructLog is emitted to the EVM each cycle and lists information about the current internal state // prior to the execution of the statement. +//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go type StructLog struct { - Pc uint64 - Op OpCode - Gas uint64 - GasCost uint64 - Memory []byte - Stack []*big.Int - Storage map[common.Hash]common.Hash - Depth int - Err error + Pc uint64 `json:"pc"` + Op OpCode `json:"op"` + Gas uint64 `json:"gas"` + GasCost uint64 `json:"gasCost"` + Memory []byte `json:"memory"` + Stack []*big.Int `json:"stack"` + Storage map[common.Hash]common.Hash `json:-` + Depth int `json:"depth"` + Err error `json:"error"` } -type StructLogJsonOut struct { - Pc uint64 `json:"pc"` - Op OpCode `json:"op"` - OpName string `json:"opName"` - Gas math.HexOrDecimal64 `json:"gas"` - GasCost math.HexOrDecimal64 `json:"gasCost"` - Memory string `json:"memory"` - Stack hexArray `json:"stack"` - //Storage map[common.Hash]common.Hash `json:"storage"` - Depth int `json:"depth"` - //Err error -} -type hexArray []*big.Int - -// MarshalJSON encodes the memory as 32-byte hex strings instead of bigints -func (a hexArray) MarshalJSON() ([]byte, error) { - if items := ([]*big.Int)(a); len(items) > 0 { - hexitems := make([]*math.HexOrDecimal256, len(items)) - for i, item := range items { - x := math.HexOrDecimal256(*item) - hexitems[i] = &x - } - return json.Marshal(hexitems) - } - return []byte("[]"), nil +func (s *StructLog) OpName() string { + return s.Op.String() } -// MarshalJSON encodes StructLog for json output -func (s StructLog) MarshalJSON() ([]byte, error) { - var enc StructLogJsonOut - enc.Pc = s.Pc - enc.Op = s.Op - enc.OpName = s.Op.String() - enc.Gas = math.HexOrDecimal64(s.Gas) - enc.GasCost = math.HexOrDecimal64(s.GasCost) - //enc.Memory = fmt.Sprintf("0x%v", len(common.Bytes2Hex(s.Memory))/2) - enc.Memory = fmt.Sprintf("%v bytes", len(s.Memory)) +func (s *StructLog) MemorySize() string { + return fmt.Sprintf("%v", len(s.Memory)) +} - enc.Stack = s.Stack - //enc.Storage = s.Storage - enc.Depth = s.Depth - return json.Marshal(&enc) +type structLogMarshaling struct { + Stack []*math.HexOrDecimal256 + Gas math.HexOrDecimal64 + GasCost math.HexOrDecimal64 + OpName string `json:"opName"` + MemorySize string `json:"memSize"` } // Tracer is used to collect execution traces from an EVM transaction @@ -129,12 +101,6 @@ type StructLogger struct { changedValues map[common.Address]Storage } -// JSONLogger merely contains a writer, and immediately outputs to that channel, -// instead of collecting logs -type JSONLogger struct { - encoder *json.Encoder -} - // NewStructLogger returns a new logger func NewStructLogger(cfg *LogConfig) *StructLogger { logger := &StructLogger{ @@ -146,31 +112,6 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { return logger } -// NewJSONLogger returns a new JSON logger -func NewJSONLogger(writer io.Writer) *JSONLogger { - logger := &JSONLogger{ - encoder: json.NewEncoder(writer), - } - return logger -} - -// CaptureState outputs state information on the logger -func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { - log := StructLog{pc, op, gas + cost, cost, memory.Data(), stack.Data(), nil, env.depth, err} - return l.encoder.Encode(log) -} -func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error { - type endLog struct { - Output string `json:"output"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - Time time.Duration `json:"time"` - } - - log := endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t} - return l.encoder.Encode(log) - -} - // CaptureState logs a new structured log message and pushes it out to the environment // // CaptureState also tracks SSTORE ops to track dirty values. From 0e250abf3237e9b39803f52563ae676db31433fd Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 7 Jun 2017 15:01:35 +0200 Subject: [PATCH 6/7] cmd/evm: fixups for json logger --- cmd/evm/json_logger.go | 23 +++++++---------------- core/vm/gen_structlog.go | 11 ++++++----- core/vm/logger.go | 10 ++++++---- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/cmd/evm/json_logger.go b/cmd/evm/json_logger.go index 420b51676362..a84d5daebd1e 100644 --- a/cmd/evm/json_logger.go +++ b/cmd/evm/json_logger.go @@ -26,23 +26,17 @@ import ( "github.com/ethereum/go-ethereum/core/vm" ) -// JSONLogger merely contains a writer, and immediately outputs to that channel, -// instead of collecting logs type JSONLogger struct { encoder *json.Encoder } -// NewJSONLogger returns a new JSON logger func NewJSONLogger(writer io.Writer) *JSONLogger { - logger := &JSONLogger{ - encoder: json.NewEncoder(writer), - } - return logger + return &JSONLogger{json.NewEncoder(writer)} } -// CaptureState outputs state information on the logger +// CaptureState outputs state information on the logger. func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { - log := vm.StructLog{ + return l.encoder.Encode(vm.StructLog{ Pc: pc, Op: op, Gas: gas + cost, @@ -51,19 +45,16 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos Stack: stack.Data(), Storage: nil, Depth: depth, - Err: err} - return l.encoder.Encode(log) + Err: err, + }) } -// CaptureEnd is triggered at end of execution +// CaptureEnd is triggered at end of execution. func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error { type endLog struct { Output string `json:"output"` GasUsed math.HexOrDecimal64 `json:"gasUsed"` Time time.Duration `json:"time"` } - - log := endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t} - return l.encoder.Encode(log) - + return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t}) } diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go index fec0e3619690..1c86b2256fa1 100644 --- a/core/vm/gen_structlog.go +++ b/core/vm/gen_structlog.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" ) @@ -16,13 +17,13 @@ func (s StructLog) MarshalJSON() ([]byte, error) { Op OpCode `json:"op"` Gas math.HexOrDecimal64 `json:"gas"` GasCost math.HexOrDecimal64 `json:"gasCost"` - Memory []byte `json:"memory"` + Memory hexutil.Bytes `json:"memory"` Stack []*math.HexOrDecimal256 `json:"stack"` - Storage map[common.Hash]common.Hash `json:-` + Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` Err error `json:"error"` OpName string `json:"opName"` - MemorySize string `json:"memSize"` + MemorySize int `json:"memSize"` } var enc StructLog enc.Pc = s.Pc @@ -50,9 +51,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { Op *OpCode `json:"op"` Gas *math.HexOrDecimal64 `json:"gas"` GasCost *math.HexOrDecimal64 `json:"gasCost"` - Memory []byte `json:"memory"` + Memory hexutil.Bytes `json:"memory"` Stack []*math.HexOrDecimal256 `json:"stack"` - Storage map[common.Hash]common.Hash `json:-` + Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` Err *error `json:"error"` } diff --git a/core/vm/logger.go b/core/vm/logger.go index df9e3a514efe..d95f462bc2f4 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -24,6 +24,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" ) @@ -58,7 +59,7 @@ type StructLog struct { GasCost uint64 `json:"gasCost"` Memory []byte `json:"memory"` Stack []*big.Int `json:"stack"` - Storage map[common.Hash]common.Hash `json:-` + Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` Err error `json:"error"` } @@ -67,16 +68,17 @@ func (s *StructLog) OpName() string { return s.Op.String() } -func (s *StructLog) MemorySize() string { - return fmt.Sprintf("%v", len(s.Memory)) +func (s *StructLog) MemorySize() int { + return len(s.Memory) } type structLogMarshaling struct { Stack []*math.HexOrDecimal256 Gas math.HexOrDecimal64 GasCost math.HexOrDecimal64 + Memory hexutil.Bytes OpName string `json:"opName"` - MemorySize string `json:"memSize"` + MemorySize int `json:"memSize"` } // Tracer is used to collect execution traces from an EVM transaction From 2561ab932aa5a2ea6474afc205c2d6e863e7ab46 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 7 Jun 2017 15:23:59 +0200 Subject: [PATCH 7/7] core/vm: fixup formatting --- core/vm/logger.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/core/vm/logger.go b/core/vm/logger.go index d95f462bc2f4..405ab169cf91 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -49,9 +49,10 @@ type LogConfig struct { Limit int // maximum length of output, but zero means unlimited } +//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go + // StructLog is emitted to the EVM each cycle and lists information about the current internal state // prior to the execution of the statement. -//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go type StructLog struct { Pc uint64 `json:"pc"` Op OpCode `json:"op"` @@ -64,14 +65,7 @@ type StructLog struct { Err error `json:"error"` } -func (s *StructLog) OpName() string { - return s.Op.String() -} - -func (s *StructLog) MemorySize() int { - return len(s.Memory) -} - +// overrides for gencodec type structLogMarshaling struct { Stack []*math.HexOrDecimal256 Gas math.HexOrDecimal64 @@ -81,6 +75,14 @@ type structLogMarshaling struct { MemorySize int `json:"memSize"` } +func (s *StructLog) OpName() string { + return s.Op.String() +} + +func (s *StructLog) MemorySize() int { + return len(s.Memory) +} + // Tracer is used to collect execution traces from an EVM transaction // execution. CaptureState is called for each step of the VM with the // current VM state. @@ -184,6 +186,7 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui l.logs = append(l.logs, log) return nil } + func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error { fmt.Printf("0x%x", output) return nil