Compare commits

...

5 Commits

Author SHA1 Message Date
lightclient c8d625e326
Merge 2ae47cf2c9 into ab4a1cc01f 2024-11-25 10:43:19 +01:00
Martin HS ab4a1cc01f
eth/tracers/logger: fix json-logger output missing (#30804)
Fixes a flaw introduced in
https://github.com/ethereum/go-ethereum/pull/29795 , discovered while
reviewing https://github.com/ethereum/go-ethereum/pull/30633 .
2024-11-25 10:07:50 +01:00
lightclient 2ae47cf2c9
cmd/evm: print state root for jsonl format 2024-11-21 22:22:47 +08:00
lightclient 092b99f49f
cmd/evm: review fixes 2024-11-21 17:23:07 +08:00
lightclient 7c34200d74
cmd/evm: unify staterunner and blockrunner more, add human-readable output, stateless cross-checking option 2024-11-21 16:48:38 +08:00
14 changed files with 540 additions and 514 deletions

View File

@ -22,79 +22,84 @@ import (
"fmt" "fmt"
"os" "os"
"regexp" "regexp"
"sort" "slices"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/exp/maps"
) )
var RunFlag = &cli.StringFlag{
Name: "run",
Value: ".*",
Usage: "Run only those tests matching the regular expression.",
}
var blockTestCommand = &cli.Command{ var blockTestCommand = &cli.Command{
Action: blockTestCmd, Action: blockTestCmd,
Name: "blocktest", Name: "blocktest",
Usage: "Executes the given blockchain tests", Usage: "Executes the given blockchain tests",
ArgsUsage: "<file>", ArgsUsage: "<path>",
Flags: []cli.Flag{RunFlag}, Flags: slices.Concat([]cli.Flag{
DumpFlag,
HumanReadableFlag,
RunFlag,
WitnessCrossCheckFlag,
}, traceFlags),
} }
func blockTestCmd(ctx *cli.Context) error { func blockTestCmd(ctx *cli.Context) error {
if len(ctx.Args().First()) == 0 { path := ctx.Args().First()
return errors.New("path-to-test argument required") if len(path) == 0 {
return errors.New("path argument required")
} }
var (
var tracer *tracing.Hooks collected = collectJSONFiles(path)
// Configure the EVM logger results []testResult
if ctx.Bool(MachineFlag.Name) { )
tracer = logger.NewJSONLogger(&logger.Config{ for _, fname := range collected {
EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), r, err := runBlockTest(ctx, fname)
DisableStack: ctx.Bool(DisableStackFlag.Name),
DisableStorage: ctx.Bool(DisableStorageFlag.Name),
EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name),
}, os.Stderr)
}
// Load the test content from the input file
src, err := os.ReadFile(ctx.Args().First())
if err != nil { if err != nil {
return err return err
} }
var tests map[string]tests.BlockTest results = append(results, r...)
}
report(ctx, results)
return nil
}
func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
src, err := os.ReadFile(fname)
if err != nil {
return nil, err
}
var tests map[string]*tests.BlockTest
if err = json.Unmarshal(src, &tests); err != nil { if err = json.Unmarshal(src, &tests); err != nil {
return err return nil, err
} }
re, err := regexp.Compile(ctx.String(RunFlag.Name)) re, err := regexp.Compile(ctx.String(RunFlag.Name))
if err != nil { if err != nil {
return fmt.Errorf("invalid regex -%s: %v", RunFlag.Name, err) return nil, fmt.Errorf("invalid regex -%s: %v", RunFlag.Name, err)
} }
tracer := tracerFromFlags(ctx)
// Run them in order // Pull out keys to sort and ensure tests are run in order.
var keys []string keys := maps.Keys(tests)
for key := range tests { slices.Sort(keys)
keys = append(keys, key)
} // Run all the tests.
sort.Strings(keys) var results []testResult
for _, name := range keys { for _, name := range keys {
if !re.MatchString(name) { if !re.MatchString(name) {
continue continue
} }
test := tests[name] result := &testResult{Name: name, Pass: true}
if err := test.Run(false, rawdb.HashScheme, false, tracer, func(res error, chain *core.BlockChain) { if err := tests[name].Run(false, rawdb.HashScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) {
if ctx.Bool(DumpFlag.Name) { if ctx.Bool(DumpFlag.Name) {
if state, _ := chain.State(); state != nil { if s, _ := chain.State(); s != nil {
fmt.Println(string(state.Dump(nil))) result.State = dump(s)
} }
} }
}); err != nil { }); err != nil {
return fmt.Errorf("test %v: %w", name, err) result.Pass, result.Error = false, err.Error()
} }
results = append(results, *result)
} }
return nil return results, nil
} }

View File

@ -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 <http://www.gnu.org/licenses/>.
package main
import (
"errors"
"fmt"
"os"
"github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
"github.com/urfave/cli/v2"
)
var compileCommand = &cli.Command{
Action: compileCmd,
Name: "compile",
Usage: "Compiles easm source to evm binary",
ArgsUsage: "<file>",
}
func compileCmd(ctx *cli.Context) error {
debug := ctx.Bool(DebugFlag.Name)
if len(ctx.Args().First()) == 0 {
return errors.New("filename required")
}
fn := ctx.Args().First()
src, err := os.ReadFile(fn)
if err != nil {
return err
}
bin, err := compiler.Compile(fn, src, debug)
if err != nil {
return err
}
fmt.Println(bin)
return nil
}

View File

@ -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 <http://www.gnu.org/licenses/>.
package main
import (
"errors"
"fmt"
"os"
"strings"
"github.com/ethereum/go-ethereum/core/asm"
"github.com/urfave/cli/v2"
)
var disasmCommand = &cli.Command{
Action: disasmCmd,
Name: "disasm",
Usage: "Disassembles evm binary",
ArgsUsage: "<file>",
}
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)
}

49
cmd/evm/eest.go Normal file
View File

@ -0,0 +1,49 @@
// 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 <http://www.gnu.org/licenses/>.
package main
import "regexp"
// 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 gnerated the test, e.g. eip7702
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 {
var (
pattern = `tests\/([^\/]+)\/([^\/]+)\/([^:]+)::([^[]+)\[fork_([^-\]]+)-[^-]+-(.+)\]`
re = regexp.MustCompile(pattern)
)
match := re.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],
}
}

View File

@ -31,13 +31,41 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var jt vm.JumpTable
const initcode = "INITCODE"
func init() { func init() {
jt = vm.NewPragueEOFInstructionSetForTesting() jt = vm.NewPragueEOFInstructionSetForTesting()
} }
var ( var (
jt vm.JumpTable hexFlag = &cli.StringFlag{
initcode = "INITCODE" Name: "hex",
Usage: "Single container data parse and validation",
}
refTestFlag = &cli.StringFlag{
Name: "test",
Usage: "Path to EOF validation reference test.",
}
eofParseCommand = &cli.Command{
Name: "eofparse",
Aliases: []string{"eof"},
Usage: "Parses hex eof container and returns validation errors (if any)",
Action: eofParseAction,
Flags: []cli.Flag{
hexFlag,
refTestFlag,
},
}
eofDumpCommand = &cli.Command{
Name: "eofdump",
Usage: "Parses hex eof container and prints out human-readable representation of the container.",
Action: eofDumpAction,
Flags: []cli.Flag{
hexFlag,
},
}
) )
func eofParseAction(ctx *cli.Context) error { func eofParseAction(ctx *cli.Context) error {

View File

@ -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 <http://www.gnu.org/licenses/>.
package compiler
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/core/asm"
)
func Compile(fn string, src []byte, debug bool) (string, error) {
compiler := asm.NewCompiler(debug)
compiler.Feed(asm.Lex(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
}

View File

@ -19,11 +19,14 @@ package main
import ( import (
"fmt" "fmt"
"math/big" "io/fs"
"os" "os"
"slices" "path/filepath"
"github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool" "github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -33,122 +36,100 @@ import (
_ "github.com/ethereum/go-ethereum/eth/tracers/native" _ "github.com/ethereum/go-ethereum/eth/tracers/native"
) )
// 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 ( var (
DebugFlag = &cli.BoolFlag{ // Test running flags.
Name: "debug", RunFlag = &cli.StringFlag{
Usage: "output full trace logs", Name: "run",
Category: flags.VMCategory, Value: ".*",
} Usage: "Run only those tests matching the regular expression.",
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),
Category: flags.VMCategory,
}
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,
}
InputFileFlag = &cli.StringFlag{
Name: "inputfile",
Usage: "file containing input for the EVM",
Category: flags.VMCategory,
} }
BenchFlag = &cli.BoolFlag{ BenchFlag = &cli.BoolFlag{
Name: "bench", Name: "bench",
Usage: "benchmark the execution", Usage: "benchmark the execution",
Category: flags.VMCategory, Category: flags.VMCategory,
} }
CreateFlag = &cli.BoolFlag{ WitnessCrossCheckFlag = &cli.BoolFlag{
Name: "create", Name: "cross-check",
Usage: "indicates the action should be create rather than call", Aliases: []string{"xc"},
Category: flags.VMCategory, Usage: "Cross-check stateful execution against stateless, verifying the witness generation.",
} }
GenesisFlag = &cli.StringFlag{
Name: "prestate", // Debugging flags.
Usage: "JSON file with prestate (genesis) config", DumpFlag = &cli.BoolFlag{
Category: flags.VMCategory, Name: "dump",
Usage: "dumps the state after the run",
}
HumanReadableFlag = &cli.BoolFlag{
Name: "human",
Usage: "\"Human-readable\" output",
}
StatDumpFlag = &cli.BoolFlag{
Name: "statdump",
Usage: "displays stack and heap memory information",
}
// 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 (struct|json)",
Value: "struct",
Category: traceCategory,
}
TraceDisableMemoryFlag = &cli.BoolFlag{
Name: "trace.nomemory",
Aliases: []string{"nomemory"},
Value: true,
Usage: "disable memory output",
Category: traceCategory,
}
TraceDisableStackFlag = &cli.BoolFlag{
Name: "trace.nostack",
Aliases: []string{"nostack"},
Usage: "disable stack output",
Category: traceCategory,
}
TraceDisableStorageFlag = &cli.BoolFlag{
Name: "trace.nostorage",
Aliases: []string{"nostorage"},
Usage: "disable storage output",
Category: traceCategory,
}
TraceDisableReturnDataFlag = &cli.BoolFlag{
Name: "trace.noreturndata",
Aliases: []string{"noreturndata"},
Value: true,
Usage: "enable 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{ MachineFlag = &cli.BoolFlag{
Name: "json", Name: "json",
Usage: "output trace logs in machine readable format (json)", Usage: "output trace logs in machine readable format, json (deprecated)",
Category: flags.VMCategory, Hidden: true,
} Category: traceCategory,
SenderFlag = &cli.StringFlag{
Name: "sender",
Usage: "The transaction origin",
Category: flags.VMCategory,
}
ReceiverFlag = &cli.StringFlag{
Name: "receiver",
Usage: "The transaction receiver (execution context)",
Category: flags.VMCategory,
}
DisableMemoryFlag = &cli.BoolFlag{
Name: "nomemory",
Value: true,
Usage: "disable memory output",
Category: flags.VMCategory,
}
DisableStackFlag = &cli.BoolFlag{
Name: "nostack",
Usage: "disable stack output",
Category: flags.VMCategory,
}
DisableStorageFlag = &cli.BoolFlag{
Name: "nostorage",
Usage: "disable storage output",
Category: flags.VMCategory,
}
DisableReturnDataFlag = &cli.BoolFlag{
Name: "noreturndata",
Value: true,
Usage: "enable return data output",
Category: flags.VMCategory,
}
refTestFlag = &cli.StringFlag{
Name: "test",
Usage: "Path to EOF validation reference test.",
}
hexFlag = &cli.StringFlag{
Name: "hex",
Usage: "single container data parse and validation",
} }
) )
// Command definitions.
var ( var (
stateTransitionCommand = &cli.Command{ stateTransitionCommand = &cli.Command{
Name: "transition", Name: "transition",
@ -175,7 +156,6 @@ var (
t8ntool.RewardFlag, t8ntool.RewardFlag,
}, },
} }
transactionCommand = &cli.Command{ transactionCommand = &cli.Command{
Name: "transaction", Name: "transaction",
Aliases: []string{"t9n"}, Aliases: []string{"t9n"},
@ -203,62 +183,27 @@ var (
t8ntool.SealCliqueFlag, t8ntool.SealCliqueFlag,
}, },
} }
eofParseCommand = &cli.Command{
Name: "eofparse",
Aliases: []string{"eof"},
Usage: "Parses hex eof container and returns validation errors (if any)",
Action: eofParseAction,
Flags: []cli.Flag{
hexFlag,
refTestFlag,
},
}
eofDumpCommand = &cli.Command{
Name: "eofdump",
Usage: "Parses hex eof container and prints out human-readable representation of the container.",
Action: eofDumpAction,
Flags: []cli.Flag{
hexFlag,
},
}
) )
// vmFlags contains flags related to running the EVM.
var vmFlags = []cli.Flag{
CodeFlag,
CodeFileFlag,
CreateFlag,
GasFlag,
PriceFlag,
ValueFlag,
InputFlag,
InputFileFlag,
GenesisFlag,
SenderFlag,
ReceiverFlag,
}
// traceFlags contains flags that configure tracing output. // traceFlags contains flags that configure tracing output.
var traceFlags = []cli.Flag{ var traceFlags = []cli.Flag{
BenchFlag, TraceFlag,
TraceFormatFlag,
TraceDisableStackFlag,
TraceDisableMemoryFlag,
TraceDisableStorageFlag,
TraceDisableReturnDataFlag,
// deprecated
DebugFlag, DebugFlag,
DumpFlag,
MachineFlag, MachineFlag,
StatDumpFlag,
DisableMemoryFlag,
DisableStackFlag,
DisableStorageFlag,
DisableReturnDataFlag,
} }
var app = flags.NewApp("the evm command line interface") var app = flags.NewApp("the evm command line interface")
func init() { func init() {
app.Flags = slices.Concat(vmFlags, traceFlags, debug.Flags) app.Flags = debug.Flags
app.Commands = []*cli.Command{ app.Commands = []*cli.Command{
compileCommand,
disasmCommand,
runCommand, runCommand,
blockTestCommand, blockTestCommand,
stateTestCommand, stateTestCommand,
@ -280,11 +225,56 @@ func init() {
func main() { func main() {
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
code := 1
if ec, ok := err.(*t8ntool.NumberedError); ok {
code = ec.ExitCode()
}
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(code) os.Exit(1)
} }
} }
// tracerFromFlags parses the cli flags and returns the specified tracer.
func tracerFromFlags(ctx *cli.Context) *tracing.Hooks {
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) && ctx.String(TraceFormatFlag.Name) == "struct":
return logger.NewStructLogger(config).Hooks()
case ctx.Bool(TraceFlag.Name) && ctx.String(TraceFormatFlag.Name) == "json":
return logger.NewJSONLogger(config, os.Stderr)
case ctx.Bool(MachineFlag.Name):
return logger.NewJSONLogger(config, os.Stderr)
case ctx.Bool(DebugFlag.Name):
return logger.NewStructLogger(config).Hooks()
default:
return nil
}
}
// collectJSONFiles walks the given path and accumulates all files with json
// extension.
func collectJSONFiles(path string) []string {
var out []string
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 {
fmt.Fprintln(os.Stderr, err)
}
return out
}
// dump returns a state dump for the most current trie.
func dump(s *state.StateDB) *state.Dump {
root := s.IntermediateRoot(false)
cpy, _ := state.New(root, s.Database())
dump := cpy.RawDump(nil)
return &dump
}

87
cmd/evm/reporter.go Normal file
View File

@ -0,0 +1,87 @@
// 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 <http://www.gnu.org/licenses/>.
package main
import (
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/urfave/cli/v2"
)
const (
PASS = "\033[32mPASS\033[0m"
FAIL = "\033[31mFAIL\033[0m"
)
// 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))
}

View File

@ -25,10 +25,10 @@ import (
"os" "os"
goruntime "runtime" goruntime "runtime"
"slices" "slices"
"strings"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -51,14 +51,82 @@ var runCommand = &cli.Command{
Usage: "Run arbitrary evm binary", Usage: "Run arbitrary evm binary",
ArgsUsage: "<code>", ArgsUsage: "<code>",
Description: `The run command runs arbitrary EVM code.`, Description: `The run command runs arbitrary EVM code.`,
Flags: slices.Concat(vmFlags, traceFlags), Flags: slices.Concat([]cli.Flag{
BenchFlag,
CodeFileFlag,
CreateFlag,
GasFlag,
GenesisFlag,
InputFlag,
InputFileFlag,
PriceFlag,
ReceiverFlag,
SenderFlag,
ValueFlag,
StatDumpFlag,
}, 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 // readGenesis will read the given JSON format genesis file and return
// the initialized Genesis structure // the initialized Genesis structure
func readGenesis(genesisPath string) *core.Genesis { func readGenesis(genesisPath string) *core.Genesis {
// Make sure we have a valid genesis JSON // Make sure we have a valid genesis JSON
//genesisPath := ctx.Args().First()
if len(genesisPath) == 0 { if len(genesisPath) == 0 {
utils.Fatalf("Must supply path to genesis JSON file") utils.Fatalf("Must supply path to genesis JSON file")
} }
@ -127,10 +195,10 @@ func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) ([]byte, exe
func runCmd(ctx *cli.Context) error { func runCmd(ctx *cli.Context) error {
logconfig := &logger.Config{ logconfig := &logger.Config{
EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), EnableMemory: !ctx.Bool(TraceDisableMemoryFlag.Name),
DisableStack: ctx.Bool(DisableStackFlag.Name), DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
DisableStorage: ctx.Bool(DisableStorageFlag.Name), DisableStorage: ctx.Bool(TraceDisableStorageFlag.Name),
EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), EnableReturnData: !ctx.Bool(TraceDisableReturnDataFlag.Name),
Debug: ctx.Bool(DebugFlag.Name), Debug: ctx.Bool(DebugFlag.Name),
} }
@ -187,48 +255,35 @@ func runCmd(ctx *cli.Context) error {
var code []byte var code []byte
codeFileFlag := ctx.String(CodeFileFlag.Name) codeFileFlag := ctx.String(CodeFileFlag.Name)
codeFlag := ctx.String(CodeFlag.Name) hexcode := ctx.Args().First()
// The '--code' or '--codefile' flag overrides code in state // The '--codefile' flag overrides code in state
if codeFileFlag != "" || codeFlag != "" {
var hexcode []byte
if codeFileFlag != "" {
var err error
// If - is specified, it means that code comes from stdin
if codeFileFlag == "-" { if codeFileFlag == "-" {
//Try reading from stdin // If - is specified, it means that code comes from stdin
if hexcode, err = io.ReadAll(os.Stdin); err != nil { // Try reading from stdin
input, err := io.ReadAll(os.Stdin)
if err != nil {
fmt.Printf("Could not load code from stdin: %v\n", err) fmt.Printf("Could not load code from stdin: %v\n", err)
os.Exit(1) os.Exit(1)
} }
} else { hexcode = string(input)
} else if codeFileFlag != "" {
// Codefile with hex assembly // Codefile with hex assembly
if hexcode, err = os.ReadFile(codeFileFlag); err != nil { input, err := os.ReadFile(codeFileFlag)
if err != nil {
fmt.Printf("Could not load code from file: %v\n", err) fmt.Printf("Could not load code from file: %v\n", err)
os.Exit(1) os.Exit(1)
} }
hexcode = string(input)
} }
} else {
hexcode = []byte(codeFlag) hexcode = strings.TrimSpace(hexcode)
}
hexcode = bytes.TrimSpace(hexcode)
if len(hexcode)%2 != 0 { if len(hexcode)%2 != 0 {
fmt.Printf("Invalid input length for hex data (%d)\n", len(hexcode)) fmt.Printf("Invalid input length for hex data (%d)\n", len(hexcode))
os.Exit(1) os.Exit(1)
} }
code = common.FromHex(string(hexcode)) code = common.FromHex(hexcode)
} else if fn := ctx.Args().First(); len(fn) > 0 {
// EASM-file to compile
src, err := os.ReadFile(fn)
if err != nil {
return err
}
bin, err := compiler.Compile(fn, src, false)
if err != nil {
return err
}
code = common.Hex2Bytes(bin)
}
runtimeConfig := runtime.Config{ runtimeConfig := runtime.Config{
Origin: sender, Origin: sender,
State: statedb, State: statedb,

View File

@ -21,12 +21,12 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"regexp"
"slices"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -35,157 +35,124 @@ import (
var ( var (
forkFlag = &cli.StringFlag{ forkFlag = &cli.StringFlag{
Name: "statetest.fork", Name: "statetest.fork",
Usage: "The hard-fork to run the test against", Usage: "Only run tests for the specified fork.",
Category: flags.VMCategory, Category: flags.VMCategory,
} }
idxFlag = &cli.IntFlag{ idxFlag = &cli.IntFlag{
Name: "statetest.index", Name: "statetest.index",
Usage: "The index of the subtest to run", Usage: "The index of the subtest to run.",
Category: flags.VMCategory, Category: flags.VMCategory,
Value: -1, // default to select all subtest indices Value: -1, // default to select all subtest indices
} }
testNameFlag = &cli.StringFlag{
Name: "statetest.name",
Usage: "The name of the state test to run",
Category: flags.VMCategory,
}
) )
var stateTestCommand = &cli.Command{ var stateTestCommand = &cli.Command{
Action: stateTestCmd, Action: stateTestCmd,
Name: "statetest", Name: "statetest",
Usage: "Executes the given state tests. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).", Usage: "Executes the given state tests. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).",
ArgsUsage: "<file>", ArgsUsage: "<file>",
Flags: []cli.Flag{ Flags: slices.Concat([]cli.Flag{
forkFlag, DumpFlag,
idxFlag, HumanReadableFlag,
testNameFlag, RunFlag,
}, }, traceFlags),
}
// StatetestResult 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 StatetestResult 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"`
BenchStats *execStats `json:"benchStats,omitempty"`
} }
func stateTestCmd(ctx *cli.Context) error { func stateTestCmd(ctx *cli.Context) error {
// Configure the EVM logger path := ctx.Args().First()
config := &logger.Config{
EnableMemory: !ctx.Bool(DisableMemoryFlag.Name),
DisableStack: ctx.Bool(DisableStackFlag.Name),
DisableStorage: ctx.Bool(DisableStorageFlag.Name),
EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name),
}
var cfg vm.Config
switch {
case ctx.Bool(MachineFlag.Name):
cfg.Tracer = logger.NewJSONLogger(config, os.Stderr)
case ctx.Bool(DebugFlag.Name): // If path is provided, run the tests at that path.
cfg.Tracer = logger.NewStructLogger(config).Hooks() if len(path) != 0 {
var (
collected = collectJSONFiles(path)
results []testResult
)
for _, fname := range collected {
r, err := runStateTest(ctx, fname)
if err != nil {
return err
} }
// Load the test content from the input file results = append(results, r...)
if len(ctx.Args().First()) != 0 {
return runStateTest(ctx, ctx.Args().First(), cfg, ctx.Bool(DumpFlag.Name), ctx.Bool(BenchFlag.Name))
} }
// Read filenames from stdin and execute back-to-back report(ctx, results)
return nil
}
// Otherwise, read filenames from stdin and execute back-to-back.
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() { for scanner.Scan() {
fname := scanner.Text() fname := scanner.Text()
if len(fname) == 0 { if len(fname) == 0 {
return nil return nil
} }
if err := runStateTest(ctx, fname, cfg, ctx.Bool(DumpFlag.Name), ctx.Bool(BenchFlag.Name)); err != nil { results, err := runStateTest(ctx, fname)
if err != nil {
return err return err
} }
report(ctx, results)
} }
return nil return nil
} }
type stateTestCase struct {
name string
test tests.StateTest
st tests.StateSubtest
}
// collectMatchedSubtests returns test cases which match against provided filtering CLI parameters
func collectMatchedSubtests(ctx *cli.Context, testsByName map[string]tests.StateTest) []stateTestCase {
var res []stateTestCase
subtestName := ctx.String(testNameFlag.Name)
if subtestName != "" {
if subtest, ok := testsByName[subtestName]; ok {
testsByName := make(map[string]tests.StateTest)
testsByName[subtestName] = subtest
}
}
idx := ctx.Int(idxFlag.Name)
fork := ctx.String(forkFlag.Name)
for key, test := range testsByName {
for _, st := range test.Subtests() {
if idx != -1 && st.Index != idx {
continue
}
if fork != "" && st.Fork != fork {
continue
}
res = append(res, stateTestCase{name: key, st: st, test: test})
}
}
return res
}
// runStateTest loads the state-test given by fname, and executes the test. // runStateTest loads the state-test given by fname, and executes the test.
func runStateTest(ctx *cli.Context, fname string, cfg vm.Config, dump bool, bench bool) error { func runStateTest(ctx *cli.Context, fname string) ([]testResult, error) {
src, err := os.ReadFile(fname) src, err := os.ReadFile(fname)
if err != nil { if err != nil {
return err return nil, err
} }
var testsByName map[string]tests.StateTest var testsByName map[string]tests.StateTest
if err := json.Unmarshal(src, &testsByName); err != nil { if err := json.Unmarshal(src, &testsByName); err != nil {
return err return nil, fmt.Errorf("unable to read test file %s: %w", fname, err)
} }
matchingTests := collectMatchedSubtests(ctx, testsByName) cfg := vm.Config{Tracer: tracerFromFlags(ctx)}
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 // Iterate over all the tests, run them and aggregate the results
var results []StatetestResult results := make([]testResult, 0, len(testsByName))
for _, test := range matchingTests { for key, test := range testsByName {
if !re.MatchString(key) {
continue
}
for i, st := range test.Subtests() {
if idx := ctx.Int(idxFlag.Name); idx != -1 && idx != i {
// 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 // Run the test and aggregate the result
result := &StatetestResult{Name: test.name, Fork: test.st.Fork, Pass: true} result := &testResult{Name: key, Fork: st.Fork, Pass: true}
test.test.Run(test.st, cfg, false, rawdb.HashScheme, func(err error, tstate *tests.StateTestState) { test.Run(st, cfg, false, rawdb.HashScheme, func(err error, state *tests.StateTestState) {
var root common.Hash var root common.Hash
if tstate.StateDB != nil { if state.StateDB != nil {
root = tstate.StateDB.IntermediateRoot(false) root = state.StateDB.IntermediateRoot(false)
result.Root = &root result.Root = &root
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root) fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root)
if dump { // Dump any state to aid debugging // Dump any state to aid debugging.
cpy, _ := state.New(root, tstate.StateDB.Database()) if ctx.Bool(DumpFlag.Name) {
dump := cpy.RawDump(nil) result.State = dump(state.StateDB)
result.State = &dump
} }
} }
if err != nil { // Collect bench stats if requested.
// Test failed, mark as so if ctx.Bool(BenchFlag.Name) {
result.Pass, result.Error = false, err.Error()
}
})
if bench {
_, stats, _ := timedExec(true, func() ([]byte, uint64, error) { _, stats, _ := timedExec(true, func() ([]byte, uint64, error) {
_, _, gasUsed, _ := test.test.RunNoVerify(test.st, cfg, false, rawdb.HashScheme) _, _, gasUsed, _ := test.RunNoVerify(st, cfg, false, rawdb.HashScheme)
return nil, gasUsed, nil return nil, gasUsed, nil
}) })
result.BenchStats = &stats result.Stats = &stats
} }
if err != nil {
// Test failed, mark as so.
result.Pass, result.Error = false, err.Error()
return
}
})
results = append(results, *result) results = append(results, *result)
} }
out, _ := json.MarshalIndent(results, "", " ") }
fmt.Println(string(out)) return results, nil
return nil
} }

View File

@ -1926,7 +1926,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
task := types.NewBlockWithHeader(context).WithBody(*block.Body()) task := types.NewBlockWithHeader(context).WithBody(*block.Body())
// Run the stateless self-cross-validation // Run the stateless self-cross-validation
crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, task, witness) crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, bc.vmConfig, task, witness)
if err != nil { if err != nil {
return nil, fmt.Errorf("stateless self-validation failed: %v", err) return nil, fmt.Errorf("stateless self-validation failed: %v", err)
} }

View File

@ -40,7 +40,7 @@ import (
// - It cannot be placed outside of core, because it needs to construct a dud headerchain // - It cannot be placed outside of core, because it needs to construct a dud headerchain
// //
// TODO(karalabe): Would be nice to resolve both issues above somehow and move it. // TODO(karalabe): Would be nice to resolve both issues above somehow and move it.
func ExecuteStateless(config *params.ChainConfig, block *types.Block, witness *stateless.Witness) (common.Hash, common.Hash, error) { func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *types.Block, witness *stateless.Witness) (common.Hash, common.Hash, error) {
// Sanity check if the supplied block accidentally contains a set root or // Sanity check if the supplied block accidentally contains a set root or
// receipt hash. If so, be very loud, but still continue. // receipt hash. If so, be very loud, but still continue.
if block.Root() != (common.Hash{}) { if block.Root() != (common.Hash{}) {
@ -66,7 +66,7 @@ func ExecuteStateless(config *params.ChainConfig, block *types.Block, witness *s
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
// Run the stateless blocks processing and self-validate certain fields // Run the stateless blocks processing and self-validate certain fields
res, err := processor.Process(block, db, vm.Config{}) res, err := processor.Process(block, db, vmconfig)
if err != nil { if err != nil {
return common.Hash{}, common.Hash{}, err return common.Hash{}, common.Hash{}, err
} }

View File

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/internal/version"
@ -995,7 +996,7 @@ func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, v
api.lastNewPayloadLock.Unlock() api.lastNewPayloadLock.Unlock()
log.Trace("Executing block statelessly", "number", block.Number(), "hash", params.BlockHash) log.Trace("Executing block statelessly", "number", block.Number(), "hash", params.BlockHash)
stateRoot, receiptRoot, err := core.ExecuteStateless(api.eth.BlockChain().Config(), block, witness) stateRoot, receiptRoot, err := core.ExecuteStateless(api.eth.BlockChain().Config(), vm.Config{}, block, witness)
if err != nil { if err != nil {
log.Warn("ExecuteStatelessPayload: execution failed", "err", err) log.Warn("ExecuteStatelessPayload: execution failed", "err", err)
errorMsg := err.Error() errorMsg := err.Error()

View File

@ -71,7 +71,7 @@ func NewJSONLogger(cfg *Config, writer io.Writer) *tracing.Hooks {
l.hooks = &tracing.Hooks{ l.hooks = &tracing.Hooks{
OnTxStart: l.OnTxStart, OnTxStart: l.OnTxStart,
OnSystemCallStart: l.onSystemCallStart, OnSystemCallStart: l.onSystemCallStart,
OnExit: l.OnEnd, OnExit: l.OnExit,
OnOpcode: l.OnOpcode, OnOpcode: l.OnOpcode,
OnFault: l.OnFault, OnFault: l.OnFault,
} }
@ -152,13 +152,6 @@ func (l *jsonLogger) OnEnter(depth int, typ byte, from common.Address, to common
l.encoder.Encode(frame) l.encoder.Encode(frame)
} }
func (l *jsonLogger) OnEnd(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if depth > 0 {
return
}
l.OnExit(depth, output, gasUsed, err, false)
}
func (l *jsonLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { func (l *jsonLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
type endLog struct { type endLog struct {
Output string `json:"output"` Output string `json:"output"`