Skip to content

fix(api): abort EVM execution when the trace timeout fires#4918

Open
raullenchai wants to merge 1 commit into
masterfrom
fix/evm-cancel-on-trace-timeout
Open

fix(api): abort EVM execution when the trace timeout fires#4918
raullenchai wants to merge 1 commit into
masterfrom
fix/evm-cancel-on-trace-timeout

Conversation

@raullenchai

Copy link
Copy Markdown
Member

Context

Follow-up to #4886 (merged), which made debug_trace enforce its timeout by stopping the tracer. Review of that PR flagged that stopping the tracer alone doesn't cancel the EVM — for the struct logger and native tracers, Stop only sets tracer state so hooks return early and GetResult reports the timeout; the EVM keeps executing opcodes until gas exhaustion. Upstream geth pairs tracer.Stop with evm.Cancel() for exactly this reason. This PR completes that fix. (It was developed alongside #4886 but missed the merge; carrying it here as its own PR.)

Fix

A TraceCanceller travels in the context from the trace entry points (traceContext/traceBlock). executeInEVM registers each EVM's Cancel with it, and the parseTracer timeout watchdog fires tracer.Stop and canceller.Cancel(). EVMs registered after the watchdog fires abort immediately at registration. The consensus path (block production/validation via newWorkingSet/PutBlock) never carries a canceller, so opcode execution there is never abortable.

Non-consensus, non-hardfork — trace/simulation path only.

Testing

  • TestTraceCancellerAbortsEVM: an infinite-loop contract (JUMPDEST;PUSH1 0;JUMP) with a 100M gas budget returns in ~100ms without consuming the budget when cancelled; uncancelled it burns the full limit to out-of-gas.
  • Unit tests for register/Cancel ordering, idempotency, and register-after-fired.
  • go build, evm + api trace tests pass.
  • codex review: CONVERGED.

🤖 Generated with Claude Code

Stopping the tracer alone only stops result collection — the EVM kept
executing until gas exhaustion, undercutting the timeout's DoS protection.
Pair tracer.Stop with EVM cancellation like upstream geth: a TraceCanceller
travels in the context from the trace entry points (traceContext/traceBlock),
executeInEVM registers each EVM's Cancel with it, and the parseTracer
watchdog fires both. Once fired, EVMs created later in a block trace abort
immediately at registration. The consensus path never carries a canceller.

Verified by a regression test executing an infinite-loop contract
(JUMPDEST;PUSH1 0;JUMP) with a 100M gas budget: cancelled run returns in
~100ms without consuming the gas budget; uncancelled it burns the full
limit to out-of-gas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@raullenchai raullenchai requested a review from a team as a code owner July 12, 2026 01:05
@sonarqubecloud

Copy link
Copy Markdown

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.

2 participants