Repository navigation
Conversation
The three places that finalise an evmc_result all ended with
Result.gas_left = <instance>.getGas();
which overwrites whatever the halt paths had already decided. The instance's
gas counter is not authoritative after a halt: the interpreter zeroes the
*frame's* gas (interpreter.cpp) and the JIT trap path never touches gas at
all, so an exceptional halt handed the caller back gas the EVM had already
burned. evmone applies the opposite rule in make_execution_result - "an
exceptional halt consumes all gas; only a success or revert keeps gas_left" -
and that is the consensus behaviour.
Route the three sites through one helper that applies that rule. Observed on
mainnet blocks 25818502 and 25818530, where a nested frame halted with
EVMC_INVALID_MEMORY_ACCESS and returned 339011 of its 440977 gas limit.
This is necessary but not sufficient for those two blocks: the halt itself is
a separate JIT defect (a null-pointer dereference inside JIT'd code that the
trap handler reports as out-of-bounds memory). Both blocks replay correctly
under mode=interpreter, so the remaining defect is JIT-only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0187PTseLY2NbzxKrnstDWwS
|
No benchmark summary available. |
|
No benchmark summary available. |
⚡ Performance Regression Check Results
|
|
Superseded by DTVMStack#605, which targets upstream |
|
Correction to the description above, for anyone reading this closed PR as history. The paragraph citing
The null faulting address itself comes from the signal, not from the handler's frame guess, so "the fault is at a null pointer" stands; only the corroborating The JIT defect behind blocks 25818502 and 25818530 remains open and is being investigated with a read that bypasses the handler's frame attribution entirely. |
|
Follow-up: reason 2 in my correction above is itself wrong, and the conclusion has changed. Amending rather than editing, to keep the trail. Reason 2 (frame misattribution) is withdrawn. A read that bypasses the handler entirely — Reason 1 is now confirmed, and is sufficient on its own. The dump shows So the original conclusion stands on firmer ground than the reasoning I gave for it: The defect is no longer open. The mechanism is lazy allocation, not nested calls: a frame starts with |
|
Current state of the record — this comment supersedes my two above. They are left in place for the trail, but read this one. The thread got hard to follow because I corrected twice and overstated once. Consolidated: On
|
The three places that finalise an
evmc_resultall ended withwhich unconditionally overwrites whatever the halt paths had already decided.
The instance's gas counter is not authoritative after a halt: the interpreter
zeroes the frame's gas, and the JIT trap path never touches gas at all. So
an exceptional halt handed the caller back gas the EVM had already burned.
evmone states the opposite rule directly, in
make_execution_result:This routes the three sites through one helper that applies that rule.
How it was found
Mainnet witness replay, blocks 25818502 and 25818530: a nested frame halted with
EVMC_INVALID_MEMORY_ACCESSand returned 339011 of its 440977 gas limit. Thehost bridge (abmcar/reth#1) rejects any halted result that retained gas, which is what surfaced
it.
Regression risk
Low, and bounded by construction. The bridge already rejected every result
that was both halted and carrying non-zero gas, so the only executions whose
behaviour changes are ones that were already failing. Under the interpreter the
frame's gas was zeroed anyway; the difference is that the frame's decision now
survives to the caller.
This is necessary but not sufficient for those two blocks
The halt itself is a separate JIT defect. Trap instrumentation shows
faulting=(nil)withmemsize=1088— a null-pointer dereference inside JIT'dcode, far from the 16 MB memory bound, which the trap handler classifies
uniformly as out-of-bounds memory and reports as
EVMC_INVALID_MEMORY_ACCESS.Decisive isolation: same library, same two blocks, with
mode=interpreterbothpass, with access counts matching the reference leg exactly (5153/5153 and
3296/3296) and verified post-state roots. So the remaining defect is JIT-only
— not EVM semantics, not the host bridge.
With only this gas fix applied, block 25818502 no longer triggers a host fault
but its block gas used still disagrees (27730760 against an expected 27644811),
and 25818530 times out. The null dereference is still open and being tracked
separately.
Base
Branches from
feat/evm-persistent-code-cacheat35d2f13, so the diff is thischange alone. The commit was cherry-picked over
37b36b1(the benchmark cacheknob, sent as its own PR) to keep the two independent; the patch and message are
byte-identical to the original —
git patch-idmatches. Note that the validatedlibrary (
bbddb301…) was built from the original commit in place; this exacttree has not itself been compiled.