Skip to content

[rpc] Reify exceptions as error values in eth_simulateV1 - #2559

Open
dhil wants to merge 1 commit into
dhil/simulation-error-2from
dhil/simulation-error-3
Open

dhil wants to merge 1 commit into
dhil/simulation-error-2from
dhil/simulation-error-3

Conversation

@dhil

@dhil dhil commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

This patch installs an exception handler in the body of eth_simulateV1 to catch exceptions thrown from deep within the execution stack, turning them into appropriate error values. This change enchances the modularity and reusability of eth_simulateV1 in terms of external code interfacing with it.

@dhil
dhil added this pull request to stack #2558 September 14, 2026 20:59
Copilot AI lite review requested due to automatic review settings September 14, 2026 20:59
Comment on lines +233 to +234
BOOST_OUTCOME_TRY(
apply_state_overrides(block_state, incarnation, state_overrides));

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.

[P2] This changes the observable eth_call behavior, not just eth_simulateV1: previously a can_merge failure threw MonadException, which the eth_call submit lambda's catch (MonadException const &) handler mapped to EVMC_INTERNAL_ERROR; now the error flows through the res.has_error() branch (monad_executor.cpp:1541), which unconditionally reports EVMC_REJECTED. If that status change for state-override failures on eth_call isn't intended, the eth_call error branch should map the status via simulation_error_info the way the eth_simulate path does (monad_executor.cpp:2027).

Comment on lines +1089 to +1092
catch (...) {
return Result<nlohmann::json>::error_type{
SimulationError::InternalError};
}

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.

[P3] catch (...) collapses every MonadException thrown inside eth_simulate_impl (e.g. MONAD_ASSERT_THROW(sender.has_value(), RECOVER_SENDER_ERR_MSG) in recover_senders_and_authorities, reached via the parent/grandparent context loads at lines 809/824) into the generic "internal error" message. The outer handler in submit_eth_simulate_to_pool previously surfaced e.message() to the client with the same EVMC_INTERNAL_ERROR status, so this is a diagnosability regression for RPC users; consider a preceding catch (MonadException const &e) that logs or preserves the specific message.

@github-actions github-actions Bot 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.

Review Summary

This PR converts the MONAD_ASSERT_THROW in apply_state_overrides into a Result<void> returning SimulationError::StateOverrideFailure, and wraps eth_simulate_impl in a function-level try/catch (...) that reifies escaped exceptions as SimulationError::InternalError. The new enum values are appended (no renumbering), both are handled in the simulation_status switch and the quick_status_code_from_enum mappings, and simulation_error_test.cpp covers the new messages. The enum is C++-only and does not cross the bindgen FFI boundary; monad_executor.h is untouched. Replacing a throw with an error value in RPC-facing code is a good direction, and the single-commit PR is well scoped.

Two findings, neither blocking:

  • [P2] The apply_state_overrides signature change also alters eth_call behavior: a state-override merge failure now surfaces as EVMC_REJECTED (via the generic res.has_error() branch at monad_executor.cpp:1541) instead of the previous EVMC_INTERNAL_ERROR from the MonadException handler. The PR description scopes the change to eth_simulateV1, so this looks unintended.
  • [P3] The blanket catch (...) in eth_simulate_impl discards MonadException messages (e.g. "failed to recover sender") that the outer handler previously returned to the client, degrading them to a generic "internal error".

Verdict: CORRECT

🤖 Generated with Claude Code

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.

🟡 Changes recommended

Resolve the changed eth_call error/status behavior before approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR converts eth_simulateV1 execution exceptions and state-override failures into structured error values.

Changes:

  • Adds StateOverrideFailure and InternalError.
  • Maps errors to statuses and messages.
  • Adds error-mapping tests.
  • Catches deep execution exceptions during simulation.
File summaries
File Description
category/rpc/simulation_error.hpp Adds simulation error types.
category/rpc/simulation_error.cpp Maps errors to statuses and messages.
category/rpc/simulation_error_test.cpp Tests new error mappings.
category/rpc/monad_executor.cpp Reifies state and execution failures.
Review details

Suppressed comments (1)

category/rpc/monad_executor.cpp:196

  • [P2] This helper is also used by eth_call_impl, whose caller treats every Result error as EVMC_REJECTED (see monad_executor.cpp:1541-1545). Before this change a failed can_merge threw MonadException, so the outer handler returned EVMC_INTERNAL_ERROR; now the same state-override failure is propagated as SimulationError and reported as rejected. Preserve the previous exception/status behavior for eth_call or map this specific error to an internal status there.
        if (MONAD_UNLIKELY(!block_state.can_merge(state))) {
            return SimulationError::StateOverrideFailure;
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +1089 to +1092
catch (...) {
return Result<nlohmann::json>::error_type{
SimulationError::InternalError};
}
This patch installs an exception handler in the body of
`eth_simulateV1` to catch exceptions thrown from deep within the
execution stack, turning them into appropriate error values. This
change enchances the modularity and reusability of `eth_simulateV1` in
terms of external code interfacing with it.
@Baltoli
Baltoli force-pushed the dhil/simulation-error-3 branch from 6521601 to d8fb5c7 Compare September 15, 2026 11:45
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