diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 78cd5c1ccb..3a4cc51c03 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -23,6 +23,7 @@ import ( "io/ioutil" "os" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/tests" @@ -38,84 +39,81 @@ var stateTestCommand = cli.Command{ } type StatetestResult struct { - Name string `json:"name"` - Pass bool `json:"pass"` - Fork string `json:"fork"` - Error string `json:"error,omitempty"` + Name string `json:"name"` + Pass bool `json:"pass"` + Fork string `json:"fork"` + Error string `json:"error,omitempty"` + State *state.Dump `json:"state,omitempty"` } func stateTestCmd(ctx *cli.Context) error { + if len(ctx.Args().First()) == 0 { + return errors.New("path-to-test argument required") + } + // Configure the go-ethereum logger glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name))) log.Root().SetHandler(glogger) - logconfig := &vm.LogConfig{ + + // Configure the EVM logger + config := &vm.LogConfig{ DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), DisableStack: ctx.GlobalBool(DisableStackFlag.Name), } var ( - tracer vm.Tracer - debugLogger *vm.StructLogger - // statedb *state.StateDB - // chainConfig *params.ChainConfig + tracer vm.Tracer + debugger *vm.StructLogger ) - if ctx.GlobalBool(MachineFlag.Name) { - tracer = NewJSONLogger(logconfig, os.Stderr) - } else if ctx.GlobalBool(DebugFlag.Name) { - debugLogger = vm.NewStructLogger(logconfig) - tracer = debugLogger - } else { - debugLogger = vm.NewStructLogger(logconfig) - } + switch { + case ctx.GlobalBool(MachineFlag.Name): + tracer = NewJSONLogger(config, os.Stderr) - if len(ctx.Args().First()) == 0 { - return errors.New("filename required") - } + case ctx.GlobalBool(DebugFlag.Name): + debugger = vm.NewStructLogger(config) + tracer = debugger - fn := ctx.Args().First() - src, err := ioutil.ReadFile(fn) + default: + debugger = vm.NewStructLogger(config) + } + // Load the test content from the input file + src, err := ioutil.ReadFile(ctx.Args().First()) if err != nil { return err } - var tests map[string]tests.StateTest - if err = json.Unmarshal(src, &tests); err != nil { return err } - - var results = make([]StatetestResult, 0, len(tests)) - + // Iterate over all the tests, run them and aggregate the results cfg := vm.Config{ Tracer: tracer, Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name), } - + results := make([]StatetestResult, 0, len(tests)) for key, test := range tests { for _, st := range test.Subtests() { - result := &StatetestResult{ - Name: key, - Fork: st.Fork, - Pass: true, - } - - if err = test.Run(st, cfg); err != nil { - result.Error = err.Error() - result.Pass = false + // Run the test and aggregate the result + result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true} + if state, err := test.Run(st, cfg); err != nil { + // Test failed, mark as so and dump any state to aid debugging + result.Pass, result.Error = false, err.Error() + if ctx.GlobalBool(DumpFlag.Name) && state != nil { + dump := state.RawDump() + result.State = &dump + } } results = append(results, *result) + // Print any structured logs collected if ctx.GlobalBool(DebugFlag.Name) { - if debugLogger != nil { + if debugger != nil { fmt.Fprintln(os.Stderr, "#### TRACE ####") - vm.WriteTrace(os.Stderr, debugLogger.StructLogs()) + vm.WriteTrace(os.Stderr, debugger.StructLogs()) } } } } - json.NewEncoder(os.Stdout).Encode(results) - - if err != nil { - return err - } + out, _ := json.MarshalIndent(results, "", " ") + fmt.Println(string(out)) return nil } diff --git a/tests/state_test.go b/tests/state_test.go index 4b6ba8b31b..1fb7f5908f 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -50,7 +50,8 @@ func TestState(t *testing.T) { t.Skip("constantinople not supported yet") } withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { - return st.checkFailure(t, name, test.Run(subtest, vmconfig)) + _, err := test.Run(subtest, vmconfig) + return st.checkFailure(t, name, err) }) }) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index ecaa6c6684..64bf09cb42 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -120,10 +120,10 @@ func (t *StateTest) Subtests() []StateSubtest { } // Run executes a specific subtest. -func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) error { +func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateDB, error) { config, ok := Forks[subtest.Fork] if !ok { - return UnsupportedForkError{subtest.Fork} + return nil, UnsupportedForkError{subtest.Fork} } block, _ := t.genesis(config).ToBlock() db, _ := ethdb.NewMemDatabase() @@ -132,7 +132,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) error { post := t.json.Post[subtest.Fork][subtest.Index] msg, err := t.json.Tx.toMessage(post) if err != nil { - return err + return nil, err } context := core.NewEVMContext(msg, block.Header(), nil, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash @@ -145,13 +145,13 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) error { statedb.RevertToSnapshot(snapshot) } if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) { - return fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) + return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) } root, _ := statedb.CommitTo(db, config.IsEIP158(block.Number())) if root != common.Hash(post.Root) { - return fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) + return statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) } - return nil + return statedb, nil } func (t *StateTest) gasLimit(subtest StateSubtest) uint64 {