1use alloy_eips::BlockNumHash;
2use alloy_primitives::{BlockHash, BlockNumber, B256};
3use metrics::{Counter, Histogram};
4use reth_chain_state::{EthPrimitives, StateTrieOverlayManager};
5use reth_db_api::{tables, transaction::DbTx, DatabaseError};
6use reth_errors::{ProviderError, ProviderResult};
7use reth_metrics::Metrics;
8use reth_primitives_traits::{
9 dashmap::{self, DashMap},
10 NodePrimitives,
11};
12use reth_prune_types::PruneSegment;
13use reth_stages_types::StageId;
14use reth_storage_api::{
15 BlockNumReader, ChangeSetReader, DBProvider, DatabaseProviderFactory,
16 DatabaseProviderROFactory, PruneCheckpointReader, StageCheckpointReader,
17 StorageChangeSetReader, StorageSettingsCache,
18};
19use reth_trie::{
20 hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory},
21 trie_cursor::{InMemoryTrieCursor, TrieCursor, TrieCursorFactory, TrieStorageCursor},
22 updates::TrieUpdatesSorted,
23 HashedPostStateSorted,
24};
25use reth_trie_db::{
26 ChangesetCache, DatabaseAccountTrieCursor, DatabaseHashedCursorFactory,
27 DatabaseStorageTrieCursor, LegacyKeyAdapter, PackedAccountsTrie, PackedKeyAdapter,
28 PackedStoragesTrie,
29};
30use std::{
31 ops::RangeInclusive,
32 sync::Arc,
33 time::{Duration, Instant},
34};
35use tracing::{debug, debug_span, instrument};
36
37#[derive(Clone, Metrics)]
39#[metrics(scope = "storage.providers.overlay")]
40pub(crate) struct OverlayStateProviderMetrics {
41 create_provider_duration: Histogram,
43 retrieve_trie_reverts_duration: Histogram,
45 retrieve_hashed_state_reverts_duration: Histogram,
47 trie_updates_size: Histogram,
49 hashed_state_size: Histogram,
51 database_provider_ro_duration: Histogram,
53 overlay_cache_misses: Counter,
55}
56
57#[derive(Debug, Clone)]
59pub(super) struct Overlay {
60 pub(super) trie_updates: Arc<TrieUpdatesSorted>,
61 pub(super) hashed_post_state: Arc<HashedPostStateSorted>,
62}
63
64#[derive(Debug, Clone)]
66pub(super) enum OverlaySource<N: NodePrimitives = EthPrimitives> {
67 Immediate {
69 trie: Arc<TrieUpdatesSorted>,
74 state: Arc<HashedPostStateSorted>,
76 },
77 Managed {
79 manager: StateTrieOverlayManager<N>,
81 state: Arc<HashedPostStateSorted>,
85 },
86}
87
88#[derive(Debug, Clone)]
93pub struct OverlayBuilder<N: NodePrimitives = EthPrimitives> {
94 parent_hash: B256,
96 overlay_source: Option<OverlaySource<N>>,
98 changeset_cache: ChangesetCache,
100 metrics: OverlayStateProviderMetrics,
102}
103
104impl<N: NodePrimitives> OverlayBuilder<N> {
105 pub fn new(parent_hash: B256, changeset_cache: ChangesetCache) -> Self {
107 Self {
108 parent_hash,
109 overlay_source: None,
110 changeset_cache,
111 metrics: OverlayStateProviderMetrics::default(),
112 }
113 }
114
115 pub(super) fn with_overlay_source(mut self, source: Option<OverlaySource<N>>) -> Self {
119 self.overlay_source = source;
120 self
121 }
122
123 pub fn with_state_trie_overlay_manager(
125 mut self,
126 state_trie_overlay_manager: StateTrieOverlayManager<N>,
127 ) -> Self {
128 self.overlay_source = Some(OverlaySource::Managed {
129 manager: state_trie_overlay_manager,
130 state: Arc::new(HashedPostStateSorted::default()),
131 });
132 self
133 }
134
135 pub fn with_hashed_state_overlay(
137 mut self,
138 hashed_state_overlay: Option<Arc<HashedPostStateSorted>>,
139 ) -> Self {
140 if let Some(state) = hashed_state_overlay {
141 match &mut self.overlay_source {
142 Some(OverlaySource::Managed { state: managed_state, .. }) => {
143 *managed_state = state;
144 }
145 _ => {
146 self.overlay_source = Some(OverlaySource::Immediate {
147 trie: Arc::new(TrieUpdatesSorted::default()),
148 state,
149 });
150 }
151 }
152 }
153 self
154 }
155
156 pub fn with_extended_hashed_state_overlay(mut self, other: HashedPostStateSorted) -> Self {
160 match &mut self.overlay_source {
161 Some(OverlaySource::Immediate { state, .. } | OverlaySource::Managed { state, .. }) => {
162 Arc::make_mut(state).extend_ref_and_sort(&other);
163 }
164 None => {
165 self.overlay_source = Some(OverlaySource::Immediate {
166 trie: Arc::new(TrieUpdatesSorted::default()),
167 state: Arc::new(other),
168 });
169 }
170 }
171 self
172 }
173
174 fn resolve_overlays(
176 &self,
177 anchor_hash: BlockHash,
178 ) -> ProviderResult<(Arc<TrieUpdatesSorted>, Arc<HashedPostStateSorted>)> {
179 match &self.overlay_source {
180 Some(OverlaySource::Managed { manager, state }) => {
181 let (trie, mut overlay_state) = if anchor_hash == self.parent_hash {
182 (
183 Arc::new(TrieUpdatesSorted::default()),
184 Arc::new(HashedPostStateSorted::default()),
185 )
186 } else {
187 manager
188 .overlay_for_parent(self.parent_hash, anchor_hash)
189 .map_err(ProviderError::other)?
190 };
191
192 if overlay_state.is_empty() {
193 overlay_state = Arc::clone(state);
194 } else if !state.is_empty() {
195 Arc::make_mut(&mut overlay_state).extend_ref_and_sort(state);
196 }
197
198 Ok((trie, overlay_state))
199 }
200 Some(OverlaySource::Immediate { trie, state }) => {
201 if anchor_hash != self.parent_hash {
202 return Err(ProviderError::other(std::io::Error::other(format!(
203 "anchor_hash {anchor_hash} doesn't match OverlayBuilder's configured parent ({})",
204 self.parent_hash
205 ))))
206 }
207 Ok((Arc::clone(trie), Arc::clone(state)))
208 }
209 None => Ok((
210 Arc::new(TrieUpdatesSorted::default()),
211 Arc::new(HashedPostStateSorted::default()),
212 )),
213 }
214 }
215
216 fn get_db_tip_block<Provider>(&self, provider: &Provider) -> ProviderResult<BlockNumHash>
219 where
220 Provider: StageCheckpointReader + BlockNumReader,
221 {
222 let block_number = provider
223 .get_stage_checkpoint(StageId::Finish)?
224 .as_ref()
225 .map(|chk| chk.block_number)
226 .ok_or_else(|| ProviderError::InsufficientChangesets {
227 requested: 0,
228 available: 0..=0,
229 })?;
230 let hash = provider
231 .convert_number(block_number.into())?
232 .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?;
233 Ok(BlockNumHash::new(block_number, hash))
234 }
235
236 fn reverts_required<Provider>(
242 &self,
243 provider: &Provider,
244 db_tip_block: BlockNumHash,
245 anchor_hash: B256,
246 ) -> ProviderResult<Option<RangeInclusive<BlockNumber>>>
247 where
248 Provider: BlockNumReader + PruneCheckpointReader,
249 {
250 if db_tip_block.hash == anchor_hash {
252 return Ok(None)
253 }
254
255 let anchor_number = provider
256 .convert_hash_or_number(anchor_hash.into())?
257 .ok_or(ProviderError::BlockHashNotFound(anchor_hash))?;
258
259 let prune_checkpoint = provider.get_prune_checkpoint(PruneSegment::AccountHistory)?;
263 let lower_bound = prune_checkpoint
264 .and_then(|chk| chk.block_number)
265 .map(|block_number| block_number + 1)
266 .unwrap_or_default();
267
268 let available_range = lower_bound..=db_tip_block.number;
269
270 if !available_range.contains(&anchor_number) {
272 return Err(ProviderError::InsufficientChangesets {
273 requested: anchor_number,
274 available: available_range,
275 });
276 }
277
278 Ok(Some(anchor_number + 1..=db_tip_block.number))
279 }
280
281 #[instrument(
283 level = "debug",
284 target = "providers::state::overlay",
285 skip_all,
286 fields(?db_tip_block, parent_hash = ?self.parent_hash)
287 )]
288 fn calculate_overlay<Provider>(
289 &self,
290 provider: &Provider,
291 db_tip_block: BlockNumHash,
292 ) -> ProviderResult<Overlay>
293 where
294 Provider: ChangeSetReader
295 + StorageChangeSetReader
296 + DBProvider
297 + BlockNumReader
298 + StageCheckpointReader
299 + PruneCheckpointReader
300 + StorageSettingsCache,
301 {
302 let retrieve_trie_reverts_duration;
306 let retrieve_hashed_state_reverts_duration;
307 let trie_updates_total_len;
308 let hashed_state_updates_total_len;
309 let anchor_hash = match &self.overlay_source {
310 Some(OverlaySource::Managed { manager, .. }) => {
311 let parent_is_persisted = provider
312 .convert_hash_or_number(self.parent_hash.into())?
313 .is_some_and(|parent_number| parent_number <= db_tip_block.number);
314 if parent_is_persisted {
315 self.parent_hash
316 } else {
317 manager
318 .anchor_for_parent(self.parent_hash, db_tip_block.hash)
319 .ok_or(ProviderError::BlockHashNotFound(self.parent_hash))?
320 }
321 }
322 _ => self.parent_hash,
323 };
324
325 let (trie_updates, hashed_post_state) = if let Some(revert_blocks) =
327 self.reverts_required(provider, db_tip_block, anchor_hash)?
328 {
329 debug!(
330 target: "providers::state::overlay",
331 ?revert_blocks,
332 "Collecting trie reverts for overlay state provider"
333 );
334
335 let trie_reverts = {
337 let _guard =
338 debug_span!(target: "providers::state::overlay", "retrieving_trie_reverts")
339 .entered();
340
341 let start = Instant::now();
342
343 let accumulated_reverts =
346 self.changeset_cache.get_or_compute_range(provider, revert_blocks.clone())?;
347
348 retrieve_trie_reverts_duration = start.elapsed();
349 accumulated_reverts
350 };
351
352 let mut hashed_state_reverts = {
354 let _guard = debug_span!(target: "providers::state::overlay", "retrieving_hashed_state_reverts").entered();
355
356 let start = Instant::now();
357 let res = reth_trie_db::from_reverts_auto(provider, revert_blocks)?;
358 retrieve_hashed_state_reverts_duration = start.elapsed();
359 res
360 };
361
362 let (overlay_trie, overlay_state) = self.resolve_overlays(anchor_hash)?;
365
366 let trie_updates = if trie_reverts.is_empty() {
367 overlay_trie
368 } else if !overlay_trie.is_empty() {
369 let mut trie_reverts = (*trie_reverts).clone();
370 trie_reverts.extend_ref_and_sort(&overlay_trie);
371 Arc::new(trie_reverts)
372 } else {
373 trie_reverts
374 };
375
376 let hashed_state_updates = if hashed_state_reverts.is_empty() {
377 overlay_state
378 } else if !overlay_state.is_empty() {
379 hashed_state_reverts.extend_ref_and_sort(&overlay_state);
380 Arc::new(hashed_state_reverts)
381 } else {
382 Arc::new(hashed_state_reverts)
383 };
384
385 trie_updates_total_len = trie_updates.total_len();
386 hashed_state_updates_total_len = hashed_state_updates.total_len();
387
388 debug!(
389 target: "providers::state::overlay",
390 num_trie_updates = ?trie_updates_total_len,
391 num_state_updates = ?hashed_state_updates_total_len,
392 "Reverted to anchor block",
393 );
394
395 (trie_updates, hashed_state_updates)
396 } else {
397 let (trie_updates, hashed_state) = self.resolve_overlays(db_tip_block.hash)?;
399
400 retrieve_trie_reverts_duration = Duration::ZERO;
401 retrieve_hashed_state_reverts_duration = Duration::ZERO;
402 trie_updates_total_len = trie_updates.total_len();
403 hashed_state_updates_total_len = hashed_state.total_len();
404
405 (trie_updates, hashed_state)
406 };
407
408 self.metrics
410 .retrieve_trie_reverts_duration
411 .record(retrieve_trie_reverts_duration.as_secs_f64());
412 self.metrics
413 .retrieve_hashed_state_reverts_duration
414 .record(retrieve_hashed_state_reverts_duration.as_secs_f64());
415 self.metrics.trie_updates_size.record(trie_updates_total_len as f64);
416 self.metrics.hashed_state_size.record(hashed_state_updates_total_len as f64);
417
418 Ok(Overlay { trie_updates, hashed_post_state })
419 }
420
421 #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
423 pub(super) fn build_overlay<Provider>(&self, provider: &Provider) -> ProviderResult<Overlay>
424 where
425 Provider: StageCheckpointReader
426 + PruneCheckpointReader
427 + ChangeSetReader
428 + StorageChangeSetReader
429 + DBProvider
430 + BlockNumReader
431 + StorageSettingsCache,
432 {
433 let db_tip_block = self.get_db_tip_block(provider)?;
434 self.calculate_overlay(provider, db_tip_block)
435 }
436}
437
438#[derive(Debug, Clone)]
443pub struct OverlayStateProviderFactory<F, N: NodePrimitives = EthPrimitives> {
444 factory: F,
446 overlay_builder: OverlayBuilder<N>,
448 overlay_cache: Arc<DashMap<BlockHash, Overlay>>,
451}
452
453impl<F, N: NodePrimitives> OverlayStateProviderFactory<F, N> {
454 pub fn new(factory: F, overlay_builder: OverlayBuilder<N>) -> Self {
456 Self { factory, overlay_builder, overlay_cache: Default::default() }
457 }
458
459 pub fn with_hashed_state_overlay(
461 mut self,
462 hashed_state_overlay: Option<Arc<HashedPostStateSorted>>,
463 ) -> Self {
464 self.overlay_builder = self.overlay_builder.with_hashed_state_overlay(hashed_state_overlay);
465 self.overlay_cache = Default::default();
466 self
467 }
468
469 pub fn with_extended_hashed_state_overlay(mut self, other: HashedPostStateSorted) -> Self {
471 self.overlay_builder = self.overlay_builder.with_extended_hashed_state_overlay(other);
472 self.overlay_cache = Default::default();
473 self
474 }
475
476 #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
479 fn get_overlay<Provider>(&self, provider: &Provider) -> ProviderResult<Overlay>
480 where
481 Provider: StageCheckpointReader
482 + PruneCheckpointReader
483 + ChangeSetReader
484 + StorageChangeSetReader
485 + DBProvider
486 + BlockNumReader
487 + StorageSettingsCache,
488 {
489 let db_tip_block = self.overlay_builder.get_db_tip_block(provider)?;
490
491 let overlay = match self.overlay_cache.entry(db_tip_block.hash) {
492 dashmap::Entry::Occupied(entry) => entry.get().clone(),
493 dashmap::Entry::Vacant(entry) => {
494 self.overlay_builder.metrics.overlay_cache_misses.increment(1);
495 let overlay = self.overlay_builder.build_overlay(provider)?;
496 entry.insert(overlay.clone());
497 overlay
498 }
499 };
500
501 Ok(overlay)
502 }
503}
504
505impl<F, N> DatabaseProviderROFactory for OverlayStateProviderFactory<F, N>
506where
507 N: NodePrimitives,
508 F: DatabaseProviderFactory,
509 F::Provider: StageCheckpointReader
510 + PruneCheckpointReader
511 + DBProvider
512 + BlockNumReader
513 + ChangeSetReader
514 + StorageChangeSetReader
515 + StorageSettingsCache,
516{
517 type Provider = OverlayStateProvider<F::Provider>;
518
519 #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
521 fn database_provider_ro(&self) -> ProviderResult<OverlayStateProvider<F::Provider>> {
522 let overall_start = Instant::now();
523
524 let provider = {
526 let start = Instant::now();
527 let res = self.factory.database_provider_ro()?;
528 self.overlay_builder.metrics.create_provider_duration.record(start.elapsed());
529 res
530 };
531
532 let Overlay { trie_updates, hashed_post_state } = self.get_overlay(&provider)?;
533
534 let is_v2 = provider.cached_storage_settings().is_v2();
535 self.overlay_builder.metrics.database_provider_ro_duration.record(overall_start.elapsed());
536 Ok(OverlayStateProvider::new(provider, trie_updates, hashed_post_state, is_v2))
537 }
538}
539
540#[derive(Debug)]
546pub struct OverlayStateProvider<Provider: DBProvider> {
547 provider: Provider,
548 trie_updates: Arc<TrieUpdatesSorted>,
549 hashed_post_state: Arc<HashedPostStateSorted>,
550 is_v2: bool,
551}
552
553impl<Provider> OverlayStateProvider<Provider>
554where
555 Provider: DBProvider,
556{
557 pub const fn new(
560 provider: Provider,
561 trie_updates: Arc<TrieUpdatesSorted>,
562 hashed_post_state: Arc<HashedPostStateSorted>,
563 is_v2: bool,
564 ) -> Self {
565 Self { provider, trie_updates, hashed_post_state, is_v2 }
566 }
567}
568
569impl<Provider> TrieCursorFactory for OverlayStateProvider<Provider>
570where
571 Provider: DBProvider,
572 Provider::Tx: DbTx,
573{
574 type AccountTrieCursor<'a>
575 = InMemoryTrieCursor<'a, Box<dyn TrieCursor + Send + 'a>>
576 where
577 Self: 'a;
578
579 type StorageTrieCursor<'a>
580 = InMemoryTrieCursor<'a, Box<dyn TrieStorageCursor + Send + 'a>>
581 where
582 Self: 'a;
583
584 fn account_trie_cursor(&self) -> Result<Self::AccountTrieCursor<'_>, DatabaseError> {
585 let tx = self.provider.tx_ref();
586 let trie_updates = self.trie_updates.as_ref();
587 let cursor: Box<dyn TrieCursor + Send> = if self.is_v2 {
588 Box::new(DatabaseAccountTrieCursor::<_, PackedKeyAdapter>::new(
589 tx.cursor_read::<PackedAccountsTrie>()?,
590 ))
591 } else {
592 Box::new(DatabaseAccountTrieCursor::<_, LegacyKeyAdapter>::new(
593 tx.cursor_read::<tables::AccountsTrie>()?,
594 ))
595 };
596 Ok(InMemoryTrieCursor::new_account(cursor, trie_updates))
597 }
598
599 fn storage_trie_cursor(
600 &self,
601 hashed_address: B256,
602 ) -> Result<Self::StorageTrieCursor<'_>, DatabaseError> {
603 let tx = self.provider.tx_ref();
604 let trie_updates = self.trie_updates.as_ref();
605 let cursor: Box<dyn TrieStorageCursor + Send> = if self.is_v2 {
606 Box::new(DatabaseStorageTrieCursor::<_, PackedKeyAdapter>::new(
607 tx.cursor_dup_read::<PackedStoragesTrie>()?,
608 hashed_address,
609 ))
610 } else {
611 Box::new(DatabaseStorageTrieCursor::<_, LegacyKeyAdapter>::new(
612 tx.cursor_dup_read::<tables::StoragesTrie>()?,
613 hashed_address,
614 ))
615 };
616 Ok(InMemoryTrieCursor::new_storage(cursor, trie_updates, hashed_address))
617 }
618}
619
620impl<Provider> HashedCursorFactory for OverlayStateProvider<Provider>
621where
622 Provider: DBProvider,
623{
624 type AccountCursor<'a>
625 = <HashedPostStateCursorFactory<
626 DatabaseHashedCursorFactory<&'a Provider::Tx>,
627 &'a Arc<HashedPostStateSorted>,
628 > as HashedCursorFactory>::AccountCursor<'a>
629 where
630 Self: 'a;
631
632 type StorageCursor<'a>
633 = <HashedPostStateCursorFactory<
634 DatabaseHashedCursorFactory<&'a Provider::Tx>,
635 &'a Arc<HashedPostStateSorted>,
636 > as HashedCursorFactory>::StorageCursor<'a>
637 where
638 Self: 'a;
639
640 fn hashed_account_cursor(&self) -> Result<Self::AccountCursor<'_>, DatabaseError> {
641 let db_hashed_cursor_factory = DatabaseHashedCursorFactory::new(self.provider.tx_ref());
642 let hashed_cursor_factory =
643 HashedPostStateCursorFactory::new(db_hashed_cursor_factory, &self.hashed_post_state);
644 hashed_cursor_factory.hashed_account_cursor()
645 }
646
647 fn hashed_storage_cursor(
648 &self,
649 hashed_address: B256,
650 ) -> Result<Self::StorageCursor<'_>, DatabaseError> {
651 let db_hashed_cursor_factory = DatabaseHashedCursorFactory::new(self.provider.tx_ref());
652 let hashed_cursor_factory =
653 HashedPostStateCursorFactory::new(db_hashed_cursor_factory, &self.hashed_post_state);
654 hashed_cursor_factory.hashed_storage_cursor(hashed_address)
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661 use reth_primitives_traits::Account;
662 use reth_trie::HashedPostState;
663
664 #[test]
665 fn managed_overlay_skips_manager_for_persisted_parent() {
666 let parent_hash = B256::with_last_byte(1);
667 let builder = OverlayBuilder::<EthPrimitives>::new(parent_hash, ChangesetCache::default())
668 .with_state_trie_overlay_manager(StateTrieOverlayManager::default());
669
670 let (trie, state) = builder.resolve_overlays(parent_hash).unwrap();
671 assert!(trie.is_empty());
672 assert!(state.is_empty());
673 }
674
675 #[test]
676 fn managed_overlay_errors_if_parent_is_not_persisted_or_managed() {
677 let parent_hash = B256::with_last_byte(1);
678 let anchor_hash = B256::with_last_byte(2);
679 let builder = OverlayBuilder::<EthPrimitives>::new(parent_hash, ChangesetCache::default())
680 .with_state_trie_overlay_manager(StateTrieOverlayManager::default());
681
682 let err = builder.resolve_overlays(anchor_hash).unwrap_err();
683
684 assert!(err.to_string().contains("cannot be anchored"));
685 }
686
687 #[test]
688 fn extending_hashed_state_keeps_managed_overlay_source() {
689 let parent_hash = B256::with_last_byte(1);
690 let hashed_state = HashedPostState::default()
691 .with_accounts([(B256::with_last_byte(2), Some(Account::default()))])
692 .into_sorted();
693 let builder = OverlayBuilder::<EthPrimitives>::new(parent_hash, ChangesetCache::default())
694 .with_state_trie_overlay_manager(StateTrieOverlayManager::default())
695 .with_extended_hashed_state_overlay(hashed_state);
696
697 let Some(OverlaySource::Managed { state, .. }) = builder.overlay_source else {
698 panic!("expected managed overlay source")
699 };
700 assert_eq!(state.total_len(), 1);
701 }
702}