1use crate::{database_state_frontiers, OverlayManager, OverlayStateProvider};
11use alloy_eips::BlockNumHash;
12use alloy_primitives::{map::B256Map, BlockNumber, B256};
13use parking_lot::RwLock;
14use reth_metrics::{
15 metrics::{Counter, Gauge},
16 Metrics,
17};
18use reth_primitives_traits::{AlloyBlockHeader, FastInstant as Instant, NodePrimitives};
19use reth_storage_api::{
20 BlockNumReader, ChangeSetReader, DBProvider, PruneCheckpointReader, StageCheckpointReader,
21 StorageChangeSetReader, StorageSettingsCache,
22};
23use reth_storage_errors::provider::{ProviderError, ProviderResult};
24use reth_trie::trie_cursor::{InMemoryTrieCursorFactory, TrieCursor, TrieCursorFactory};
25use reth_trie_common::updates::{StorageTrieUpdatesSorted, TrieUpdatesSorted};
26use reth_trie_db::{DatabaseTrieCursorFactory, TrieTableAdapter};
27use std::{
28 collections::{BTreeMap, HashMap},
29 ops::RangeInclusive,
30 sync::Arc,
31};
32use tracing::{debug, warn};
33
34#[cfg(test)]
35use reth_trie::{changesets::compute_trie_changesets, HashedPostStateSorted, TrieInputSorted};
36#[cfg(test)]
37use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseHashedPostState, DatabaseStateRoot};
38
39pub(crate) fn compute_block_trie_updates<N, Provider>(
68 overlay_manager: &OverlayManager<N>,
69 provider: &Provider,
70 block_number: BlockNumber,
71) -> ProviderResult<TrieUpdatesSorted>
72where
73 N: NodePrimitives,
74 Provider: DBProvider
75 + ChangeSetReader
76 + StorageChangeSetReader
77 + PruneCheckpointReader
78 + StageCheckpointReader
79 + BlockNumReader
80 + StorageSettingsCache,
81{
82 reth_trie_db::with_adapter!(provider, |A| {
83 compute_block_trie_updates_inner::<_, _, A>(overlay_manager, provider, block_number)
84 })
85}
86
87fn compute_block_trie_updates_inner<N, Provider, A>(
88 overlay_manager: &OverlayManager<N>,
89 provider: &Provider,
90 block_number: BlockNumber,
91) -> ProviderResult<TrieUpdatesSorted>
92where
93 N: NodePrimitives,
94 Provider: DBProvider
95 + ChangeSetReader
96 + StorageChangeSetReader
97 + PruneCheckpointReader
98 + StageCheckpointReader
99 + BlockNumReader
100 + StorageSettingsCache,
101 A: TrieTableAdapter,
102{
103 let tx = provider.tx_ref();
104 let cache = overlay_manager.changeset_cache();
105 let (partial_state_trie, finish) = database_state_frontiers(provider)?;
106
107 let changesets = cache.get_or_compute(
109 overlay_manager,
110 provider,
111 block_number,
112 partial_state_trie,
113 finish,
114 )?;
115
116 let reverts = cache.get_or_compute_range(
118 overlay_manager,
119 provider,
120 (block_number + 1)..=finish.number,
121 partial_state_trie,
122 finish,
123 true,
124 )?;
125
126 let db_cursor_factory = DatabaseTrieCursorFactory::<_, A>::new(tx);
129 let cursor_factory = InMemoryTrieCursorFactory::new(db_cursor_factory, &reverts);
130
131 let account_nodes_ref = changesets.account_nodes_ref();
133 let mut account_nodes = Vec::with_capacity(account_nodes_ref.len());
134 let mut account_cursor = cursor_factory.account_trie_cursor()?;
135
136 for (nibbles, _old_node) in account_nodes_ref {
138 let node_value = account_cursor.seek_exact(*nibbles)?.map(|(_, node)| node);
140 account_nodes.push((*nibbles, node_value));
141 }
142
143 let mut storage_tries = B256Map::default();
145
146 for (hashed_address, storage_changeset) in changesets.storage_tries_ref() {
148 let mut storage_cursor = cursor_factory.storage_trie_cursor(*hashed_address)?;
149 let storage_nodes_ref = storage_changeset.storage_nodes_ref();
150 let mut storage_nodes = Vec::with_capacity(storage_nodes_ref.len());
151
152 for (nibbles, _old_node) in storage_nodes_ref {
154 let node_value = storage_cursor.seek_exact(*nibbles)?.map(|(_, node)| node);
156 storage_nodes.push((*nibbles, node_value));
157 }
158
159 storage_tries.insert(*hashed_address, StorageTrieUpdatesSorted { storage_nodes });
160 }
161
162 Ok(TrieUpdatesSorted::new(account_nodes, storage_tries))
163}
164
165#[derive(Debug)]
169pub(crate) struct ChangesetCache {
170 inner: RwLock<ChangesetCacheInner>,
171}
172
173impl Default for ChangesetCache {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179impl ChangesetCache {
180 pub(crate) fn new() -> Self {
185 Self { inner: RwLock::new(ChangesetCacheInner::new()) }
186 }
187
188 pub(crate) fn evict(&self, up_to_block: BlockNumber) {
198 self.inner.write().evict(up_to_block)
199 }
200
201 pub(crate) fn get_or_compute<N, P>(
215 &self,
216 overlay_manager: &OverlayManager<N>,
217 provider: &P,
218 block_number: BlockNumber,
219 partial_state_trie: BlockNumHash,
220 finish: BlockNumHash,
221 ) -> ProviderResult<Arc<TrieUpdatesSorted>>
222 where
223 N: NodePrimitives,
224 P: DBProvider
225 + ChangeSetReader
226 + StorageChangeSetReader
227 + StageCheckpointReader
228 + PruneCheckpointReader
229 + BlockNumReader
230 + StorageSettingsCache,
231 {
232 self.get_or_compute_range(
233 overlay_manager,
234 provider,
235 block_number..=block_number,
236 partial_state_trie,
237 finish,
238 true,
239 )
240 }
241
242 pub(crate) fn get_or_compute_range<N, P>(
272 &self,
273 overlay_manager: &OverlayManager<N>,
274 provider: &P,
275 range: RangeInclusive<BlockNumber>,
276 partial_state_trie: BlockNumHash,
277 finish: BlockNumHash,
278 write_reverts_to_cache: bool,
279 ) -> ProviderResult<Arc<TrieUpdatesSorted>>
280 where
281 N: NodePrimitives,
282 P: DBProvider
283 + ChangeSetReader
284 + StorageChangeSetReader
285 + StageCheckpointReader
286 + PruneCheckpointReader
287 + BlockNumReader
288 + StorageSettingsCache,
289 {
290 let start_block = *range.start();
291 let end_block = *range.end();
292 let timer = Instant::now();
293
294 if end_block > finish.number {
295 return Err(ProviderError::InsufficientChangesets {
296 requested: end_block,
297 available: 0..=finish.number,
298 });
299 }
300
301 debug!(
302 target: "trie::changeset_cache",
303 start_block,
304 end_block,
305 ?partial_state_trie,
306 ?finish,
307 "Starting get_or_compute_range"
308 );
309
310 if start_block > end_block {
311 debug!(
312 target: "trie::changeset_cache",
313 start_block,
314 end_block,
315 "Empty changeset range requested"
316 );
317 return Ok(Arc::new(TrieUpdatesSorted::default()))
318 }
319
320 let end_block_hash = provider.block_hash(end_block)?.ok_or_else(|| {
321 ProviderError::other(std::io::Error::new(
322 std::io::ErrorKind::NotFound,
323 format!("block hash not found for block number {}", end_block),
324 ))
325 })?;
326 let range_key = ChangesetRangeKey::new(start_block, end_block, end_block_hash);
327
328 if let Some(accumulated_reverts) = self.inner.read().get(&range_key) {
329 let elapsed = timer.elapsed();
330
331 debug!(
332 target: "trie::changeset_cache",
333 ?elapsed,
334 start_block,
335 end_block,
336 ?end_block_hash,
337 num_blocks = end_block.saturating_sub(start_block).saturating_add(1),
338 "Changeset cache HIT for block range"
339 );
340
341 return Ok(accumulated_reverts)
342 }
343
344 let mut cached_reverts =
345 Vec::with_capacity(end_block.saturating_sub(start_block).saturating_add(1) as usize);
346 let mut all_cached = true;
347
348 for block_number in range.rev() {
349 let block_hash = if block_number == end_block {
351 end_block_hash
352 } else {
353 provider.block_hash(block_number)?.ok_or_else(|| {
354 ProviderError::other(std::io::Error::new(
355 std::io::ErrorKind::NotFound,
356 format!("block hash not found for block number {}", block_number),
357 ))
358 })?
359 };
360
361 debug!(
362 target: "trie::changeset_cache",
363 block_number,
364 ?block_hash,
365 "Looked up block hash for block number in range"
366 );
367
368 let block_key = ChangesetRangeKey::single(block_number, block_hash);
369 if let Some(changesets) = self.inner.read().get(&block_key) {
370 cached_reverts.push(changesets);
371 } else {
372 all_cached = false;
373 break
374 }
375 }
376
377 if all_cached {
378 cached_reverts.reverse();
380 let accumulated_reverts = Arc::new(TrieUpdatesSorted::merge_slice(&cached_reverts));
381 let elapsed = timer.elapsed();
382
383 let num_account_nodes = accumulated_reverts.account_nodes_ref().len();
384 let num_storage_tries = accumulated_reverts.storage_tries_ref().len();
385
386 debug!(
387 target: "trie::changeset_cache",
388 ?elapsed,
389 start_block,
390 end_block,
391 num_blocks = end_block.saturating_sub(start_block).saturating_add(1),
392 num_account_nodes,
393 num_storage_tries,
394 "Finished accumulating cached trie reverts for block range"
395 );
396
397 if write_reverts_to_cache {
398 self.inner.write().insert(range_key, Arc::clone(&accumulated_reverts));
399 }
400 return Ok(accumulated_reverts)
401 }
402
403 warn!(
404 target: "trie::changeset_cache",
405 start_block,
406 end_block,
407 "Changeset cache MISS in range, falling back to aggregate DB-based computation"
408 );
409
410 let overlay = overlay_manager
411 .overlay_builder(finish.hash)
412 .with_no_reverts()
413 .build_state_trie_overlay_at_frontiers(provider, partial_state_trie, finish, true)?;
414
415 let mut forward_updates = overlay_manager
418 .parent_chain(finish.hash)
419 .take_while(|block| block.recovered_block().number() >= start_block)
420 .map(|block| (block.recovered_block().number(), block.trie_updates()))
421 .collect::<Vec<_>>();
422 forward_updates.reverse();
423
424 let state_trie_provider = OverlayStateProvider::<&P, N>::new_with_state_trie(
425 provider,
426 overlay,
427 provider.cached_storage_settings().is_v2(),
428 );
429
430 let accumulated_reverts = Arc::new(reth_trie_db::compute_range_trie_changesets(
431 provider,
432 &state_trie_provider,
433 &forward_updates,
434 start_block..=end_block,
435 finish.number,
436 )?);
437
438 let elapsed = timer.elapsed();
439
440 let num_account_nodes = accumulated_reverts.account_nodes_ref().len();
441 let num_storage_tries = accumulated_reverts.storage_tries_ref().len();
442
443 debug!(
444 target: "trie::changeset_cache",
445 ?elapsed,
446 start_block,
447 end_block,
448 ?end_block_hash,
449 num_blocks = end_block.saturating_sub(start_block).saturating_add(1),
450 num_account_nodes,
451 num_storage_tries,
452 "Finished accumulating trie reverts for block range"
453 );
454
455 if write_reverts_to_cache {
456 self.inner.write().insert(range_key, Arc::clone(&accumulated_reverts));
457 }
458
459 Ok(accumulated_reverts)
460 }
461}
462
463#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
469struct ChangesetRangeKey {
470 start_block: BlockNumber,
471 end_block: BlockNumber,
472 end_block_hash: B256,
473}
474
475impl ChangesetRangeKey {
476 const fn new(start_block: BlockNumber, end_block: BlockNumber, end_block_hash: B256) -> Self {
477 Self { start_block, end_block, end_block_hash }
478 }
479
480 const fn single(block_number: BlockNumber, block_hash: B256) -> Self {
481 Self::new(block_number, block_number, block_hash)
482 }
483}
484
485#[derive(Debug)]
506struct ChangesetCacheInner {
507 entries: HashMap<ChangesetRangeKey, Arc<TrieUpdatesSorted>>,
509
510 range_starts: BTreeMap<BlockNumber, Vec<ChangesetRangeKey>>,
512
513 metrics: ChangesetCacheMetrics,
515}
516
517#[derive(Metrics, Clone)]
522#[metrics(scope = "trie.changeset_cache")]
523struct ChangesetCacheMetrics {
524 hits: Counter,
526
527 misses: Counter,
529
530 evictions: Counter,
532
533 size: Gauge,
535}
536
537impl Default for ChangesetCacheInner {
538 fn default() -> Self {
539 Self::new()
540 }
541}
542
543impl ChangesetCacheInner {
544 fn new() -> Self {
549 Self { entries: HashMap::new(), range_starts: BTreeMap::new(), metrics: Default::default() }
550 }
551
552 fn get(&self, key: &ChangesetRangeKey) -> Option<Arc<TrieUpdatesSorted>> {
553 match self.entries.get(key) {
554 Some(changesets) => {
555 self.metrics.hits.increment(1);
556 Some(Arc::clone(changesets))
557 }
558 None => {
559 self.metrics.misses.increment(1);
560 None
561 }
562 }
563 }
564
565 fn insert(&mut self, key: ChangesetRangeKey, changesets: Arc<TrieUpdatesSorted>) {
566 debug!(
567 target: "trie::changeset_cache",
568 ?key,
569 cache_size_before = self.entries.len(),
570 "Inserting changeset into cache"
571 );
572
573 let is_new_entry = self.entries.insert(key, changesets).is_none();
574
575 if is_new_entry {
576 self.range_starts.entry(key.start_block).or_default().push(key);
577 }
578
579 self.metrics.size.set(self.entries.len() as f64);
581
582 debug!(
583 target: "trie::changeset_cache",
584 ?key,
585 cache_size_after = self.entries.len(),
586 "Changeset inserted into cache"
587 );
588 }
589
590 fn evict(&mut self, up_to_block: BlockNumber) {
591 debug!(
592 target: "trie::changeset_cache",
593 up_to_block,
594 cache_size_before = self.entries.len(),
595 "Starting cache eviction"
596 );
597
598 let range_starts_to_evict: Vec<u64> =
600 self.range_starts.range(..up_to_block).map(|(num, _)| *num).collect();
601
602 let mut evicted_count = 0;
604
605 for start_block in &range_starts_to_evict {
606 if let Some(keys) = self.range_starts.remove(start_block) {
607 debug!(
608 target: "trie::changeset_cache",
609 start_block,
610 num_ranges = keys.len(),
611 "Evicting ranges from cache"
612 );
613 for key in keys {
614 if self.entries.remove(&key).is_some() {
615 evicted_count += 1;
616 }
617 }
618 }
619 }
620
621 debug!(
622 target: "trie::changeset_cache",
623 up_to_block,
624 evicted_count,
625 cache_size_after = self.entries.len(),
626 "Finished cache eviction"
627 );
628
629 if evicted_count > 0 {
631 self.metrics.evictions.increment(evicted_count as u64);
632 self.metrics.size.set(self.entries.len() as f64);
633 }
634 }
635}
636
637#[cfg(test)]
638mod tests {
639 use super::*;
640 use crate::StateTrieOverlay;
641 use alloy_consensus::Header;
642 use alloy_primitives::{
643 keccak256,
644 map::{B256Map, HashMap},
645 Address, U256,
646 };
647 use reth_chain_state::{test_utils::TestBlockBuilder, ExecutedBlock};
648 use reth_db::{
649 models::{AccountBeforeTx, BlockNumberAddress},
650 tables,
651 transaction::DbTxMut,
652 };
653 use reth_primitives_traits::{Account, StorageEntry};
654 use reth_provider::{
655 test_utils::create_test_provider_factory, BlockWriter, StaticFileProviderFactory,
656 StaticFileSegment, StaticFileWriter,
657 };
658 use reth_stages_types::{FinishCheckpoint, StageCheckpoint, StageId};
659 use reth_storage_api::{StageCheckpointWriter, TrieWriter};
660 use reth_trie::{BranchNodeCompact, Nibbles, SortedTrieData, StateRoot};
661
662 fn create_test_changesets() -> Arc<TrieUpdatesSorted> {
664 Arc::new(TrieUpdatesSorted::new(vec![], B256Map::default()))
665 }
666
667 fn empty_overlay() -> StateTrieOverlay {
668 StateTrieOverlay::new(TrieInputSorted::default())
669 }
670
671 fn insert_test_changesets(
672 cache: &mut ChangesetCacheInner,
673 block_hash: B256,
674 block_number: BlockNumber,
675 changesets: Arc<TrieUpdatesSorted>,
676 ) {
677 cache.insert(ChangesetRangeKey::single(block_number, block_hash), changesets);
678 }
679
680 fn get_test_changesets(
681 cache: &ChangesetCacheInner,
682 block_hash: B256,
683 block_number: BlockNumber,
684 ) -> Option<Arc<TrieUpdatesSorted>> {
685 cache.get(&ChangesetRangeKey::single(block_number, block_hash))
686 }
687
688 fn test_account(balance: u64) -> Account {
689 Account { balance: U256::from(balance), ..Default::default() }
690 }
691
692 fn test_storage(slot: u64, value: u64) -> StorageEntry {
693 StorageEntry { key: B256::from(U256::from(slot)), value: U256::from(value) }
694 }
695
696 fn seed_headers(
697 factory: &impl StaticFileProviderFactory<
698 Primitives: reth_primitives_traits::NodePrimitives<BlockHeader = Header>,
699 >,
700 end_block: BlockNumber,
701 ) {
702 let static_file_provider = factory.static_file_provider();
703 let mut header_writer =
704 static_file_provider.latest_writer(StaticFileSegment::Headers).unwrap();
705 for block_number in 0..=end_block {
706 let header = Header { number: block_number, ..Default::default() };
707 header_writer
708 .append_header(&header, &B256::with_last_byte(block_number as u8))
709 .unwrap();
710 }
711 header_writer.commit().unwrap();
712 }
713
714 fn legacy_compute_range_trie_changesets<Provider>(
715 provider: &Provider,
716 range: RangeInclusive<BlockNumber>,
717 ) -> TrieUpdatesSorted
718 where
719 Provider: DBProvider
720 + ChangeSetReader
721 + StorageChangeSetReader
722 + BlockNumReader
723 + StorageSettingsCache,
724 {
725 let mut accumulated_reverts = TrieUpdatesSorted::default();
726 for block_number in range.rev() {
727 let changesets = legacy_compute_block_trie_changesets(provider, block_number);
728 accumulated_reverts.extend_ref_and_sort(&changesets);
729 }
730 accumulated_reverts
731 }
732
733 fn legacy_compute_block_trie_changesets<Provider>(
734 provider: &Provider,
735 block_number: BlockNumber,
736 ) -> TrieUpdatesSorted
737 where
738 Provider: DBProvider
739 + ChangeSetReader
740 + StorageChangeSetReader
741 + BlockNumReader
742 + StorageSettingsCache,
743 {
744 reth_trie_db::with_adapter!(provider, |A| {
745 legacy_compute_block_trie_changesets_inner::<_, A>(provider, block_number)
746 })
747 }
748
749 fn legacy_compute_block_trie_changesets_inner<Provider, A>(
750 provider: &Provider,
751 block_number: BlockNumber,
752 ) -> TrieUpdatesSorted
753 where
754 Provider: DBProvider
755 + ChangeSetReader
756 + StorageChangeSetReader
757 + BlockNumReader
758 + StorageSettingsCache,
759 A: TrieTableAdapter,
760 {
761 let individual_state_revert =
762 HashedPostStateSorted::from_reverts(provider, block_number..=block_number).unwrap();
763 let cumulative_state_revert =
764 HashedPostStateSorted::from_reverts(provider, (block_number + 1)..).unwrap();
765
766 let mut cumulative_state_revert_prev = cumulative_state_revert.clone();
767 cumulative_state_revert_prev.extend_ref_and_sort(&individual_state_revert);
768
769 type DbStateRoot<'a, TX, A> =
770 StateRoot<DatabaseTrieCursorFactory<&'a TX, A>, DatabaseHashedCursorFactory<&'a TX>>;
771
772 let input_prev = TrieInputSorted::new(
773 Arc::default(),
774 Arc::new(cumulative_state_revert_prev.clone()),
775 cumulative_state_revert_prev.construct_prefix_sets(),
776 );
777 let cumulative_trie_updates_prev =
778 DbStateRoot::<_, A>::overlay_root_from_nodes_with_updates(
779 provider.tx_ref(),
780 input_prev,
781 )
782 .unwrap()
783 .1
784 .into_sorted();
785
786 let input = TrieInputSorted::new(
787 Arc::new(cumulative_trie_updates_prev.clone()),
788 Arc::new(cumulative_state_revert),
789 individual_state_revert.construct_prefix_sets(),
790 );
791 let trie_updates =
792 DbStateRoot::<_, A>::overlay_root_from_nodes_with_updates(provider.tx_ref(), input)
793 .unwrap()
794 .1
795 .into_sorted();
796
797 let db_cursor_factory = DatabaseTrieCursorFactory::<_, A>::new(provider.tx_ref());
798 let state_provider_factory =
799 InMemoryTrieCursorFactory::new(db_cursor_factory, &cumulative_trie_updates_prev);
800
801 compute_trie_changesets(&state_provider_factory, &trie_updates).unwrap()
802 }
803
804 fn seed_tip_trie_tables<Provider, A>(provider: &Provider)
805 where
806 Provider: DBProvider + TrieWriter,
807 A: TrieTableAdapter,
808 {
809 type DbStateRoot<'a, TX, A> =
810 StateRoot<DatabaseTrieCursorFactory<&'a TX, A>, DatabaseHashedCursorFactory<&'a TX>>;
811
812 let (_, trie_updates) =
813 DbStateRoot::<_, A>::from_tx(provider.tx_ref()).root_with_updates().unwrap();
814 provider.write_trie_updates(trie_updates).unwrap();
815 }
816
817 #[test]
818 fn consumers_restore_trie_values_masked_by_deferred_blocks() {
819 let manager = OverlayManager::<reth_ethereum_primitives::EthPrimitives>::default();
820 let factory = create_test_provider_factory();
821 let blocks = TestBlockBuilder::eth().get_executed_blocks(0..11).collect::<Vec<_>>();
822 let provider = factory.provider_rw().unwrap();
823 for block in &blocks {
824 provider.insert_block(block.recovered_block()).unwrap();
825 }
826 provider
827 .save_stage_checkpoint(
828 StageId::Finish,
829 StageCheckpoint::new(10)
830 .with_finish_stage_checkpoint(FinishCheckpoint { partial_state_trie: Some(5) }),
831 )
832 .unwrap();
833 let path = Nibbles::from_nibbles([1]);
834 let updates = |mask| {
835 Arc::new(TrieUpdatesSorted::new(
836 vec![(path, Some(BranchNodeCompact::new(mask, 0, 0, vec![], None)))],
837 B256Map::default(),
838 ))
839 };
840 let block4 = updates(1);
841 let block9 = updates(2);
842 let masked = TrieUpdatesSorted::disjointed_merge_batch(&[&block4], &[&block9]);
844 assert!(masked.is_empty());
845 provider.write_trie_updates_sorted(&masked).unwrap();
846 provider.commit().unwrap();
847 let provider = factory.provider_rw().unwrap();
848 for (start, end, before) in [
849 (4, 4, Arc::new(TrieUpdatesSorted::new(vec![(path, None)], B256Map::default()))),
850 (5, 10, Arc::clone(&block4)),
851 (5, 5, Arc::default()),
852 ] {
853 manager.changeset_cache().inner.write().insert(
854 ChangesetRangeKey::new(start, end, blocks[end as usize].recovered_block().hash()),
855 before,
856 );
857 }
858 let (frontier, finish) = database_state_frontiers(&*provider).unwrap();
859 let overlay = manager
860 .overlay_builder(blocks[4].recovered_block().hash())
861 .build_state_trie_overlay_at_frontiers(&*provider, frontier, finish, true)
862 .unwrap();
863 assert_eq!(overlay.input().nodes, block4);
864 assert_eq!(manager.compute_block_trie_updates(&*provider, 4).unwrap(), *block4);
865
866 manager.evict_cached_changesets(11);
869 for number in 5..=10 {
870 insert_test_changesets(
871 &mut manager.changeset_cache().inner.write(),
872 blocks[number as usize].recovered_block().hash(),
873 number,
874 Arc::default(),
875 );
876 }
877 let cached = || manager.changeset_cache().inner.read().entries.len();
878 let builder = manager.overlay_builder(blocks[4].recovered_block().hash());
879 builder
880 .clone()
881 .without_trie_revert_caching()
882 .build_state_trie_overlay_at_frontiers(&*provider, frontier, finish, true)
883 .unwrap();
884 assert_eq!(cached(), 6);
885 builder.build_state_trie_overlay_at_frontiers(&*provider, frontier, finish, true).unwrap();
886 assert_eq!(cached(), 7);
887 }
888
889 #[test]
890 fn calculated_reverts_preserve_deletions_across_frontiers() {
891 let factory = create_test_provider_factory();
892 let blocks = TestBlockBuilder::eth().get_executed_blocks(0..4).collect::<Vec<_>>();
893 let address = Address::with_last_byte(1);
894 let hashed_address = keccak256(address);
895 let removed = Nibbles::from_nibbles([1]);
896 let replaced = Nibbles::from_nibbles([2]);
897 let node = BranchNodeCompact::new(0b0001, 0, 0, vec![], None);
898 let storage_updates = |storage_nodes| {
899 TrieUpdatesSorted::new(
900 vec![],
901 B256Map::from_iter([(hashed_address, StorageTrieUpdatesSorted { storage_nodes })]),
902 )
903 };
904 let finish_updates =
905 Arc::new(storage_updates(vec![(removed, None), (replaced, Some(node.clone()))]));
906 let provider_rw = factory.provider_rw().unwrap();
907 for block in &blocks {
908 provider_rw.insert_block(block.recovered_block()).unwrap();
909 }
910 provider_rw
911 .write_trie_updates_sorted(&storage_updates(vec![(removed, Some(node.clone()))]))
912 .unwrap();
913 provider_rw
915 .tx_ref()
916 .put::<tables::AccountChangeSets>(1, AccountBeforeTx { address, info: None })
917 .unwrap();
918 provider_rw
919 .save_stage_checkpoint(
920 StageId::Finish,
921 StageCheckpoint::new(2)
922 .with_finish_stage_checkpoint(FinishCheckpoint { partial_state_trie: Some(1) }),
923 )
924 .unwrap();
925 provider_rw.commit().unwrap();
926
927 let manager = OverlayManager::<reth_ethereum_primitives::EthPrimitives>::default();
928 for (block, updates) in [
929 (&blocks[1], Arc::new(storage_updates(vec![(removed, Some(node))]))),
930 (&blocks[2], Arc::clone(&finish_updates)),
931 (&blocks[3], Arc::default()),
932 ] {
933 manager.insert_block(ExecutedBlock::new(
934 Arc::clone(&block.recovered_block),
935 Arc::clone(&block.execution_output),
936 SortedTrieData::new(Arc::default(), updates),
937 ));
938 }
939 let before = factory.provider().unwrap();
940 let cache = manager.changeset_cache();
941 let (frontier, finish) = database_state_frontiers(&before).unwrap();
942 let result =
943 cache.get_or_compute_range(&manager, &before, 1..=2, frontier, finish, true).unwrap();
944 assert_eq!(
945 result.storage_tries_ref()[&hashed_address].storage_nodes_ref(),
946 &[(removed, None), (replaced, None)],
947 );
948
949 let provider_rw = factory.provider_rw().unwrap();
950 provider_rw.write_trie_updates_sorted(&finish_updates).unwrap();
951 provider_rw.save_stage_checkpoint(StageId::Finish, StageCheckpoint::new(3)).unwrap();
952 provider_rw.commit().unwrap();
953 let after = factory.provider().unwrap();
954 let (frontier, finish) = database_state_frontiers(&after).unwrap();
955 let advanced =
956 cache.get_or_compute_range(&manager, &after, 1..=2, frontier, finish, true).unwrap();
957 assert!(Arc::ptr_eq(&advanced, &result));
958
959 cache.evict(2);
961 let advanced =
962 cache.get_or_compute_range(&manager, &after, 1..=2, frontier, finish, true).unwrap();
963 assert_eq!(advanced, result);
964
965 let cached = manager.get_or_compute_cached_changesets_range(&before, 1..=2).unwrap();
967 assert!(Arc::ptr_eq(&cached, &advanced));
968 }
969
970 #[test]
971 fn calculated_historical_range_excludes_tail_reverts() {
972 let factory = create_test_provider_factory();
973 let provider = factory.provider_rw().unwrap();
974 let address = Address::with_last_byte(1);
975 let hashed_address = keccak256(address);
976 let path = Nibbles::from_nibbles([1]);
977 let base_nodes = Arc::new(TrieUpdatesSorted::new(
978 vec![],
979 B256Map::from_iter([(
980 hashed_address,
981 StorageTrieUpdatesSorted {
982 storage_nodes: vec![(
983 path,
984 Some(BranchNodeCompact::new(0b0001, 0, 0, vec![], None)),
985 )],
986 },
987 )]),
988 ));
989 provider
991 .tx_ref()
992 .put::<tables::AccountChangeSets>(3, AccountBeforeTx { address, info: None })
993 .unwrap();
994 let state_trie_provider =
995 OverlayStateProvider::<&_, reth_ethereum_primitives::EthPrimitives>::new_with_state_trie(
996 &*provider,
997 StateTrieOverlay::new(TrieInputSorted::new(
998 Arc::clone(&base_nodes),
999 Arc::default(),
1000 Default::default(),
1001 )),
1002 provider.cached_storage_settings().is_v2(),
1003 );
1004
1005 let end = 2;
1006 for range in [1..=end, end + 1..=end] {
1007 let result = reth_trie_db::compute_range_trie_changesets(
1008 &*provider,
1009 &state_trie_provider,
1010 &[(3, Arc::clone(&base_nodes))],
1011 range,
1012 3,
1013 )
1014 .unwrap();
1015 assert!(result.is_empty());
1016 }
1017 }
1018
1019 #[test]
1020 fn persisted_range_retains_transient_nodes_without_executed_blocks() {
1021 let factory = create_test_provider_factory();
1022 seed_headers(&factory, 2);
1023 let provider = factory.provider_rw().unwrap();
1024 provider.save_stage_checkpoint(StageId::Finish, StageCheckpoint::new(2)).unwrap();
1025 let address = Address::with_last_byte(1);
1026 let hashed_address = keccak256(address);
1027 provider
1028 .tx_ref()
1029 .put::<tables::AccountChangeSets>(1, AccountBeforeTx { address, info: None })
1030 .unwrap();
1031 provider
1032 .tx_ref()
1033 .put::<tables::AccountChangeSets>(
1034 2,
1035 AccountBeforeTx { address, info: Some(test_account(1)) },
1036 )
1037 .unwrap();
1038 for slot in 0..257 {
1041 provider
1042 .tx_ref()
1043 .put::<tables::StorageChangeSets>(
1044 BlockNumberAddress((1, address)),
1045 test_storage(slot, 0),
1046 )
1047 .unwrap();
1048 provider
1049 .tx_ref()
1050 .put::<tables::StorageChangeSets>(
1051 BlockNumberAddress((2, address)),
1052 test_storage(slot, 1),
1053 )
1054 .unwrap();
1055 }
1056 let state_trie_provider =
1057 OverlayStateProvider::<&_, reth_ethereum_primitives::EthPrimitives>::new_with_state_trie(
1058 &*provider,
1059 empty_overlay(),
1060 provider.cached_storage_settings().is_v2(),
1061 );
1062 let result = reth_trie_db::compute_range_trie_changesets(
1063 &*provider,
1064 &state_trie_provider,
1065 &[],
1066 1..=2,
1067 2,
1068 )
1069 .unwrap();
1070 let nodes = result.storage_tries_ref()[&hashed_address].storage_nodes_ref();
1071 assert!(!nodes.is_empty());
1072 assert!(nodes.iter().all(|(_, node)| node.is_none()));
1073
1074 let first = reth_trie_db::compute_range_trie_changesets(
1075 &*provider,
1076 &state_trie_provider,
1077 &[],
1078 1..=1,
1079 2,
1080 )
1081 .unwrap();
1082 let second = reth_trie_db::compute_range_trie_changesets(
1083 &*provider,
1084 &state_trie_provider,
1085 &[],
1086 2..=2,
1087 2,
1088 )
1089 .unwrap();
1090 let mut merged = second.clone();
1091 merged.extend_ref_and_sort(&first);
1092 assert_eq!(result, merged);
1093 let forward = [(1, Arc::new(second.clone())), (2, Arc::new(first.clone()))];
1094 assert_eq!(
1095 reth_trie_db::compute_range_trie_changesets(
1096 &*provider,
1097 &state_trie_provider,
1098 &forward,
1099 1..=2,
1100 2
1101 )
1102 .unwrap(),
1103 result,
1104 );
1105 let manager = OverlayManager::<reth_ethereum_primitives::EthPrimitives>::default();
1106 assert_eq!(manager.compute_block_trie_updates(&*provider, 1).unwrap(), second);
1107 assert_eq!(manager.compute_block_trie_updates(&*provider, 2).unwrap(), first);
1108
1109 let at_first = OverlayStateProvider::<&_, reth_ethereum_primitives::EthPrimitives>::new_with_state_trie(
1111 &*provider,
1112 StateTrieOverlay::new(TrieInputSorted::new(
1113 Arc::new(second),
1114 Arc::new(HashedPostStateSorted::from_reverts(&*provider, 2..=2).unwrap()),
1115 Default::default(),
1116 )),
1117 provider.cached_storage_settings().is_v2(),
1118 );
1119 let at_first =
1120 reth_trie_db::compute_range_trie_changesets(&*provider, &at_first, &[], 1..=1, 1)
1121 .unwrap();
1122 assert_eq!(first, at_first);
1123 }
1124
1125 #[test]
1126 fn cached_range_merge_keeps_oldest_revert_values() {
1127 let factory = create_test_provider_factory();
1128 seed_headers(&factory, 2);
1129
1130 let provider = factory.provider_rw().unwrap();
1131 provider.save_stage_checkpoint(StageId::Finish, StageCheckpoint::new(2)).unwrap();
1132
1133 let cache = ChangesetCache::new();
1134 let path = Nibbles::from_nibbles([0x1, 0x2]);
1135 let older_node = BranchNodeCompact::new(0b0001, 0, 0, vec![], None);
1136 let newer_node = BranchNodeCompact::new(0b0010, 0, 0, vec![], None);
1137
1138 {
1139 let mut cache = cache.inner.write();
1140 insert_test_changesets(
1141 &mut cache,
1142 B256::with_last_byte(1),
1143 1,
1144 Arc::new(TrieUpdatesSorted::new(
1145 vec![(path, Some(older_node.clone()))],
1146 B256Map::default(),
1147 )),
1148 );
1149 insert_test_changesets(
1150 &mut cache,
1151 B256::with_last_byte(2),
1152 2,
1153 Arc::new(TrieUpdatesSorted::new(
1154 vec![(path, Some(newer_node))],
1155 B256Map::default(),
1156 )),
1157 );
1158 }
1159
1160 let overlay_manager = OverlayManager::<reth_ethereum_primitives::EthPrimitives>::default();
1161 let (partial_state_trie, finish) = database_state_frontiers(&*provider).unwrap();
1162 let accumulated = cache
1163 .get_or_compute_range(
1164 &overlay_manager,
1165 &*provider,
1166 1..=2,
1167 partial_state_trie,
1168 finish,
1169 true,
1170 )
1171 .unwrap();
1172 assert_eq!(accumulated.account_nodes_ref(), &[(path, Some(older_node))]);
1173 }
1174
1175 #[test]
1176 fn aggregate_range_reverts_to_pre_range_state() {
1177 let factory = create_test_provider_factory();
1178 seed_headers(&factory, 3);
1179
1180 let provider = factory.provider_rw().unwrap();
1181 let address = Address::with_last_byte(1);
1182 let hashed_address = keccak256(address);
1183 let slot1 = B256::with_last_byte(1);
1184 let slot2 = B256::with_last_byte(2);
1185 let account1 = test_account(10);
1186 let account2 = test_account(20);
1187 let account3 = test_account(30);
1188
1189 provider.tx_ref().put::<tables::HashedAccounts>(hashed_address, account3).unwrap();
1190 provider
1191 .tx_ref()
1192 .put::<tables::HashedStorages>(
1193 hashed_address,
1194 StorageEntry { key: keccak256(slot1), value: U256::from(25) },
1195 )
1196 .unwrap();
1197 provider
1198 .tx_ref()
1199 .put::<tables::HashedStorages>(
1200 hashed_address,
1201 StorageEntry { key: keccak256(slot2), value: U256::from(20) },
1202 )
1203 .unwrap();
1204
1205 provider
1206 .tx_ref()
1207 .put::<tables::AccountChangeSets>(1, AccountBeforeTx { address, info: None })
1208 .unwrap();
1209 provider
1210 .tx_ref()
1211 .put::<tables::AccountChangeSets>(2, AccountBeforeTx { address, info: Some(account1) })
1212 .unwrap();
1213 provider
1214 .tx_ref()
1215 .put::<tables::AccountChangeSets>(3, AccountBeforeTx { address, info: Some(account2) })
1216 .unwrap();
1217
1218 provider
1219 .tx_ref()
1220 .put::<tables::StorageChangeSets>(BlockNumberAddress((1, address)), test_storage(1, 0))
1221 .unwrap();
1222 provider
1223 .tx_ref()
1224 .put::<tables::StorageChangeSets>(BlockNumberAddress((1, address)), test_storage(2, 0))
1225 .unwrap();
1226 provider
1227 .tx_ref()
1228 .put::<tables::StorageChangeSets>(
1229 BlockNumberAddress((2, address)),
1230 StorageEntry { key: slot1, value: U256::from(10) },
1231 )
1232 .unwrap();
1233 provider
1234 .tx_ref()
1235 .put::<tables::StorageChangeSets>(
1236 BlockNumberAddress((3, address)),
1237 StorageEntry { key: slot1, value: U256::from(15) },
1238 )
1239 .unwrap();
1240
1241 provider.save_stage_checkpoint(StageId::Finish, StageCheckpoint::new(3)).unwrap();
1242 reth_trie_db::with_adapter!(provider, |A| seed_tip_trie_tables::<_, A>(&*provider));
1243
1244 let overlay = empty_overlay();
1245 let state_trie_provider =
1246 OverlayStateProvider::<&_, reth_ethereum_primitives::EthPrimitives>::new_with_state_trie(
1247 &*provider,
1248 overlay,
1249 provider.cached_storage_settings().is_v2(),
1250 );
1251 let actual = reth_trie_db::compute_range_trie_changesets(
1252 &*provider,
1253 &state_trie_provider,
1254 &[],
1255 1..=3,
1256 3,
1257 )
1258 .unwrap();
1259 assert!(actual.storage_tries_ref().get(&hashed_address).is_none());
1260
1261 let cache = ChangesetCache::new();
1262 let overlay_manager = OverlayManager::<reth_ethereum_primitives::EthPrimitives>::default();
1263 let (partial_state_trie, finish) = database_state_frontiers(&*provider).unwrap();
1264 let uncached = cache
1265 .get_or_compute_range(
1266 &overlay_manager,
1267 &*provider,
1268 1..=3,
1269 partial_state_trie,
1270 finish,
1271 false,
1272 )
1273 .unwrap();
1274 assert_eq!(*uncached, actual);
1275 assert!(cache.inner.read().entries.is_empty());
1276
1277 let from_cache_api = cache
1278 .get_or_compute_range(
1279 &overlay_manager,
1280 &*provider,
1281 1..=3,
1282 partial_state_trie,
1283 finish,
1284 true,
1285 )
1286 .unwrap();
1287 assert_eq!(*from_cache_api, actual);
1288 assert_eq!(cache.inner.read().entries.len(), 1);
1289
1290 let block_changesets = cache
1291 .get_or_compute(&overlay_manager, &*provider, 2, partial_state_trie, finish)
1292 .unwrap();
1293 assert_eq!(*block_changesets, legacy_compute_block_trie_changesets(&*provider, 2));
1294 assert_eq!(cache.inner.read().entries.len(), 2);
1295 }
1296
1297 #[test]
1298 fn aggregate_range_matches_legacy_per_block_merge_with_storage_wipe() {
1299 let factory = create_test_provider_factory();
1300 seed_headers(&factory, 3);
1301
1302 let provider = factory.provider_rw().unwrap();
1303 let address = Address::with_last_byte(1);
1304 let slot1 = B256::with_last_byte(1);
1305 let slot2 = B256::with_last_byte(2);
1306 let account1 = test_account(10);
1307 let account2 = test_account(20);
1308
1309 provider
1310 .tx_ref()
1311 .put::<tables::AccountChangeSets>(1, AccountBeforeTx { address, info: None })
1312 .unwrap();
1313 provider
1314 .tx_ref()
1315 .put::<tables::AccountChangeSets>(2, AccountBeforeTx { address, info: Some(account1) })
1316 .unwrap();
1317 provider
1318 .tx_ref()
1319 .put::<tables::AccountChangeSets>(3, AccountBeforeTx { address, info: Some(account2) })
1320 .unwrap();
1321
1322 provider
1323 .tx_ref()
1324 .put::<tables::StorageChangeSets>(BlockNumberAddress((1, address)), test_storage(1, 0))
1325 .unwrap();
1326 provider
1327 .tx_ref()
1328 .put::<tables::StorageChangeSets>(BlockNumberAddress((1, address)), test_storage(2, 0))
1329 .unwrap();
1330 provider
1331 .tx_ref()
1332 .put::<tables::StorageChangeSets>(
1333 BlockNumberAddress((2, address)),
1334 StorageEntry { key: slot1, value: U256::from(10) },
1335 )
1336 .unwrap();
1337 provider
1338 .tx_ref()
1339 .put::<tables::StorageChangeSets>(
1340 BlockNumberAddress((3, address)),
1341 StorageEntry { key: slot1, value: U256::from(15) },
1342 )
1343 .unwrap();
1344 provider
1345 .tx_ref()
1346 .put::<tables::StorageChangeSets>(
1347 BlockNumberAddress((3, address)),
1348 StorageEntry { key: slot2, value: U256::from(20) },
1349 )
1350 .unwrap();
1351
1352 provider.save_stage_checkpoint(StageId::Finish, StageCheckpoint::new(3)).unwrap();
1353 reth_trie_db::with_adapter!(provider, |A| seed_tip_trie_tables::<_, A>(&*provider));
1354
1355 let expected = legacy_compute_range_trie_changesets(&*provider, 2..=3);
1356 let overlay = empty_overlay();
1357 let state_trie_provider =
1358 OverlayStateProvider::<&_, reth_ethereum_primitives::EthPrimitives>::new_with_state_trie(
1359 &*provider,
1360 overlay,
1361 provider.cached_storage_settings().is_v2(),
1362 );
1363 let actual = reth_trie_db::compute_range_trie_changesets(
1364 &*provider,
1365 &state_trie_provider,
1366 &[],
1367 2..=3,
1368 3,
1369 )
1370 .unwrap();
1371 assert_eq!(actual, expected);
1372 }
1373
1374 #[test]
1375 fn test_eviction_when_explicitly_called() {
1376 let mut cache = ChangesetCacheInner::new();
1377
1378 let mut hashes = Vec::new();
1380 for i in 0..15 {
1381 let hash = B256::random();
1382 insert_test_changesets(&mut cache, hash, i, create_test_changesets());
1383 hashes.push((i, hash));
1384 }
1385
1386 assert_eq!(cache.entries.len(), 15);
1388
1389 cache.evict(4);
1391
1392 assert_eq!(cache.entries.len(), 11); for i in 0..4 {
1397 assert!(
1398 get_test_changesets(&cache, hashes[i as usize].1, i).is_none(),
1399 "Block {} should be evicted",
1400 i
1401 );
1402 }
1403
1404 for i in 4..15 {
1406 assert!(
1407 get_test_changesets(&cache, hashes[i as usize].1, i).is_some(),
1408 "Block {} should be present",
1409 i
1410 );
1411 }
1412 }
1413
1414 #[test]
1415 fn test_eviction_with_persistence_watermark() {
1416 let mut cache = ChangesetCacheInner::new();
1417
1418 let mut hashes = HashMap::new();
1420 for i in 100..=165 {
1421 let hash = B256::random();
1422 insert_test_changesets(&mut cache, hash, i, create_test_changesets());
1423 hashes.insert(i, hash);
1424 }
1425
1426 assert_eq!(cache.entries.len(), 66);
1428
1429 cache.evict(100);
1432
1433 assert_eq!(cache.entries.len(), 66);
1435
1436 cache.evict(101);
1439
1440 assert_eq!(cache.entries.len(), 65);
1442 assert!(get_test_changesets(&cache, hashes[&100], 100).is_none());
1443 assert!(get_test_changesets(&cache, hashes[&101], 101).is_some());
1444 }
1445
1446 #[test]
1447 fn test_out_of_order_inserts_with_explicit_eviction() {
1448 let mut cache = ChangesetCacheInner::new();
1449
1450 let hash_10 = B256::random();
1452 insert_test_changesets(&mut cache, hash_10, 10, create_test_changesets());
1453
1454 let hash_5 = B256::random();
1455 insert_test_changesets(&mut cache, hash_5, 5, create_test_changesets());
1456
1457 let hash_15 = B256::random();
1458 insert_test_changesets(&mut cache, hash_15, 15, create_test_changesets());
1459
1460 let hash_3 = B256::random();
1461 insert_test_changesets(&mut cache, hash_3, 3, create_test_changesets());
1462
1463 assert_eq!(cache.entries.len(), 4);
1465
1466 cache.evict(5);
1468
1469 assert!(get_test_changesets(&cache, hash_3, 3).is_none(), "Block 3 should be evicted");
1470 assert!(get_test_changesets(&cache, hash_5, 5).is_some(), "Block 5 should be present");
1471 assert!(get_test_changesets(&cache, hash_10, 10).is_some(), "Block 10 should be present");
1472 assert!(get_test_changesets(&cache, hash_15, 15).is_some(), "Block 15 should be present");
1473 }
1474
1475 #[test]
1476 fn test_ranges_with_same_numbers_and_different_end_hashes_are_distinct() {
1477 let mut cache = ChangesetCacheInner::new();
1478 let path = Nibbles::from_nibbles_unchecked([0x01]);
1479 let hash_a = B256::with_last_byte(1);
1480 let hash_b = B256::with_last_byte(2);
1481 let key_a = ChangesetRangeKey::new(10, 20, hash_a);
1482 let key_b = ChangesetRangeKey::new(10, 20, hash_b);
1483 let changesets_a = Arc::new(TrieUpdatesSorted::new(
1484 vec![(path, Some(BranchNodeCompact::new(0b0001, 0, 0, vec![], None)))],
1485 B256Map::default(),
1486 ));
1487 let changesets_b = Arc::new(TrieUpdatesSorted::new(
1488 vec![(path, Some(BranchNodeCompact::new(0b0010, 0, 0, vec![], None)))],
1489 B256Map::default(),
1490 ));
1491
1492 cache.insert(key_a, Arc::clone(&changesets_a));
1493 cache.insert(key_b, Arc::clone(&changesets_b));
1494
1495 assert_eq!(cache.entries.len(), 2);
1496 assert_eq!(
1497 cache.get(&key_a).unwrap().account_nodes_ref(),
1498 changesets_a.account_nodes_ref()
1499 );
1500 assert_eq!(
1501 cache.get(&key_b).unwrap().account_nodes_ref(),
1502 changesets_b.account_nodes_ref()
1503 );
1504
1505 cache.evict(11);
1506 assert!(cache.get(&key_a).is_none());
1507 assert!(cache.get(&key_b).is_none());
1508 }
1509
1510 #[test]
1511 fn test_eviction_removes_all_side_chains() {
1512 let mut cache = ChangesetCacheInner::new();
1513
1514 let hash_10a = B256::random();
1516 let hash_10b = B256::random();
1517 let hash_10c = B256::random();
1518 insert_test_changesets(&mut cache, hash_10a, 10, create_test_changesets());
1519 insert_test_changesets(&mut cache, hash_10b, 10, create_test_changesets());
1520 insert_test_changesets(&mut cache, hash_10c, 10, create_test_changesets());
1521
1522 let hash_20 = B256::random();
1523 insert_test_changesets(&mut cache, hash_20, 20, create_test_changesets());
1524
1525 assert_eq!(cache.entries.len(), 4);
1526
1527 cache.evict(15);
1529
1530 assert_eq!(cache.entries.len(), 1);
1531 assert!(get_test_changesets(&cache, hash_10a, 10).is_none());
1532 assert!(get_test_changesets(&cache, hash_10b, 10).is_none());
1533 assert!(get_test_changesets(&cache, hash_10c, 10).is_none());
1534 assert!(get_test_changesets(&cache, hash_20, 20).is_some());
1535 }
1536}