Skip to content

Add latest flashblock simulation mode - #923

Open
karankurbur wants to merge 4 commits into
mainfrom
codex/use-latest-flashblock-simulate
Open

Add latest flashblock simulation mode#923
karankurbur wants to merge 4 commits into
mainfrom
codex/use-latest-flashblock-simulate

Conversation

@karankurbur

@karankurbur karankurbur commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add optional useLatestFlashblock request parameter to simulate_unsignedUserOp, defaulting to false.
  • Wire the simulate RPC to use the latest executed flashblock state when requested, while preserving the existing latest/full-block execution path otherwise.
  • Validate flashblock usage at the endpoint boundary: reject when flashblocks are disabled, when no latest flashblock exists yet, or when an explicit block is also provided.
  • Add endpoint-level integration tests for the new flashblock guard behavior.

Validation

  • cargo fmt
  • cargo test -p world-chain-rpc
  • cargo check -p world-chain-node
  • cargo check
  • git diff --check

cargo clippy -p world-chain-rpc --all-targets -- -D warnings could not run because this pinned nightly reports cargo-clippy is not applicable to nightly-2026-07-01-aarch64-apple-darwin.


Note

Medium Risk
Changes how simulation state is chosen for a new RPC flag and depends on flashblock pending state staying consistent with ETH pending behavior; default behavior is unchanged.

Overview
Adds optional useLatestFlashblock (default false) on simulate_unsignedUserOp so simulations can run against the latest executed flashblock instead of a canonical block.

When the flag is set, block/state resolution reads the pending flashblock from a shared watch receiver, uses that block’s header, and builds state from the parent block plus a BlockState overlay over the executed flashblock. The existing block-id path is unchanged when the flag is off.

The node threads the same pending_block() receiver used for flashblocks ETH APIs into WorldChainAddOns and the Simulate RPC via with_latest_flashblock. Requests are rejected if flashblocks are disabled, no flashblock is available yet, or block is set together with useLatestFlashblock.

Unit and integration tests cover deserialization, param validation, and the RPC error paths.

Reviewed by Cursor Bugbot for commit a018d3e. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5d05519. Configure here.

Comment thread crates/rpc/src/simulate.rs

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 an opt-in simulation mode for simulate_unsignedUserOp that runs against the latest executed flashblock state (when available), while keeping the existing “latest/full-block” resolution as the default behavior.

Changes:

  • Adds useLatestFlashblock (default false) to SimulateUnsignedUserOpRequest and validates it (rejects when block is also provided).
  • Wires the RPC simulate implementation to optionally build a state provider from the latest executed flashblock via a watch::Receiver.
  • Adds unit + endpoint-level integration tests for serde defaults and invalid-parameter guard rails.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
crates/rpc/src/simulate.rs Adds the useLatestFlashblock request flag and routes state/header selection through a new flashblock-backed path when enabled.
crates/rpc/src/lib.rs Re-exports LatestFlashblockReceiver for node integration.
crates/node/src/context.rs Clones the pending-block receiver so it can be supplied to both flashblocks eth_ and simulate wiring.
crates/node/src/add_ons.rs Threads the pending-block receiver into Simulate::from_eth_api when the simulate namespace is enabled.
crates/rpc/tests/simulate_latest_flashblock.rs New integration tests asserting INVALID_PARAMS errors for flashblock-disabled, missing flashblock, and conflicting block inputs.
crates/rpc/Cargo.toml Adds world-chain-test-utils as a dev-dependency for the new integration test.
Cargo.lock Lockfile update for the new dev-dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor Author

Addressed the parent state lookup review note in latest_flashblock_state: the flashblock overlay now uses state_by_block_hash(header.parent_hash) instead of history_by_block_hash(...), so it can resolve live tip/in-memory parent state.

Local follow-up validation:

  • cargo fmt passed
  • git diff --check passed
  • Cargo compile/test rerun was blocked in this fresh checkout by local disk space while building librocksdb-sys (No space left on device, filesystem had ~1.1GiB free).

Copy link
Copy Markdown
Contributor Author

Backwards compatibility pass pushed in 97f95b63:

  • Restored the original Simulate::new(client, evm_config, task_pool, task_guard) signature.
  • Restored the original Simulate::from_eth_api(client, evm_config, eth_api) signature.
  • Restored the original WorldChainAddOns::new(...) signature.
  • Added explicit .with_latest_flashblock(...) opt-in methods for the new flashblock receiver wiring.
  • Added an endpoint-level integration test proving requests that omit useLatestFlashblock still default to the latest full-block path and do not require flashblocks.

cargo fmt, cargo metadata --no-deps, and git diff --check pass locally. Full cargo test/check reruns remain blocked by local disk space while compiling librocksdb-sys in the fresh checkout (No space left on device, about 1.0GiB free).

Comment on lines 297 to +313
@@ -290,6 +310,7 @@ where
simulate_enabled,
witness,
)
.with_latest_flashblock(pending_block)

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.

nit: is there a way we could avoid adding this plumbing type code to all these places? codex said we could add a trait to FlashblocksEthApi? Something like this

pub trait PendingFlashblockSource {
    fn pending_flashblock_receiver(&self) -> Option<LatestFlashblockReceiver>;
}

codex said,

FlashblocksEthApi could implement this by cloning its optional watch receiver. Then Simulate::from_eth_api could require SpawnBlocking + PendingFlashblockSource and obtain the receiver from registry.eth_api(). That should eliminate the pending_block field and with_latest_flashblock method on WorldChainAddOns, including the repeated forwarding through its generic builder transformations.

just a nit though.

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.

agreed

/// Cross-request cache for resolved token metadata. LRU-bounded.
type MetadataCache = Arc<Mutex<LruCache<Address, AssetInfo>>>;

pub type LatestFlashblockReceiver =

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.

Remove this type alias, it's just an indirection that makes the logic harder to read

Comment on lines 297 to +313
@@ -290,6 +310,7 @@ where
simulate_enabled,
witness,
)
.with_latest_flashblock(pending_block)

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.

agreed


/// Configures access to the latest flashblock-backed pending block, when
/// flashblocks are enabled.
pub fn with_latest_flashblock(

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.

Remove this, just add it to WorldChainAddOns::new

simulate_enabled,
witness,
)
.with_latest_flashblock(pending_block)

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.

Remove

simulate_enabled,
witness,
)
.with_latest_flashblock(pending_block)

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.

Remove

simulate_enabled,
witness,
)
.with_latest_flashblock(pending_block)

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.

Remove

simulate_enabled,
witness,
)
.with_latest_flashblock(pending_block)

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.

Remove

/// Whether to simulate against the latest flashblock state instead of the
/// latest full block state. Defaults to `false`.
#[serde(default)]
pub use_latest_flashblock: bool,

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.

You are breaking the wire format backwards compatibility of the request by not making this an Option

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.

5 participants