Skip to content

fix(flashblocks): commit cached state to EVM in execute_with_cached_data#2653

Draft
meyer9 wants to merge 1 commit into
mainfrom
meyer9/fix-cached-execution-receipt-mismatch
Draft

fix(flashblocks): commit cached state to EVM in execute_with_cached_data#2653
meyer9 wants to merge 1 commit into
mainfrom
meyer9/fix-cached-execution-receipt-mismatch

Conversation

@meyer9
Copy link
Copy Markdown
Contributor

@meyer9 meyer9 commented May 12, 2026

When reusing results from a previous flashblock, the cached EvmState was returned but never committed to the EVM database. Subsequent transactions executed freshly (not from cache) then ran against stale state, producing incorrect logs and receipts that diverged from what the final block-building executor produces, causing receipt root mismatches during block validation.

Apply the same account pre-loading + commit pattern used by execute_with_evm.

When reusing results from a previous flashblock, the cached EvmState was
returned but never committed to the EVM database. Subsequent transactions
executed freshly (not from cache) then ran against stale state, producing
incorrect logs and receipts that diverged from what the final block-building
executor produces, causing receipt root mismatches during block validation.

Apply the same account pre-loading + commit pattern used by execute_with_evm.
@cb-heimdall
Copy link
Copy Markdown
Collaborator

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
base Ignored Ignored May 12, 2026 3:45am

Request Review

Comment on lines +224 to +229
for address in state.keys() {
self.evm.db_mut().basic(*address).map_err(|err| {
StateProcessorError::Execution(ExecutionError::EvmEnv(err.to_string()))
})?;
}
self.evm.db_mut().commit(state.clone());
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.

execute_with_evm also updates self.state_overrides with balance, nonce, code, and storage diffs for every touched account (lines 293-306). The cached path skips this entirely.

state_overrides is consumed downstream by RPC endpoints (eth_call, eth_estimateGas, eth_simulateV1) via PendingBlocks::get_state_overrides() — see rpc/eth.rs:399, :440, :471. When transactions take the cached path, their state changes won't be reflected in state_overrides, causing RPC simulations against the pending block to run against stale state.

The EvmState has the account info but uses a different structure (revm::state::Account) than what state_overrides expects (AccountOverride with balance/nonce/code/state_diff). The conversion logic from execute_with_evm` (lines 293-306) should be replicated here, or better, extracted into a shared helper to avoid divergence.

Separately: the state.clone() on line 229 clones the entire HashMap<Address, Account> including all storage slots. The EvmState is already consumed by value into ExecutedPendingTransaction, so it cannot be moved here. This is fine for correctness but worth noting as a cost proportional to the number of touched accounts/slots per cached transaction.

@github-actions
Copy link
Copy Markdown
Contributor

Review Summary

The core fix is correct: committing the cached EvmState to the EVM database is necessary so that subsequent fresh transactions execute against up-to-date state. The regression test is thorough and validates the specific nonce-staleness scenario.

Finding: Missing state_overrides update in the cached path

execute_with_evm updates self.state_overrides with balance, nonce, code, and storage diffs for every touched account (lines 293-306), but execute_with_cached_data does not replicate this. state_overrides flows through to RPC simulation endpoints (eth_call, eth_estimateGas, eth_simulateV1) via PendingBlocks::get_state_overrides(). Transactions taking the cached path will have their state changes invisible to RPC simulations against the pending block.

This is the same class of bug being fixed here (cached path diverging from the fresh path), just affecting a different downstream consumer. Consider extracting the state_overrides update logic into a shared helper to prevent the two paths from diverging again in the future.

See inline comment for details.

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