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