Skip to main content

reth_storage_overlay/
builder.rs

1use crate::{manager::OverlayCacheConfig, OverlayManager};
2use alloy_eips::BlockNumHash;
3use alloy_primitives::{
4    map::{AddressMap, AddressSet, B256Map, U256Map},
5    Address, BlockHash, BlockNumber, B256, U256,
6};
7use metrics::{Counter, Histogram};
8use reth_chain_state::{BlockState, ExecutedBlock};
9use reth_errors::{ProviderError, ProviderResult};
10use reth_ethereum_primitives::EthPrimitives;
11use reth_metrics::Metrics;
12use reth_primitives_traits::{AlloyBlockHeader, NodePrimitives};
13use reth_prune_types::PruneSegment;
14use reth_stages_types::StageId;
15use reth_storage_api::{
16    BlockNumReader, ChangeSetReader, DBProvider, PruneCheckpointReader, StageCheckpointReader,
17    StorageChangeSetReader, StorageSettingsCache,
18};
19use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted};
20use reth_trie_db::DatabaseHashedPostState;
21use revm::{bytecode::Bytecode, database::BundleState, state::AccountInfo};
22use std::{
23    ops::RangeInclusive,
24    sync::Arc,
25    time::{Duration, Instant},
26};
27use tracing::{debug, debug_span, instrument};
28
29/// Contains the trie and hashed-state data required to initialize an overlay state provider.
30#[derive(Debug, Clone)]
31pub struct StateTrieOverlay {
32    /// Trie updates overlay.
33    pub trie_updates: Arc<TrieUpdatesSorted>,
34    /// Hashed state overlay.
35    pub hashed_post_state: Arc<HashedPostStateSorted>,
36    /// Whether construction was skipped because a reused sparse trie covers this range.
37    skipped_for_reused_sparse_trie: bool,
38}
39
40impl StateTrieOverlay {
41    pub(crate) const fn new(
42        trie_updates: Arc<TrieUpdatesSorted>,
43        hashed_post_state: Arc<HashedPostStateSorted>,
44    ) -> Self {
45        Self { trie_updates, hashed_post_state, skipped_for_reused_sparse_trie: false }
46    }
47
48    fn empty() -> Self {
49        Self {
50            trie_updates: Arc::new(TrieUpdatesSorted::default()),
51            hashed_post_state: Arc::new(HashedPostStateSorted::default()),
52            skipped_for_reused_sparse_trie: true,
53        }
54    }
55
56    pub(crate) const fn skipped_for_reused_sparse_trie(&self) -> bool {
57        self.skipped_for_reused_sparse_trie
58    }
59}
60
61/// Execution state required to initialize an overlay state provider.
62///
63/// Account entries preserve known non-existence, while storage and code entries contain only data
64/// explicitly observed during execution. Accounts never retain database-context-local lookup IDs.
65#[derive(Clone, Debug, Default)]
66pub struct ExecutionOverlay {
67    /// In-memory block hashes in ascending block-number order.
68    block_hashes: Vec<BlockNumHash>,
69    /// Account state by address, without database-context-local [`AccountInfo::account_id`] hints.
70    accounts: AddressMap<Option<AccountInfo>>,
71    /// Storage values by address and slot.
72    storage: AddressMap<U256Map<U256>>,
73    /// Accounts whose storage was wiped during execution.
74    ///
75    /// An absent slot for one of these accounts is known to be zero, rather than falling back to
76    /// the durable database state.
77    storage_wipes: AddressSet,
78    /// Bytecode by code hash.
79    code_hashes: B256Map<Bytecode>,
80}
81
82impl ExecutionOverlay {
83    /// Returns the in-memory block hashes in ascending block-number order.
84    pub const fn block_hashes(&self) -> &[BlockNumHash] {
85        self.block_hashes.as_slice()
86    }
87
88    /// Returns the account state by address.
89    pub const fn accounts(&self) -> &AddressMap<Option<AccountInfo>> {
90        &self.accounts
91    }
92
93    /// Returns the storage values by address and slot.
94    pub const fn storage(&self) -> &AddressMap<U256Map<U256>> {
95        &self.storage
96    }
97
98    /// Returns an explicitly observed storage value, or zero when the account's storage was
99    /// wiped.
100    pub(crate) fn storage_value(&self, address: Address, slot: U256) -> Option<U256> {
101        self.storage
102            .get(&address)
103            .and_then(|storage| storage.get(&slot))
104            .copied()
105            .or_else(|| self.storage_wipes.contains(&address).then_some(U256::ZERO))
106    }
107
108    /// Returns the bytecode by code hash.
109    pub const fn code_hashes(&self) -> &B256Map<Bytecode> {
110        &self.code_hashes
111    }
112
113    #[cfg(test)]
114    pub(crate) const fn block_hashes_mut(&mut self) -> &mut Vec<BlockNumHash> {
115        &mut self.block_hashes
116    }
117
118    #[cfg(test)]
119    pub(crate) const fn accounts_mut(&mut self) -> &mut AddressMap<Option<AccountInfo>> {
120        &mut self.accounts
121    }
122
123    #[cfg(test)]
124    pub(crate) const fn storage_mut(&mut self) -> &mut AddressMap<U256Map<U256>> {
125        &mut self.storage
126    }
127
128    #[cfg(test)]
129    pub(crate) const fn code_hashes_mut(&mut self) -> &mut B256Map<Bytecode> {
130        &mut self.code_hashes
131    }
132
133    /// Extends this overlay with the execution state of a later block.
134    pub(crate) fn extend_block<N: NodePrimitives>(&mut self, block: &ExecutedBlock<N>) {
135        self.block_hashes.push(block.recovered_block().num_hash());
136        self.extend_state(&block.execution_output.state);
137    }
138
139    /// Extends this overlay with a later bundle state.
140    ///
141    /// [`AccountInfo::account_id`] is a lookup hint owned by the database context that assigned it
142    /// and cannot be reused by the overlay's database context. All other account fields are
143    /// preserved.
144    fn extend_state(&mut self, state: &BundleState) {
145        let (accounts, storage, storage_wipes, code_hashes) =
146            (&mut self.accounts, &mut self.storage, &mut self.storage_wipes, &mut self.code_hashes);
147
148        #[allow(unused_mut)]
149        let mut extend_accounts_and_storage = || {
150            for (address, account) in state.state() {
151                accounts.insert(*address, Self::normalized_account_info(account.info.clone()));
152                if account.was_destroyed() {
153                    storage_wipes.insert(*address);
154                    storage.remove(address);
155                }
156                let account_storage = storage.entry(*address).or_default();
157                for (slot, value) in &account.storage {
158                    account_storage.insert(*slot, value.present_value);
159                }
160            }
161        };
162        #[allow(unused_mut)]
163        let mut extend_code_hashes = || {
164            code_hashes.extend(state.contracts.iter().map(|(hash, code)| (*hash, code.clone())));
165        };
166
167        #[cfg(feature = "rayon")]
168        rayon::join(extend_accounts_and_storage, extend_code_hashes);
169
170        #[cfg(not(feature = "rayon"))]
171        {
172            extend_accounts_and_storage();
173            extend_code_hashes();
174        }
175    }
176
177    #[cfg(test)]
178    fn extend_overlay(&mut self, other: &Self) {
179        self.block_hashes.extend_from_slice(&other.block_hashes);
180        self.accounts.extend(
181            other
182                .accounts
183                .iter()
184                .map(|(address, info)| (*address, Self::normalized_account_info(info.clone()))),
185        );
186        for address in &other.storage_wipes {
187            self.storage.remove(address);
188        }
189        for (address, slots) in &other.storage {
190            self.storage
191                .entry(*address)
192                .or_default()
193                .extend(slots.iter().map(|(slot, value)| (*slot, *value)));
194        }
195        self.storage_wipes.extend(other.storage_wipes.iter().copied());
196        self.code_hashes.extend(other.code_hashes.iter().map(|(hash, code)| (*hash, code.clone())));
197    }
198
199    /// Removes the database-local account lookup hint before caching account state.
200    ///
201    /// `account_id` indexes the database or BAL context that produced the [`AccountInfo`]. A later
202    /// execution context can assign that ID to a different account, so it must not cross the
203    /// execution-overlay boundary.
204    const fn normalized_account_info(mut info: Option<AccountInfo>) -> Option<AccountInfo> {
205        if let Some(info) = &mut info {
206            info.account_id = None;
207        }
208        info
209    }
210}
211
212/// Builder for calculating trie and hashed-state overlays.
213///
214/// This stores the overlay manager, overlay configuration, and the logic for resolving overlays
215/// and collecting reverts.
216#[derive(Debug, Clone)]
217pub struct OverlayBuilder<N: NodePrimitives = EthPrimitives> {
218    /// Parent hash requested by the caller.
219    parent_hash: B256,
220    /// Manager used for cached changesets and overlays.
221    overlay_manager: OverlayManager<N>,
222    /// Snapshot of the in-memory chain ending at the requested parent.
223    parent_state: Option<BlockState<N>>,
224    /// Anchor hash of the reused sparse trie, if this task reused one.
225    reused_sparse_trie_anchor_hash: Option<B256>,
226    /// Whether building the overlay may query revert changesets.
227    no_reverts: bool,
228    /// Cache behavior for the overlays this builder resolves.
229    overlay_cache_config: OverlayCacheConfig,
230    /// Metrics for overlay construction.
231    metrics: OverlayBuilderMetrics,
232}
233
234impl<N: NodePrimitives> OverlayBuilder<N> {
235    /// Create a new manager-backed overlay builder.
236    pub(crate) fn new(
237        parent_hash: B256,
238        parent_state: Option<BlockState<N>>,
239        overlay_manager: OverlayManager<N>,
240    ) -> Self {
241        Self {
242            parent_hash,
243            overlay_manager,
244            parent_state,
245            reused_sparse_trie_anchor_hash: None,
246            no_reverts: false,
247            overlay_cache_config: OverlayCacheConfig::default(),
248            metrics: OverlayBuilderMetrics::default(),
249        }
250    }
251
252    /// Skips managed overlay construction when the sparse trie was reused and the DB tip is
253    /// already covered by its anchor-to-parent range.
254    pub(crate) const fn with_skip_overlay_for_reused_sparse_trie(
255        mut self,
256        anchor_hash: B256,
257    ) -> Self {
258        self.reused_sparse_trie_anchor_hash = Some(anchor_hash);
259        self
260    }
261
262    /// Returns an error instead of querying revert changesets when reverts are required.
263    pub(crate) const fn with_no_reverts(mut self) -> Self {
264        self.no_reverts = true;
265        self
266    }
267
268    /// Appends an executed block to this builder's in-memory parent state.
269    pub fn with_appended_block(mut self, block: ExecutedBlock<N>) -> Self {
270        debug_assert_eq!(block.recovered_block().parent_hash(), self.parent_hash);
271        self.parent_hash = block.recovered_block().hash();
272        self.parent_state =
273            Some(BlockState::with_parent(block, self.parent_state.take().map(Arc::new)));
274        self.reused_sparse_trie_anchor_hash = None;
275        self.overlay_cache_config.write_to_cache = false;
276        self
277    }
278
279    /// Returns the durable anchor to use for this builder's parent.
280    #[cfg(test)]
281    fn anchor_at_parent<Provider>(&self, provider: &Provider) -> ProviderResult<AnchorForParent>
282    where
283        Provider: StageCheckpointReader + BlockNumReader + PruneCheckpointReader,
284    {
285        let (partial_state_trie, finish) = database_state_frontiers(provider)?;
286        self.anchor_at_parent_with_frontiers(provider, partial_state_trie, finish)
287    }
288
289    /// Returns the durable anchor to use for this builder's parent using known frontiers.
290    fn anchor_at_parent_with_frontiers<Provider>(
291        &self,
292        provider: &Provider,
293        partial_state_trie: BlockNumHash,
294        finish: BlockNumHash,
295    ) -> ProviderResult<AnchorForParent>
296    where
297        Provider: BlockNumReader + PruneCheckpointReader,
298    {
299        use std::io::Error;
300
301        let mut in_mem_chain = self
302            .parent_state
303            .iter()
304            .flat_map(|state| state.chain())
305            .map(BlockState::block_ref)
306            .peekable();
307        let persisted_parent = match in_mem_chain
308            .peek()
309            .filter(|block| block.recovered_block().hash() == self.parent_hash)
310            .map(|block| block.recovered_block().number())
311        {
312            Some(parent_number) if parent_number > partial_state_trie.number => None,
313            Some(parent_number)
314                if parent_number == partial_state_trie.number &&
315                    self.parent_hash == partial_state_trie.hash =>
316            {
317                Some(parent_number)
318            }
319            Some(parent_number) => (provider.block_hash(parent_number)? == Some(self.parent_hash))
320                .then_some(parent_number),
321            None if self.parent_hash == partial_state_trie.hash => Some(partial_state_trie.number),
322            None => provider
323                .block_number(self.parent_hash)?
324                .filter(|&number| number <= partial_state_trie.number),
325        };
326
327        let mut finish_seen = self.parent_hash == finish.hash;
328        let anchor = if let Some(parent_number) = persisted_parent {
329            BlockNumHash::new(parent_number, self.parent_hash)
330        } else {
331            let mut in_mem_chain = in_mem_chain.inspect(|block| {
332                finish_seen |= block.recovered_block().hash() == finish.hash;
333            });
334
335            if let Some(anchor) =
336                anchor_for_parent_in(self.parent_hash, &mut in_mem_chain, partial_state_trie)
337            {
338                anchor
339            } else {
340                let anchor_number = provider
341                    .convert_hash_or_number(self.parent_hash.into())?
342                    .ok_or(ProviderError::BlockHashNotFound(self.parent_hash))?;
343                BlockNumHash::new(anchor_number, self.parent_hash)
344            }
345        };
346
347        finish_seen |= anchor.hash == finish.hash;
348
349        if anchor.number > partial_state_trie.number {
350            return Err(ProviderError::other(Error::other(format!(
351                "overlay anchor #{} ({}) is after partial state trie frontier #{} ({}); missing trie updates for blocks #{}..=#{}",
352                anchor.number,
353                anchor.hash,
354                partial_state_trie.number,
355                partial_state_trie.hash,
356                partial_state_trie.number + 1,
357                anchor.number,
358            ))))
359        }
360
361        // If the Finish block (db tip) was seen in the in-memory chain then we know that anchor is
362        // on the same chain as partial_state_trie as well. Given that anchor <= partial_state_trie,
363        // we can be sure that the in-memory chain is a superset of partial_state_trie+1..finish,
364        // and therefore can be used without reverts.
365        if finish_seen {
366            return Ok(AnchorForParent::NoReverts { anchor })
367        }
368
369        // Reverts reconstruct canonical state by block number, so they cannot recover an anchor
370        // from another fork even if its number is covered by the available changesets.
371        if provider.block_hash(anchor.number)? != Some(anchor.hash) {
372            return Err(ProviderError::BlockHashNotFound(anchor.hash))
373        }
374
375        // Otherwise reverts are required; we check the changesets to make sure they are actually
376        // available before signaling that they are required.
377        let account_history = provider
378            .get_prune_checkpoint(PruneSegment::AccountHistory)?
379            .and_then(|checkpoint| checkpoint.block_number);
380        let storage_history = provider
381            .get_prune_checkpoint(PruneSegment::StorageHistory)?
382            .and_then(|checkpoint| checkpoint.block_number);
383        let lower_bound = account_history.max(storage_history).unwrap_or_default();
384        let available_range = lower_bound..=finish.number;
385        if !available_range.contains(&anchor.number) {
386            return Err(ProviderError::InsufficientChangesets {
387                requested: anchor.number,
388                available: available_range,
389            })
390        }
391
392        Ok(AnchorForParent::RevertsRequired { anchor, finish })
393    }
394
395    /// Builds the effective state trie overlay for the given provider.
396    #[cfg(test)]
397    #[instrument(level = "debug", target = "storage::overlay", skip_all)]
398    fn build_state_trie_overlay<Provider>(
399        &self,
400        provider: &Provider,
401    ) -> ProviderResult<StateTrieOverlay>
402    where
403        Provider: StageCheckpointReader
404            + PruneCheckpointReader
405            + ChangeSetReader
406            + StorageChangeSetReader
407            + DBProvider
408            + BlockNumReader
409            + StorageSettingsCache,
410    {
411        let (state_trie_tip_block, finish_tip_block) = database_state_frontiers(provider)?;
412        self.build_state_trie_overlay_at_frontiers(provider, state_trie_tip_block, finish_tip_block)
413    }
414
415    /// Builds the effective state trie overlay using frontiers already read from the provider.
416    ///
417    /// This is useful for callers that key an overlay cache by the durable frontiers.
418    #[instrument(
419        level = "debug",
420        target = "storage::overlay",
421        skip_all,
422        fields(?state_trie_tip_block, ?finish_tip_block, parent_hash = ?self.parent_hash)
423    )]
424    pub(crate) fn build_state_trie_overlay_at_frontiers<Provider>(
425        &self,
426        provider: &Provider,
427        state_trie_tip_block: BlockNumHash,
428        finish_tip_block: BlockNumHash,
429    ) -> ProviderResult<StateTrieOverlay>
430    where
431        Provider: ChangeSetReader
432            + StorageChangeSetReader
433            + DBProvider
434            + BlockNumReader
435            + StageCheckpointReader
436            + PruneCheckpointReader
437            + StorageSettingsCache,
438    {
439        let retrieve_trie_reverts_duration;
440        let retrieve_hashed_state_reverts_duration;
441        let trie_updates_total_len;
442        let hashed_state_updates_total_len;
443
444        let anchor_for_parent =
445            self.anchor_at_parent_with_frontiers(provider, state_trie_tip_block, finish_tip_block)?;
446
447        // Collect any reverts which are required to bring the DB view back to the anchor hash.
448        let (trie_updates, hashed_post_state) = match &anchor_for_parent {
449            AnchorForParent::RevertsRequired { anchor, .. } => {
450                let revert_blocks =
451                    self.revert_blocks(&anchor_for_parent)?.expect("reverts are required");
452
453                debug!(
454                    target: "storage::overlay",
455                    ?revert_blocks,
456                    ?anchor,
457                    "Collecting trie reverts for overlay state provider"
458                );
459
460                let trie_reverts = {
461                    let _guard = debug_span!(target: "storage::overlay", "retrieving_trie_reverts")
462                        .entered();
463                    let start = Instant::now();
464                    let accumulated_reverts =
465                        self.overlay_manager.get_or_compute_cached_changesets_range_at_frontiers(
466                            provider,
467                            revert_blocks.clone(),
468                            state_trie_tip_block,
469                            finish_tip_block,
470                        )?;
471                    retrieve_trie_reverts_duration = start.elapsed();
472                    accumulated_reverts
473                };
474
475                let mut hashed_state_reverts = {
476                    let _guard =
477                        debug_span!(target: "storage::overlay", "retrieving_hashed_state_reverts")
478                            .entered();
479                    let start = Instant::now();
480                    let res = HashedPostStateSorted::from_reverts(provider, revert_blocks)?;
481                    retrieve_hashed_state_reverts_duration = start.elapsed();
482                    res
483                };
484
485                // Resolve overlays and extend reverts with them. If reverts are empty, use overlays
486                // directly to avoid cloning.
487                let (overlay_trie, overlay_state) =
488                    self.resolve_state_trie_overlays(anchor.hash)?;
489
490                let trie_updates = if trie_reverts.is_empty() {
491                    overlay_trie
492                } else if !overlay_trie.is_empty() {
493                    let mut trie_reverts = (*trie_reverts).clone();
494                    trie_reverts.extend_ref_and_sort(&overlay_trie);
495                    Arc::new(trie_reverts)
496                } else {
497                    trie_reverts
498                };
499
500                let hashed_state_updates = if hashed_state_reverts.is_empty() {
501                    overlay_state
502                } else if !overlay_state.is_empty() {
503                    hashed_state_reverts.extend_ref_and_sort(&overlay_state);
504                    Arc::new(hashed_state_reverts)
505                } else {
506                    Arc::new(hashed_state_reverts)
507                };
508
509                trie_updates_total_len = trie_updates.total_len();
510                hashed_state_updates_total_len = hashed_state_updates.total_len();
511
512                debug!(
513                    target: "storage::overlay",
514                    num_trie_updates = ?trie_updates_total_len,
515                    num_state_updates = ?hashed_state_updates_total_len,
516                    ?anchor,
517                    "Reverted to anchor block",
518                );
519
520                (trie_updates, hashed_state_updates)
521            }
522            AnchorForParent::NoReverts { anchor } => {
523                // If no reverts are needed, use the manager overlay directly unless the reused
524                // sparse trie already covers both durable frontiers through the
525                // requested parent.
526                if self.should_skip_overlay_for_reused_sparse_trie(
527                    state_trie_tip_block.hash,
528                    finish_tip_block.hash,
529                ) {
530                    self.metrics.sparse_trie_overlay_skips.increment(1);
531
532                    return Ok(StateTrieOverlay::empty())
533                }
534
535                let (trie_updates, hashed_post_state) =
536                    self.resolve_state_trie_overlays(anchor.hash)?;
537
538                retrieve_trie_reverts_duration = Duration::ZERO;
539                retrieve_hashed_state_reverts_duration = Duration::ZERO;
540                trie_updates_total_len = trie_updates.total_len();
541                hashed_state_updates_total_len = hashed_post_state.total_len();
542
543                debug!(
544                    target: "storage::overlay",
545                    num_trie_updates = trie_updates_total_len,
546                    num_state_updates = hashed_state_updates_total_len,
547                    ?anchor,
548                    "Built overlay directly from durable frontier"
549                );
550
551                (trie_updates, hashed_post_state)
552            }
553        };
554
555        self.metrics
556            .retrieve_trie_reverts_duration
557            .record(retrieve_trie_reverts_duration.as_secs_f64());
558        self.metrics
559            .retrieve_hashed_state_reverts_duration
560            .record(retrieve_hashed_state_reverts_duration.as_secs_f64());
561        self.metrics.trie_updates_size.record(trie_updates_total_len as f64);
562        self.metrics.hashed_state_size.record(hashed_state_updates_total_len as f64);
563
564        Ok(StateTrieOverlay::new(trie_updates, hashed_post_state))
565    }
566
567    /// Returns the in-memory execution overlay and the block for historical fallback reads.
568    #[cfg(test)]
569    #[instrument(level = "debug", target = "storage::overlay", skip_all)]
570    fn execution_overlay<Provider>(
571        &self,
572        provider: &Provider,
573    ) -> ProviderResult<(Arc<ExecutionOverlay>, Option<BlockNumber>)>
574    where
575        Provider: StageCheckpointReader
576            + PruneCheckpointReader
577            + ChangeSetReader
578            + StorageChangeSetReader
579            + DBProvider
580            + BlockNumReader,
581    {
582        let (state_trie_tip_block, finish_tip_block) = database_state_frontiers(provider)?;
583        self.execution_overlay_at_frontiers(provider, state_trie_tip_block, finish_tip_block)
584    }
585
586    /// Returns the in-memory execution overlay using frontiers already read from the provider.
587    #[instrument(
588        level = "trace",
589        target = "storage::overlay",
590        skip_all,
591        fields(?state_trie_tip_block, ?finish_tip_block, parent_hash = ?self.parent_hash)
592    )]
593    pub(crate) fn execution_overlay_at_frontiers<Provider>(
594        &self,
595        provider: &Provider,
596        state_trie_tip_block: BlockNumHash,
597        finish_tip_block: BlockNumHash,
598    ) -> ProviderResult<(Arc<ExecutionOverlay>, Option<BlockNumber>)>
599    where
600        Provider: ChangeSetReader
601            + StorageChangeSetReader
602            + DBProvider
603            + BlockNumReader
604            + PruneCheckpointReader,
605    {
606        let anchor_for_parent =
607            self.anchor_at_parent_with_frontiers(provider, state_trie_tip_block, finish_tip_block)?;
608        let (anchor_hash, fallback_block_number) = match anchor_for_parent {
609            AnchorForParent::RevertsRequired { anchor, .. } => {
610                (anchor.hash, Some(anchor.number + 1))
611            }
612            AnchorForParent::NoReverts { anchor } => (anchor.hash, None),
613        };
614        Ok((self.resolve_execution_overlay(anchor_hash)?, fallback_block_number))
615    }
616
617    /// Resolves the effective overlay (trie updates, hashed state).
618    fn resolve_state_trie_overlays(
619        &self,
620        anchor_hash: BlockHash,
621    ) -> ProviderResult<(Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>)> {
622        if anchor_hash == self.parent_hash {
623            Ok((Arc::new(TrieUpdatesSorted::default()), Arc::new(HashedPostStateSorted::default())))
624        } else {
625            let parent_state = self.parent_state.as_ref().ok_or_else(|| {
626                ProviderError::other(std::io::Error::other(
627                    "state trie overlay cannot be anchored without in-memory parent state",
628                ))
629            })?;
630            self.overlay_manager
631                .overlay_for_parent(parent_state, anchor_hash, self.overlay_cache_config)
632                .map_err(ProviderError::other)
633        }
634    }
635
636    /// Resolves the execution overlay for the configured in-memory source.
637    fn resolve_execution_overlay(
638        &self,
639        anchor_hash: BlockHash,
640    ) -> ProviderResult<Arc<ExecutionOverlay>> {
641        if anchor_hash == self.parent_hash {
642            Ok(Arc::new(ExecutionOverlay::default()))
643        } else {
644            let parent_state = self.parent_state.as_ref().ok_or_else(|| {
645                ProviderError::other(std::io::Error::other("missing in-memory parent state"))
646            })?;
647            self.overlay_manager
648                .execution_overlay_for_block_state(
649                    parent_state,
650                    anchor_hash,
651                    self.overlay_cache_config,
652                )
653                .map_err(ProviderError::other)
654        }
655    }
656
657    /// Returns the blocks to revert from Finish to the selected anchor, if any.
658    fn revert_blocks(
659        &self,
660        anchor_for_parent: &AnchorForParent,
661    ) -> ProviderResult<Option<RangeInclusive<BlockNumber>>> {
662        match anchor_for_parent {
663            AnchorForParent::NoReverts { .. } => Ok(None),
664            AnchorForParent::RevertsRequired { anchor, finish, .. } => {
665                if self.no_reverts {
666                    return Err(ProviderError::other(std::io::Error::other(format!(
667                        "reverts are disabled, but overlay for parent {} requires reverting Finish #{} ({}) to anchor #{} ({})",
668                        self.parent_hash, finish.number, finish.hash, anchor.number, anchor.hash,
669                    ))))
670                }
671                Ok(Some(anchor.number + 1..=finish.number))
672            }
673        }
674    }
675
676    /// Returns true if managed overlay resolution can be skipped for this builder.
677    fn should_skip_overlay_for_reused_sparse_trie(
678        &self,
679        state_trie_tip_hash: B256,
680        finish_tip_hash: B256,
681    ) -> bool {
682        let Some(anchor_hash) = self.reused_sparse_trie_anchor_hash else { return false };
683
684        self.contains_hash(anchor_hash, state_trie_tip_hash) &&
685            self.contains_hash(anchor_hash, finish_tip_hash)
686    }
687
688    fn contains_hash(&self, anchor_hash: B256, hash: B256) -> bool {
689        let mut current_hash = self.parent_hash;
690        let mut blocks = self.parent_state.iter().flat_map(|state| state.chain());
691
692        loop {
693            if current_hash == hash {
694                return true
695            }
696            if current_hash == anchor_hash {
697                return false
698            }
699
700            let Some(block) = blocks.next() else { return false };
701            current_hash = block.block_ref().recovered_block().parent_hash();
702        }
703    }
704}
705
706/// Returns the highest blocks whose state/trie data and non-state/trie data are durably
707/// available in the database.
708pub(crate) fn database_state_frontiers<Provider>(
709    provider: &Provider,
710) -> ProviderResult<(BlockNumHash, BlockNumHash)>
711where
712    Provider: StageCheckpointReader + BlockNumReader,
713{
714    let checkpoint = provider
715        .get_stage_checkpoint(StageId::Finish)?
716        .ok_or_else(|| ProviderError::InsufficientChangesets { requested: 0, available: 0..=0 })?;
717    let state_trie_tip_number = checkpoint
718        .finish_stage_checkpoint()
719        .and_then(|finish| finish.partial_state_trie())
720        .unwrap_or(checkpoint.block_number);
721    let state_trie_tip_hash = provider
722        .convert_number(state_trie_tip_number.into())?
723        .ok_or_else(|| ProviderError::HeaderNotFound(state_trie_tip_number.into()))?;
724    let finish_tip_number = checkpoint.block_number;
725    let finish_tip_hash = provider
726        .convert_number(finish_tip_number.into())?
727        .ok_or_else(|| ProviderError::HeaderNotFound(finish_tip_number.into()))?;
728
729    Ok((
730        BlockNumHash::new(state_trie_tip_number, state_trie_tip_hash),
731        BlockNumHash::new(finish_tip_number, finish_tip_hash),
732    ))
733}
734
735/// Metrics for overlay construction.
736#[derive(Clone, Metrics)]
737#[metrics(scope = "storage.overlay.builder")]
738struct OverlayBuilderMetrics {
739    /// Duration of retrieving trie updates from the database.
740    retrieve_trie_reverts_duration: Histogram,
741    /// Duration of retrieving hashed state from the database.
742    retrieve_hashed_state_reverts_duration: Histogram,
743    /// Size of trie updates (number of entries).
744    trie_updates_size: Histogram,
745    /// Size of hashed state (number of entries).
746    hashed_state_size: Histogram,
747    /// Number of managed overlay creations skipped because the reused sparse trie already covers
748    /// the DB tip to parent range.
749    sparse_trie_overlay_skips: Counter,
750}
751
752fn anchor_for_parent_in<'a, N: NodePrimitives + 'a>(
753    parent_hash: B256,
754    in_mem_chain: impl Iterator<Item = &'a ExecutedBlock<N>>,
755    preferred_anchor: BlockNumHash,
756) -> Option<BlockNumHash> {
757    if parent_hash == preferred_anchor.hash {
758        return Some(preferred_anchor)
759    }
760
761    let mut anchor = None;
762
763    for block in in_mem_chain {
764        let block_parent = block.recovered_block().parent_num_hash();
765
766        if block_parent.hash == preferred_anchor.hash {
767            return Some(preferred_anchor)
768        }
769        anchor = Some(block_parent);
770    }
771
772    anchor
773}
774
775/// Describes whether an overlay must revert the database before using its anchor.
776#[derive(Debug)]
777enum AnchorForParent {
778    /// The in-memory chain covers the durable frontiers through this anchor.
779    NoReverts {
780        /// Block to anchor the overlay to.
781        anchor: BlockNumHash,
782    },
783    /// The database must be reverted from `finish` to `anchor` first.
784    RevertsRequired {
785        /// Block to anchor the overlay to.
786        anchor: BlockNumHash,
787        /// Current Finish frontier.
788        finish: BlockNumHash,
789    },
790}
791
792#[cfg(test)]
793mod tests {
794    use super::*;
795    use alloy_primitives::{map::HashMap, Address, U256};
796    use reth_chain_state::{test_utils::TestBlockBuilder, ExecutedBlock};
797    use reth_db::{
798        models::{AccountBeforeTx, BlockNumberAddress},
799        tables,
800        transaction::DbTxMut,
801    };
802    use reth_primitives_traits::{Account, StorageEntry};
803    use reth_provider::{
804        test_utils::{create_test_provider_factory, MockNodeTypesWithDB},
805        BlockWriter, ProviderFactory,
806    };
807    use reth_stages_types::{FinishCheckpoint, StageCheckpoint};
808    use reth_storage_api::StageCheckpointWriter;
809    use reth_trie::{BranchNodeCompact, ComputedTrieData, HashedPostState, HashedStorage, Nibbles};
810    use revm::{
811        bytecode::Bytecode,
812        database::{AccountStatus, BundleAccount, BundleState},
813        state::{AccountId, AccountInfo},
814    };
815
816    fn with_unique_trie_data(
817        block: &ExecutedBlock<EthPrimitives>,
818        id: u8,
819    ) -> ExecutedBlock<EthPrimitives> {
820        let hashed_address = B256::with_last_byte(id);
821        let hashed_slot = B256::with_last_byte(id.saturating_add(32));
822        let hashed_state = HashedPostState::default()
823            .with_accounts([(hashed_address, Some(Account::default()))])
824            .with_storages([(
825                hashed_address,
826                HashedStorage::from_iter([(hashed_slot, U256::from(id))]),
827            )])
828            .into_sorted();
829        let trie_updates = TrieUpdatesSorted::new(
830            vec![(
831                Nibbles::from_nibbles([id]),
832                Some(BranchNodeCompact::new(0, 0, 0, vec![], None)),
833            )],
834            Default::default(),
835        );
836        let address = Address::with_last_byte(id);
837        let slot = U256::from(id);
838        let code_hash = B256::with_last_byte(id.saturating_add(64));
839        let state = BundleState::builder(block.block_number()..=block.block_number())
840            .state_present_account_info(
841                address,
842                AccountInfo {
843                    nonce: id as u64,
844                    balance: U256::from(id),
845                    account_id: AccountId::new(id as usize),
846                    ..Default::default()
847                },
848            )
849            .state_storage(address, HashMap::from_iter([(slot, (U256::ZERO, U256::from(id)))]))
850            .contract(code_hash, Bytecode::new_raw(vec![id].into()))
851            .build();
852        let mut execution_output = (*block.execution_output).clone();
853        execution_output.state = state;
854
855        ExecutedBlock::new(
856            Arc::clone(&block.recovered_block),
857            Arc::new(execution_output),
858            ComputedTrieData::new(Arc::new(hashed_state), Arc::new(trie_updates)),
859        )
860    }
861
862    fn test_blocks() -> Vec<ExecutedBlock<EthPrimitives>> {
863        TestBlockBuilder::eth()
864            .get_executed_blocks(0..5)
865            .enumerate()
866            .map(|(index, block)| with_unique_trie_data(&block, index as u8 + 1))
867            .collect()
868    }
869
870    fn setup_frontiers(
871        state_trie_tip_index: usize,
872        finish_tip_index: usize,
873    ) -> (ProviderFactory<MockNodeTypesWithDB>, Vec<ExecutedBlock<EthPrimitives>>) {
874        let factory = create_test_provider_factory();
875        let blocks = test_blocks();
876        let provider_rw = factory.provider_rw().unwrap();
877        for block in &blocks[..=finish_tip_index] {
878            provider_rw.insert_block(block.recovered_block()).unwrap();
879        }
880        provider_rw
881            .save_stage_checkpoint(
882                StageId::Finish,
883                StageCheckpoint::new(blocks[finish_tip_index].block_number())
884                    .with_finish_stage_checkpoint(FinishCheckpoint {
885                        partial_state_trie: Some(blocks[state_trie_tip_index].block_number()),
886                    }),
887            )
888            .unwrap();
889        provider_rw.commit().unwrap();
890
891        (factory, blocks)
892    }
893
894    fn account_keys(overlay: &StateTrieOverlay) -> Vec<B256> {
895        overlay.hashed_post_state.accounts.iter().map(|(key, _)| *key).collect()
896    }
897
898    fn account_node_paths(overlay: &StateTrieOverlay) -> Vec<Nibbles> {
899        overlay.trie_updates.account_nodes_ref().iter().map(|(path, _)| *path).collect()
900    }
901
902    #[test]
903    fn execution_overlay_extends_bundle_state_without_account_ids() {
904        let address = Address::with_last_byte(1);
905        let slot = U256::from(2);
906        let value = U256::from(3);
907        let code = Bytecode::new_raw(vec![0x60, 0x00].into());
908        let code_hash = code.hash_slow();
909        let account = AccountInfo {
910            nonce: 4,
911            balance: U256::from(5),
912            code_hash,
913            code: Some(code.clone()),
914            account_id: AccountId::new(6),
915        };
916        let state = BundleState::builder(0..=0)
917            .state_present_account_info(address, account.clone())
918            .state_storage(address, HashMap::from_iter([(slot, (U256::ZERO, value))]))
919            .contract(code_hash, code.clone())
920            .build();
921        assert!(state.state()[&address].info.as_ref().unwrap().account_id.is_some());
922
923        let mut overlay = ExecutionOverlay::default();
924        overlay.extend_state(&state);
925
926        let stored_account = overlay.accounts[&address].as_ref().unwrap();
927        assert_eq!(stored_account.account_id, None);
928        assert_eq!(
929            stored_account,
930            &AccountInfo { account_id: None, ..account },
931            "normalization must preserve durable account fields"
932        );
933        assert_eq!(stored_account.code, Some(code.clone()));
934        assert_eq!(overlay.storage[&address][&slot], value);
935        assert_eq!(overlay.code_hashes[&code_hash], code);
936    }
937
938    #[test]
939    fn execution_overlay_zeroes_unobserved_storage_for_destroyed_accounts() {
940        let address = Address::with_last_byte(1);
941        let mut state = BundleState::default();
942        state.state.insert(
943            address,
944            BundleAccount::new(
945                Some(AccountInfo::default()),
946                None,
947                Default::default(),
948                AccountStatus::Destroyed,
949            ),
950        );
951
952        let mut overlay = ExecutionOverlay::default();
953        overlay.extend_state(&state);
954
955        assert_eq!(overlay.storage_value(address, U256::ZERO), Some(U256::ZERO));
956    }
957
958    #[test]
959    fn execution_overlay_composition_uses_later_values_and_normalizes_accounts() {
960        let address = Address::with_last_byte(1);
961        let retained_address = Address::with_last_byte(2);
962        let slot = U256::from(3);
963        let retained_slot = U256::from(4);
964        let first_code_hash = B256::with_last_byte(5);
965        let later_code_hash = B256::with_last_byte(6);
966        let first_block = BlockNumHash::new(1, B256::with_last_byte(7));
967        let later_block = BlockNumHash::new(2, B256::with_last_byte(8));
968
969        let mut overlay = ExecutionOverlay::default();
970        overlay.block_hashes.push(first_block);
971        overlay.accounts.insert(
972            address,
973            Some(AccountInfo { nonce: 1, account_id: None, ..Default::default() }),
974        );
975        overlay.accounts.insert(retained_address, Some(AccountInfo::default()));
976        overlay.storage.entry(address).or_default().insert(slot, U256::from(9));
977        overlay.storage.entry(address).or_default().insert(retained_slot, U256::from(10));
978        overlay.code_hashes.insert(first_code_hash, Bytecode::new_raw(vec![1].into()));
979
980        let mut later = ExecutionOverlay::default();
981        later.block_hashes.push(later_block);
982        later.accounts.insert(
983            address,
984            Some(AccountInfo { nonce: 11, account_id: AccountId::new(12), ..Default::default() }),
985        );
986        later.storage.entry(address).or_default().insert(slot, U256::from(13));
987        later.storage_wipes.insert(address);
988        later.code_hashes.insert(later_code_hash, Bytecode::new_raw(vec![2].into()));
989
990        overlay.extend_overlay(&later);
991
992        assert!(later.accounts[&address].as_ref().unwrap().account_id.is_some());
993        assert_eq!(overlay.block_hashes, vec![first_block, later_block]);
994        assert_eq!(overlay.accounts[&address].as_ref().unwrap().nonce, 11);
995        assert_eq!(overlay.accounts[&address].as_ref().unwrap().account_id, None);
996        assert!(overlay.accounts.contains_key(&retained_address));
997        assert_eq!(overlay.storage[&address][&slot], U256::from(13));
998        assert!(!overlay.storage[&address].contains_key(&retained_slot));
999        assert_eq!(overlay.storage_value(address, U256::from(14)), Some(U256::ZERO));
1000        assert!(overlay.code_hashes.contains_key(&first_code_hash));
1001        assert!(overlay.code_hashes.contains_key(&later_code_hash));
1002    }
1003
1004    #[test]
1005    fn managed_overlay_starts_at_state_trie_frontier() {
1006        let (factory, blocks) = setup_frontiers(1, 3);
1007        let manager = OverlayManager::default();
1008        for block in &blocks[2..=4] {
1009            manager.insert_block(block.clone());
1010        }
1011        let provider = factory.provider().unwrap();
1012
1013        for (parent_index, expected_ids) in [(3, vec![3, 4]), (4, vec![3, 4, 5])] {
1014            let overlay = manager
1015                .overlay_builder(blocks[parent_index].recovered_block().hash())
1016                .build_state_trie_overlay(&provider)
1017                .unwrap();
1018
1019            assert_eq!(
1020                account_keys(&overlay),
1021                expected_ids.iter().copied().map(B256::with_last_byte).collect::<Vec<_>>()
1022            );
1023            assert_eq!(
1024                account_node_paths(&overlay),
1025                expected_ids
1026                    .iter()
1027                    .copied()
1028                    .map(|id| Nibbles::from_nibbles([id]))
1029                    .collect::<Vec<_>>()
1030            );
1031        }
1032    }
1033
1034    #[test]
1035    fn managed_overlay_skips_when_finish_is_the_anchor() {
1036        let (factory, blocks) = setup_frontiers(3, 3);
1037        let manager = OverlayManager::default();
1038        manager.insert_block(blocks[4].clone());
1039        let provider = factory.provider().unwrap();
1040
1041        let overlay = manager
1042            .overlay_builder(blocks[4].recovered_block().hash())
1043            .with_skip_overlay_for_reused_sparse_trie(blocks[3].recovered_block().hash())
1044            .build_state_trie_overlay(&provider)
1045            .unwrap();
1046
1047        assert!(overlay.hashed_post_state.is_empty());
1048        assert!(overlay.trie_updates.is_empty());
1049    }
1050
1051    #[test]
1052    fn no_reverts_errors_when_reverts_are_required() {
1053        let (factory, blocks) = setup_frontiers(2, 3);
1054        let provider = factory.provider().unwrap();
1055
1056        let builder = OverlayManager::<EthPrimitives>::default()
1057            .overlay_builder(blocks[1].recovered_block().hash())
1058            .with_no_reverts();
1059        let error = builder.build_state_trie_overlay(&provider).unwrap_err();
1060
1061        assert!(error.to_string().contains("reverts are disabled"));
1062    }
1063
1064    #[test]
1065    fn appended_overlay_rejects_noncanonical_anchor_when_reverts_are_required() {
1066        let (factory, blocks) = setup_frontiers(1, 3);
1067        let provider = factory.provider().unwrap();
1068        let parent_hash = B256::with_last_byte(100);
1069        assert_ne!(parent_hash, blocks[1].recovered_block().hash());
1070        let block = TestBlockBuilder::eth()
1071            .get_executed_block_with_number(blocks[2].block_number(), parent_hash);
1072        let builder =
1073            OverlayManager::default().overlay_builder(parent_hash).with_appended_block(block);
1074
1075        assert!(matches!(
1076            builder.execution_overlay(&provider),
1077            Err(ProviderError::BlockHashNotFound(hash)) if hash == parent_hash
1078        ));
1079        assert!(matches!(
1080            builder.build_state_trie_overlay(&provider),
1081            Err(ProviderError::BlockHashNotFound(hash)) if hash == parent_hash
1082        ));
1083    }
1084
1085    #[test]
1086    fn execution_overlay_marks_historical_fallback() {
1087        let (factory, blocks) = setup_frontiers(1, 3);
1088        let provider_rw = factory.provider_rw().unwrap();
1089        let address = Address::with_last_byte(1);
1090        let slot = U256::from(5);
1091
1092        provider_rw
1093            .tx_ref()
1094            .put::<tables::AccountChangeSets>(
1095                2,
1096                AccountBeforeTx {
1097                    address,
1098                    info: Some(Account { balance: U256::from(10), ..Default::default() }),
1099                },
1100            )
1101            .unwrap();
1102        provider_rw
1103            .tx_ref()
1104            .put::<tables::AccountChangeSets>(
1105                3,
1106                AccountBeforeTx {
1107                    address,
1108                    info: Some(Account { balance: U256::from(20), ..Default::default() }),
1109                },
1110            )
1111            .unwrap();
1112        for (block_number, value) in [(2, 10), (3, 15)] {
1113            provider_rw
1114                .tx_ref()
1115                .put::<tables::StorageChangeSets>(
1116                    BlockNumberAddress((block_number, address)),
1117                    StorageEntry { key: B256::from(slot), value: U256::from(value) },
1118                )
1119                .unwrap();
1120        }
1121        provider_rw.commit().unwrap();
1122
1123        let provider = factory.provider().unwrap();
1124        let (overlay, fallback_block_number) = OverlayManager::<EthPrimitives>::default()
1125            .overlay_builder(blocks[1].recovered_block().hash())
1126            .execution_overlay(&provider)
1127            .unwrap();
1128
1129        assert_eq!(fallback_block_number, Some(2));
1130        assert!(overlay.accounts.is_empty());
1131        assert!(overlay.storage.is_empty());
1132        assert!(overlay.code_hashes.is_empty());
1133    }
1134
1135    #[test]
1136    fn execution_overlay_uses_managed_blocks_after_the_anchor() {
1137        let (factory, blocks) = setup_frontiers(1, 3);
1138        let manager = OverlayManager::default();
1139        for block in &blocks[2..=4] {
1140            manager.insert_block(block.clone());
1141        }
1142        let provider = factory.provider().unwrap();
1143
1144        let (overlay, fallback_block_number) = manager
1145            .overlay_builder(blocks[3].recovered_block().hash())
1146            .execution_overlay(&provider)
1147            .unwrap();
1148
1149        assert_eq!(fallback_block_number, None);
1150
1151        for id in [3, 4] {
1152            let address = Address::with_last_byte(id);
1153            let slot = U256::from(id);
1154            assert_eq!(overlay.accounts[&address].as_ref().unwrap().balance, U256::from(id));
1155            assert_eq!(overlay.accounts[&address].as_ref().unwrap().account_id, None);
1156            assert_eq!(overlay.storage[&address][&slot], U256::from(id));
1157            assert!(overlay.code_hashes.contains_key(&B256::with_last_byte(id + 64)));
1158        }
1159        assert_eq!(
1160            overlay.block_hashes,
1161            blocks[2..=3]
1162                .iter()
1163                .map(|block| block.recovered_block().num_hash())
1164                .collect::<Vec<_>>()
1165        );
1166    }
1167
1168    #[test]
1169    fn execution_overlay_marks_historical_fallback_for_managed_fork() {
1170        let (factory, blocks) = setup_frontiers(1, 3);
1171        let address = Address::with_last_byte(1);
1172        let slot = U256::from(1);
1173        let provider_rw = factory.provider_rw().unwrap();
1174        for (block_number, balance, storage_value) in [(2u64, 10u64, 10u64), (3u64, 20u64, 15u64)] {
1175            provider_rw
1176                .tx_ref()
1177                .put::<tables::AccountChangeSets>(
1178                    block_number,
1179                    AccountBeforeTx {
1180                        address,
1181                        info: Some(Account { balance: U256::from(balance), ..Default::default() }),
1182                    },
1183                )
1184                .unwrap();
1185            provider_rw
1186                .tx_ref()
1187                .put::<tables::StorageChangeSets>(
1188                    BlockNumberAddress((block_number, address)),
1189                    StorageEntry { key: B256::from(slot), value: U256::from(storage_value) },
1190                )
1191                .unwrap();
1192        }
1193        provider_rw.commit().unwrap();
1194
1195        let mut side_chain_builder = TestBlockBuilder::eth();
1196        let side_block_two = side_chain_builder.get_executed_block_with_number(
1197            blocks[2].block_number(),
1198            blocks[1].recovered_block().hash(),
1199        );
1200        let side_block_two = with_unique_trie_data(&side_block_two, 1);
1201        let side_block_three = side_chain_builder.get_executed_block_with_number(
1202            blocks[3].block_number(),
1203            side_block_two.recovered_block().hash(),
1204        );
1205        let side_block_three = with_unique_trie_data(&side_block_three, 1);
1206        assert_ne!(
1207            side_block_three.recovered_block().hash(),
1208            blocks[3].recovered_block().hash(),
1209            "the managed chain must not contain the durable Finish block"
1210        );
1211
1212        let manager = OverlayManager::default();
1213        manager.insert_block(side_block_two.clone());
1214        manager.insert_block(side_block_three.clone());
1215        let provider = factory.provider().unwrap();
1216
1217        let (overlay, fallback_block_number) = manager
1218            .overlay_builder(side_block_three.recovered_block().hash())
1219            .execution_overlay(&provider)
1220            .unwrap();
1221
1222        assert_eq!(fallback_block_number, Some(2));
1223
1224        assert_eq!(overlay.accounts[&address].as_ref().unwrap().balance, U256::from(1));
1225        assert_eq!(overlay.accounts[&address].as_ref().unwrap().account_id, None);
1226        assert_eq!(overlay.storage[&address][&slot], U256::from(1));
1227        assert_eq!(
1228            overlay.block_hashes,
1229            [side_block_two, side_block_three]
1230                .iter()
1231                .map(|block| block.recovered_block().num_hash())
1232                .collect::<Vec<_>>()
1233        );
1234    }
1235
1236    #[test]
1237    fn execution_overlay_no_revert_path_discards_account_ids() {
1238        let (factory, blocks) = setup_frontiers(1, 1);
1239        let manager = OverlayManager::default();
1240        for block in &blocks[2..=3] {
1241            manager.insert_block(block.clone());
1242        }
1243        let provider = factory.provider().unwrap();
1244
1245        let (overlay, fallback_block_number) = manager
1246            .overlay_builder(blocks[3].recovered_block().hash())
1247            .execution_overlay(&provider)
1248            .unwrap();
1249
1250        assert_eq!(fallback_block_number, None);
1251        assert_eq!(overlay.accounts.len(), 2);
1252        assert!(overlay.accounts.values().flatten().all(|account| account.account_id.is_none()));
1253    }
1254
1255    #[test]
1256    fn managed_overlay_uses_persisted_parent_even_if_retained() {
1257        let (factory, blocks) = setup_frontiers(2, 3);
1258        let manager = OverlayManager::default();
1259        manager.insert_block(blocks[1].clone());
1260        let provider = factory.provider().unwrap();
1261        let builder = manager.overlay_builder(blocks[1].recovered_block().hash());
1262        match builder.anchor_at_parent(&provider).unwrap() {
1263            AnchorForParent::RevertsRequired { anchor, finish } => {
1264                assert_eq!(anchor, blocks[1].recovered_block().num_hash());
1265                assert_eq!(finish, blocks[3].recovered_block().num_hash());
1266            }
1267            AnchorForParent::NoReverts { .. } => {
1268                panic!("persisted parent below Finish must require reverts")
1269            }
1270        }
1271    }
1272
1273    #[test]
1274    fn overlay_after_state_trie_frontier_requires_managed_coverage() {
1275        let (factory, blocks) = setup_frontiers(1, 3);
1276        let provider = factory.provider().unwrap();
1277        let error = OverlayManager::<EthPrimitives>::default()
1278            .overlay_builder(blocks[3].recovered_block().hash())
1279            .build_state_trie_overlay(&provider)
1280            .unwrap_err();
1281
1282        assert!(
1283            error.to_string().contains("is after partial state trie frontier"),
1284            "unexpected error: {error}"
1285        );
1286    }
1287
1288    #[test]
1289    fn managed_overlay_errors_if_parent_is_not_persisted_or_managed_across_frontiers() {
1290        let (factory, blocks) = setup_frontiers(1, 3);
1291        let provider = factory.provider().unwrap();
1292        let parent_hash = blocks[3].recovered_block().hash();
1293        let error = OverlayManager::<EthPrimitives>::default()
1294            .overlay_builder(parent_hash)
1295            .build_state_trie_overlay(&provider)
1296            .unwrap_err();
1297
1298        assert!(error.to_string().contains("is after partial state trie frontier"));
1299    }
1300
1301    #[test]
1302    fn managed_overlay_skips_manager_for_persisted_parent() {
1303        let parent_hash = B256::with_last_byte(1);
1304        let builder = OverlayManager::<EthPrimitives>::default().overlay_builder(parent_hash);
1305
1306        let (trie, state) = builder.resolve_state_trie_overlays(parent_hash).unwrap();
1307        assert!(trie.is_empty());
1308        assert!(state.is_empty());
1309    }
1310
1311    #[test]
1312    fn managed_overlay_errors_if_parent_is_not_persisted_or_managed() {
1313        let parent_hash = B256::with_last_byte(1);
1314        let anchor_hash = B256::with_last_byte(2);
1315        let builder = OverlayManager::<EthPrimitives>::default().overlay_builder(parent_hash);
1316
1317        let err = builder.resolve_state_trie_overlays(anchor_hash).unwrap_err();
1318
1319        assert!(err.to_string().contains("cannot be anchored"));
1320    }
1321
1322    #[test]
1323    fn managed_overlay_skip_requires_both_frontiers() {
1324        let parent_hash = B256::with_last_byte(1);
1325        let builder = OverlayManager::<EthPrimitives>::default().overlay_builder(parent_hash);
1326        assert!(!builder.should_skip_overlay_for_reused_sparse_trie(parent_hash, parent_hash));
1327
1328        let builder = builder.with_skip_overlay_for_reused_sparse_trie(parent_hash);
1329        assert!(builder.should_skip_overlay_for_reused_sparse_trie(parent_hash, parent_hash));
1330        assert!(!builder
1331            .should_skip_overlay_for_reused_sparse_trie(B256::with_last_byte(3), parent_hash,));
1332
1333        let blocks = test_blocks();
1334        let manager = OverlayManager::default();
1335        for block in &blocks[2..=4] {
1336            manager.insert_block(block.clone());
1337        }
1338        let builder = manager
1339            .overlay_builder(blocks[4].recovered_block().hash())
1340            .with_skip_overlay_for_reused_sparse_trie(blocks[1].recovered_block().hash());
1341        assert!(builder.should_skip_overlay_for_reused_sparse_trie(
1342            blocks[1].recovered_block().hash(),
1343            blocks[3].recovered_block().hash(),
1344        ));
1345
1346        let builder =
1347            builder.with_skip_overlay_for_reused_sparse_trie(blocks[2].recovered_block().hash());
1348        assert!(!builder.should_skip_overlay_for_reused_sparse_trie(
1349            blocks[1].recovered_block().hash(),
1350            blocks[3].recovered_block().hash(),
1351        ));
1352    }
1353}