Re-bootstrap flashblock state per block instead of carrying it forward#9
Merged
sduchesneau merged 1 commit intoJun 10, 2026
Merged
Conversation
The firehose flashblocks processor carried the revm `State` across block boundaries on the sequential fast path. The reused `State`'s read cache could drift from its validated post-state bundle, so the next block executed on a subtly-wrong starting state and produced a wrong state root — surfacing as intermittent "canonical contradicts recomputed in-flight tip" resets. It was node-specific and non-deterministic because whether a given block was carried vs freshly bootstrapped/replayed depended on each node's canonical-commit timing; execution itself was correct (receipts/gas/logs matched canonical), only the carried starting state was wrong. Drop the carried `State` at each block transition so every block re-bootstraps from the canonical parent (or buffers pending → replays when the parent isn't committed yet). Mark parent blocks available in the carry-forward tests, which now exercise the re-bootstrap path.
Author
|
I think this is the best choice, as it removes tons of UNDO events empirically:
|
maoueh
approved these changes
Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The firehose flashblocks processor carried the revm
Stateacross block boundaries on the sequential fast path. The reusedState's read cache could drift from its validated post-state bundle, so the next block executed on a subtly-wrong starting state and produced a wrong state root — surfacing as intermittentcanonical contradicts recomputed in-flight tip(andis_final hash mismatch on next-base transition) resets.It was node-specific and non-deterministic: whether a given block was carried-forward vs freshly bootstrapped/replayed depends on each node's canonical-commit timing, and the bug only manifests on the carried path.
Diagnosis
A temporary diagnostic pass (now removed) established, on every divergence:
bootstrapped=false, replayed=false— always a carried, incrementally-executed block; never a fresh/replayed one.receipts_root,transactions_root,gas_used,logs_bloomall matched canonical — only the post-state root diverged, broadly (dozens of storage slots across WETH/AMM contracts), the signature of a wrong starting state cascading through execution.Fix
Drop the carried
Stateat each block transition (accumulated_db = None), so every block re-bootstraps from the canonical parent (or buffers pending → replays when the parent isn't committed yet). The speculative state-root precompute already refetches a fresh provider, so is_final latency is unaffected.Also documents why
execute_flashblockdiscards theBlockExecutionResult(the per-flashblock executor restarts receiptcumulative_gas_usedat 0, which nothing on the wire/state path consumes).The carry-forward tests now mark the parent block available, exercising the re-bootstrap path.
Testing
35 crate tests pass; clippy and the node binary build clean. Confirmed in production: the resets stopped.
Follow-up (not in this PR)
Optionally bootstrap via
state_by_block_hash(parent_hash)(served by the in-memory tree pre-commit) so blocks never fall to pending→replay, preserving per-flashblock emission granularity.