Skip to content

execution/vm: add --use-gevm flag to opt-in to GEVM as the EVM#21070

Closed
Giulio2002 wants to merge 2 commits intoerigontech:mainfrom
Giulio2002:gevm-integration
Closed

execution/vm: add --use-gevm flag to opt-in to GEVM as the EVM#21070
Giulio2002 wants to merge 2 commits intoerigontech:mainfrom
Giulio2002:gevm-integration

Conversation

@Giulio2002
Copy link
Copy Markdown
Collaborator

Summary

Adds an opt-in --use-gevm CLI flag on both cmd/erigon and cmd/integration that routes block execution through GEVM instead of the legacy go-ethereum-derived interpreter under execution/vm/. Default unset — legacy interpreter and all existing code paths unchanged.

GEVM is brought in as a regular Go module (require github.com/Giulio2002/gevm, no replace to a local path). The companion GEVM PR is Giulio2002/gevm#2; this PR pins commit 92fa74d.

Why

GEVM is a separately-maintained EVM that's faster than the legacy interpreter on real workloads. This wiring lets Erigon optionally drive state transitions through it without taking on a hard dependency: legacy stays the default, the binary still ships, every existing test still runs.

What's wired

  • CLI flag: --use-gevm on cmd/erigon and cmd/integration (every stage subcommand). Plumbed via node/cli/flags.gonode/ethconfigvm.Config.UseGevm.
  • Adapter (execution/vm/gevm/adapter.go, new): BlockExecutor wraps gevmhost.Evm for the block lifecycle — NewBlockExecutor in InitializeBlock, ExecuteTx per tx, FinalizeBlock once. Reads route through state.NewReaderV3(domains.AsGetter(tx)) — the same thin reader legacy IBS uses; no adapter-side cache. Writes go through Erigon's existing stateWriter; GEVM's journal flushes once per block.
  • Staged sync (execution/stagedsync/exec3_serial.go, exec3_gevm.go): executeBlockGevm branch when cfg.vmConfig.UseGevm. parallel = false is forced when GEVM is on (the BlockExecutor is serial-only by design — races on block-scoped state if the parallel executor runs).
  • Tests (execution/tests/testutil/, execution/execmodule/execmoduletester/): USE_GEVM=1 env var routes the EEST and state-test harness through the GEVM path so both interpreters are exercised by make test-short.

What's NOT in scope

  • RPC trace / simulation APIs (rpc/jsonrpc/, rpc/transactions/, rpc/rpchelper/, cmd/rpcdaemon/) continue to use the legacy interpreter regardless of --use-gevm. The flag only swaps the execution-path interpreter, not the RPC-side trace/sim helpers.
  • Amsterdam (Glamsterdam) EIPs — out of scope for this swap; mainnet is currently on Fusaka.
  • No changes to execution/vm/ legacy interpreter source — it stays as upstream ships it.

Test plan

  • go build ./... clean
  • make test-short passes with --use-gevm UNSET (no regression to legacy path)
  • make test-short passes with USE_GEVM=1 for the in-scope packages (execution/vm/..., execution/exec/..., execution/stagedsync/..., execution/runtime/..., execution/tests/...)
  • EEST blockchain test suite (execution/tests/eest_*) passes on both paths
  • integration stage_exec --reset --chain mainnet --datadir <pre-synced> followed by BATCH_COMMITMENTS=false integration stage_exec --no-commit --chain mainnet --datadir <pre-synced> (legacy serial replay) and BATCH_COMMITMENTS=false integration stage_exec --no-commit --use-gevm --chain mainnet --datadir <pre-synced> (GEVM serial replay) both complete with per-block state-root match against canonical mainnet over block range 24,978,234–24,980,902 (2,668 Fusaka blocks); zero Wrong trie root on either run

Performance (Fusaka mainnet, stage_exec --no-commit, serial executor for both)

run wall-clock
legacy (EXEC3_PARALLEL=false BATCH_COMMITMENTS=false) 253.00 s
GEVM (BATCH_COMMITMENTS=false --use-gevm) 207.74 s
ratio 1.218× (GEVM 21.8% faster)

Both runs verified correct end-to-end on the same 2,668-block window with per-block [commitment] processed state-root checks active.

Origin

Generated by an automated worker→verifier loop (gpt-5.5 worker + gpt-5.5 verifier) over ~30 attempts. The verifier framework gates on six independent checks: no cheating diffs, GEVM module wired, --use-gevm flag wired, stage_exec correctness + ≥1.19× perf, full Go test suite passes on both paths, and EEST fixtures pass on both paths. All six green on attempt 30.

🤖 Generated by an automated worker→verifier loop.

Adds an opt-in `--use-gevm` flag on both `cmd/erigon` and
`cmd/integration` that selects GEVM (github.com/Giulio2002/gevm)
as the EVM driving block execution. Default unset — legacy
interpreter unchanged.

Wiring
- node/cli, node/ethconfig, node/eth/backend, cmd/utils:
  --use-gevm flag plumbed into ethconfig and through into the
  staged-sync executor's vm.Config.
- cmd/integration/commands/{flags,stages}.go: --use-gevm on
  every stage subcommand (stage_exec is what V03/V05 measure).
- execution/vm/interpreter.go: vm.Config gains a UseGevm bool.

Adapter
- execution/vm/gevm/ (new): BlockExecutor wrapping
  gevmhost.Evm. Per-block lifecycle: NewBlockExecutor in
  InitializeBlock; ExecuteTx per tx; FinalizeBlock once.
  Reads via state.NewReaderV3(domains.AsGetter(tx)) — exact
  same path legacy IBS uses, no adapter cache. Writes through
  Erigon's existing stateWriter; GEVM's journal flushes once
  per block at FinalizeBlock.
- execution/stagedsync/exec3_gevm.go (new): per-block dispatch
  shim used by exec3_serial.go's executeBlockGevm path.
- execution/tests/testutil/gevm.go (new): plumbs USE_GEVM=1
  through the EEST + state-test harness so the GEVM path is
  exercised in tests.

Staged-sync
- execution/stagedsync/exec3_serial.go: executeBlockGevm
  branch — when cfg.vmConfig.UseGevm, route to BlockExecutor;
  otherwise legacy TxnExecutor unchanged.
- execution/stagedsync/{exec3,stage_execute,stageloop}.go:
  force parallel=false on the GEVM path (BlockExecutor is
  serial-only by design, races on block-scoped state if
  EXEC3_PARALLEL=true).
- execution/state/{rw_v3,history_reader_v3}.go: minor
  per-block-scoped helpers used by the adapter; legacy paths
  unchanged.

Tests
- execution/execmodule/execmoduletester/exec_module_tester.go,
  execution/tests/testutil/{block,state}_test_util.go:
  testutil reads USE_GEVM env var and propagates UseGevm into
  vm.Config so EEST + state-tests run on either interpreter.
- execution/builder/exec.go, execution/exec/block_assembler.go,
  execution/tracing/calltracer/calltracer.go,
  execution/commitment/commitmentdb/commitment_context.go,
  cl/spectest/spectest/suite.go: minor signature plumbing.

Module
- go.mod / go.sum: require github.com/Giulio2002/gevm pinned
  at commit 92fa74d (companion PR
  Giulio2002/gevm#2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Giulio2002 Giulio2002 marked this pull request as draft May 8, 2026 14:39
GEVM dropped the parallel ReaderOps callback surface and unified
on the state.Database interface. Update the Erigon adapter:

- New gevmDatabase struct wraps erigonstate.StateReader (with the
  *Raw fast-path when the reader exposes it) and implements all
  six gevmstate.Database methods directly.
- NewEvm replaces NewEvmWithReaderOps in the construction site.
- readerOps() helper removed.

CodeByHash and BlockHash on the wrapper return zero/nil — neither
is exercised on the Erigon path: Code(address) is the loadCode
read shape, and BlockEnv.GetHash is the BLOCKHASH source.

go.mod re-pinned to the matching GEVM commit
(5376a47e22e4eb29b2a3231131a1cc1a4e1b678e).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Giulio2002
Copy link
Copy Markdown
Collaborator Author

will make a new one

@Giulio2002 Giulio2002 closed this May 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant