Skip to content

rpc: add eth_call execution timeout via VM deadline polling - #2523

Open
Chen-Yifan wants to merge 1 commit into
mainfrom
vicky/eth-call-timeout
Open

Chen-Yifan wants to merge 1 commit into
mainfrom
vicky/eth-call-timeout

Conversation

@Chen-Yifan

@Chen-Yifan Chen-Yifan commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

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_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: 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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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::Context deadline fields and deadline polling (amortized + unconditional variants), plus StatusCode::Cancelled mapped to EVMC_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.

Comment thread category/rpc/monad_executor.cpp Outdated
@Chen-Yifan
Chen-Yifan force-pushed the vicky/eth-call-timeout branch from cf4ced7 to c805435 Compare August 27, 2026 20:37
@dhil
dhil self-requested a review August 28, 2026 13:47
@Chen-Yifan
Chen-Yifan force-pushed the vicky/eth-call-timeout branch 3 times, most recently from 93850f3 to 868c774 Compare August 28, 2026 18:14
@Chen-Yifan
Chen-Yifan marked this pull request as ready for review August 28, 2026 18:31
Comment thread category/rpc/monad_executor.h
Comment thread category/rpc/monad_executor.h
Comment thread category/rpc/monad_executor.cpp
@Chen-Yifan
Chen-Yifan force-pushed the vicky/eth-call-timeout branch from 868c774 to 102901c Compare August 28, 2026 18:47

@dhil dhil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial review. Mostly just some comments about comments. Though, I think the MONAD_ASSERT must be changed.

Comment thread category/execution/ethereum/execute_transaction.cpp Outdated
Comment thread category/vm/runtime/call.cpp Outdated
Comment thread category/vm/runtime/create.cpp Outdated
Comment thread category/vm/runtime/types.hpp Outdated
Comment thread category/vm/host.hpp Outdated
Comment thread test/vm/unit/monad_vm_interface_tests.cpp
@Chen-Yifan
Chen-Yifan force-pushed the vicky/eth-call-timeout branch 2 times, most recently from 0e994dd to 30e2afc Compare September 16, 2026 15:19
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
Chen-Yifan force-pushed the vicky/eth-call-timeout branch from 30e2afc to 8023c14 Compare September 16, 2026 18:17
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.

3 participants