diff --git a/cmd/evm/main.go b/cmd/evm/main.go index e85d31d03d..db6b671557 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 22538d7b13..33dd58b5d3 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 825025b05f..0f17840bf5 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) {