Skip to main content

reth_provider/providers/state/
overlay.rs

1use alloy_eips::BlockNumHash;
2use alloy_primitives::{BlockHash, BlockNumber, B256};
3use metrics::{Counter, Histogram};
4use reth_chain_state::{EthPrimitives, StateTrieOverlayManager};
5use reth_db_api::{tables, transaction::DbTx, DatabaseError};
6use reth_errors::{ProviderError, ProviderResult};
7use reth_metrics::Metrics;
8use reth_primitives_traits::{
9    dashmap::{self, DashMap},
10    NodePrimitives,
11};
12use reth_prune_types::PruneSegment;
13use reth_stages_types::StageId;
14use reth_storage_api::{
15    BlockNumReader, ChangeSetReader, DBProvider, DatabaseProviderFactory,
16    DatabaseProviderROFactory, PruneCheckpointReader, StageCheckpointReader,
17    StorageChangeSetReader, StorageSettingsCache,
18};
19use reth_trie::{
20    hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory},
21    trie_cursor::{InMemoryTrieCursor, TrieCursor, TrieCursorFactory, TrieStorageCursor},
22    updates::TrieUpdatesSorted,
23    HashedPostStateSorted,
24};
25use reth_trie_db::{
26    ChangesetCache, DatabaseAccountTrieCursor, DatabaseHashedCursorFactory,
27    DatabaseStorageTrieCursor, LegacyKeyAdapter, PackedAccountsTrie, PackedKeyAdapter,
28    PackedStoragesTrie,
29};
30use std::{
31    ops::RangeInclusive,
32    sync::Arc,
33    time::{Duration, Instant},
34};
35use tracing::{debug, debug_span, instrument};
36
37/// Metrics for overlay state provider operations.
38#[derive(Clone, Metrics)]
39#[metrics(scope = "storage.providers.overlay")]
40pub(crate) struct OverlayStateProviderMetrics {
41    /// Duration of creating the database provider transaction
42    create_provider_duration: Histogram,
43    /// Duration of retrieving trie updates from the database
44    retrieve_trie_reverts_duration: Histogram,
45    /// Duration of retrieving hashed state from the database
46    retrieve_hashed_state_reverts_duration: Histogram,
47    /// Size of trie updates (number of entries)
48    trie_updates_size: Histogram,
49    /// Size of hashed state (number of entries)
50    hashed_state_size: Histogram,
51    /// Overall duration of the [`OverlayStateProviderFactory::database_provider_ro`] call
52    database_provider_ro_duration: Histogram,
53    /// Number of cache misses when fetching [`Overlay`]s from the overlay cache.
54    overlay_cache_misses: Counter,
55}
56
57/// Contains all fields required to initialize an [`OverlayStateProvider`].
58#[derive(Debug, Clone)]
59pub(super) struct Overlay {
60    pub(super) trie_updates: Arc<TrieUpdatesSorted>,
61    pub(super) hashed_post_state: Arc<HashedPostStateSorted>,
62}
63
64/// Source of overlay data for [`OverlayStateProviderFactory`].
65#[derive(Debug, Clone)]
66pub(super) enum OverlaySource<N: NodePrimitives = EthPrimitives> {
67    /// Immediate overlay with already-computed data.
68    Immediate {
69        /// Trie updates overlay.
70        ///
71        /// This can be non-empty when a caller starts with an explicit `TrieInputSorted`, such
72        /// as historical providers.
73        trie: Arc<TrieUpdatesSorted>,
74        /// Hashed state overlay.
75        state: Arc<HashedPostStateSorted>,
76    },
77    /// Manager-backed overlay for in-memory state, with optional immediate overlay data.
78    Managed {
79        /// Manager used to resolve in-memory parent state if the parent is not persisted.
80        manager: StateTrieOverlayManager<N>,
81        /// Immediate hashed state overlay applied on top of any manager-produced overlay.
82        ///
83        /// This is populated by the `with_hashed_state_overlay` methods.
84        state: Arc<HashedPostStateSorted>,
85    },
86}
87
88/// Builder for calculating trie and hashed-state overlays.
89///
90/// This stores the overlay configuration and the logic for resolving overlays and collecting
91/// reverts. It is intentionally independent from any provider factory or overlay cache.
92#[derive(Debug, Clone)]
93pub struct OverlayBuilder<N: NodePrimitives = EthPrimitives> {
94    /// Parent hash requested by the caller.
95    parent_hash: B256,
96    /// Optional overlay source.
97    overlay_source: Option<OverlaySource<N>>,
98    /// Changeset cache handle for retrieving trie changesets
99    changeset_cache: ChangesetCache,
100    /// Metrics for tracking provider operations
101    metrics: OverlayStateProviderMetrics,
102}
103
104impl<N: NodePrimitives> OverlayBuilder<N> {
105    /// Create a new overlay builder.
106    pub fn new(parent_hash: B256, changeset_cache: ChangesetCache) -> Self {
107        Self {
108            parent_hash,
109            overlay_source: None,
110            changeset_cache,
111            metrics: OverlayStateProviderMetrics::default(),
112        }
113    }
114
115    /// Set the overlay source.
116    ///
117    /// This overlay will be applied on top of any reverts.
118    pub(super) fn with_overlay_source(mut self, source: Option<OverlaySource<N>>) -> Self {
119        self.overlay_source = source;
120        self
121    }
122
123    /// Set the state trie overlay manager used to resolve in-memory parent state.
124    pub fn with_state_trie_overlay_manager(
125        mut self,
126        state_trie_overlay_manager: StateTrieOverlayManager<N>,
127    ) -> Self {
128        self.overlay_source = Some(OverlaySource::Managed {
129            manager: state_trie_overlay_manager,
130            state: Arc::new(HashedPostStateSorted::default()),
131        });
132        self
133    }
134
135    /// Set the hashed state overlay.
136    pub fn with_hashed_state_overlay(
137        mut self,
138        hashed_state_overlay: Option<Arc<HashedPostStateSorted>>,
139    ) -> Self {
140        if let Some(state) = hashed_state_overlay {
141            match &mut self.overlay_source {
142                Some(OverlaySource::Managed { state: managed_state, .. }) => {
143                    *managed_state = state;
144                }
145                _ => {
146                    self.overlay_source = Some(OverlaySource::Immediate {
147                        trie: Arc::new(TrieUpdatesSorted::default()),
148                        state,
149                    });
150                }
151            }
152        }
153        self
154    }
155
156    /// Extends the existing hashed state overlay with the given [`HashedPostStateSorted`].
157    ///
158    /// If no overlay exists, creates an immediate overlay with the given state.
159    pub fn with_extended_hashed_state_overlay(mut self, other: HashedPostStateSorted) -> Self {
160        match &mut self.overlay_source {
161            Some(OverlaySource::Immediate { state, .. } | OverlaySource::Managed { state, .. }) => {
162                Arc::make_mut(state).extend_ref_and_sort(&other);
163            }
164            None => {
165                self.overlay_source = Some(OverlaySource::Immediate {
166                    trie: Arc::new(TrieUpdatesSorted::default()),
167                    state: Arc::new(other),
168                });
169            }
170        }
171        self
172    }
173
174    /// Resolves the effective overlay (trie updates, hashed state).
175    fn resolve_overlays(
176        &self,
177        anchor_hash: BlockHash,
178    ) -> ProviderResult<(Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>)> {
179        match &self.overlay_source {
180            Some(OverlaySource::Managed { manager, state }) => {
181                let (trie, mut overlay_state) = if anchor_hash == self.parent_hash {
182                    (
183                        Arc::new(TrieUpdatesSorted::default()),
184                        Arc::new(HashedPostStateSorted::default()),
185                    )
186                } else {
187                    manager
188                        .overlay_for_parent(self.parent_hash, anchor_hash)
189                        .map_err(ProviderError::other)?
190                };
191
192                if overlay_state.is_empty() {
193                    overlay_state = Arc::clone(state);
194                } else if !state.is_empty() {
195                    Arc::make_mut(&mut overlay_state).extend_ref_and_sort(state);
196                }
197
198                Ok((trie, overlay_state))
199            }
200            Some(OverlaySource::Immediate { trie, state }) => {
201                if anchor_hash != self.parent_hash {
202                    return Err(ProviderError::other(std::io::Error::other(format!(
203                        "anchor_hash {anchor_hash} doesn't match OverlayBuilder's configured parent ({})",
204                        self.parent_hash
205                    ))))
206                }
207                Ok((Arc::clone(trie), Arc::clone(state)))
208            }
209            None => Ok((
210                Arc::new(TrieUpdatesSorted::default()),
211                Arc::new(HashedPostStateSorted::default()),
212            )),
213        }
214    }
215
216    /// Returns the block which is at the tip of the DB, i.e. the block which the state tables of
217    /// the DB are currently synced to.
218    fn get_db_tip_block<Provider>(&self, provider: &Provider) -> ProviderResult<BlockNumHash>
219    where
220        Provider: StageCheckpointReader + BlockNumReader,
221    {
222        let block_number = provider
223            .get_stage_checkpoint(StageId::Finish)?
224            .as_ref()
225            .map(|chk| chk.block_number)
226            .ok_or_else(|| ProviderError::InsufficientChangesets {
227                requested: 0,
228                available: 0..=0,
229            })?;
230        let hash = provider
231            .convert_number(block_number.into())?
232            .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?;
233        Ok(BlockNumHash::new(block_number, hash))
234    }
235
236    /// Returns whether or not it is required to collect reverts, and validates that there are
237    /// sufficient changesets to revert to the requested block number if so.
238    ///
239    /// Takes into account both the stage checkpoint and the prune checkpoint to determine the
240    /// available data range.
241    fn reverts_required<Provider>(
242        &self,
243        provider: &Provider,
244        db_tip_block: BlockNumHash,
245        anchor_hash: B256,
246    ) -> ProviderResult<Option<RangeInclusive<BlockNumber>>>
247    where
248        Provider: BlockNumReader + PruneCheckpointReader,
249    {
250        // If the anchor is the DB tip then there won't be any reverts necessary.
251        if db_tip_block.hash == anchor_hash {
252            return Ok(None)
253        }
254
255        let anchor_number = provider
256            .convert_hash_or_number(anchor_hash.into())?
257            .ok_or(ProviderError::BlockHashNotFound(anchor_hash))?;
258
259        // Check account history prune checkpoint to determine the lower bound of available data.
260        // The prune checkpoint's block_number is the highest pruned block, so data is available
261        // starting from the next block.
262        let prune_checkpoint = provider.get_prune_checkpoint(PruneSegment::AccountHistory)?;
263        let lower_bound = prune_checkpoint
264            .and_then(|chk| chk.block_number)
265            .map(|block_number| block_number + 1)
266            .unwrap_or_default();
267
268        let available_range = lower_bound..=db_tip_block.number;
269
270        // Check if the requested block is within the available range
271        if !available_range.contains(&anchor_number) {
272            return Err(ProviderError::InsufficientChangesets {
273                requested: anchor_number,
274                available: available_range,
275            });
276        }
277
278        Ok(Some(anchor_number + 1..=db_tip_block.number))
279    }
280
281    /// Calculates a new [`Overlay`] given a transaction and the current db tip.
282    #[instrument(
283        level = "debug",
284        target = "providers::state::overlay",
285        skip_all,
286        fields(?db_tip_block, parent_hash = ?self.parent_hash)
287    )]
288    fn calculate_overlay<Provider>(
289        &self,
290        provider: &Provider,
291        db_tip_block: BlockNumHash,
292    ) -> ProviderResult<Overlay>
293    where
294        Provider: ChangeSetReader
295            + StorageChangeSetReader
296            + DBProvider
297            + BlockNumReader
298            + StageCheckpointReader
299            + PruneCheckpointReader
300            + StorageSettingsCache,
301    {
302        //
303        // Set up variables we'll use for recording metrics. There's two different code-paths here,
304        // and we want to make sure both record metrics, so we do metrics recording after.
305        let retrieve_trie_reverts_duration;
306        let retrieve_hashed_state_reverts_duration;
307        let trie_updates_total_len;
308        let hashed_state_updates_total_len;
309        let anchor_hash = match &self.overlay_source {
310            Some(OverlaySource::Managed { manager, .. }) => {
311                let parent_is_persisted = provider
312                    .convert_hash_or_number(self.parent_hash.into())?
313                    .is_some_and(|parent_number| parent_number <= db_tip_block.number);
314                if parent_is_persisted {
315                    self.parent_hash
316                } else {
317                    manager
318                        .anchor_for_parent(self.parent_hash, db_tip_block.hash)
319                        .ok_or(ProviderError::BlockHashNotFound(self.parent_hash))?
320                }
321            }
322            _ => self.parent_hash,
323        };
324
325        // Collect any reverts which are required to bring the DB view back to the anchor hash.
326        let (trie_updates, hashed_post_state) = if let Some(revert_blocks) =
327            self.reverts_required(provider, db_tip_block, anchor_hash)?
328        {
329            debug!(
330                target: "providers::state::overlay",
331                ?revert_blocks,
332                "Collecting trie reverts for overlay state provider"
333            );
334
335            // Collect trie reverts using changeset cache
336            let trie_reverts = {
337                let _guard =
338                    debug_span!(target: "providers::state::overlay", "retrieving_trie_reverts")
339                        .entered();
340
341                let start = Instant::now();
342
343                // Use changeset cache to retrieve and accumulate reverts to restore state after
344                // from_block
345                let accumulated_reverts =
346                    self.changeset_cache.get_or_compute_range(provider, revert_blocks.clone())?;
347
348                retrieve_trie_reverts_duration = start.elapsed();
349                accumulated_reverts
350            };
351
352            // Collect state reverts
353            let mut hashed_state_reverts = {
354                let _guard = debug_span!(target: "providers::state::overlay", "retrieving_hashed_state_reverts").entered();
355
356                let start = Instant::now();
357                let res = reth_trie_db::from_reverts_auto(provider, revert_blocks)?;
358                retrieve_hashed_state_reverts_duration = start.elapsed();
359                res
360            };
361
362            // Resolve overlays and extend reverts with them.
363            // If reverts are empty, use overlays directly to avoid cloning.
364            let (overlay_trie, overlay_state) = self.resolve_overlays(anchor_hash)?;
365
366            let trie_updates = if trie_reverts.is_empty() {
367                overlay_trie
368            } else if !overlay_trie.is_empty() {
369                let mut trie_reverts = (*trie_reverts).clone();
370                trie_reverts.extend_ref_and_sort(&overlay_trie);
371                Arc::new(trie_reverts)
372            } else {
373                trie_reverts
374            };
375
376            let hashed_state_updates = if hashed_state_reverts.is_empty() {
377                overlay_state
378            } else if !overlay_state.is_empty() {
379                hashed_state_reverts.extend_ref_and_sort(&overlay_state);
380                Arc::new(hashed_state_reverts)
381            } else {
382                Arc::new(hashed_state_reverts)
383            };
384
385            trie_updates_total_len = trie_updates.total_len();
386            hashed_state_updates_total_len = hashed_state_updates.total_len();
387
388            debug!(
389                target: "providers::state::overlay",
390                num_trie_updates = ?trie_updates_total_len,
391                num_state_updates = ?hashed_state_updates_total_len,
392                "Reverted to anchor block",
393            );
394
395            (trie_updates, hashed_state_updates)
396        } else {
397            // If no reverts are needed then the db tip is the anchor hash. Use overlays directly.
398            let (trie_updates, hashed_state) = self.resolve_overlays(db_tip_block.hash)?;
399
400            retrieve_trie_reverts_duration = Duration::ZERO;
401            retrieve_hashed_state_reverts_duration = Duration::ZERO;
402            trie_updates_total_len = trie_updates.total_len();
403            hashed_state_updates_total_len = hashed_state.total_len();
404
405            (trie_updates, hashed_state)
406        };
407
408        // Record metrics
409        self.metrics
410            .retrieve_trie_reverts_duration
411            .record(retrieve_trie_reverts_duration.as_secs_f64());
412        self.metrics
413            .retrieve_hashed_state_reverts_duration
414            .record(retrieve_hashed_state_reverts_duration.as_secs_f64());
415        self.metrics.trie_updates_size.record(trie_updates_total_len as f64);
416        self.metrics.hashed_state_size.record(hashed_state_updates_total_len as f64);
417
418        Ok(Overlay { trie_updates, hashed_post_state })
419    }
420
421    /// Builds the effective overlay for the given provider.
422    #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
423    pub(super) fn build_overlay<Provider>(&self, provider: &Provider) -> ProviderResult<Overlay>
424    where
425        Provider: StageCheckpointReader
426            + PruneCheckpointReader
427            + ChangeSetReader
428            + StorageChangeSetReader
429            + DBProvider
430            + BlockNumReader
431            + StorageSettingsCache,
432    {
433        let db_tip_block = self.get_db_tip_block(provider)?;
434        self.calculate_overlay(provider, db_tip_block)
435    }
436}
437
438/// Factory for creating overlay state providers with optional reverts and overlays.
439///
440/// This factory allows building an `OverlayStateProvider` whose DB state has been reverted to a
441/// particular block, and/or with additional overlay information added on top.
442#[derive(Debug, Clone)]
443pub struct OverlayStateProviderFactory<F, N: NodePrimitives = EthPrimitives> {
444    /// The underlying database provider factory
445    factory: F,
446    /// Overlay builder containing the configuration and overlay calculation logic.
447    overlay_builder: OverlayBuilder<N>,
448    /// A cache which maps `db_tip -> Overlay`. If the db tip changes during usage of the factory
449    /// then a new entry will get added to this, but in most cases only one entry is present.
450    overlay_cache: Arc<DashMap<BlockHash, Overlay>>,
451}
452
453impl<F, N: NodePrimitives> OverlayStateProviderFactory<F, N> {
454    /// Create a new overlay state provider factory
455    pub fn new(factory: F, overlay_builder: OverlayBuilder<N>) -> Self {
456        Self { factory, overlay_builder, overlay_cache: Default::default() }
457    }
458
459    /// Set the hashed state overlay.
460    pub fn with_hashed_state_overlay(
461        mut self,
462        hashed_state_overlay: Option<Arc<HashedPostStateSorted>>,
463    ) -> Self {
464        self.overlay_builder = self.overlay_builder.with_hashed_state_overlay(hashed_state_overlay);
465        self.overlay_cache = Default::default();
466        self
467    }
468
469    /// Extends the existing hashed state overlay with the given [`HashedPostStateSorted`].
470    pub fn with_extended_hashed_state_overlay(mut self, other: HashedPostStateSorted) -> Self {
471        self.overlay_builder = self.overlay_builder.with_extended_hashed_state_overlay(other);
472        self.overlay_cache = Default::default();
473        self
474    }
475
476    /// Fetches an [`Overlay`] from the cache based on the current db tip block. If there is no
477    /// cached value then this calculates the [`Overlay`] and populates the cache.
478    #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
479    fn get_overlay<Provider>(&self, provider: &Provider) -> ProviderResult<Overlay>
480    where
481        Provider: StageCheckpointReader
482            + PruneCheckpointReader
483            + ChangeSetReader
484            + StorageChangeSetReader
485            + DBProvider
486            + BlockNumReader
487            + StorageSettingsCache,
488    {
489        let db_tip_block = self.overlay_builder.get_db_tip_block(provider)?;
490
491        let overlay = match self.overlay_cache.entry(db_tip_block.hash) {
492            dashmap::Entry::Occupied(entry) => entry.get().clone(),
493            dashmap::Entry::Vacant(entry) => {
494                self.overlay_builder.metrics.overlay_cache_misses.increment(1);
495                let overlay = self.overlay_builder.build_overlay(provider)?;
496                entry.insert(overlay.clone());
497                overlay
498            }
499        };
500
501        Ok(overlay)
502    }
503}
504
505impl<F, N> DatabaseProviderROFactory for OverlayStateProviderFactory<F, N>
506where
507    N: NodePrimitives,
508    F: DatabaseProviderFactory,
509    F::Provider: StageCheckpointReader
510        + PruneCheckpointReader
511        + DBProvider
512        + BlockNumReader
513        + ChangeSetReader
514        + StorageChangeSetReader
515        + StorageSettingsCache,
516{
517    type Provider = OverlayStateProvider<F::Provider>;
518
519    /// Create a read-only [`OverlayStateProvider`].
520    #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
521    fn database_provider_ro(&self) -> ProviderResult<OverlayStateProvider<F::Provider>> {
522        let overall_start = Instant::now();
523
524        // Get a read-only provider
525        let provider = {
526            let start = Instant::now();
527            let res = self.factory.database_provider_ro()?;
528            self.overlay_builder.metrics.create_provider_duration.record(start.elapsed());
529            res
530        };
531
532        let Overlay { trie_updates, hashed_post_state } = self.get_overlay(&provider)?;
533
534        let is_v2 = provider.cached_storage_settings().is_v2();
535        self.overlay_builder.metrics.database_provider_ro_duration.record(overall_start.elapsed());
536        Ok(OverlayStateProvider::new(provider, trie_updates, hashed_post_state, is_v2))
537    }
538}
539
540/// State provider with in-memory overlay from trie updates and hashed post state.
541///
542/// This provider uses in-memory trie updates and hashed post state as an overlay
543/// on top of a database provider, implementing [`TrieCursorFactory`] and [`HashedCursorFactory`]
544/// using the in-memory overlay factories.
545#[derive(Debug)]
546pub struct OverlayStateProvider<Provider: DBProvider> {
547    provider: Provider,
548    trie_updates: Arc<TrieUpdatesSorted>,
549    hashed_post_state: Arc<HashedPostStateSorted>,
550    is_v2: bool,
551}
552
553impl<Provider> OverlayStateProvider<Provider>
554where
555    Provider: DBProvider,
556{
557    /// Create new overlay state provider. The `Provider` must be cloneable, which generally means
558    /// it should be wrapped in an `Arc`.
559    pub const fn new(
560        provider: Provider,
561        trie_updates: Arc<TrieUpdatesSorted>,
562        hashed_post_state: Arc<HashedPostStateSorted>,
563        is_v2: bool,
564    ) -> Self {
565        Self { provider, trie_updates, hashed_post_state, is_v2 }
566    }
567}
568
569impl<Provider> TrieCursorFactory for OverlayStateProvider<Provider>
570where
571    Provider: DBProvider,
572    Provider::Tx: DbTx,
573{
574    type AccountTrieCursor<'a>
575        = InMemoryTrieCursor<'a, Box<dyn TrieCursor + Send + 'a>>
576    where
577        Self: 'a;
578
579    type StorageTrieCursor<'a>
580        = InMemoryTrieCursor<'a, Box<dyn TrieStorageCursor + Send + 'a>>
581    where
582        Self: 'a;
583
584    fn account_trie_cursor(&self) -> Result<Self::AccountTrieCursor<'_>, DatabaseError> {
585        let tx = self.provider.tx_ref();
586        let trie_updates = self.trie_updates.as_ref();
587        let cursor: Box<dyn TrieCursor + Send> = if self.is_v2 {
588            Box::new(DatabaseAccountTrieCursor::<_, PackedKeyAdapter>::new(
589                tx.cursor_read::<PackedAccountsTrie>()?,
590            ))
591        } else {
592            Box::new(DatabaseAccountTrieCursor::<_, LegacyKeyAdapter>::new(
593                tx.cursor_read::<tables::AccountsTrie>()?,
594            ))
595        };
596        Ok(InMemoryTrieCursor::new_account(cursor, trie_updates))
597    }
598
599    fn storage_trie_cursor(
600        &self,
601        hashed_address: B256,
602    ) -> Result<Self::StorageTrieCursor<'_>, DatabaseError> {
603        let tx = self.provider.tx_ref();
604        let trie_updates = self.trie_updates.as_ref();
605        let cursor: Box<dyn TrieStorageCursor + Send> = if self.is_v2 {
606            Box::new(DatabaseStorageTrieCursor::<_, PackedKeyAdapter>::new(
607                tx.cursor_dup_read::<PackedStoragesTrie>()?,
608                hashed_address,
609            ))
610        } else {
611            Box::new(DatabaseStorageTrieCursor::<_, LegacyKeyAdapter>::new(
612                tx.cursor_dup_read::<tables::StoragesTrie>()?,
613                hashed_address,
614            ))
615        };
616        Ok(InMemoryTrieCursor::new_storage(cursor, trie_updates, hashed_address))
617    }
618}
619
620impl<Provider> HashedCursorFactory for OverlayStateProvider<Provider>
621where
622    Provider: DBProvider,
623{
624    type AccountCursor<'a>
625        = <HashedPostStateCursorFactory<
626        DatabaseHashedCursorFactory<&'a Provider::Tx>,
627        &'a Arc<HashedPostStateSorted>,
628    > as HashedCursorFactory>::AccountCursor<'a>
629    where
630        Self: 'a;
631
632    type StorageCursor<'a>
633        = <HashedPostStateCursorFactory<
634        DatabaseHashedCursorFactory<&'a Provider::Tx>,
635        &'a Arc<HashedPostStateSorted>,
636    > as HashedCursorFactory>::StorageCursor<'a>
637    where
638        Self: 'a;
639
640    fn hashed_account_cursor(&self) -> Result<Self::AccountCursor<'_>, DatabaseError> {
641        let db_hashed_cursor_factory = DatabaseHashedCursorFactory::new(self.provider.tx_ref());
642        let hashed_cursor_factory =
643            HashedPostStateCursorFactory::new(db_hashed_cursor_factory, &self.hashed_post_state);
644        hashed_cursor_factory.hashed_account_cursor()
645    }
646
647    fn hashed_storage_cursor(
648        &self,
649        hashed_address: B256,
650    ) -> Result<Self::StorageCursor<'_>, DatabaseError> {
651        let db_hashed_cursor_factory = DatabaseHashedCursorFactory::new(self.provider.tx_ref());
652        let hashed_cursor_factory =
653            HashedPostStateCursorFactory::new(db_hashed_cursor_factory, &self.hashed_post_state);
654        hashed_cursor_factory.hashed_storage_cursor(hashed_address)
655    }
656}
657
658#[cfg(test)]
659mod tests {
660    use super::*;
661    use reth_primitives_traits::Account;
662    use reth_trie::HashedPostState;
663
664    #[test]
665    fn managed_overlay_skips_manager_for_persisted_parent() {
666        let parent_hash = B256::with_last_byte(1);
667        let builder = OverlayBuilder::<EthPrimitives>::new(parent_hash, ChangesetCache::default())
668            .with_state_trie_overlay_manager(StateTrieOverlayManager::default());
669
670        let (trie, state) = builder.resolve_overlays(parent_hash).unwrap();
671        assert!(trie.is_empty());
672        assert!(state.is_empty());
673    }
674
675    #[test]
676    fn managed_overlay_errors_if_parent_is_not_persisted_or_managed() {
677        let parent_hash = B256::with_last_byte(1);
678        let anchor_hash = B256::with_last_byte(2);
679        let builder = OverlayBuilder::<EthPrimitives>::new(parent_hash, ChangesetCache::default())
680            .with_state_trie_overlay_manager(StateTrieOverlayManager::default());
681
682        let err = builder.resolve_overlays(anchor_hash).unwrap_err();
683
684        assert!(err.to_string().contains("cannot be anchored"));
685    }
686
687    #[test]
688    fn extending_hashed_state_keeps_managed_overlay_source() {
689        let parent_hash = B256::with_last_byte(1);
690        let hashed_state = HashedPostState::default()
691            .with_accounts([(B256::with_last_byte(2), Some(Account::default()))])
692            .into_sorted();
693        let builder = OverlayBuilder::<EthPrimitives>::new(parent_hash, ChangesetCache::default())
694            .with_state_trie_overlay_manager(StateTrieOverlayManager::default())
695            .with_extended_hashed_state_overlay(hashed_state);
696
697        let Some(OverlaySource::Managed { state, .. }) = builder.overlay_source else {
698            panic!("expected managed overlay source")
699        };
700        assert_eq!(state.total_len(), 1);
701    }
702}