cmd/evm: add stdin support to blocktest command (#32824)

Enable blocktest to read filenames from stdin when no path argument is
provided, matching the existing statetest behavior. This allows
efficient batch processing of blockchain tests.

Usage:
- Single file: evm blocktest <path>
- Batch mode: find tests/ -name "*.json" | evm blocktest

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: MariusVanDerWijden <m.vanderwijden@live.de>
Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
Bhargava Shastry 2025-11-28 11:22:36 +01:00 committed by GitHub
parent f691d661c4
commit fed8e09ab0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 73 additions and 14 deletions

View file

@ -17,16 +17,18 @@
package main package main
import ( import (
"bufio"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"maps" "maps"
"os" "os"
"regexp" "regexp"
"slices" "slices"
"github.com/ethereum/go-ethereum/common"
"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/log"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -34,33 +36,52 @@ import (
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. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).",
ArgsUsage: "<path>", ArgsUsage: "<path>",
Flags: slices.Concat([]cli.Flag{ Flags: slices.Concat([]cli.Flag{
DumpFlag, DumpFlag,
HumanReadableFlag, HumanReadableFlag,
RunFlag, RunFlag,
WitnessCrossCheckFlag, WitnessCrossCheckFlag,
FuzzFlag,
}, traceFlags), }, traceFlags),
} }
func blockTestCmd(ctx *cli.Context) error { func blockTestCmd(ctx *cli.Context) error {
path := ctx.Args().First() path := ctx.Args().First()
if len(path) == 0 {
return errors.New("path argument required") // If path is provided, run the tests at that path.
if len(path) != 0 {
var (
collected = collectFiles(path)
results []testResult
)
for _, fname := range collected {
r, err := runBlockTest(ctx, fname)
if err != nil {
return err
}
results = append(results, r...)
}
report(ctx, results)
return nil
} }
var ( // Otherwise, read filenames from stdin and execute back-to-back.
collected = collectFiles(path) scanner := bufio.NewScanner(os.Stdin)
results []testResult for scanner.Scan() {
) fname := scanner.Text()
for _, fname := range collected { if len(fname) == 0 {
r, err := runBlockTest(ctx, fname) return nil
}
results, err := runBlockTest(ctx, fname)
if err != nil { if err != nil {
return err return err
} }
results = append(results, r...) // During fuzzing, we report the result after every block
if !ctx.IsSet(FuzzFlag.Name) {
report(ctx, results)
}
} }
report(ctx, results)
return nil return nil
} }
@ -79,6 +100,11 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
} }
tracer := tracerFromFlags(ctx) tracer := tracerFromFlags(ctx)
// Suppress INFO logs during fuzzing
if ctx.IsSet(FuzzFlag.Name) {
log.SetDefault(log.NewLogger(log.DiscardHandler()))
}
// Pull out keys to sort and ensure tests are run in order. // Pull out keys to sort and ensure tests are run in order.
keys := slices.Sorted(maps.Keys(tests)) keys := slices.Sorted(maps.Keys(tests))
@ -88,16 +114,35 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
if !re.MatchString(name) { if !re.MatchString(name) {
continue continue
} }
test := tests[name]
result := &testResult{Name: name, Pass: true} result := &testResult{Name: name, Pass: true}
if err := tests[name].Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) { var finalRoot *common.Hash
if err := test.Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) {
if ctx.Bool(DumpFlag.Name) { if ctx.Bool(DumpFlag.Name) {
if s, _ := chain.State(); s != nil { if s, _ := chain.State(); s != nil {
result.State = dump(s) result.State = dump(s)
} }
} }
// Capture final state root for end marker
if chain != nil {
root := chain.CurrentBlock().Root
finalRoot = &root
}
}); err != nil { }); err != nil {
result.Pass, result.Error = false, err.Error() result.Pass, result.Error = false, err.Error()
} }
// Always assign fork (regardless of pass/fail or tracer)
result.Fork = test.Network()
// Assign root if test succeeded
if result.Pass && finalRoot != nil {
result.Root = finalRoot
}
// When fuzzing, write results after every block
if ctx.IsSet(FuzzFlag.Name) {
report(ctx, []testResult{*result})
}
results = append(results, *result) results = append(results, *result)
} }
return results, nil return results, nil

View file

@ -55,6 +55,11 @@ var (
Usage: "benchmark the execution", Usage: "benchmark the execution",
Category: flags.VMCategory, Category: flags.VMCategory,
} }
FuzzFlag = &cli.BoolFlag{
Name: "fuzz",
Usage: "adapts output format for fuzzing",
Category: flags.VMCategory,
}
WitnessCrossCheckFlag = &cli.BoolFlag{ WitnessCrossCheckFlag = &cli.BoolFlag{
Name: "cross-check", Name: "cross-check",
Aliases: []string{"xc"}, Aliases: []string{"xc"},

View file

@ -116,6 +116,15 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
if !ok { if !ok {
return UnsupportedForkError{t.json.Network} return UnsupportedForkError{t.json.Network}
} }
return t.run(config, snapshotter, scheme, witness, tracer, postCheck)
}
// Network returns the network/fork name for this test.
func (t *BlockTest) Network() string {
return t.json.Network
}
func (t *BlockTest) run(config *params.ChainConfig, snapshotter bool, scheme string, witness bool, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) {
// import pre accounts & construct test genesis block & state root // import pre accounts & construct test genesis block & state root
// Commit genesis state // Commit genesis state
var ( var (
@ -260,7 +269,7 @@ func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error)
} }
if b.BlockHeader == nil { if b.BlockHeader == nil {
if data, err := json.MarshalIndent(cb.Header(), "", " "); err == nil { if data, err := json.MarshalIndent(cb.Header(), "", " "); err == nil {
fmt.Fprintf(os.Stderr, "block (index %d) insertion should have failed due to: %v:\n%v\n", fmt.Fprintf(os.Stdout, "block (index %d) insertion should have failed due to: %v:\n%v\n",
bi, b.ExpectException, string(data)) bi, b.ExpectException, string(data))
} }
return nil, fmt.Errorf("block (index %d) insertion should have failed due to: %v", return nil, fmt.Errorf("block (index %d) insertion should have failed due to: %v",