Add latest flashblock simulation mode - #923
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ 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.
There was a problem hiding this comment.
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(defaultfalse) toSimulateUnsignedUserOpRequestand validates it (rejects whenblockis 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.
|
Addressed the parent state lookup review note in Local follow-up validation:
|
|
Backwards compatibility pass pushed in
|
| @@ -290,6 +310,7 @@ where | |||
| simulate_enabled, | |||
| witness, | |||
| ) | |||
| .with_latest_flashblock(pending_block) | |||
There was a problem hiding this comment.
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,
FlashblocksEthApicould implement this by cloning its optional watch receiver. ThenSimulate::from_eth_apicould requireSpawnBlocking + PendingFlashblockSourceand obtain the receiver fromregistry.eth_api(). That should eliminate thepending_blockfield andwith_latest_flashblockmethod onWorldChainAddOns, including the repeated forwarding through its generic builder transformations.
just a nit though.
| /// Cross-request cache for resolved token metadata. LRU-bounded. | ||
| type MetadataCache = Arc<Mutex<LruCache<Address, AssetInfo>>>; | ||
|
|
||
| pub type LatestFlashblockReceiver = |
There was a problem hiding this comment.
Remove this type alias, it's just an indirection that makes the logic harder to read
| @@ -290,6 +310,7 @@ where | |||
| simulate_enabled, | |||
| witness, | |||
| ) | |||
| .with_latest_flashblock(pending_block) | |||
|
|
||
| /// Configures access to the latest flashblock-backed pending block, when | ||
| /// flashblocks are enabled. | ||
| pub fn with_latest_flashblock( |
There was a problem hiding this comment.
Remove this, just add it to WorldChainAddOns::new
| simulate_enabled, | ||
| witness, | ||
| ) | ||
| .with_latest_flashblock(pending_block) |
| simulate_enabled, | ||
| witness, | ||
| ) | ||
| .with_latest_flashblock(pending_block) |
| simulate_enabled, | ||
| witness, | ||
| ) | ||
| .with_latest_flashblock(pending_block) |
| simulate_enabled, | ||
| witness, | ||
| ) | ||
| .with_latest_flashblock(pending_block) |
| /// 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, |
There was a problem hiding this comment.
You are breaking the wire format backwards compatibility of the request by not making this an Option

Summary
useLatestFlashblockrequest parameter tosimulate_unsignedUserOp, defaulting tofalse.blockis also provided.Validation
cargo fmtcargo test -p world-chain-rpccargo check -p world-chain-nodecargo checkgit diff --checkcargo clippy -p world-chain-rpc --all-targets -- -D warningscould not run because this pinned nightly reportscargo-clippyis not applicable tonightly-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(defaultfalse) onsimulate_unsignedUserOpso 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
watchreceiver, uses that block’s header, and builds state from the parent block plus aBlockStateoverlay 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 intoWorldChainAddOnsand theSimulateRPC viawith_latest_flashblock. Requests are rejected if flashblocks are disabled, no flashblock is available yet, orblockis set together withuseLatestFlashblock.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.