Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions crates/firehose-flashblocks/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,13 @@ where
}
}
state.start_block(flashblock);
// Do not carry the EVM `State` across block boundaries. The next block
// re-bootstraps a fresh `State` from the canonical parent on its first
// execution (or buffers pending → replays if the parent isn't committed
// yet). Reusing the carried `State` let its read cache drift from the
// validated bundle and corrupt subsequent blocks — the divergence was
// confined entirely to carried, incrementally-executed blocks.
state.accumulated_db = None;
if awaiting {
// The previous block was replayed and has not yet been confirmed
// by a canonical-block notification. Defer this transition: the
Expand Down Expand Up @@ -934,6 +941,9 @@ where

let parent_block = block_number.saturating_sub(1);

// The carried `State` is dropped at every block boundary (see the `FirstOfNextBlock`
// transition), so this bootstraps a fresh `State` from the canonical parent on every
// block's first execution.
if accumulated_db.is_none() {
match self.try_bootstrap_provider(parent_block) {
Some(provider) => {
Expand Down Expand Up @@ -967,10 +977,7 @@ where
// canonical-chain commit: the in-memory state may be queryable while
// `header_by_number` (which reads the canonical chain) still returns None.
// Treat that the same as a bootstrap failure — buffer the sequence so the
// canonical-block notification replays it once the header lands. Otherwise the
// error would bubble out of `process_inner`, the outer `process` would reset
// state, and subsequent flashblocks for the same block would be dropped as
// "no in-flight sequence" — losing the whole block on the flashblock stream.
// canonical-block notification replays it once the header lands.
match self.client.header_by_number(parent_block) {
Ok(Some(_)) => {}
Ok(None) | Err(_) => {
Expand Down Expand Up @@ -1696,6 +1703,12 @@ where
}
}

// The returned `BlockExecutionResult` is intentionally dropped. A fresh
// `BaseBlockExecutor` is created per `execute_flashblock` call, so its receipts'
// `cumulative_gas_used` restarts at 0 each pass (per-flashblock, not block-cumulative).
// That is harmless here: the FIRE BLOCK is built from the tracer's own per-tx accounting
// (not these receipts), the state root is derived from the bundle, and fee-vault credits
// use per-tx gas — nothing consumes this value.
executor.finish().map_err(|e| Error::Execution(Box::new(e)))?;

// Promote the per-tx cache transitions accumulated by the EVM into
Expand Down
11 changes: 11 additions & 0 deletions crates/firehose-flashblocks/tests/flashblock_sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ fn two_blocks_with_deltas() {
let genesis = test_genesis();
let genesis_hash = BaseChainSpec::from_genesis(genesis.clone()).inner.genesis_hash();
let client = GenesisClient::new(genesis);
// The processor re-bootstraps each block's state from its (committed) parent rather than
// carrying the in-flight `State` forward, so block 2's parent (block 1) must be available.
client.mark_canonical_block_available(1);
let ts = 0x67d00000u64;

// Block 2's `parent_hash` must equal block 1's locally-recomputed hash so the
Expand Down Expand Up @@ -821,6 +824,8 @@ fn is_final_emitted_on_next_base_match() {
let genesis = test_genesis();
let genesis_hash = BaseChainSpec::from_genesis(genesis.clone()).inner.genesis_hash();
let client = GenesisClient::new(genesis);
// Re-bootstrap per block: block 2's parent (block 1) must be available.
client.mark_canonical_block_available(1);
let ts = 0x67d00000u64;

// First, build a placeholder block-1 sequence to extract the recomputed hash via
Expand Down Expand Up @@ -932,6 +937,8 @@ fn squash_does_not_apply_across_block_boundaries() {
let genesis = test_genesis();
let genesis_hash = BaseChainSpec::from_genesis(genesis.clone()).inner.genesis_hash();
let client = GenesisClient::new(genesis);
// Re-bootstrap per block: block 2's parent (block 1) must be available.
client.mark_canonical_block_available(1);
let ts = 0x67d00000u64;

let placeholder =
Expand Down Expand Up @@ -1310,6 +1317,8 @@ fn next_base_accepted_when_delta_diff_block_hash_diverges_from_recompute() {
let genesis = test_genesis();
let genesis_hash = BaseChainSpec::from_genesis(genesis.clone()).inner.genesis_hash();
let client = GenesisClient::new(genesis);
// Re-bootstrap per block: block 2's parent (block 1) must be available.
client.mark_canonical_block_available(1);
let ts = 0x67d00000u64;

let placeholder = vec![
Expand Down Expand Up @@ -1481,6 +1490,8 @@ fn next_base_accepted_without_peek_when_recompute_matches() {
let genesis = test_genesis();
let genesis_hash = BaseChainSpec::from_genesis(genesis.clone()).inner.genesis_hash();
let client = GenesisClient::new(genesis);
// Re-bootstrap per block: block 2's parent (block 1) must be available.
client.mark_canonical_block_available(1);
let ts = 0x67d00000u64;

let placeholder =
Expand Down
Loading