From bcb008f33d8d46cce31ae352741d983d5c42bfe2 Mon Sep 17 00:00:00 2001
From: Daniel Liu <139250065@qq.com>
Date: Thu, 16 Apr 2026 08:24:01 +0800
Subject: [PATCH] refactor(cmd/evm): improve block/state test runner #30633
---
cmd/evm/disasm.go | 55 ----
cmd/evm/eest.go | 50 ++++
cmd/evm/internal/compiler/compiler.go | 39 ---
cmd/evm/main.go | 282 +++++++++++--------
cmd/evm/reporter.go | 103 +++++++
cmd/evm/runner.go | 350 ++++++++++++++++--------
cmd/evm/{compiler.go => runner_test.go} | 49 ++--
cmd/evm/staterunner.go | 184 +++++++++----
tests/state_test_util.go | 31 ++-
9 files changed, 734 insertions(+), 409 deletions(-)
delete mode 100644 cmd/evm/disasm.go
create mode 100644 cmd/evm/eest.go
delete mode 100644 cmd/evm/internal/compiler/compiler.go
create mode 100644 cmd/evm/reporter.go
rename cmd/evm/{compiler.go => runner_test.go} (51%)
diff --git a/cmd/evm/disasm.go b/cmd/evm/disasm.go
deleted file mode 100644
index 2e09f5aab693..000000000000
--- a/cmd/evm/disasm.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "errors"
- "fmt"
- "os"
- "strings"
-
- "github.com/XinFinOrg/XDPoSChain/core/asm"
- "github.com/urfave/cli/v2"
-)
-
-var disasmCommand = &cli.Command{
- Action: disasmCmd,
- Name: "disasm",
- Usage: "disassembles evm binary",
- ArgsUsage: "",
-}
-
-func disasmCmd(ctx *cli.Context) error {
- var in string
- switch {
- case len(ctx.Args().First()) > 0:
- fn := ctx.Args().First()
- input, err := os.ReadFile(fn)
- if err != nil {
- return err
- }
- in = string(input)
- case ctx.IsSet(InputFlag.Name):
- in = ctx.String(InputFlag.Name)
- default:
- return errors.New("missing filename or --input value")
- }
-
- code := strings.TrimSpace(in)
- fmt.Printf("%v\n", code)
- return asm.PrintDisassembled(code)
-}
diff --git a/cmd/evm/eest.go b/cmd/evm/eest.go
new file mode 100644
index 000000000000..91aca188c21c
--- /dev/null
+++ b/cmd/evm/eest.go
@@ -0,0 +1,50 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+
+package main
+
+import "regexp"
+
+var (
+ eestTestMetadataPattern = `tests\/([^\/]+)\/([^\/]+)\/([^:]+)::([^[]+)\[fork_([^-\]]+)-[^-]+-(.+)\]`
+ eestTestMetadataRegexp = regexp.MustCompile(eestTestMetadataPattern)
+)
+
+// testMetadata provides more granular access to the test information encoded
+// within its filename by the execution spec test (EEST).
+type testMetadata struct {
+ fork string
+ module string // which python module generated the test, e.g. EIP-7702
+ file string // exact file the test came from, e.g. test_gas.py
+ function string // func that created the test, e.g. test_valid_mcopy_operations
+ parameters string // the name of the parameters which were used to fill the test, e.g. zero_inputs
+}
+
+// parseTestMetadata reads a test name and parses out more specific information
+// about the test.
+func parseTestMetadata(s string) *testMetadata {
+ match := eestTestMetadataRegexp.FindStringSubmatch(s)
+ if len(match) == 0 {
+ return nil
+ }
+ return &testMetadata{
+ fork: match[5],
+ module: match[2],
+ file: match[3],
+ function: match[4],
+ parameters: match[6],
+ }
+}
diff --git a/cmd/evm/internal/compiler/compiler.go b/cmd/evm/internal/compiler/compiler.go
deleted file mode 100644
index 2d86df4a60a5..000000000000
--- a/cmd/evm/internal/compiler/compiler.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum 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 General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package compiler
-
-import (
- "errors"
- "fmt"
-
- "github.com/XinFinOrg/XDPoSChain/core/asm"
-)
-
-func Compile(fn string, src []byte, debug bool) (string, error) {
- compiler := asm.NewCompiler(debug)
- compiler.Feed(asm.Lex(fn, src, debug))
-
- bin, compileErrors := compiler.Compile()
- if len(compileErrors) > 0 {
- // report errors
- for _, err := range compileErrors {
- fmt.Printf("%s:%v\n", fn, err)
- }
- return "", errors.New("compiling failed")
- }
- return bin, nil
-}
diff --git a/cmd/evm/main.go b/cmd/evm/main.go
index ec9d3e42457f..4a007a7a361e 100644
--- a/cmd/evm/main.go
+++ b/cmd/evm/main.go
@@ -19,158 +19,140 @@ package main
import (
"fmt"
- "math/big"
+ "io/fs"
"os"
+ "path/filepath"
+ "slices"
+ "github.com/XinFinOrg/XDPoSChain/core/state"
+ "github.com/XinFinOrg/XDPoSChain/core/tracing"
+ "github.com/XinFinOrg/XDPoSChain/eth/tracers/logger"
+ "github.com/XinFinOrg/XDPoSChain/internal/debug"
"github.com/XinFinOrg/XDPoSChain/internal/flags"
"github.com/urfave/cli/v2"
)
-var (
- app = flags.NewApp("the evm command line interface")
-)
+// Some other nice-to-haves:
+// * accumulate traces into an object to bundle with test
+// * write tx identifier for trace before hand (blocktest only)
+// * combine blocktest and statetest runner logic using unified test interface
+
+const traceCategory = "TRACING"
var (
- DebugFlag = &cli.BoolFlag{
- Name: "debug",
- Usage: "output full trace logs",
+ // Test running flags.
+ RunFlag = &cli.StringFlag{
+ Name: "run",
+ Value: ".*",
+ Usage: "Run only those tests matching the regular expression.",
Category: flags.VMCategory,
}
- MemProfileFlag = &cli.StringFlag{
- Name: "memprofile",
- Usage: "creates a memory profile at the given path",
- Category: flags.VMCategory,
- }
- CPUProfileFlag = &cli.StringFlag{
- Name: "cpuprofile",
- Usage: "creates a CPU profile at the given path",
- Category: flags.VMCategory,
- }
- StatDumpFlag = &cli.BoolFlag{
- Name: "statdump",
- Usage: "displays stack and heap memory information",
- Category: flags.VMCategory,
- }
- CodeFlag = &cli.StringFlag{
- Name: "code",
- Usage: "EVM code",
- Category: flags.VMCategory,
- }
- CodeFileFlag = &cli.StringFlag{
- Name: "codefile",
- Usage: "File containing EVM code. If '-' is specified, code is read from stdin ",
- Category: flags.VMCategory,
- }
- GasFlag = &cli.Uint64Flag{
- Name: "gas",
- Usage: "gas limit for the evm",
- Value: 10000000000,
- Category: flags.VMCategory,
- }
- PriceFlag = &flags.BigFlag{
- Name: "price",
- Usage: "price set for the evm",
- Value: new(big.Int),
- Category: flags.VMCategory,
- }
- ValueFlag = &flags.BigFlag{
- Name: "value",
- Usage: "value set for the evm",
- Value: new(big.Int),
+ BenchFlag = &cli.BoolFlag{
+ Name: "bench",
+ Usage: "benchmark the execution",
Category: flags.VMCategory,
}
+
+ // Debugging flags.
DumpFlag = &cli.BoolFlag{
Name: "dump",
Usage: "dumps the state after the run",
Category: flags.VMCategory,
}
- InputFlag = &cli.StringFlag{
- Name: "input",
- Usage: "input for the EVM",
- Category: flags.VMCategory,
- }
- VerbosityFlag = &cli.IntFlag{
- Name: "verbosity",
- Usage: "sets the verbosity level",
- Category: flags.VMCategory,
- }
- CreateFlag = &cli.BoolFlag{
- Name: "create",
- Usage: "indicates the action should be create rather than call",
- Category: flags.VMCategory,
- }
- GenesisFlag = &cli.StringFlag{
- Name: "prestate",
- Usage: "JSON file with prestate (genesis) config",
- Category: flags.VMCategory,
+ HumanReadableFlag = &cli.BoolFlag{
+ Name: "human",
+ Usage: "\"Human-readable\" output",
}
- MachineFlag = &cli.BoolFlag{
- Name: "json",
- Usage: "output trace logs in machine readable format (json)",
- Category: flags.VMCategory,
- }
- SenderFlag = &cli.StringFlag{
- Name: "sender",
- Usage: "The transaction origin",
- Category: flags.VMCategory,
- }
- ReceiverFlag = &cli.StringFlag{
- Name: "receiver",
- Usage: "The transaction receiver (execution context)",
+ StatDumpFlag = &cli.BoolFlag{
+ Name: "statdump",
+ Usage: "displays stack and heap memory information",
Category: flags.VMCategory,
}
- DisableMemoryFlag = &cli.BoolFlag{
- Name: "nomemory",
+
+ // Tracing flags.
+ TraceFlag = &cli.BoolFlag{
+ Name: "trace",
+ Usage: "Enable tracing and output trace log.",
+ Category: traceCategory,
+ }
+ TraceFormatFlag = &cli.StringFlag{
+ Name: "trace.format",
+ Usage: "Trace output format to use (json|struct|md)",
+ Value: "json",
+ Category: traceCategory,
+ }
+ TraceDisableMemoryFlag = &cli.BoolFlag{
+ Name: "trace.nomemory",
+ Aliases: []string{"nomemory"},
Value: true,
Usage: "disable memory output",
- Category: flags.VMCategory,
+ Category: traceCategory,
}
- DisableStackFlag = &cli.BoolFlag{
- Name: "nostack",
+ TraceDisableStackFlag = &cli.BoolFlag{
+ Name: "trace.nostack",
+ Aliases: []string{"nostack"},
Usage: "disable stack output",
- Category: flags.VMCategory,
+ Category: traceCategory,
}
- DisableStorageFlag = &cli.BoolFlag{
- Name: "nostorage",
+ TraceDisableStorageFlag = &cli.BoolFlag{
+ Name: "trace.nostorage",
+ Aliases: []string{"nostorage"},
Usage: "disable storage output",
- Category: flags.VMCategory,
+ Category: traceCategory,
}
- DisableReturnDataFlag = &cli.BoolFlag{
- Name: "noreturndata",
+ TraceDisableReturnDataFlag = &cli.BoolFlag{
+ Name: "trace.noreturndata",
+ Aliases: []string{"noreturndata"},
Value: true,
- Usage: "enable return data output",
- Category: flags.VMCategory,
+ Usage: "disable return data output",
+ Category: traceCategory,
+ }
+
+ // Deprecated flags.
+ DebugFlag = &cli.BoolFlag{
+ Name: "debug",
+ Usage: "output full trace logs (deprecated)",
+ Hidden: true,
+ Category: traceCategory,
+ }
+ MachineFlag = &cli.BoolFlag{
+ Name: "json",
+ Usage: "output trace logs in machine readable format, json (deprecated)",
+ Hidden: true,
+ Category: traceCategory,
}
)
+// traceFlags contains flags that configure tracing output.
+var traceFlags = []cli.Flag{
+ TraceFlag,
+ TraceFormatFlag,
+ TraceDisableStackFlag,
+ TraceDisableMemoryFlag,
+ TraceDisableStorageFlag,
+ TraceDisableReturnDataFlag,
+
+ // deprecated
+ DebugFlag,
+ MachineFlag,
+}
+
+var app = flags.NewApp("the evm command line interface")
+
func init() {
- app.Flags = []cli.Flag{
- CreateFlag,
- DebugFlag,
- VerbosityFlag,
- CodeFlag,
- CodeFileFlag,
- GasFlag,
- PriceFlag,
- ValueFlag,
- DumpFlag,
- InputFlag,
- MemProfileFlag,
- CPUProfileFlag,
- StatDumpFlag,
- GenesisFlag,
- MachineFlag,
- SenderFlag,
- ReceiverFlag,
- DisableMemoryFlag,
- DisableStackFlag,
- }
+ app.Flags = debug.Flags
app.Commands = []*cli.Command{
- compileCommand,
- disasmCommand,
runCommand,
stateTestCommand,
}
+ app.Before = func(ctx *cli.Context) error {
+ flags.MigrateGlobalFlags(ctx)
+ return debug.Setup(ctx)
+ }
+ app.After = func(ctx *cli.Context) error {
+ debug.Exit()
+ return nil
+ }
}
func main() {
@@ -179,3 +161,71 @@ func main() {
os.Exit(1)
}
}
+
+// tracerFromFlags parses the cli flags and returns the specified tracer, or an error for unknown formats.
+func tracerFromFlags(ctx *cli.Context) (*tracing.Hooks, error) {
+ config := &logger.Config{
+ EnableMemory: !ctx.Bool(TraceDisableMemoryFlag.Name),
+ DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
+ DisableStorage: ctx.Bool(TraceDisableStorageFlag.Name),
+ EnableReturnData: !ctx.Bool(TraceDisableReturnDataFlag.Name),
+ }
+ switch {
+ case ctx.Bool(TraceFlag.Name):
+ switch format := ctx.String(TraceFormatFlag.Name); format {
+ case "struct":
+ return logger.NewStreamingStructLogger(config, os.Stderr).Hooks(), nil
+ case "json":
+ return logger.NewJSONLogger(config, os.Stderr), nil
+ case "md", "markdown":
+ return logger.NewMarkdownLogger(config, os.Stderr).Hooks(), nil
+ default:
+ return nil, cli.Exit(fmt.Sprintf("unknown trace format: %q", format), 1)
+ }
+ // Deprecated ways of configuring tracing.
+ case ctx.Bool(MachineFlag.Name):
+ return logger.NewJSONLogger(config, os.Stderr), nil
+ case ctx.Bool(DebugFlag.Name):
+ return logger.NewStreamingStructLogger(config, os.Stderr).Hooks(), nil
+ default:
+ return nil, nil
+ }
+}
+
+// collectFiles walks the given path. If the path is a directory, it returns
+// a list of all .json files found recursively under the directory.
+// Otherwise, if path points to a file, it returns that path.
+func collectFiles(path string) ([]string, error) {
+ var out []string
+ if info, err := os.Stat(path); err == nil && !info.IsDir() {
+ // User explicitly pointed out a file, ignore extension.
+ return []string{path}, nil
+ }
+ err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if !info.IsDir() && filepath.Ext(info.Name()) == ".json" {
+ out = append(out, path)
+ }
+ return nil
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to collect files from %q: %v", path, err)
+ }
+ if len(out) > 1 {
+ slices.Sort(out)
+ }
+ return out, nil
+}
+
+// dump returns a state dump for the most current trie.
+func dump(s *state.StateDB) *state.Dump {
+ root := s.IntermediateRoot(false)
+ cpy, err := state.New(root, s.Database())
+ if err != nil {
+ return nil
+ }
+ dump := cpy.RawDump(nil)
+ return &dump
+}
diff --git a/cmd/evm/reporter.go b/cmd/evm/reporter.go
new file mode 100644
index 000000000000..9f71c6f7f467
--- /dev/null
+++ b/cmd/evm/reporter.go
@@ -0,0 +1,103 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "github.com/XinFinOrg/XDPoSChain/common"
+ "github.com/XinFinOrg/XDPoSChain/core/state"
+ "github.com/urfave/cli/v2"
+ "golang.org/x/term"
+)
+
+var (
+ PASS string
+ FAIL string
+)
+
+func init() {
+ if isTerminal(os.Stdout.Fd()) {
+ PASS = "\033[32mPASS\033[0m"
+ FAIL = "\033[31mFAIL\033[0m"
+ } else {
+ PASS = "PASS"
+ FAIL = "FAIL"
+ }
+}
+
+func isTerminal(fd uintptr) bool {
+ return term.IsTerminal(int(fd))
+}
+
+// testResult contains the execution status after running a state test, any
+// error that might have occurred and a dump of the final state if requested.
+type testResult struct {
+ Name string `json:"name"`
+ Pass bool `json:"pass"`
+ Root *common.Hash `json:"stateRoot,omitempty"`
+ Fork string `json:"fork"`
+ Error string `json:"error,omitempty"`
+ State *state.Dump `json:"state,omitempty"`
+ Stats *execStats `json:"benchStats,omitempty"`
+}
+
+func (r testResult) String() string {
+ var status string
+ if r.Pass {
+ status = fmt.Sprintf("[%s]", PASS)
+ } else {
+ status = fmt.Sprintf("[%s]", FAIL)
+ }
+ info := r.Name
+ m := parseTestMetadata(r.Name)
+ if m != nil {
+ info = fmt.Sprintf("%s %s, param=%s", m.module, m.function, m.parameters)
+ }
+ var extra string
+ if !r.Pass {
+ extra = fmt.Sprintf(", err=%v, fork=%s", r.Error, r.Fork)
+ }
+ out := fmt.Sprintf("%s %s%s", status, info, extra)
+ if r.State != nil {
+ state, _ := json.MarshalIndent(r.State, "", " ")
+ out += "\n" + string(state)
+ }
+ return out
+}
+
+// report prints the after-test summary.
+func report(ctx *cli.Context, results []testResult) {
+ if ctx.Bool(HumanReadableFlag.Name) {
+ pass := 0
+ for _, r := range results {
+ if r.Pass {
+ pass++
+ }
+ }
+ for _, r := range results {
+ fmt.Println(r)
+ }
+ fmt.Println("--")
+ fmt.Printf("%d tests passed, %d tests failed.\n", pass, len(results)-pass)
+ return
+ }
+ out, _ := json.MarshalIndent(results, "", " ")
+ fmt.Println(string(out))
+}
diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go
index fe6f898c2f31..5603bdcd109a 100644
--- a/cmd/evm/runner.go
+++ b/cmd/evm/runner.go
@@ -20,15 +20,17 @@ import (
"bytes"
"encoding/hex"
"encoding/json"
+ "errors"
"fmt"
"io"
"math/big"
"os"
goruntime "runtime"
- "runtime/pprof"
+ "slices"
+ "strings"
+ "testing"
"time"
- "github.com/XinFinOrg/XDPoSChain/cmd/evm/internal/compiler"
"github.com/XinFinOrg/XDPoSChain/cmd/utils"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core"
@@ -38,9 +40,9 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/core/vm/runtime"
- "github.com/XinFinOrg/XDPoSChain/eth/tracers/logger"
"github.com/XinFinOrg/XDPoSChain/internal/flags"
"github.com/XinFinOrg/XDPoSChain/params"
+ "github.com/XinFinOrg/XDPoSChain/trie"
"github.com/urfave/cli/v2"
)
@@ -50,13 +52,83 @@ var runCommand = &cli.Command{
Usage: "run arbitrary evm binary",
ArgsUsage: "",
Description: `The run command runs arbitrary EVM code.`,
+ Flags: slices.Concat([]cli.Flag{
+ BenchFlag,
+ CodeFileFlag,
+ CreateFlag,
+ GasFlag,
+ GenesisFlag,
+ InputFlag,
+ InputFileFlag,
+ PriceFlag,
+ ReceiverFlag,
+ SenderFlag,
+ ValueFlag,
+ StatDumpFlag,
+ DumpFlag,
+ }, traceFlags),
}
+var (
+ CodeFileFlag = &cli.StringFlag{
+ Name: "codefile",
+ Usage: "File containing EVM code. If '-' is specified, code is read from stdin ",
+ Category: flags.VMCategory,
+ }
+ CreateFlag = &cli.BoolFlag{
+ Name: "create",
+ Usage: "Indicates the action should be create rather than call",
+ Category: flags.VMCategory,
+ }
+ GasFlag = &cli.Uint64Flag{
+ Name: "gas",
+ Usage: "Gas limit for the evm",
+ Value: 10000000000,
+ Category: flags.VMCategory,
+ }
+ GenesisFlag = &cli.StringFlag{
+ Name: "prestate",
+ Usage: "JSON file with prestate (genesis) config",
+ Category: flags.VMCategory,
+ }
+ InputFlag = &cli.StringFlag{
+ Name: "input",
+ Usage: "Input for the EVM",
+ Category: flags.VMCategory,
+ }
+ InputFileFlag = &cli.StringFlag{
+ Name: "inputfile",
+ Usage: "File containing input for the EVM",
+ Category: flags.VMCategory,
+ }
+ PriceFlag = &flags.BigFlag{
+ Name: "price",
+ Usage: "Price set for the evm",
+ Value: new(big.Int),
+ Category: flags.VMCategory,
+ }
+ ReceiverFlag = &cli.StringFlag{
+ Name: "receiver",
+ Usage: "The transaction receiver (execution context)",
+ Category: flags.VMCategory,
+ }
+ SenderFlag = &cli.StringFlag{
+ Name: "sender",
+ Usage: "The transaction origin",
+ Category: flags.VMCategory,
+ }
+ ValueFlag = &flags.BigFlag{
+ Name: "value",
+ Usage: "Value set for the evm",
+ Value: new(big.Int),
+ Category: flags.VMCategory,
+ }
+)
+
// 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")
}
@@ -73,96 +145,143 @@ func readGenesis(genesisPath string) *core.Genesis {
return genesis
}
-func runCmd(ctx *cli.Context) error {
- logconfig := &logger.Config{
- EnableMemory: !ctx.Bool(DisableMemoryFlag.Name),
- DisableStack: ctx.Bool(DisableStackFlag.Name),
- DisableStorage: ctx.Bool(DisableStorageFlag.Name),
- EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name),
- Debug: ctx.Bool(DebugFlag.Name),
+type execStats struct {
+ Time time.Duration `json:"time"` // The execution Time.
+ Allocs int64 `json:"allocs"` // The number of heap allocations during execution.
+ BytesAllocated int64 `json:"bytesAllocated"` // The cumulative number of bytes allocated during execution.
+ GasUsed uint64 `json:"gasUsed"` // the amount of gas used during execution
+}
+
+var errInconsistentBenchmarkResult = errors.New("benchmark execution was nondeterministic")
+
+func newBenchmarkMismatchError(format string, args ...any) error {
+ return fmt.Errorf("%w: "+format, append([]any{errInconsistentBenchmarkResult}, args...)...)
+}
+
+func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) ([]byte, execStats, error) {
+ if bench {
+ testing.Init()
+ // Do one warm-up run
+ output, gasUsed, err := execFunc()
+ var benchErr error
+ result := testing.Benchmark(func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ haveOutput, haveGasUsed, haveErr := execFunc()
+ if !bytes.Equal(haveOutput, output) {
+ benchErr = newBenchmarkMismatchError("output differs\nhave %x\nwant %x", haveOutput, output)
+ b.StopTimer()
+ return
+ }
+ if haveGasUsed != gasUsed {
+ benchErr = newBenchmarkMismatchError("gas differs, have %v want %v", haveGasUsed, gasUsed)
+ b.StopTimer()
+ return
+ }
+ if (haveErr == nil) != (err == nil) || (haveErr != nil && err != nil && haveErr.Error() != err.Error()) {
+ benchErr = newBenchmarkMismatchError("err differs, have %v want %v", haveErr, err)
+ b.StopTimer()
+ return
+ }
+ }
+ })
+ if benchErr != nil {
+ return output, execStats{GasUsed: gasUsed}, benchErr
+ }
+ // Get the average execution time from the benchmarking result.
+ // There are other useful stats here that could be reported.
+ stats := execStats{
+ Time: time.Duration(result.NsPerOp()),
+ Allocs: result.AllocsPerOp(),
+ BytesAllocated: result.AllocedBytesPerOp(),
+ GasUsed: gasUsed,
+ }
+ return output, stats, err
+ }
+ var memStatsBefore, memStatsAfter goruntime.MemStats
+ goruntime.ReadMemStats(&memStatsBefore)
+ t0 := time.Now()
+ output, gasUsed, err := execFunc()
+ duration := time.Since(t0)
+ goruntime.ReadMemStats(&memStatsAfter)
+ stats := execStats{
+ Time: duration,
+ Allocs: int64(memStatsAfter.Mallocs - memStatsBefore.Mallocs),
+ BytesAllocated: int64(memStatsAfter.TotalAlloc - memStatsBefore.TotalAlloc),
+ GasUsed: gasUsed,
}
+ return output, stats, err
+}
+func runCmd(ctx *cli.Context) error {
var (
- tracer *tracing.Hooks
- statedb *state.StateDB
- chainConfig *params.ChainConfig
- sender = common.StringToAddress("sender")
- receiver = common.StringToAddress("receiver")
- genesisConfig *core.Genesis
+ tracer *tracing.Hooks
+ prestate *state.StateDB
+ chainConfig *params.ChainConfig
+ sender = common.BytesToAddress([]byte("sender"))
+ receiver = common.BytesToAddress([]byte("receiver"))
+ preimages = ctx.Bool(DumpFlag.Name)
)
- if ctx.Bool(MachineFlag.Name) {
- tracer = logger.NewJSONLogger(logconfig, os.Stdout)
- } else if ctx.Bool(DebugFlag.Name) {
- tracer = logger.NewStreamingStructLogger(logconfig, os.Stderr).Hooks()
+ var err error
+ tracer, err = tracerFromFlags(ctx)
+ if err != nil {
+ return err
}
-
+ initialGas := ctx.Uint64(GasFlag.Name)
+ genesisConfig := new(core.Genesis)
+ genesisConfig.GasLimit = initialGas
if ctx.String(GenesisFlag.Name) != "" {
- gen := readGenesis(ctx.String(GenesisFlag.Name))
- genesisConfig = gen
- db := rawdb.NewMemoryDatabase()
- genesis := gen.MustCommit(db)
- statedb, _ = state.New(genesis.Root(), state.NewDatabase(db))
- chainConfig = gen.Config
+ genesisConfig = readGenesis(ctx.String(GenesisFlag.Name))
+ if genesisConfig.GasLimit != 0 {
+ initialGas = genesisConfig.GasLimit
+ }
} else {
- db := rawdb.NewMemoryDatabase()
- statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(db))
- genesisConfig = new(core.Genesis)
+ genesisConfig.Config = params.AllDevChainProtocolChanges
}
+
+ db := rawdb.NewMemoryDatabase()
+ triedb := trie.NewDatabaseWithConfig(db, &trie.Config{Preimages: preimages})
+ defer triedb.Close()
+ genesis := genesisConfig.MustCommit(db)
+ sdb := state.NewDatabaseWithNodeDB(db, triedb)
+ prestate, _ = state.New(genesis.Root(), sdb)
+ chainConfig = genesisConfig.Config
+
if ctx.String(SenderFlag.Name) != "" {
sender = common.HexToAddress(ctx.String(SenderFlag.Name))
}
- statedb.CreateAccount(sender)
if ctx.String(ReceiverFlag.Name) != "" {
receiver = common.HexToAddress(ctx.String(ReceiverFlag.Name))
}
- var (
- code []byte
- ret []byte
- err error
- )
- // The '--code' or '--codefile' flag overrides code in state
- if ctx.String(CodeFileFlag.Name) != "" {
- var hexcode []byte
- var err error
+ var code []byte
+ codeFileFlag := ctx.String(CodeFileFlag.Name)
+ hexcode := ctx.Args().First()
+
+ // The '--codefile' flag overrides code in state
+ if codeFileFlag == "-" {
// If - is specified, it means that code comes from stdin
- if ctx.String(CodeFileFlag.Name) == "-" {
- //Try reading from stdin
- if hexcode, err = io.ReadAll(os.Stdin); err != nil {
- fmt.Printf("Could not load code from stdin: %v\n", err)
- os.Exit(1)
- }
- } else {
- // Codefile with hex assembly
- if hexcode, err = os.ReadFile(ctx.String(CodeFileFlag.Name)); err != nil {
- fmt.Printf("Could not load code from file: %v\n", err)
- os.Exit(1)
- }
- }
- code = common.Hex2Bytes(string(bytes.TrimRight(hexcode, "\n")))
- } else if ctx.String(CodeFlag.Name) != "" {
- code = common.Hex2Bytes(ctx.String(CodeFlag.Name))
- } else if fn := ctx.Args().First(); len(fn) > 0 {
- // EASM-file to compile
- src, err := os.ReadFile(fn)
+ input, err := io.ReadAll(os.Stdin)
if err != nil {
- return err
+ return cli.Exit(fmt.Sprintf("Could not load code from stdin: %v", err), 1)
}
- bin, err := compiler.Compile(fn, src, false)
+ hexcode = string(input)
+ } else if codeFileFlag != "" {
+ // Codefile with hex assembly
+ input, err := os.ReadFile(codeFileFlag)
if err != nil {
- return err
+ return cli.Exit(fmt.Sprintf("Could not load code from file: %v", err), 1)
}
- code = common.Hex2Bytes(bin)
+ hexcode = string(input)
}
- initialGas := ctx.Uint64(GasFlag.Name)
- if genesisConfig.GasLimit != 0 {
- initialGas = genesisConfig.GasLimit
- }
+ hexcode = strings.TrimSpace(hexcode)
+ code = common.FromHex(hexcode)
+
runtimeConfig := runtime.Config{
Origin: sender,
- State: statedb,
+ State: prestate,
GasLimit: initialGas,
GasPrice: flags.GlobalBig(ctx, PriceFlag.Name),
Value: flags.GlobalBig(ctx, ValueFlag.Name),
@@ -170,56 +289,66 @@ func runCmd(ctx *cli.Context) error {
Time: genesisConfig.Timestamp,
Coinbase: genesisConfig.Coinbase,
BlockNumber: new(big.Int).SetUint64(genesisConfig.Number),
+ BaseFee: genesisConfig.BaseFee,
EVMConfig: vm.Config{
Tracer: tracer,
},
}
- if cpuProfilePath := ctx.String(CPUProfileFlag.Name); cpuProfilePath != "" {
- f, err := os.Create(cpuProfilePath)
- if err != nil {
- fmt.Println("could not create CPU profile: ", err)
- os.Exit(1)
- }
- if err := pprof.StartCPUProfile(f); err != nil {
- fmt.Println("could not start CPU profile: ", err)
- os.Exit(1)
- }
- defer pprof.StopCPUProfile()
- }
-
if chainConfig != nil {
runtimeConfig.ChainConfig = chainConfig
+ } else {
+ runtimeConfig.ChainConfig = params.AllDevChainProtocolChanges
}
- tstart := time.Now()
- var leftOverGas uint64
+
+ var hexInput []byte
+ if inputFileFlag := ctx.String(InputFileFlag.Name); inputFileFlag != "" {
+ var err error
+ if hexInput, err = os.ReadFile(inputFileFlag); err != nil {
+ return cli.Exit(fmt.Sprintf("could not load input from file: %v", err), 1)
+ }
+ } else {
+ hexInput = []byte(ctx.String(InputFlag.Name))
+ }
+ hexInput = bytes.TrimSpace(hexInput)
+ input := common.FromHex(string(hexInput))
+
+ var execFunc func() ([]byte, uint64, error)
if ctx.Bool(CreateFlag.Name) {
- input := append(code, common.Hex2Bytes(ctx.String(InputFlag.Name))...)
- ret, _, leftOverGas, err = runtime.Create(input, &runtimeConfig)
+ input = append(code, input...)
+ execFunc = func() ([]byte, uint64, error) {
+ // don't mutate the state!
+ runtimeConfig.State = prestate.Copy()
+ output, _, gasLeft, err := runtime.Create(input, &runtimeConfig)
+ return output, initialGas - gasLeft, err
+ }
} else {
if len(code) > 0 {
- statedb.SetCode(receiver, code)
+ prestate.SetCode(receiver, code)
+ }
+ execFunc = func() ([]byte, uint64, error) {
+ // don't mutate the state!
+ runtimeConfig.State = prestate.Copy()
+ output, gasLeft, err := runtime.Call(receiver, input, &runtimeConfig)
+ return output, initialGas - gasLeft, err
}
- ret, leftOverGas, err = runtime.Call(receiver, common.Hex2Bytes(ctx.String(InputFlag.Name)), &runtimeConfig)
}
- execTime := time.Since(tstart)
- if ctx.Bool(DumpFlag.Name) {
- statedb.Commit(genesisConfig.Number, true)
- fmt.Println(string(statedb.Dump(nil)))
- }
+ bench := ctx.Bool(BenchFlag.Name)
+ output, stats, err := timedExec(bench, execFunc)
- if memProfilePath := ctx.String(MemProfileFlag.Name); memProfilePath != "" {
- f, err := os.Create(memProfilePath)
+ if ctx.Bool(DumpFlag.Name) {
+ root, err := runtimeConfig.State.Commit(genesisConfig.Number, true)
if err != nil {
- fmt.Println("could not create memory profile: ", err)
- os.Exit(1)
+ fmt.Printf("Failed to commit changes %v\n", err)
+ return err
}
- if err := pprof.WriteHeapProfile(f); err != nil {
- fmt.Println("could not write memory profile: ", err)
- os.Exit(1)
+ dumpdb, err := state.New(root, sdb)
+ if err != nil {
+ fmt.Printf("Failed to open statedb %v\n", err)
+ return err
}
- f.Close()
+ fmt.Println(string(dumpdb.Dump(nil)))
}
if ctx.Bool(DebugFlag.Name) {
@@ -229,20 +358,15 @@ func runCmd(ctx *cli.Context) error {
}
}
- if ctx.Bool(StatDumpFlag.Name) {
- var mem goruntime.MemStats
- goruntime.ReadMemStats(&mem)
- fmt.Fprintf(os.Stderr, `evm execution time: %v
-heap objects: %d
-allocations: %d
-total allocations: %d
-GC calls: %d
-Gas used: %d
-
-`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas)
+ if bench || ctx.Bool(StatDumpFlag.Name) {
+ fmt.Fprintf(os.Stderr, `EVM gas used: %d
+execution time: %v
+allocations: %d
+allocated bytes: %d
+`, stats.GasUsed, stats.Time, stats.Allocs, stats.BytesAllocated)
}
if tracer == nil {
- fmt.Printf("%#x\n", ret)
+ fmt.Printf("%#x\n", output)
if err != nil {
fmt.Printf(" error: %v\n", err)
}
diff --git a/cmd/evm/compiler.go b/cmd/evm/runner_test.go
similarity index 51%
rename from cmd/evm/compiler.go
rename to cmd/evm/runner_test.go
index b2f45e2f911f..652cf850c0a3 100644
--- a/cmd/evm/compiler.go
+++ b/cmd/evm/runner_test.go
@@ -1,4 +1,4 @@
-// Copyright 2017 The go-ethereum Authors
+// Copyright 2024 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
@@ -18,37 +18,32 @@ package main
import (
"errors"
- "fmt"
- "os"
-
- "github.com/XinFinOrg/XDPoSChain/cmd/evm/internal/compiler"
- "github.com/urfave/cli/v2"
+ "testing"
)
-var compileCommand = &cli.Command{
- Action: compileCmd,
- Name: "compile",
- Usage: "compiles easm source to evm binary",
- ArgsUsage: "",
-}
-
-func compileCmd(ctx *cli.Context) error {
- debug := ctx.Bool(DebugFlag.Name)
+func TestTimedExecBenchNondeterministicReturnsError(t *testing.T) {
+ t.Parallel()
- if len(ctx.Args().First()) == 0 {
- return errors.New("filename required")
+ var calls int
+ execFunc := func() ([]byte, uint64, error) {
+ calls++
+ if calls == 1 {
+ return []byte{0x01}, 7, nil
+ }
+ return []byte{0x02}, 7, nil
}
- fn := ctx.Args().First()
- src, err := os.ReadFile(fn)
- if err != nil {
- return err
- }
+ defer func() {
+ if recovered := recover(); recovered != nil {
+ t.Fatalf("timedExec panicked instead of returning an error: %v", recovered)
+ }
+ }()
- bin, err := compiler.Compile(fn, src, debug)
- if err != nil {
- return err
+ _, _, err := timedExec(true, execFunc)
+ if err == nil {
+ t.Fatal("expected nondeterministic benchmark run to return an error")
+ }
+ if !errors.Is(err, errInconsistentBenchmarkResult) {
+ t.Fatalf("expected errInconsistentBenchmarkResult, got %v", err)
}
- fmt.Println(bin)
- return nil
}
diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go
index ca86eb8cace8..b84740429f48 100644
--- a/cmd/evm/staterunner.go
+++ b/cmd/evm/staterunner.go
@@ -17,86 +17,170 @@
package main
import (
+ "bufio"
"encoding/json"
- "errors"
"fmt"
"os"
+ "regexp"
+ "slices"
+ "strings"
- "github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/vm"
- "github.com/XinFinOrg/XDPoSChain/eth/tracers/logger"
+ "github.com/XinFinOrg/XDPoSChain/internal/flags"
"github.com/XinFinOrg/XDPoSChain/tests"
"github.com/urfave/cli/v2"
+ "golang.org/x/term"
+)
+
+var (
+ forkFlag = &cli.StringFlag{
+ Name: "statetest.fork",
+ Usage: "Only run tests for the specified fork.",
+ Category: flags.VMCategory,
+ }
+ idxFlag = &cli.IntFlag{
+ Name: "statetest.index",
+ Usage: "The index of the subtest to run.",
+ Category: flags.VMCategory,
+ Value: -1, // default to select all subtest indices
+ }
)
var stateTestCommand = &cli.Command{
Action: stateTestCmd,
Name: "statetest",
- Usage: "executes the given state tests",
+ Usage: "Executes the given state tests. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).",
ArgsUsage: "",
-}
-
-type StatetestResult struct {
- Name string `json:"name"`
- Pass bool `json:"pass"`
- Fork string `json:"fork"`
- Error string `json:"error,omitempty"`
- State *state.Dump `json:"state,omitempty"`
+ Flags: slices.Concat([]cli.Flag{
+ BenchFlag,
+ DumpFlag,
+ forkFlag,
+ HumanReadableFlag,
+ idxFlag,
+ RunFlag,
+ }, traceFlags),
}
func stateTestCmd(ctx *cli.Context) error {
- if len(ctx.Args().First()) == 0 {
- return errors.New("path-to-test argument required")
- }
+ path := ctx.Args().First()
- // Configure the EVM logger
- config := &logger.Config{
- EnableMemory: !ctx.Bool(DisableMemoryFlag.Name),
- DisableStack: ctx.Bool(DisableStackFlag.Name),
- DisableStorage: ctx.Bool(DisableStorageFlag.Name),
- EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name),
+ // If path is provided, run the tests at that path.
+ if len(path) != 0 {
+ collected, err := collectFiles(path)
+ if err != nil {
+ return cli.Exit(err.Error(), 1)
+ }
+ var results []testResult
+ for _, fname := range collected {
+ r, err := runStateTest(ctx, fname)
+ if err != nil {
+ return err
+ }
+ results = append(results, r...)
+ }
+ report(ctx, results)
+ return nil
}
+ // Otherwise, read filenames from stdin and execute back-to-back.
+ // If stdin is a terminal, print error and exit instead of blocking.
+ if term.IsTerminal(int(os.Stdin.Fd())) {
+ return fmt.Errorf("no input file provided and stdin is a terminal; please provide a path argument or pipe filenames via stdin")
+ }
+ scanner := bufio.NewScanner(os.Stdin)
+ var results []testResult
+ for scanner.Scan() {
+ fname := scanner.Text()
+ fname = strings.TrimSpace(fname)
+ if len(fname) == 0 {
+ continue
+ }
+ r, err := runStateTest(ctx, fname)
+ if err != nil {
+ return err
+ }
+ results = append(results, r...)
+ }
+ if err := scanner.Err(); err != nil {
+ return err
+ }
+ report(ctx, results)
+ return nil
+}
- var cfg vm.Config
- switch {
- case ctx.Bool(MachineFlag.Name):
- cfg.Tracer = logger.NewJSONLogger(config, os.Stderr)
-
- case ctx.Bool(DebugFlag.Name):
- cfg.Tracer = logger.NewStructLogger(config).Hooks()
+// runStateTest loads the state-test given by fname, and executes the test.
+func runStateTest(ctx *cli.Context, fname string) ([]testResult, error) {
+ src, err := os.ReadFile(fname)
+ if err != nil {
+ return nil, err
}
- // Load the test content from the input file
- src, err := os.ReadFile(ctx.Args().First())
+ var testsByName map[string]tests.StateTest
+ if err := json.Unmarshal(src, &testsByName); err != nil {
+ return nil, fmt.Errorf("unable to read test file %s: %w", fname, err)
+ }
+
+ tracer, err := tracerFromFlags(ctx)
if err != nil {
- return err
+ return nil, err
}
- var tests map[string]tests.StateTest
- if err = json.Unmarshal(src, &tests); err != nil {
- return err
+ cfg := vm.Config{Tracer: tracer}
+ re, err := regexp.Compile(ctx.String(RunFlag.Name))
+ if err != nil {
+ return nil, fmt.Errorf("invalid regex --%s: %v", RunFlag.Name, err)
}
+
// Iterate over all the tests, run them and aggregate the results
- results := make([]StatetestResult, 0, len(tests))
- for key, test := range tests {
+ results := make([]testResult, 0, len(testsByName))
+ for key, test := range testsByName {
+ if !re.MatchString(key) {
+ continue
+ }
for _, st := range test.Subtests() {
+ if idx := ctx.Int(idxFlag.Name); idx != -1 && idx != st.Index {
+ // If specific index requested, skip all tests that do not match.
+ continue
+ }
+ if fork := ctx.String(forkFlag.Name); fork != "" && st.Fork != fork {
+ // If specific fork requested, skip all tests that do not match.
+ continue
+ }
// Run the test and aggregate the result
- result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true}
- s, err := test.Run(st, cfg)
- if err != nil {
- // Test failed, mark as so and dump any state to aid debugging
- result.Pass, result.Error = false, err.Error()
- if ctx.Bool(DumpFlag.Name) && s != nil {
- dump := s.RawDump(nil)
- result.State = &dump
+ result := &testResult{Name: key, Fork: st.Fork, Pass: true}
+ statedb, gasUsed, err := test.RunWithGas(st, cfg)
+ if statedb != nil {
+ root := statedb.IntermediateRoot(false)
+ result.Root = &root
+ if ctx.Bool(DumpFlag.Name) {
+ fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root)
+ result.State = dump(statedb)
}
}
- // print state root for evmlab tracing (already committed above, so no need to delete objects again
- if ctx.Bool(MachineFlag.Name) && s != nil {
- fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", s.IntermediateRoot(false))
+ if ctx.Bool(BenchFlag.Name) {
+ benchCfg := cfg
+ benchCfg.Tracer = nil
+ _, stats, benchErr := timedExec(true, func() ([]byte, uint64, error) {
+ _, gasUsed, err := test.RunWithGas(st, benchCfg)
+ return nil, gasUsed, err
+ })
+ result.Stats = &stats
+ if benchErr != nil {
+ result.Pass = false
+ if result.Error != "" {
+ result.Error += "; "
+ }
+ result.Error += "bench error: " + benchErr.Error()
+ }
+ }
+ if err != nil {
+ result.Pass = false
+ if result.Error != "" {
+ result.Error += "; "
+ }
+ result.Error += err.Error()
+ } else if result.Stats == nil && ctx.Bool(BenchFlag.Name) {
+ result.Stats = &execStats{GasUsed: gasUsed}
}
results = append(results, *result)
}
}
- out, _ := json.MarshalIndent(results, "", " ")
- fmt.Println(string(out))
- return nil
+ return results, nil
}
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index 27b6ad19fe48..ac5e6755ebd7 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -154,9 +154,16 @@ func (t *StateTest) Subtests() []StateSubtest {
// Run executes a specific subtest.
func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateDB, error) {
+ statedb, _, err := t.RunWithGas(subtest, vmconfig)
+ return statedb, err
+}
+
+// RunWithGas executes a specific subtest and returns the resulting state along
+// with the gas used by message execution.
+func (t *StateTest) RunWithGas(subtest StateSubtest, vmconfig vm.Config) (*state.StateDB, uint64, error) {
config, ok := Forks[subtest.Fork]
if !ok {
- return nil, UnsupportedForkError{subtest.Fork}
+ return nil, 0, UnsupportedForkError{subtest.Fork}
}
block := t.genesis(config).ToBlock()
db := rawdb.NewMemoryDatabase()
@@ -174,7 +181,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateD
post := t.json.Post[subtest.Fork][subtest.Index]
msg, err := t.json.Tx.toMessage(post, baseFee)
if err != nil {
- return nil, err
+ return nil, 0, err
}
// Prepare the EVM.
@@ -191,24 +198,30 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateD
gaspool.AddGas(block.GasLimit())
coinbase := &t.json.Env.Coinbase
- if _, err := core.ApplyMessage(evm, msg, gaspool, *coinbase); err != nil {
+ result, err := core.ApplyMessage(evm, msg, gaspool, *coinbase)
+ if err != nil {
statedb.RevertToSnapshot(snapshot)
}
if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) {
- return statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs)
+ return statedb, 0, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs)
}
// Commit block
root, _ := statedb.Commit(block.NumberU64(), config.IsEIP158(block.Number()))
if root != common.Hash(post.Root) {
- return statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root)
+ return statedb, 0, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root)
}
// Re-init the post-state instance for further operation
- statedb, err = state.New(root, statedb.Database())
- if err != nil {
- return nil, err
+ statedb, err2 := state.New(root, statedb.Database())
+ if err2 != nil {
+ return nil, 0, err2
+ }
+ var gasUsed uint64
+ if result != nil {
+ gasUsed = result.UsedGas
}
- return statedb, nil
+ // If we got an error from ApplyMessage, but post-state is correct, clear err (unless internal failure)
+ return statedb, gasUsed, nil
}
func (t *StateTest) gasLimit(subtest StateSubtest) uint64 {