Skip to main content

reth_storage_overlay/
manager.rs

1//! Flattened state trie overlays for in-memory blocks.
2//!
3//! Payload validation needs a view of the state trie as of an in-memory parent block even when that
4//! parent has not been persisted yet. [`OverlayManager`] tracks those in-memory blocks and
5//! builds reusable flattened state trie overlays on demand.
6
7use crate::{
8    changeset_cache::compute_block_trie_updates, database_state_frontiers, ChangesetCache,
9    OverlayBuilder,
10};
11use alloy_eips::BlockNumHash;
12use alloy_primitives::{BlockNumber, B256};
13use parking_lot::Mutex;
14use reth_chain_state::{ExecutedBlock, PreservedSparseTrie};
15use reth_errors::ProviderResult;
16use reth_ethereum_primitives::EthPrimitives;
17use reth_metrics::{
18    metrics::{Counter, Histogram},
19    Metrics,
20};
21use reth_primitives_traits::{
22    dashmap::{mapref::entry::Entry, DashMap},
23    AlloyBlockHeader, FastInstant, NodePrimitives,
24};
25use reth_storage_api::{
26    BlockNumReader, ChangeSetReader, DBProvider, PruneCheckpointReader, StageCheckpointReader,
27    StorageChangeSetReader, StorageSettingsCache,
28};
29#[cfg(feature = "rayon")]
30use reth_tasks::WorkerPool;
31use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted, TrieInputSorted};
32use std::{
33    fmt,
34    ops::RangeInclusive,
35    sync::{Arc, OnceLock},
36    time::Instant,
37};
38use tracing::{debug, trace};
39
40/// Manages flattened state trie overlays for in-memory blocks.
41///
42/// The manager owns the in-memory block graph, changeset cache, and a cache of flattened state trie
43/// overlays keyed by `(anchor_hash, tip_hash)`.
44#[derive(Clone)]
45pub struct OverlayManager<N: NodePrimitives = EthPrimitives> {
46    blocks: Arc<DashMap<B256, ExecutedBlock<N>>>,
47    overlays: Arc<DashMap<OverlayCacheKey, OverlayCacheEntry>>,
48    changeset_cache: ChangesetCache,
49    preserved_sparse_trie: Arc<Mutex<Option<PreservedSparseTrie>>>,
50    #[cfg(feature = "rayon")]
51    worker_pool: Option<Arc<WorkerPool>>,
52    metrics: StateTrieOverlayMetrics,
53}
54
55/// Metrics for state trie overlay management.
56#[derive(Clone, Metrics)]
57#[metrics(scope = "sync.block_validation.state_trie_overlay")]
58struct StateTrieOverlayMetrics {
59    /// Duration of overlay computation in seconds.
60    overlay_computation_duration_seconds: Histogram,
61    /// Number of requests satisfied by an existing overlay cache entry.
62    overlay_cache_reuses: Counter,
63    /// Number of overlay cache entries populated by computing an overlay.
64    overlay_cache_fills: Counter,
65}
66
67impl<N: NodePrimitives> Default for OverlayManager<N> {
68    fn default() -> Self {
69        Self {
70            blocks: Default::default(),
71            overlays: Default::default(),
72            changeset_cache: Default::default(),
73            preserved_sparse_trie: Default::default(),
74            #[cfg(feature = "rayon")]
75            worker_pool: None,
76            metrics: Default::default(),
77        }
78    }
79}
80
81impl<N: NodePrimitives> std::fmt::Debug for OverlayManager<N> {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        f.debug_struct("OverlayManager")
84            .field("blocks", &self.blocks.len())
85            .field("overlays", &self.overlays.len())
86            .finish()
87    }
88}
89
90impl<N: NodePrimitives> OverlayManager<N> {
91    /// Create a new [`OverlayManager`] backed by the given worker pool.
92    #[cfg(feature = "rayon")]
93    pub fn new(worker_pool: Arc<WorkerPool>) -> Self {
94        Self {
95            blocks: Default::default(),
96            overlays: Default::default(),
97            changeset_cache: Default::default(),
98            preserved_sparse_trie: Default::default(),
99            worker_pool: Some(worker_pool),
100            metrics: Default::default(),
101        }
102    }
103
104    /// Creates an overlay builder for `parent_hash`.
105    pub fn overlay_builder(&self, parent_hash: B256) -> OverlayBuilder<N> {
106        OverlayBuilder::new(parent_hash, self.clone())
107    }
108
109    pub(crate) const fn changeset_cache(&self) -> &ChangesetCache {
110        &self.changeset_cache
111    }
112
113    /// Gets or computes cached changesets for an inclusive block range.
114    pub fn get_or_compute_cached_changesets_range<P>(
115        &self,
116        provider: &P,
117        range: RangeInclusive<BlockNumber>,
118    ) -> ProviderResult<Arc<TrieUpdatesSorted>>
119    where
120        P: DBProvider
121            + ChangeSetReader
122            + StorageChangeSetReader
123            + PruneCheckpointReader
124            + StageCheckpointReader
125            + BlockNumReader
126            + StorageSettingsCache,
127    {
128        let (partial_state_trie, finish) = database_state_frontiers(provider)?;
129        self.get_or_compute_cached_changesets_range_at_frontiers(
130            provider,
131            range,
132            partial_state_trie,
133            finish,
134        )
135    }
136
137    pub(crate) fn get_or_compute_cached_changesets_range_at_frontiers<P>(
138        &self,
139        provider: &P,
140        range: RangeInclusive<BlockNumber>,
141        partial_state_trie: BlockNumHash,
142        finish: BlockNumHash,
143    ) -> ProviderResult<Arc<TrieUpdatesSorted>>
144    where
145        P: DBProvider
146            + ChangeSetReader
147            + StorageChangeSetReader
148            + PruneCheckpointReader
149            + BlockNumReader
150            + StorageSettingsCache,
151    {
152        self.changeset_cache.get_or_compute_range(self, provider, range, partial_state_trie, finish)
153    }
154
155    /// Evicts cached changesets for blocks below `up_to_block`.
156    pub fn evict_cached_changesets(&self, up_to_block: BlockNumber) {
157        self.changeset_cache.evict(up_to_block);
158    }
159
160    /// Computes the trie updates produced by `block_number`.
161    pub fn compute_block_trie_updates<P>(
162        &self,
163        provider: &P,
164        block_number: BlockNumber,
165    ) -> ProviderResult<TrieUpdatesSorted>
166    where
167        P: DBProvider
168            + ChangeSetReader
169            + StorageChangeSetReader
170            + PruneCheckpointReader
171            + StageCheckpointReader
172            + BlockNumReader
173            + StorageSettingsCache,
174    {
175        compute_block_trie_updates(self, provider, block_number)
176    }
177
178    /// Takes the preserved sparse trie if present.
179    pub fn take_sparse_trie(&self) -> Option<PreservedSparseTrie> {
180        self.preserved_sparse_trie.lock().take()
181    }
182
183    /// Stores a preserved sparse trie for later reuse.
184    pub fn store_sparse_trie(&self, trie: PreservedSparseTrie) {
185        *self.preserved_sparse_trie.lock() = Some(trie);
186    }
187
188    /// Clears any preserved sparse trie state.
189    pub fn clear_sparse_trie(&self) {
190        *self.preserved_sparse_trie.lock() = None;
191    }
192
193    /// Waits until the sparse trie lock becomes available.
194    ///
195    /// This acquires and immediately releases the lock, ensuring that any ongoing operations
196    /// complete before returning. Returns the time spent waiting for the lock.
197    pub fn wait_for_sparse_trie_availability(&self) -> std::time::Duration {
198        let start = FastInstant::now();
199        let _guard = self.preserved_sparse_trie.lock();
200        let elapsed = start.elapsed();
201        if elapsed.as_millis() > 5 {
202            debug!(
203                target: "storage::overlay::manager",
204                blocked_for=?elapsed,
205                "Waited for preserved sparse trie to become available"
206            );
207        }
208        elapsed
209    }
210
211    /// Inserts an executed in-memory block into the state trie overlay manager.
212    #[tracing::instrument(
213        level = "trace",
214        target = "storage::overlay::manager",
215        skip_all,
216        fields(
217            block_hash = %block.recovered_block().hash(),
218            parent_hash = %block.recovered_block().parent_hash(),
219            duplicate = false,
220        )
221    )]
222    pub fn insert_block(&self, block: ExecutedBlock<N>) {
223        let hash = block.recovered_block().hash();
224        let parent_hash = block.recovered_block().parent_hash();
225        let span = tracing::Span::current();
226
227        // First add the block to the live graph; duplicate inserts do not need cache work.
228        match self.blocks.entry(hash) {
229            Entry::Occupied(_) => {
230                span.record("duplicate", true);
231                debug!(
232                    target: "storage::overlay::manager",
233                    %hash,
234                    %parent_hash,
235                    "state trie overlay block already inserted"
236                );
237                return
238            }
239            Entry::Vacant(entry) => {
240                entry.insert(block);
241            }
242        }
243
244        debug!(
245            target: "storage::overlay::manager",
246            %hash,
247            %parent_hash,
248            "inserted block into state trie overlay manager"
249        );
250    }
251
252    /// Removes blocks from the live block graph and prunes cached overlays that can no longer be
253    /// built from the remaining blocks.
254    #[tracing::instrument(
255        level = "trace",
256        target = "storage::overlay::manager",
257        skip_all,
258        fields(
259            block_count = tracing::field::Empty,
260            removed_blocks = tracing::field::Empty,
261            pruned_overlays = tracing::field::Empty,
262        )
263    )]
264    pub fn remove_blocks(&self, hashes: impl IntoIterator<Item = B256>) {
265        let span = tracing::Span::current();
266
267        // Remove blocks first, then prune overlays against the remaining block graph.
268        let mut block_count = 0usize;
269        let mut removed_blocks = 0usize;
270        let mut pruned_overlays = 0usize;
271        for hash in hashes {
272            block_count += 1;
273            removed_blocks += self.blocks.remove(&hash).is_some() as usize;
274        }
275        span.record("block_count", block_count);
276        span.record("removed_blocks", removed_blocks);
277
278        if removed_blocks > 0 {
279            let overlays_before = self.overlays.len();
280            self.overlays.retain(|key, _| {
281                self.contains_hash(key.tip_hash, key.anchor_hash, key.anchor_hash)
282            });
283            pruned_overlays = overlays_before.saturating_sub(self.overlays.len());
284            span.record("pruned_overlays", pruned_overlays);
285        }
286        debug!(
287            target: "storage::overlay::manager",
288            block_count,
289            removed_blocks,
290            pruned_overlays,
291            "removed blocks from state trie overlay manager"
292        );
293    }
294
295    /// Returns the flattened overlay from `anchor_hash` to `parent_hash`.
296    #[tracing::instrument(
297        level = "trace",
298        target = "storage::overlay::manager",
299        skip_all,
300        fields(tip_hash = %parent_hash, anchor_hash = %anchor_hash)
301    )]
302    pub(crate) fn overlay_for_parent(
303        &self,
304        parent_hash: B256,
305        anchor_hash: B256,
306    ) -> Result<(Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>), StateTrieOverlayError> {
307        debug!(
308            target: "storage::overlay::manager",
309            tip_hash = %parent_hash,
310            %anchor_hash,
311            "loading state trie overlay for parent"
312        );
313        let input = self.get_overlay(parent_hash, anchor_hash)?;
314        Ok((Arc::clone(&input.nodes), Arc::clone(&input.state)))
315    }
316
317    #[tracing::instrument(
318        level = "trace",
319        target = "storage::overlay::manager",
320        skip_all,
321        fields(
322            tip_hash = %tip_hash,
323            anchor_hash = %anchor_hash,
324            cache_reused = tracing::field::Empty,
325            block_count = tracing::field::Empty,
326            parent_overlay_reused = tracing::field::Empty,
327        )
328    )]
329    fn get_overlay(
330        &self,
331        tip_hash: B256,
332        anchor_hash: B256,
333    ) -> Result<Arc<TrieInputSorted>, StateTrieOverlayError> {
334        let key = OverlayCacheKey { anchor_hash, tip_hash };
335        let span = tracing::Span::current();
336
337        if let Some(entry) = self.overlays.get(&key).map(|entry| entry.value().clone()) {
338            self.record_overlay_cache_reuse(&span);
339            return Ok(match entry {
340                OverlayCacheEntry::Ready(input) => input,
341                OverlayCacheEntry::Computing(waiter) => waiter.wait(),
342            })
343        }
344        span.record("cache_reused", false);
345
346        // Resolve the block path and any cached parent overlay before locking the child entry.
347        let mut hash = tip_hash;
348        let mut blocks = Vec::new();
349        loop {
350            let block =
351                self.blocks.get(&hash).ok_or(StateTrieOverlayError { tip_hash, anchor_hash })?;
352            let parent_hash = block.recovered_block().parent_hash();
353            blocks.push(block.clone());
354
355            if parent_hash == anchor_hash {
356                break
357            }
358            hash = parent_hash;
359        }
360        span.record("block_count", blocks.len());
361        let parent_input = blocks.first().and_then(|block| {
362            let parent_hash = block.recovered_block().parent_hash();
363            (parent_hash != anchor_hash)
364                .then(|| {
365                    self.overlays
366                        .get(&OverlayCacheKey { anchor_hash, tip_hash: parent_hash })
367                        .and_then(|entry| entry.value().ready())
368                })
369                .flatten()
370        });
371        span.record("parent_overlay_reused", parent_input.is_some());
372        let compute_input = match parent_input {
373            Some(parent_input) => {
374                ComputeOverlayInput::ExtendCached { block: blocks.swap_remove(0), parent_input }
375            }
376            None => ComputeOverlayInput::MergeBlocks(blocks),
377        };
378
379        enum CacheAction {
380            Ready(Arc<TrieInputSorted>),
381            Wait(Arc<OverlayWaiter>),
382            Compute(Arc<OverlayWaiter>),
383        }
384
385        let action = match self.overlays.entry(key) {
386            Entry::Occupied(entry) => {
387                let entry = entry.get().clone();
388                self.record_overlay_cache_reuse(&span);
389                match entry {
390                    OverlayCacheEntry::Ready(input) => CacheAction::Ready(input),
391                    OverlayCacheEntry::Computing(waiter) => CacheAction::Wait(waiter),
392                }
393            }
394            Entry::Vacant(entry) => {
395                self.metrics.overlay_cache_fills.increment(1);
396                let waiter = Arc::new(OverlayWaiter::new());
397                entry.insert(OverlayCacheEntry::Computing(Arc::clone(&waiter)));
398                CacheAction::Compute(waiter)
399            }
400        };
401
402        match action {
403            CacheAction::Ready(input) => Ok(input),
404            CacheAction::Wait(waiter) => Ok(waiter.wait()),
405            CacheAction::Compute(waiter) => {
406                let input = self.compute_overlay(compute_input, anchor_hash, span);
407                waiter.finish(Arc::clone(&input));
408
409                if let Entry::Occupied(mut entry) = self.overlays.entry(key) {
410                    // The entry may have been pruned while the overlay was computing. Only cache
411                    // the result if the map still points at the waiter installed by this task.
412                    let should_publish = match entry.get() {
413                        OverlayCacheEntry::Computing(existing) => Arc::ptr_eq(existing, &waiter),
414                        OverlayCacheEntry::Ready(_) => false,
415                    };
416                    if should_publish {
417                        entry.insert(OverlayCacheEntry::Ready(Arc::clone(&input)));
418                    }
419                }
420
421                Ok(input)
422            }
423        }
424    }
425
426    fn record_overlay_cache_reuse(&self, span: &tracing::Span) {
427        self.metrics.overlay_cache_reuses.increment(1);
428        span.record("cache_reused", true);
429    }
430
431    /// Returns every in-memory block in the chain whose tip is `parent_hash`.
432    pub(crate) fn parent_chain(
433        &self,
434        parent_hash: B256,
435    ) -> impl Iterator<Item = ExecutedBlock<N>> + '_ {
436        let mut hash = parent_hash;
437        std::iter::from_fn(move || {
438            let block = self.blocks.get(&hash)?;
439            hash = block.recovered_block().parent_hash();
440            Some(block.clone())
441        })
442    }
443
444    /// Returns true if `hash` is in the parent chain segment from `anchor_hash` inclusive to
445    /// `parent_hash` inclusive.
446    pub fn contains_hash(&self, parent_hash: B256, anchor_hash: B256, hash: B256) -> bool {
447        let mut current_hash = parent_hash;
448
449        loop {
450            if current_hash == hash {
451                return true
452            }
453            if current_hash == anchor_hash {
454                return false
455            }
456
457            let Some(block) = self.blocks.get(&current_hash) else { return false };
458            current_hash = block.recovered_block().parent_hash();
459        }
460    }
461
462    fn compute_overlay(
463        &self,
464        compute_input: ComputeOverlayInput<N>,
465        anchor_hash: B256,
466        _span: tracing::Span,
467    ) -> Arc<TrieInputSorted> {
468        #[cfg(feature = "rayon")]
469        {
470            if let Some(worker_pool) = &self.worker_pool {
471                let compute_span = _span;
472                let metrics = self.metrics.clone();
473                return Arc::new(worker_pool.install_fn(move || {
474                    let _guard = compute_span.enter();
475                    compute_overlay(compute_input, anchor_hash, &metrics)
476                }))
477            }
478        }
479
480        Arc::new(compute_overlay(compute_input, anchor_hash, &self.metrics))
481    }
482}
483
484/// Error returned when a state trie overlay cannot be built from the manager's current block set.
485#[derive(Debug)]
486pub struct StateTrieOverlayError {
487    /// Requested in-memory tip hash.
488    tip_hash: B256,
489    /// Requested anchor hash.
490    anchor_hash: B256,
491}
492
493impl fmt::Display for StateTrieOverlayError {
494    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495        write!(
496            f,
497            "state trie overlay for tip {} cannot be anchored to {} with current blocks",
498            self.tip_hash, self.anchor_hash
499        )
500    }
501}
502
503impl std::error::Error for StateTrieOverlayError {}
504
505#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
506struct OverlayCacheKey {
507    anchor_hash: B256,
508    tip_hash: B256,
509}
510
511#[derive(Clone)]
512enum OverlayCacheEntry {
513    Ready(Arc<TrieInputSorted>),
514    Computing(Arc<OverlayWaiter>),
515}
516
517impl OverlayCacheEntry {
518    fn ready(&self) -> Option<Arc<TrieInputSorted>> {
519        match self {
520            Self::Ready(input) => Some(Arc::clone(input)),
521            Self::Computing(_) => None,
522        }
523    }
524}
525
526struct OverlayWaiter {
527    input: OnceLock<Arc<TrieInputSorted>>,
528}
529
530impl OverlayWaiter {
531    const fn new() -> Self {
532        Self { input: OnceLock::new() }
533    }
534
535    fn wait(&self) -> Arc<TrieInputSorted> {
536        Arc::clone(self.input.wait())
537    }
538
539    fn finish(&self, computed: Arc<TrieInputSorted>) {
540        let _ = self.input.set(computed);
541    }
542}
543
544enum ComputeOverlayInput<N: NodePrimitives> {
545    ExtendCached { block: ExecutedBlock<N>, parent_input: Arc<TrieInputSorted> },
546    MergeBlocks(Vec<ExecutedBlock<N>>),
547}
548
549#[tracing::instrument(
550    level = "trace",
551    target = "storage::overlay::manager",
552    skip_all,
553    fields(
554        anchor_hash = %anchor_hash,
555        block_count = tracing::field::Empty,
556        parent_overlay = tracing::field::Empty,
557        elapsed_us = tracing::field::Empty,
558    )
559)]
560fn compute_overlay<N: NodePrimitives>(
561    input: ComputeOverlayInput<N>,
562    anchor_hash: B256,
563    metrics: &StateTrieOverlayMetrics,
564) -> TrieInputSorted {
565    let started_at = Instant::now();
566    let block_count = match &input {
567        ComputeOverlayInput::ExtendCached { .. } => 1,
568        ComputeOverlayInput::MergeBlocks(blocks) => blocks.len(),
569    };
570    let parent_overlay = matches!(&input, ComputeOverlayInput::ExtendCached { .. });
571    tracing::Span::current().record("block_count", block_count);
572    tracing::Span::current().record("parent_overlay", parent_overlay);
573
574    let overlay = match input {
575        ComputeOverlayInput::ExtendCached { block, parent_input } => {
576            let trie_data = block.trie_data();
577
578            trace!(
579                target: "storage::overlay::manager",
580                %anchor_hash,
581                head = %block.recovered_block().hash(),
582                "extending cached parent state trie overlay"
583            );
584
585            let mut overlay = parent_input.as_ref().clone();
586            extend_overlay(
587                &mut overlay,
588                &trie_data.sorted.hashed_state,
589                &trie_data.sorted.trie_updates,
590            );
591            overlay
592        }
593        ComputeOverlayInput::MergeBlocks(blocks) => merge_blocks(blocks),
594    };
595
596    let elapsed = started_at.elapsed();
597    metrics.overlay_computation_duration_seconds.record(elapsed.as_secs_f64());
598    tracing::Span::current().record("elapsed_us", elapsed.as_micros() as u64);
599    debug!(
600        target: "storage::overlay::manager",
601        %anchor_hash,
602        block_count,
603        parent_overlay,
604        ?elapsed,
605        "computed state trie overlay"
606    );
607
608    overlay
609}
610
611fn merge_blocks<N: NodePrimitives>(blocks: Vec<ExecutedBlock<N>>) -> TrieInputSorted {
612    let trie_data = blocks.iter().map(ExecutedBlock::trie_data).collect::<Vec<_>>();
613
614    #[cfg(feature = "rayon")]
615    let (nodes, state) = rayon::join(
616        || {
617            TrieUpdatesSorted::merge_batch(
618                trie_data.iter().map(|data| Arc::clone(&data.sorted.trie_updates)),
619            )
620        },
621        || {
622            HashedPostStateSorted::merge_batch(
623                trie_data.iter().map(|data| Arc::clone(&data.sorted.hashed_state)),
624            )
625        },
626    );
627
628    #[cfg(not(feature = "rayon"))]
629    let (nodes, state) = (
630        TrieUpdatesSorted::merge_batch(
631            trie_data.iter().map(|data| Arc::clone(&data.sorted.trie_updates)),
632        ),
633        HashedPostStateSorted::merge_batch(
634            trie_data.iter().map(|data| Arc::clone(&data.sorted.hashed_state)),
635        ),
636    );
637
638    TrieInputSorted::new(nodes, state, Default::default())
639}
640
641fn extend_overlay(
642    overlay: &mut TrieInputSorted,
643    hashed_state: &HashedPostStateSorted,
644    trie_updates: &TrieUpdatesSorted,
645) {
646    #[cfg(feature = "rayon")]
647    {
648        rayon::join(
649            || {
650                if !hashed_state.is_empty() {
651                    Arc::make_mut(&mut overlay.state).extend_ref_and_sort(hashed_state);
652                }
653            },
654            || {
655                if !trie_updates.is_empty() {
656                    Arc::make_mut(&mut overlay.nodes).extend_ref_and_sort(trie_updates);
657                }
658            },
659        );
660    }
661
662    #[cfg(not(feature = "rayon"))]
663    {
664        if !hashed_state.is_empty() {
665            Arc::make_mut(&mut overlay.state).extend_ref_and_sort(hashed_state);
666        }
667        if !trie_updates.is_empty() {
668            Arc::make_mut(&mut overlay.nodes).extend_ref_and_sort(trie_updates);
669        }
670    }
671}
672
673#[cfg(test)]
674mod tests {
675    use super::*;
676    use alloy_primitives::U256;
677    use reth_chain_state::{test_utils::TestBlockBuilder, ExecutedBlock, SparseTrie};
678    use reth_ethereum_primitives::EthPrimitives;
679    use reth_primitives_traits::Account;
680    use reth_trie::{updates::TrieUpdatesSorted, ComputedTrieData, HashedPostState, HashedStorage};
681    use std::{
682        sync::{mpsc, Arc},
683        thread,
684        time::Duration,
685    };
686
687    fn with_unique_state(
688        block: &ExecutedBlock<EthPrimitives>,
689        id: u8,
690    ) -> ExecutedBlock<EthPrimitives> {
691        let hashed_address = B256::with_last_byte(id);
692        let hashed_slot = B256::with_last_byte(id.saturating_add(32));
693        let hashed_state = HashedPostState::default()
694            .with_accounts([(hashed_address, Some(Account::default()))])
695            .with_storages([(
696                hashed_address,
697                HashedStorage::from_iter([(hashed_slot, U256::from(id))]),
698            )])
699            .into_sorted();
700
701        ExecutedBlock::new(
702            Arc::clone(&block.recovered_block),
703            Arc::clone(&block.execution_output),
704            ComputedTrieData::new(Arc::new(hashed_state), Arc::new(TrieUpdatesSorted::default())),
705        )
706    }
707
708    fn test_blocks() -> Vec<ExecutedBlock<EthPrimitives>> {
709        TestBlockBuilder::eth()
710            .get_executed_blocks(1..4)
711            .enumerate()
712            .map(|(index, block)| with_unique_state(&block, index as u8 + 1))
713            .collect()
714    }
715
716    #[test]
717    fn errors_for_unknown_parent() {
718        let manager = OverlayManager::<EthPrimitives>::default();
719        let parent = B256::random();
720        let anchor = B256::random();
721
722        let err = manager.overlay_for_parent(parent, anchor).unwrap_err();
723
724        assert_eq!(err.tip_hash, parent);
725        assert_eq!(err.anchor_hash, anchor);
726    }
727
728    #[test]
729    fn builds_managed_overlay_for_inserted_blocks() {
730        let manager = OverlayManager::default();
731        let blocks = test_blocks();
732        for block in &blocks {
733            manager.insert_block(block.clone());
734        }
735
736        let anchor_hash = blocks[0].recovered_block().parent_hash();
737
738        let (_, state) =
739            manager.overlay_for_parent(blocks[2].recovered_block().hash(), anchor_hash).unwrap();
740        assert_eq!(state.accounts.len(), 3);
741
742        let short_anchor = blocks[1].recovered_block().hash();
743        let (_, short) =
744            manager.overlay_for_parent(blocks[2].recovered_block().hash(), short_anchor).unwrap();
745        assert_eq!(short.accounts.len(), 1);
746        let (_, cached_short) =
747            manager.overlay_for_parent(blocks[2].recovered_block().hash(), short_anchor).unwrap();
748        assert!(Arc::ptr_eq(&short, &cached_short));
749    }
750
751    #[test]
752    fn contains_hash_detects_hashes_from_anchor_to_parent() {
753        let manager = OverlayManager::default();
754        let blocks = test_blocks();
755        for block in &blocks {
756            manager.insert_block(block.clone());
757        }
758
759        let anchor_hash = blocks[0].recovered_block().parent_hash();
760        let parent_hash = blocks[2].recovered_block().hash();
761
762        assert!(manager.contains_hash(parent_hash, anchor_hash, anchor_hash));
763        for block in &blocks {
764            assert!(manager.contains_hash(
765                parent_hash,
766                anchor_hash,
767                block.recovered_block().hash()
768            ));
769        }
770        assert!(!manager.contains_hash(parent_hash, anchor_hash, B256::random()));
771    }
772
773    #[test]
774    fn contains_hash_rejects_hash_before_anchor() {
775        let manager = OverlayManager::default();
776        let blocks = test_blocks();
777        for block in &blocks {
778            manager.insert_block(block.clone());
779        }
780
781        let parent_hash = blocks[2].recovered_block().hash();
782        let anchor_hash = blocks[1].recovered_block().hash();
783        let before_anchor_hash = blocks[0].recovered_block().hash();
784
785        assert!(manager.contains_hash(parent_hash, anchor_hash, parent_hash));
786        assert!(manager.contains_hash(parent_hash, anchor_hash, anchor_hash));
787        assert!(!manager.contains_hash(parent_hash, anchor_hash, before_anchor_hash));
788    }
789
790    #[test]
791    fn contains_hash_rejects_unknown_anchor() {
792        let manager = OverlayManager::default();
793        let blocks = test_blocks();
794        for block in &blocks {
795            manager.insert_block(block.clone());
796        }
797
798        let parent_hash = blocks[2].recovered_block().hash();
799        let anchor_hash = B256::random();
800
801        assert!(!manager.contains_hash(parent_hash, anchor_hash, anchor_hash));
802    }
803
804    #[test]
805    fn taking_sparse_trie_removes_it() {
806        let manager = OverlayManager::<EthPrimitives>::default();
807        let state_root = B256::with_last_byte(1);
808        let other_state_root = B256::with_last_byte(2);
809        let anchor_hash = B256::with_last_byte(3);
810
811        manager.store_sparse_trie(PreservedSparseTrie::anchored(
812            SparseTrie::default(),
813            state_root,
814            anchor_hash,
815        ));
816
817        let preserved = manager.take_sparse_trie().expect("preserved trie should be available");
818        assert_eq!(preserved.state_root(), state_root);
819        assert_eq!(preserved.anchor_hash(), anchor_hash);
820        assert!(preserved.into_trie_for(other_state_root).unwrap().is_none());
821        assert!(manager.take_sparse_trie().is_none());
822    }
823
824    #[test]
825    fn required_lookup_waits_for_in_progress_overlay() {
826        let manager = OverlayManager::<EthPrimitives>::default();
827        let key = OverlayCacheKey {
828            anchor_hash: B256::with_last_byte(1),
829            tip_hash: B256::with_last_byte(2),
830        };
831        let waiter = Arc::new(OverlayWaiter::new());
832        manager.overlays.insert(key, OverlayCacheEntry::Computing(Arc::clone(&waiter)));
833
834        let (tx, rx) = mpsc::channel();
835        thread::spawn(move || {
836            let res =
837                manager.overlay_for_parent(key.tip_hash, key.anchor_hash).map(|(_, state)| state);
838            tx.send(res).unwrap();
839        });
840
841        assert!(matches!(
842            rx.recv_timeout(Duration::from_millis(50)),
843            Err(mpsc::RecvTimeoutError::Timeout)
844        ));
845
846        waiter.finish(Arc::new(TrieInputSorted::default()));
847
848        let state = rx.recv_timeout(Duration::from_secs(1)).unwrap().unwrap();
849        assert!(state.is_empty());
850    }
851
852    #[test]
853    fn prunes_cached_overlays_after_removing_blocks() {
854        let manager = OverlayManager::default();
855        let blocks = test_blocks();
856        for block in &blocks {
857            manager.insert_block(block.clone());
858        }
859
860        let original_anchor = blocks[0].recovered_block().parent_hash();
861        manager.overlay_for_parent(blocks[2].recovered_block().hash(), original_anchor).unwrap();
862
863        manager.remove_blocks([
864            blocks[0].recovered_block().hash(),
865            blocks[1].recovered_block().hash(),
866        ]);
867
868        let anchor_hash = blocks[1].recovered_block().hash();
869        assert!(manager
870            .overlay_for_parent(blocks[2].recovered_block().hash(), original_anchor)
871            .is_err());
872
873        let (_, state) =
874            manager.overlay_for_parent(blocks[2].recovered_block().hash(), anchor_hash).unwrap();
875        assert_eq!(state.accounts.len(), 1);
876    }
877}