refactor: make Context and Evm generic over DB type#20
Conversation
Add the journal/database plumbing required for correct EIP-7928 Block Access List construction: - primitives: fix TX_GAS_LIMIT_CAP to 16777216 (EIP-7825 value) - database: FallbackFns vtable gains commit_tx/discard_tx/snapshot_frame/ commit_frame/revert_frame/untrack_address hooks so a stateless witness DB can track accesses with frame-accurate reverts - context/journal: lazy access-list pre-warming (warmAccessList / setAccessList) replaces eager loadAccountWithCode+sload; adds isAddressCold/isStorageCold predicates; StorageChanged journal entry now records old_was_written for correct revert; snapshotFrame/commitFrame/revertFrame forwarded to db - handler/mainnet_builder: use warmAccessList for EIP-2929 pre-warming; call snapshotFrame/revertFrame around top-level CALL value transfer - interpreter/host: propagate snapshotFrame/commitFrame/revertFrame through CALL/CREATE frame lifecycle - interpreter/opcodes/call + host_ops: use isAddressCold/isStorageCold for pre-call gas checks without triggering DB loads; untrack_address on OOG - state: storage slot gains was_written flag for revert correctness Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove DBG prints from InMemoryDB.basic and JournalInner.setCodeWithHash - Fix isAddressCold: only treat coinbase as cross-tx warm (EIP-3651), not all previously-loaded addresses, to avoid unintended gas discounts for precompiles and access-list entries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split the CALL instruction OOG check into two stages to match EELS: 1. If remaining < call_cost_no_delegation: untrack target (oog_before_target_access) 2. If remaining < base_cost (includes delegation): halt OOG without untracking target (oog_after_target_access and oog_success_minus_1 — target IS in BAL, delegation NOT) Also add unconditional untrackAddress for OOG in EXTCODECOPY and CALL-type opcodes when gas is exhausted before the target is accessed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove FallbackFns C-style vtable and InMemoryDB.fallback field - Make Context generic: Context(comptime DB: type); DefaultContext = Context(InMemoryDB) - Add Journal(DB) tracking wrappers guarded by @hasDecl (snapshotFrame, commitFrame, revertFrame, commitTracking, discardTracking, notifyStorageSlotCommit, hasNonZeroStorageForAddress, untrackAddress, forceTrackAddress) - Replace Host.ctx with type-erased JournalVTable (18 entries); Host.init(DB, ctx, prec) and Host.fromCtx(ctx, prec) constructors; opcode handlers unchanged - Make Evm generic: EvmFor(comptime DB: type); Evm = EvmFor(InMemoryDB) alias - Make MainnetHandler.* functions accept anytype evm for zero-cost duck typing - Fix getDb() dangling pointer: change self: @this() → self: *const @this() - Update test files to use Host.fromCtx() instead of Host{ .ctx = ... } literal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecute - Add nonzero_storage_count index to InMemoryDB maintained by new private putStorage helper; hasNonZeroStorageForAddress is now O(1) instead of O(n) storage scan on every CREATE - Drop explicit `comptime DB: type` parameter from Frame.execute; infer DB via anytype ctx to remove parameter sprawl Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ransfer failure - withCfg and modifyCfgChained now call setSpecId on the returned context's journaled_state so the journal spec stays in sync - setupCallCore now calls revertFrame() before checkpointRevert() on the two early-return paths when value transfer fails, matching the pattern in mainnet_builder.zig Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| return false; | ||
| } | ||
| return self.warm_addresses.isCold(address); | ||
| } |
There was a problem hiding this comment.
isAddressCold misses access-list check causing false OOG
High Severity
The new isAddressCold only checks coinbase for addresses in evm_state with a stale transaction_id, but loadAccountMutOptionalCode (unchanged) checks the full warm_addresses.isCold(address) which also covers precompiles and access-list entries. In multi-tx block execution, an access-list address loaded in a prior tx will be reported as cold by isAddressCold but warm by accountInfo. The CALL opcode pre-check at line 138–145 uses isAddressCold and will incorrectly halt with OOG when gas is between WARM_ACCOUNT_ACCESS (100) and COLD_ACCOUNT_ACCESS (2600).
Additional Locations (1)
|
What is the reasoning behind using a witness db in zevm-stateless (and lazily decode) versus using an in-memory db where we pre-load with witness (and use a hash pool to prevent unecessary re-hashing) ? It seems fine to have a generic db interface, I am wondering what the reasoning is behind using a witness db though. If it is performance considerations, do you have performance numbers that show the witness db to be faster? moreover, I think this statement makes a false claim:
|
| /// Inner context. | ||
| chain: void, |
There was a problem hiding this comment.
what is chain with type void? should it be in the interface if it has no type and no constraints?
it looks like a pre-existing vestige. We can leave it I guess, but for a stateless client, all of the chain methods and the void type are just weird baggage.


Summary
Makes
ContextandEvmgeneric over the database type, enabling zevm to execute against any storage backend — not just the built-inInMemoryDB.Changes
Context(comptime DB: type)—Contextis now a generic type parameterized on the database.DefaultContext = Context(InMemoryDB)is exported as an alias so all existing call sites are unchanged.EvmFor(comptime DB: type)—Evmis now a generic type parameterized on the database.Evm = EvmFor(InMemoryDB)preserves the existing concrete type.FallbackFnsvtable — the comptime DB dispatch makes it unnecessary.Why a generic Context is necessary
On
main,Contextholdsjournaled_state: Journal(InMemoryDB)— the database type is hardcoded. This works for the standard execution path (blockchain tests, t8n) where all pre-state is loaded intoInMemoryDBupfront.zevm-stateless needs a different execution mode: stateless block execution. Instead of rebuilding the full pre-state in memory, it executes directly against a `WitnessDatabase` that serves every account/storage read by running a live MPT proof against the pre-state root:
Every
basic(address)/storage(address, key)call cryptographically verifies that the returned value is consistent withpre_state_root. This is the core security guarantee of stateless execution — without it, a prover could supply arbitrary account balances.Using
InMemoryDBfor this would require pre-decoding all MPT witness nodes and loading them into a hashmap upfront, which defeats the purpose: there would be no proof verification and no way to detect a malicious witness.Because Zig requires struct fields to have a compile-time-known type, `Context` must be parameterized on the DB type for this to work. `Journal(DB)` was already generic on `main`; this PR extends that to
ContextandEvm.Impact on existing users
None. All existing code that used
ContextorEvmcontinues to work via theDefaultContextandEvmaliases which resolve toContext(InMemoryDB)andEvmFor(InMemoryDB)respectively.Test plan
🤖 Generated with Claude Code
Note
High Risk
High risk because it refactors core execution plumbing (
Context,Evm,Host, journaling) and changes gas/account-access behavior around cold/warm checks, which can affect correctness of execution, state commits, and EIP-specific accounting.Overview
Makes execution generic over the database backend.
Contextis replaced withContext(DB)and aDefaultContext = Context(InMemoryDB)alias, and handlerEvmbecomesEvmFor(DB)with the existingEvmpreserved as theInMemoryDBspecialization.Reworks interpreter↔state integration to support typed DBs and correct access tracking.
Hostnow type-erases the journal behind aJournalVTable, adds cold-check helpers, and updates call/create setup/finalization to snapshot/commit/revert per-frame tracking; the mainnet handler now warms access lists without eager DB loads and commits/discards per-tx tracking alongsidecommitTx.Improves Block Access List (EIP-7928) correctness and CREATE collision checks. Opcodes (
CALL,BALANCE,EXTCODE*,SLOAD,SELFDESTRUCT) now charge warm/cold gas before loading from DB and explicitlyuntrackAddresson OOG paths to avoid recording phantom accesses;Journaladds cold-check APIs, storage write revert metadata, and optional DB hooks, whileInMemoryDBgains an O(1)hasNonZeroStorageForAddressindex used for CREATE address-collision detection.Written by Cursor Bugbot for commit b85262d. This will update automatically on new commits. Configure here.