1use crate::OverlayManager;
2use alloy_eips::BlockNumHash;
3use alloy_primitives::{BlockHash, B256};
4use metrics::{Counter, Histogram};
5use reth_chain_state::ExecutedBlock;
6use reth_errors::{ProviderError, ProviderResult};
7use reth_ethereum_primitives::EthPrimitives;
8use reth_metrics::Metrics;
9use reth_primitives_traits::{AlloyBlockHeader, NodePrimitives};
10use reth_prune_types::PruneSegment;
11use reth_stages_types::StageId;
12use reth_storage_api::{
13 BlockNumReader, ChangeSetReader, DBProvider, PruneCheckpointReader, StageCheckpointReader,
14 StorageChangeSetReader, StorageSettingsCache,
15};
16use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted};
17use reth_trie_db::DatabaseHashedPostState;
18use std::{
19 sync::Arc,
20 time::{Duration, Instant},
21};
22use tracing::{debug, debug_span, instrument};
23
24#[derive(Debug, Clone)]
26pub struct Overlay {
27 pub trie_updates: Arc<TrieUpdatesSorted>,
29 pub hashed_post_state: Arc<HashedPostStateSorted>,
31}
32
33impl Overlay {
34 fn empty() -> Self {
35 Self {
36 trie_updates: Arc::new(TrieUpdatesSorted::default()),
37 hashed_post_state: Arc::new(HashedPostStateSorted::default()),
38 }
39 }
40}
41
42#[derive(Debug, Clone)]
44pub enum OverlaySource {
45 Immediate {
47 trie: Arc<TrieUpdatesSorted>,
52 state: Arc<HashedPostStateSorted>,
54 },
55 Managed,
57}
58
59#[derive(Debug, Clone)]
64pub struct OverlayBuilder<N: NodePrimitives = EthPrimitives> {
65 parent_hash: B256,
67 overlay_source: Option<OverlaySource>,
69 overlay_manager: OverlayManager<N>,
71 reused_sparse_trie_anchor_hash: Option<B256>,
73 no_reverts: bool,
75 metrics: OverlayBuilderMetrics,
77}
78
79impl<N: NodePrimitives> OverlayBuilder<N> {
80 pub(crate) fn new(parent_hash: B256, overlay_manager: OverlayManager<N>) -> Self {
82 Self {
83 parent_hash,
84 overlay_source: Some(OverlaySource::Managed),
85 overlay_manager,
86 reused_sparse_trie_anchor_hash: None,
87 no_reverts: false,
88 metrics: OverlayBuilderMetrics::default(),
89 }
90 }
91
92 pub fn with_overlay_source(mut self, source: Option<OverlaySource>) -> Self {
96 self.overlay_source = source;
97 self
98 }
99
100 pub const fn with_skip_overlay_for_reused_sparse_trie(mut self, anchor_hash: B256) -> Self {
103 self.reused_sparse_trie_anchor_hash = Some(anchor_hash);
104 self
105 }
106
107 pub const fn with_no_reverts(mut self) -> Self {
109 self.no_reverts = true;
110 self
111 }
112
113 pub fn with_immediate_state_trie_overlay(
115 mut self,
116 state: Arc<HashedPostStateSorted>,
117 trie: Arc<TrieUpdatesSorted>,
118 ) -> Self {
119 self.overlay_source = Some(OverlaySource::Immediate { trie, state });
120 self
121 }
122
123 pub fn anchor_at_parent<Provider>(
125 &self,
126 provider: &Provider,
127 ) -> ProviderResult<AnchorForParent<N>>
128 where
129 Provider: StageCheckpointReader + BlockNumReader + PruneCheckpointReader,
130 {
131 let (partial_state_trie, finish) = database_state_frontiers(provider)?;
132 self.anchor_at_parent_with_frontiers(provider, partial_state_trie, finish)
133 }
134
135 pub fn anchor_at_parent_with_frontiers<Provider>(
137 &self,
138 provider: &Provider,
139 partial_state_trie: BlockNumHash,
140 finish: BlockNumHash,
141 ) -> ProviderResult<AnchorForParent<N>>
142 where
143 Provider: BlockNumReader + PruneCheckpointReader,
144 {
145 match &self.overlay_source {
146 Some(OverlaySource::Managed) => anchor_for_parent_with_frontiers(
147 self.parent_hash,
148 self.overlay_manager.parent_chain(self.parent_hash),
149 partial_state_trie,
150 finish,
151 provider,
152 ),
153 _ => anchor_for_parent_with_frontiers(
154 self.parent_hash,
155 std::iter::empty(),
156 partial_state_trie,
157 finish,
158 provider,
159 ),
160 }
161 }
162
163 #[instrument(level = "debug", target = "storage::overlay", skip_all)]
165 pub fn build_overlay<Provider>(&self, provider: &Provider) -> ProviderResult<Overlay>
166 where
167 Provider: StageCheckpointReader
168 + PruneCheckpointReader
169 + ChangeSetReader
170 + StorageChangeSetReader
171 + DBProvider
172 + BlockNumReader
173 + StorageSettingsCache,
174 {
175 let (state_trie_tip_block, finish_tip_block) = database_state_frontiers(provider)?;
176 self.build_overlay_at_frontiers(provider, state_trie_tip_block, finish_tip_block)
177 }
178
179 #[instrument(
183 level = "debug",
184 target = "storage::overlay",
185 skip_all,
186 fields(?state_trie_tip_block, ?finish_tip_block, parent_hash = ?self.parent_hash)
187 )]
188 pub fn build_overlay_at_frontiers<Provider>(
189 &self,
190 provider: &Provider,
191 state_trie_tip_block: BlockNumHash,
192 finish_tip_block: BlockNumHash,
193 ) -> ProviderResult<Overlay>
194 where
195 Provider: ChangeSetReader
196 + StorageChangeSetReader
197 + DBProvider
198 + BlockNumReader
199 + PruneCheckpointReader
200 + StorageSettingsCache,
201 {
202 let retrieve_trie_reverts_duration;
203 let retrieve_hashed_state_reverts_duration;
204 let trie_updates_total_len;
205 let hashed_state_updates_total_len;
206
207 let anchor_for_parent =
208 self.anchor_at_parent_with_frontiers(provider, state_trie_tip_block, finish_tip_block)?;
209
210 let (trie_updates, hashed_post_state) = match anchor_for_parent {
212 AnchorForParent::RevertsRequired { anchor, finish, .. } => {
213 if self.no_reverts {
214 return Err(ProviderError::other(std::io::Error::other(format!(
215 "reverts are disabled, but overlay for parent {} requires reverting Finish #{} ({}) to anchor #{} ({})",
216 self.parent_hash, finish.number, finish.hash, anchor.number, anchor.hash,
217 ))))
218 }
219
220 let revert_blocks = anchor.number + 1..=finish.number;
221
222 debug!(
223 target: "storage::overlay",
224 ?revert_blocks,
225 ?anchor,
226 "Collecting trie reverts for overlay state provider"
227 );
228
229 let trie_reverts = {
230 let _guard = debug_span!(target: "storage::overlay", "retrieving_trie_reverts")
231 .entered();
232 let start = Instant::now();
233 let accumulated_reverts =
234 self.overlay_manager.get_or_compute_cached_changesets_range_at_frontiers(
235 provider,
236 revert_blocks.clone(),
237 state_trie_tip_block,
238 finish_tip_block,
239 )?;
240 retrieve_trie_reverts_duration = start.elapsed();
241 accumulated_reverts
242 };
243
244 let mut hashed_state_reverts = {
245 let _guard =
246 debug_span!(target: "storage::overlay", "retrieving_hashed_state_reverts")
247 .entered();
248 let start = Instant::now();
249 let res = HashedPostStateSorted::from_reverts(provider, revert_blocks)?;
250 retrieve_hashed_state_reverts_duration = start.elapsed();
251 res
252 };
253
254 let (overlay_trie, overlay_state) = self.resolve_overlays(anchor.hash)?;
257
258 let trie_updates = if trie_reverts.is_empty() {
259 overlay_trie
260 } else if !overlay_trie.is_empty() {
261 let mut trie_reverts = (*trie_reverts).clone();
262 trie_reverts.extend_ref_and_sort(&overlay_trie);
263 Arc::new(trie_reverts)
264 } else {
265 trie_reverts
266 };
267
268 let hashed_state_updates = if hashed_state_reverts.is_empty() {
269 overlay_state
270 } else if !overlay_state.is_empty() {
271 hashed_state_reverts.extend_ref_and_sort(&overlay_state);
272 Arc::new(hashed_state_reverts)
273 } else {
274 Arc::new(hashed_state_reverts)
275 };
276
277 trie_updates_total_len = trie_updates.total_len();
278 hashed_state_updates_total_len = hashed_state_updates.total_len();
279
280 debug!(
281 target: "storage::overlay",
282 num_trie_updates = ?trie_updates_total_len,
283 num_state_updates = ?hashed_state_updates_total_len,
284 ?anchor,
285 "Reverted to anchor block",
286 );
287
288 (trie_updates, hashed_state_updates)
289 }
290 AnchorForParent::NoReverts { anchor, .. } => {
291 if self.should_skip_overlay_for_reused_sparse_trie(
295 state_trie_tip_block.hash,
296 finish_tip_block.hash,
297 ) {
298 debug!(
299 target: "storage::overlay",
300 parent_hash = %self.parent_hash,
301 state_trie_tip_hash = %state_trie_tip_block.hash,
302 finish_tip_hash = %finish_tip_block.hash,
303 sparse_trie_anchor_hash = ?self.reused_sparse_trie_anchor_hash,
304 "Skipping overlay construction because reused sparse trie covers durable frontiers to parent"
305 );
306
307 self.metrics.sparse_trie_overlay_skips.increment(1);
308
309 return Ok(Overlay::empty())
310 }
311
312 let (trie_updates, hashed_post_state) = self.resolve_overlays(anchor.hash)?;
313
314 retrieve_trie_reverts_duration = Duration::ZERO;
315 retrieve_hashed_state_reverts_duration = Duration::ZERO;
316 trie_updates_total_len = trie_updates.total_len();
317 hashed_state_updates_total_len = hashed_post_state.total_len();
318
319 debug!(
320 target: "storage::overlay",
321 num_trie_updates = trie_updates_total_len,
322 num_state_updates = hashed_state_updates_total_len,
323 ?anchor,
324 "Built overlay directly from durable frontier"
325 );
326
327 (trie_updates, hashed_post_state)
328 }
329 };
330
331 self.metrics
332 .retrieve_trie_reverts_duration
333 .record(retrieve_trie_reverts_duration.as_secs_f64());
334 self.metrics
335 .retrieve_hashed_state_reverts_duration
336 .record(retrieve_hashed_state_reverts_duration.as_secs_f64());
337 self.metrics.trie_updates_size.record(trie_updates_total_len as f64);
338 self.metrics.hashed_state_size.record(hashed_state_updates_total_len as f64);
339
340 Ok(Overlay { trie_updates, hashed_post_state })
341 }
342
343 fn resolve_overlays(
345 &self,
346 anchor_hash: BlockHash,
347 ) -> ProviderResult<(Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>)> {
348 match &self.overlay_source {
349 Some(OverlaySource::Managed) => {
350 if anchor_hash == self.parent_hash {
351 Ok((
352 Arc::new(TrieUpdatesSorted::default()),
353 Arc::new(HashedPostStateSorted::default()),
354 ))
355 } else {
356 self.overlay_manager
357 .overlay_for_parent(self.parent_hash, anchor_hash)
358 .map_err(ProviderError::other)
359 }
360 }
361 Some(OverlaySource::Immediate { trie, state }) => {
362 if anchor_hash != self.parent_hash {
363 return Err(ProviderError::other(std::io::Error::other(format!(
364 "anchor_hash {anchor_hash} doesn't match OverlayBuilder's configured parent ({})",
365 self.parent_hash
366 ))))
367 }
368 Ok((Arc::clone(trie), Arc::clone(state)))
369 }
370 None => Ok((
371 Arc::new(TrieUpdatesSorted::default()),
372 Arc::new(HashedPostStateSorted::default()),
373 )),
374 }
375 }
376
377 fn should_skip_overlay_for_reused_sparse_trie(
379 &self,
380 state_trie_tip_hash: B256,
381 finish_tip_hash: B256,
382 ) -> bool {
383 let Some(anchor_hash) = self.reused_sparse_trie_anchor_hash else { return false };
384
385 match &self.overlay_source {
386 Some(OverlaySource::Managed) => {
387 self.overlay_manager.contains_hash(
388 self.parent_hash,
389 anchor_hash,
390 state_trie_tip_hash,
391 ) && self.overlay_manager.contains_hash(
392 self.parent_hash,
393 anchor_hash,
394 finish_tip_hash,
395 )
396 }
397 _ => false,
398 }
399 }
400}
401
402pub fn database_state_frontiers<Provider>(
405 provider: &Provider,
406) -> ProviderResult<(BlockNumHash, BlockNumHash)>
407where
408 Provider: StageCheckpointReader + BlockNumReader,
409{
410 let checkpoint = provider
411 .get_stage_checkpoint(StageId::Finish)?
412 .ok_or_else(|| ProviderError::InsufficientChangesets { requested: 0, available: 0..=0 })?;
413 let state_trie_tip_number = checkpoint
414 .finish_stage_checkpoint()
415 .and_then(|finish| finish.partial_state_trie())
416 .unwrap_or(checkpoint.block_number);
417 let state_trie_tip_hash = provider
418 .convert_number(state_trie_tip_number.into())?
419 .ok_or_else(|| ProviderError::HeaderNotFound(state_trie_tip_number.into()))?;
420 let finish_tip_number = checkpoint.block_number;
421 let finish_tip_hash = provider
422 .convert_number(finish_tip_number.into())?
423 .ok_or_else(|| ProviderError::HeaderNotFound(finish_tip_number.into()))?;
424
425 Ok((
426 BlockNumHash::new(state_trie_tip_number, state_trie_tip_hash),
427 BlockNumHash::new(finish_tip_number, finish_tip_hash),
428 ))
429}
430
431#[derive(Clone, Metrics)]
433#[metrics(scope = "storage.overlay.builder")]
434struct OverlayBuilderMetrics {
435 retrieve_trie_reverts_duration: Histogram,
437 retrieve_hashed_state_reverts_duration: Histogram,
439 trie_updates_size: Histogram,
441 hashed_state_size: Histogram,
443 sparse_trie_overlay_skips: Counter,
446}
447
448fn anchor_for_parent_in<N: NodePrimitives>(
449 parent_hash: B256,
450 mut in_mem_chain: impl Iterator<Item = ExecutedBlock<N>>,
451 preferred_anchor: B256,
452) -> B256 {
453 if parent_hash == preferred_anchor {
454 return parent_hash
455 }
456
457 let mut hash = parent_hash;
458
459 loop {
460 let Some(block) = in_mem_chain.next() else { return hash };
461 let block_parent_hash = block.recovered_block().parent_hash();
462
463 if block_parent_hash == preferred_anchor {
464 return block_parent_hash
465 }
466 hash = block_parent_hash;
467 }
468}
469
470#[derive(Debug)]
472pub enum AnchorForParent<N: NodePrimitives> {
473 NoReverts {
475 anchor: BlockNumHash,
477 overlay: Vec<ExecutedBlock<N>>,
479 },
480 RevertsRequired {
482 anchor: BlockNumHash,
484 finish: BlockNumHash,
486 overlay: Vec<ExecutedBlock<N>>,
488 },
489}
490
491pub fn anchor_for_parent<N, Provider>(
498 parent_hash: B256,
499 in_mem_chain: impl Iterator<Item = ExecutedBlock<N>>,
500 provider: &Provider,
501) -> ProviderResult<AnchorForParent<N>>
502where
503 N: NodePrimitives,
504 Provider: StageCheckpointReader + BlockNumReader + PruneCheckpointReader,
505{
506 let (partial_state_trie, finish) = database_state_frontiers(provider)?;
507 anchor_for_parent_with_frontiers(
508 parent_hash,
509 in_mem_chain,
510 partial_state_trie,
511 finish,
512 provider,
513 )
514}
515
516pub fn anchor_for_parent_with_frontiers<N, Provider>(
526 parent_hash: B256,
527 in_mem_chain: impl Iterator<Item = ExecutedBlock<N>>,
528 partial_state_trie: BlockNumHash,
529 finish: BlockNumHash,
530 provider: &Provider,
531) -> ProviderResult<AnchorForParent<N>>
532where
533 N: NodePrimitives,
534 Provider: BlockNumReader + PruneCheckpointReader,
535{
536 use std::io::Error;
537
538 let persisted_parent = provider
539 .block_number(parent_hash)?
540 .filter(|&parent_number| parent_number <= partial_state_trie.number);
541
542 let mut finish_seen = parent_hash == finish.hash;
543 let (anchor, overlay) = if let Some(parent_number) = persisted_parent {
544 (BlockNumHash::new(parent_number, parent_hash), Vec::new())
545 } else {
546 let mut overlay = Vec::new();
547 let mut in_mem_chain = in_mem_chain.inspect(|block| {
548 finish_seen |= block.recovered_block().hash() == finish.hash;
549 overlay.push(block.clone());
550 });
551
552 let anchor_hash =
553 anchor_for_parent_in(parent_hash, &mut in_mem_chain, partial_state_trie.hash);
554 let anchor = if anchor_hash == partial_state_trie.hash {
555 BlockNumHash::new(partial_state_trie.number, anchor_hash)
556 } else {
557 let anchor_number = provider
558 .convert_hash_or_number(anchor_hash.into())?
559 .ok_or(ProviderError::BlockHashNotFound(anchor_hash))?;
560 BlockNumHash::new(anchor_number, anchor_hash)
561 };
562 (anchor, overlay)
563 };
564
565 finish_seen |= anchor.hash == finish.hash;
566
567 if anchor.number > partial_state_trie.number {
568 return Err(ProviderError::other(Error::other(format!(
569 "overlay anchor #{} ({}) is after partial state trie frontier #{} ({}); missing trie updates for blocks #{}..=#{}",
570 anchor.number,
571 anchor.hash,
572 partial_state_trie.number,
573 partial_state_trie.hash,
574 partial_state_trie.number + 1,
575 anchor.number,
576 ))))
577 }
578
579 if finish_seen {
584 return Ok(AnchorForParent::NoReverts { anchor, overlay })
585 }
586
587 let account_history = provider
590 .get_prune_checkpoint(PruneSegment::AccountHistory)?
591 .and_then(|checkpoint| checkpoint.block_number);
592 let storage_history = provider
593 .get_prune_checkpoint(PruneSegment::StorageHistory)?
594 .and_then(|checkpoint| checkpoint.block_number);
595 let lower_bound = account_history.max(storage_history).unwrap_or_default();
596 let available_range = lower_bound..=finish.number;
597 if !available_range.contains(&anchor.number) {
598 return Err(ProviderError::InsufficientChangesets {
599 requested: anchor.number,
600 available: available_range,
601 })
602 }
603
604 Ok(AnchorForParent::RevertsRequired { anchor, finish, overlay })
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610 use alloy_primitives::U256;
611 use reth_chain_state::{test_utils::TestBlockBuilder, ExecutedBlock};
612 use reth_primitives_traits::Account;
613 #[cfg(feature = "partial-persistence")]
614 use reth_provider::{
615 test_utils::{create_test_provider_factory, MockNodeTypesWithDB},
616 BlockWriter, ProviderFactory,
617 };
618 #[cfg(feature = "partial-persistence")]
619 #[cfg(feature = "partial-persistence")]
620 use reth_stages_types::{FinishCheckpoint, StageCheckpoint};
621 #[cfg(feature = "partial-persistence")]
622 use reth_storage_api::StageCheckpointWriter;
623 use reth_trie::{BranchNodeCompact, ComputedTrieData, HashedPostState, HashedStorage, Nibbles};
624
625 fn with_unique_trie_data(
626 block: &ExecutedBlock<EthPrimitives>,
627 id: u8,
628 ) -> ExecutedBlock<EthPrimitives> {
629 let hashed_address = B256::with_last_byte(id);
630 let hashed_slot = B256::with_last_byte(id.saturating_add(32));
631 let hashed_state = HashedPostState::default()
632 .with_accounts([(hashed_address, Some(Account::default()))])
633 .with_storages([(
634 hashed_address,
635 HashedStorage::from_iter([(hashed_slot, U256::from(id))]),
636 )])
637 .into_sorted();
638 let trie_updates = TrieUpdatesSorted::new(
639 vec![(
640 Nibbles::from_nibbles([id]),
641 Some(BranchNodeCompact::new(0, 0, 0, vec![], None)),
642 )],
643 Default::default(),
644 );
645
646 ExecutedBlock::new(
647 Arc::clone(&block.recovered_block),
648 Arc::clone(&block.execution_output),
649 ComputedTrieData::new(Arc::new(hashed_state), Arc::new(trie_updates)),
650 )
651 }
652
653 fn test_blocks() -> Vec<ExecutedBlock<EthPrimitives>> {
654 TestBlockBuilder::eth()
655 .get_executed_blocks(0..5)
656 .enumerate()
657 .map(|(index, block)| with_unique_trie_data(&block, index as u8 + 1))
658 .collect()
659 }
660
661 #[cfg(feature = "partial-persistence")]
662 fn setup_frontiers(
663 state_trie_tip_index: usize,
664 finish_tip_index: usize,
665 ) -> (ProviderFactory<MockNodeTypesWithDB>, Vec<ExecutedBlock<EthPrimitives>>) {
666 let factory = create_test_provider_factory();
667 let blocks = test_blocks();
668 let provider_rw = factory.provider_rw().unwrap();
669 for block in &blocks[..=finish_tip_index] {
670 provider_rw.insert_block(block.recovered_block()).unwrap();
671 }
672 provider_rw
673 .save_stage_checkpoint(
674 StageId::Finish,
675 StageCheckpoint::new(blocks[finish_tip_index].block_number())
676 .with_finish_stage_checkpoint(FinishCheckpoint {
677 partial_state_trie: Some(blocks[state_trie_tip_index].block_number()),
678 }),
679 )
680 .unwrap();
681 provider_rw.commit().unwrap();
682
683 (factory, blocks)
684 }
685
686 #[cfg(feature = "partial-persistence")]
687 fn account_keys(overlay: &Overlay) -> Vec<B256> {
688 overlay.hashed_post_state.accounts.iter().map(|(key, _)| *key).collect()
689 }
690
691 #[cfg(feature = "partial-persistence")]
692 fn account_node_paths(overlay: &Overlay) -> Vec<Nibbles> {
693 overlay.trie_updates.account_nodes_ref().iter().map(|(path, _)| *path).collect()
694 }
695
696 #[cfg(feature = "partial-persistence")]
697 #[test]
698 fn managed_overlay_starts_at_state_trie_frontier() {
699 let (factory, blocks) = setup_frontiers(1, 3);
700 let manager = OverlayManager::default();
701 for block in &blocks[2..=4] {
702 manager.insert_block(block.clone());
703 }
704 let provider = factory.provider().unwrap();
705
706 for (parent_index, expected_ids) in [(3, vec![3, 4]), (4, vec![3, 4, 5])] {
707 let overlay = manager
708 .overlay_builder(blocks[parent_index].recovered_block().hash())
709 .build_overlay(&provider)
710 .unwrap();
711
712 assert_eq!(
713 account_keys(&overlay),
714 expected_ids.iter().copied().map(B256::with_last_byte).collect::<Vec<_>>()
715 );
716 assert_eq!(
717 account_node_paths(&overlay),
718 expected_ids
719 .iter()
720 .copied()
721 .map(|id| Nibbles::from_nibbles([id]))
722 .collect::<Vec<_>>()
723 );
724 }
725 }
726
727 #[cfg(feature = "partial-persistence")]
728 #[test]
729 fn managed_overlay_skips_when_finish_is_the_anchor() {
730 let (factory, blocks) = setup_frontiers(3, 3);
731 let manager = OverlayManager::default();
732 manager.insert_block(blocks[4].clone());
733 let provider = factory.provider().unwrap();
734
735 let overlay = manager
736 .overlay_builder(blocks[4].recovered_block().hash())
737 .with_skip_overlay_for_reused_sparse_trie(blocks[3].recovered_block().hash())
738 .build_overlay(&provider)
739 .unwrap();
740
741 assert!(overlay.hashed_post_state.is_empty());
742 assert!(overlay.trie_updates.is_empty());
743 }
744
745 #[cfg(feature = "partial-persistence")]
746 #[test]
747 fn no_reverts_errors_when_reverts_are_required() {
748 let (factory, blocks) = setup_frontiers(2, 3);
749 let provider = factory.provider().unwrap();
750
751 let error = OverlayManager::<EthPrimitives>::default()
752 .overlay_builder(blocks[1].recovered_block().hash())
753 .with_no_reverts()
754 .build_overlay(&provider)
755 .unwrap_err();
756
757 assert!(error.to_string().contains("reverts are disabled"));
758 }
759
760 #[cfg(feature = "partial-persistence")]
761 #[test]
762 fn managed_overlay_uses_persisted_parent_even_if_retained() {
763 let (factory, blocks) = setup_frontiers(2, 3);
764 let manager = OverlayManager::default();
765 manager.insert_block(blocks[1].clone());
766 let provider = factory.provider().unwrap();
767 let builder = manager.overlay_builder(blocks[1].recovered_block().hash());
768 match builder.anchor_at_parent(&provider).unwrap() {
769 AnchorForParent::RevertsRequired { anchor, finish, overlay } => {
770 assert_eq!(anchor, blocks[1].recovered_block().num_hash());
771 assert_eq!(finish, blocks[3].recovered_block().num_hash());
772 assert!(overlay.is_empty());
773 }
774 AnchorForParent::NoReverts { .. } => {
775 panic!("persisted parent below Finish must require reverts")
776 }
777 }
778
779 let overlay = builder.build_overlay(&provider).unwrap();
780
781 assert!(overlay.hashed_post_state.is_empty());
782 assert!(overlay.trie_updates.is_empty());
783 }
784
785 #[cfg(feature = "partial-persistence")]
786 #[test]
787 fn overlay_after_state_trie_frontier_requires_managed_coverage() {
788 let (factory, blocks) = setup_frontiers(1, 3);
789 let provider = factory.provider().unwrap();
790 let error = OverlayManager::<EthPrimitives>::default()
791 .overlay_builder(blocks[3].recovered_block().hash())
792 .with_overlay_source(None)
793 .build_overlay(&provider)
794 .unwrap_err();
795
796 assert!(
797 error.to_string().contains("is after partial state trie frontier"),
798 "unexpected error: {error}"
799 );
800 }
801
802 #[cfg(feature = "partial-persistence")]
803 #[test]
804 fn managed_overlay_errors_if_parent_is_not_persisted_or_managed_across_frontiers() {
805 let (factory, blocks) = setup_frontiers(1, 3);
806 let provider = factory.provider().unwrap();
807 let parent_hash = blocks[3].recovered_block().hash();
808 let error = OverlayManager::<EthPrimitives>::default()
809 .overlay_builder(parent_hash)
810 .build_overlay(&provider)
811 .unwrap_err();
812
813 assert!(error.to_string().contains("is after partial state trie frontier"));
814 }
815
816 #[test]
817 fn managed_overlay_skips_manager_for_persisted_parent() {
818 let parent_hash = B256::with_last_byte(1);
819 let builder = OverlayManager::<EthPrimitives>::default().overlay_builder(parent_hash);
820
821 let (trie, state) = builder.resolve_overlays(parent_hash).unwrap();
822 assert!(trie.is_empty());
823 assert!(state.is_empty());
824 }
825
826 #[test]
827 fn managed_overlay_errors_if_parent_is_not_persisted_or_managed() {
828 let parent_hash = B256::with_last_byte(1);
829 let anchor_hash = B256::with_last_byte(2);
830 let builder = OverlayManager::<EthPrimitives>::default().overlay_builder(parent_hash);
831
832 let err = builder.resolve_overlays(anchor_hash).unwrap_err();
833
834 assert!(err.to_string().contains("cannot be anchored"));
835 }
836
837 #[test]
838 fn managed_overlay_skip_requires_both_frontiers() {
839 let parent_hash = B256::with_last_byte(1);
840 let builder = OverlayManager::<EthPrimitives>::default().overlay_builder(parent_hash);
841 assert!(!builder.should_skip_overlay_for_reused_sparse_trie(parent_hash, parent_hash));
842
843 let builder = builder.with_skip_overlay_for_reused_sparse_trie(parent_hash);
844 assert!(builder.should_skip_overlay_for_reused_sparse_trie(parent_hash, parent_hash));
845 assert!(!builder
846 .should_skip_overlay_for_reused_sparse_trie(B256::with_last_byte(3), parent_hash,));
847
848 let blocks = test_blocks();
849 let manager = OverlayManager::default();
850 for block in &blocks[2..=4] {
851 manager.insert_block(block.clone());
852 }
853 let builder = manager
854 .overlay_builder(blocks[4].recovered_block().hash())
855 .with_skip_overlay_for_reused_sparse_trie(blocks[1].recovered_block().hash());
856 assert!(builder.should_skip_overlay_for_reused_sparse_trie(
857 blocks[1].recovered_block().hash(),
858 blocks[3].recovered_block().hash(),
859 ));
860
861 let builder =
862 builder.with_skip_overlay_for_reused_sparse_trie(blocks[2].recovered_block().hash());
863 assert!(!builder.should_skip_overlay_for_reused_sparse_trie(
864 blocks[1].recovered_block().hash(),
865 blocks[3].recovered_block().hash(),
866 ));
867 }
868}