rpc: add eth_call execution timeout via VM deadline polling - #2523
Open
Chen-Yifan wants to merge 1 commit into
Open
Chen-Yifan wants to merge 1 commit into
Chen-Yifan wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds cooperative eth_call execution timeouts by propagating an absolute steady-clock deadline through vm::Host into runtime::Context, and polling it at existing gas-check sites so interpreter execution can abort without force-cancelling fibers.
Changes:
- Introduce
runtime::Contextdeadline fields and deadline polling (amortized + unconditional variants), plusStatusCode::Cancelledmapped toEVMC_REJECTED. - Wire the deadline through
vm::Host/vm::VM, propagate cancellation across call/create boundaries, and record cancellation on the host for reliable timeout reporting. - Rework RPC eth_call pools from gas-based (low/high) to time-budget pools (short/long) with escalation on timeout; update Rust/C FFI and tests accordingly; add design doc.
Verdict: NEEDS CHANGES
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
test/vm/unit/monad_vm_interface_tests.cpp |
Adds VM-level tests that deadline expiry aborts looping execution and that no-deadline runs to OOG. |
rust/crates/monad-ethcall/src/executor/call.rs |
Updates FFI call site to match removed gas_specified parameter. |
docs/eth-call-timeout.md |
New design doc describing deadline polling, propagation, and pool behavior. |
category/vm/vm.cpp |
Copies host deadline into each runtime context and flags host cancellation on StatusCode::Cancelled. |
category/vm/runtime/types.hpp |
Adds deadline state/polling to runtime::Context, adds Cancelled status, and hooks deadline polling into deduct_gas. |
category/vm/runtime/create.cpp |
Propagates cancellation to parent frames via unconditional deadline check after child create returns. |
category/vm/runtime/context.cpp |
Maps Cancelled to EVMC_REJECTED in the EVMC result conversion path. |
category/vm/runtime/call.cpp |
Propagates cancellation to parent frames via unconditional deadline check after child call returns. |
category/vm/interpreter/stack.hpp |
Polls deadline alongside existing interpreter gas/stack checks. |
category/vm/host.hpp |
Adds host-level deadline setter/getter and sticky execution-cancelled flag. |
category/rpc/monad_executor.h |
Updates pool naming (short/long), changes timeout semantics comment, and removes gas_specified from eth_call submit API. |
category/rpc/monad_executor.cpp |
Implements deadline-based cancellation, short→long pool escalation, removes gas clamp/OOG retry routing, and reports timeouts via host cancellation flag. |
category/rpc/monad_executor_test.cpp |
Updates tests to match new eth_call submit signature and removal of gas clamping behavior. |
category/execution/ethereum/evmc_host.hpp |
Updates size assertions for vm::Host and derived host types after adding deadline/cancellation fields. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Chen-Yifan
force-pushed
the
vicky/eth-call-timeout
branch
from
August 27, 2026 20:37
cf4ced7 to
c805435
Compare
dhil
self-requested a review
August 28, 2026 13:47
Chen-Yifan
force-pushed
the
vicky/eth-call-timeout
branch
3 times, most recently
from
August 28, 2026 18:14
93850f3 to
868c774
Compare
Chen-Yifan
marked this pull request as ready for review
August 28, 2026 18:31
Chen-Yifan
force-pushed
the
vicky/eth-call-timeout
branch
from
August 28, 2026 18:47
868c774 to
102901c
Compare
dhil
reviewed
Sep 15, 2026
dhil
left a comment
Contributor
There was a problem hiding this comment.
Initial review. Mostly just some comments about comments. Though, I think the MONAD_ASSERT must be changed.
Chen-Yifan
force-pushed
the
vicky/eth-call-timeout
branch
2 times, most recently
from
September 16, 2026 15:19
0e994dd to
30e2afc
Compare
CAVEAT: transaction timeout does not apply to precompiles. eth_call previously enforced its timeout only at queue pickup; once execution started, a slow call could pin a PriorityPool fiber indefinitely. Fibers are cooperatively scheduled, so cancellation must be cooperative: runtime::Context now carries an absolute deadline that the VM polls at its gas checks (per opcode in the interpreter, and in deduct_gas for the shared runtime functions), amortizing the clock read over 256 checks. When the deadline passes, execution exits through the existing stack unwind with the new StatusCode::Cancelled. Paths that arm no deadline pay one load and one never-taken branch per check. A cancelled child frame cancels every ancestor via an unconditional deadline check after each call/create return, so the abort cannot be mistaken for an ordinary failed subcall and produce a nondeterministic result. The VM records the cancellation on vm::Host, and the executor reports timeouts from that flag rather than inferring them from the status code (EVMC_REJECTED has other producers, e.g. precompiles) or the wall clock (a call that completes just past the deadline is a valid result and is returned). The RPC pools are reclassified from gas to time, with queuing and execution budgeted separately: the low/high gas pools become short_tx/long_tx pools, each with a queue timeout enforced at pickup (an over-waited call is rejected as busy, without executing) and an execution timeout measured from pickup that arms the VM deadline. The gas clamp with its out-of-gas retry (MONAD_ETH_CALL_LOW_GAS_LIMIT) is removed since client gas limits are not a usable routing signal. Every call starts in the short pool; a call whose execution is cancelled there escalates to the long pool, where it queues anew and gets that pool's execution budget. A cancellation in the long pool is final. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chen-Yifan
force-pushed
the
vicky/eth-call-timeout
branch
from
September 16, 2026 18:17
30e2afc to
8023c14
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CAVEAT: timeout does not apply to precompiles.
eth_call previously enforced its timeout only at queue pickup; once execution started, a slow call could pin a PriorityPool fiber indefinitely. Fibers are cooperatively scheduled, so cancellation must be cooperative: runtime::Context now carries an absolute deadline that the VM polls at its gas checks (per opcode in the interpreter, and in deduct_gas for the shared runtime functions), amortizing the clock read over 256 checks. When the deadline passes, execution exits through the existing stack unwind with the new StatusCode::Cancelled. Paths that arm no deadline pay one load and one never-taken branch per check.
A cancelled child frame cancels every ancestor via an unconditional deadline check after each call/create return, so the abort cannot be mistaken for an ordinary failed subcall and produce a nondeterministic result. The VM records the cancellation on vm::Host, and the executor reports timeouts from that flag rather than inferring them from the status code (
EVMC_REJECTEDhas other producers, e.g. precompiles) or the wall clock (a call that completes just past the deadline is a valid result and is returned).The RPC pools are reclassified from gas to time: the low/high gas pools become short_tx/long_tx timeout pools, each with a queue timeout enforced at pickup, and an execution VM timeout. The gas clamp with its out-of-gas retry (
MONAD_ETH_CALL_LOW_GAS_LIMIT) is removed since client gas limits are not a usable routing signal. Every call starts in the short pool; a call cancelled with a timeout there escalates to the long pool with a fresh budget, and a timeout in the long pool is final.Expect almost zero effect for vm performance for execution, which doesn't enforce any timeout.