Skip to main content

reth_provider/providers/state/
overlay.rs

1use alloy_primitives::{BlockHash, B256};
2use metrics::{Counter, Histogram};
3use reth_db_api::{tables, transaction::DbTx, DatabaseError};
4use reth_errors::ProviderResult;
5use reth_ethereum_primitives::EthPrimitives;
6use reth_metrics::Metrics;
7use reth_primitives_traits::{
8    dashmap::{self, DashMap},
9    NodePrimitives,
10};
11use reth_storage_api::{
12    BlockNumReader, ChangeSetReader, DBProvider, DatabaseProviderFactory,
13    DatabaseProviderROFactory, PruneCheckpointReader, StageCheckpointReader,
14    StorageChangeSetReader, StorageSettingsCache,
15};
16use reth_storage_overlay::{database_state_frontiers, Overlay, OverlayBuilder};
17use reth_trie::{
18    hashed_cursor::{HashedCursorFactory, HashedPostStateCursorFactory},
19    trie_cursor::{InMemoryTrieCursor, TrieCursor, TrieCursorFactory, TrieStorageCursor},
20    updates::TrieUpdatesSorted,
21    HashedPostStateSorted,
22};
23use reth_trie_db::{
24    DatabaseAccountTrieCursor, DatabaseHashedCursorFactory, DatabaseStorageTrieCursor,
25    LegacyKeyAdapter, PackedAccountsTrie, PackedKeyAdapter, PackedStoragesTrie,
26};
27use std::{sync::Arc, time::Instant};
28use tracing::instrument;
29
30/// Metrics for overlay state provider factory operations.
31#[derive(Clone, Metrics)]
32#[metrics(scope = "storage.providers.overlay")]
33pub(crate) struct OverlayStateProviderFactoryMetrics {
34    /// Duration of creating the database provider transaction.
35    create_provider_duration: Histogram,
36    /// Overall duration of the [`OverlayStateProviderFactory::database_provider_ro`] call.
37    database_provider_ro_duration: Histogram,
38    /// Number of cache misses when fetching [`Overlay`]s from the overlay cache.
39    overlay_cache_misses: Counter,
40}
41
42/// Factory for creating overlay state providers with optional reverts and overlays.
43///
44/// This factory allows building an `OverlayStateProvider` whose DB state has been reverted to a
45/// particular block, and/or with additional overlay information added on top.
46#[derive(Debug, Clone)]
47pub struct OverlayStateProviderFactory<F, N: NodePrimitives = EthPrimitives> {
48    /// The underlying database provider factory
49    factory: F,
50    /// Overlay builder containing the configuration and overlay calculation logic.
51    overlay_builder: OverlayBuilder<N>,
52    /// A cache which maps `(state_trie_tip, finish_tip) -> Overlay`.
53    ///
54    /// Under partial persistence the overlay depends on both durable frontiers, so both hashes are
55    /// part of the cache key.
56    overlay_cache: Arc<DashMap<(BlockHash, BlockHash), Overlay>>,
57    /// Metrics for provider factory operations.
58    metrics: OverlayStateProviderFactoryMetrics,
59}
60
61impl<F, N: NodePrimitives> OverlayStateProviderFactory<F, N> {
62    /// Create a new overlay state provider factory
63    pub fn new(factory: F, overlay_builder: OverlayBuilder<N>) -> Self {
64        Self {
65            factory,
66            overlay_builder,
67            overlay_cache: Default::default(),
68            metrics: Default::default(),
69        }
70    }
71
72    /// Skips managed overlay construction when this factory is used by a task that reused a sparse
73    /// trie covering both durable frontiers through the parent.
74    pub fn with_skip_overlay_for_reused_sparse_trie(mut self, anchor_hash: B256) -> Self {
75        self.overlay_builder =
76            self.overlay_builder.with_skip_overlay_for_reused_sparse_trie(anchor_hash);
77        self.overlay_cache = Default::default();
78        self
79    }
80
81    /// Fetches an [`Overlay`] from the cache based on the current durable frontiers. If there is no
82    /// cached value then this calculates the [`Overlay`] and populates the cache.
83    #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
84    fn get_overlay<Provider>(&self, provider: &Provider) -> ProviderResult<Overlay>
85    where
86        Provider: StageCheckpointReader
87            + PruneCheckpointReader
88            + ChangeSetReader
89            + StorageChangeSetReader
90            + DBProvider
91            + BlockNumReader
92            + StorageSettingsCache,
93    {
94        let (state_trie_tip_block, finish_tip_block) = database_state_frontiers(provider)?;
95
96        let overlay =
97            match self.overlay_cache.entry((state_trie_tip_block.hash, finish_tip_block.hash)) {
98                dashmap::Entry::Occupied(entry) => entry.get().clone(),
99                dashmap::Entry::Vacant(entry) => {
100                    self.metrics.overlay_cache_misses.increment(1);
101                    let overlay = self.overlay_builder.build_overlay_at_frontiers(
102                        provider,
103                        state_trie_tip_block,
104                        finish_tip_block,
105                    )?;
106                    entry.insert(overlay.clone());
107                    overlay
108                }
109            };
110
111        Ok(overlay)
112    }
113}
114
115impl<F, N> DatabaseProviderROFactory for OverlayStateProviderFactory<F, N>
116where
117    N: NodePrimitives,
118    F: DatabaseProviderFactory,
119    F::Provider: StageCheckpointReader
120        + PruneCheckpointReader
121        + DBProvider
122        + BlockNumReader
123        + ChangeSetReader
124        + StorageChangeSetReader
125        + StorageSettingsCache,
126{
127    type Provider = OverlayStateProvider<F::Provider>;
128
129    /// Create a read-only [`OverlayStateProvider`].
130    #[instrument(level = "debug", target = "providers::state::overlay", skip_all)]
131    fn database_provider_ro(&self) -> ProviderResult<OverlayStateProvider<F::Provider>> {
132        let overall_start = Instant::now();
133
134        // Get a read-only provider
135        let provider = {
136            let start = Instant::now();
137            let res = self.factory.database_provider_ro()?;
138            self.metrics.create_provider_duration.record(start.elapsed());
139            res
140        };
141
142        let Overlay { trie_updates, hashed_post_state } = self.get_overlay(&provider)?;
143
144        let is_v2 = provider.cached_storage_settings().is_v2();
145        self.metrics.database_provider_ro_duration.record(overall_start.elapsed());
146        Ok(OverlayStateProvider::new(provider, trie_updates, hashed_post_state, is_v2))
147    }
148}
149
150/// State provider with in-memory overlay from trie updates and hashed post state.
151///
152/// This provider uses in-memory trie updates and hashed post state as an overlay
153/// on top of a database provider, implementing [`TrieCursorFactory`] and [`HashedCursorFactory`]
154/// using the in-memory overlay factories.
155#[derive(Debug)]
156pub struct OverlayStateProvider<Provider: DBProvider> {
157    provider: Provider,
158    trie_updates: Arc<TrieUpdatesSorted>,
159    hashed_post_state: Arc<HashedPostStateSorted>,
160    is_v2: bool,
161}
162
163impl<Provider> OverlayStateProvider<Provider>
164where
165    Provider: DBProvider,
166{
167    /// Create new overlay state provider. The `Provider` must be cloneable, which generally means
168    /// it should be wrapped in an `Arc`.
169    pub const fn new(
170        provider: Provider,
171        trie_updates: Arc<TrieUpdatesSorted>,
172        hashed_post_state: Arc<HashedPostStateSorted>,
173        is_v2: bool,
174    ) -> Self {
175        Self { provider, trie_updates, hashed_post_state, is_v2 }
176    }
177}
178
179impl<Provider> TrieCursorFactory for OverlayStateProvider<Provider>
180where
181    Provider: DBProvider,
182    Provider::Tx: DbTx,
183{
184    type AccountTrieCursor<'a>
185        = InMemoryTrieCursor<'a, Box<dyn TrieCursor + Send + 'a>>
186    where
187        Self: 'a;
188
189    type StorageTrieCursor<'a>
190        = InMemoryTrieCursor<'a, Box<dyn TrieStorageCursor + Send + 'a>>
191    where
192        Self: 'a;
193
194    fn account_trie_cursor(&self) -> Result<Self::AccountTrieCursor<'_>, DatabaseError> {
195        let tx = self.provider.tx_ref();
196        let trie_updates = self.trie_updates.as_ref();
197        let cursor: Box<dyn TrieCursor + Send> = if self.is_v2 {
198            Box::new(DatabaseAccountTrieCursor::<_, PackedKeyAdapter>::new(
199                tx.cursor_read::<PackedAccountsTrie>()?,
200            ))
201        } else {
202            Box::new(DatabaseAccountTrieCursor::<_, LegacyKeyAdapter>::new(
203                tx.cursor_read::<tables::AccountsTrie>()?,
204            ))
205        };
206        Ok(InMemoryTrieCursor::new_account(cursor, trie_updates))
207    }
208
209    fn storage_trie_cursor(
210        &self,
211        hashed_address: B256,
212    ) -> Result<Self::StorageTrieCursor<'_>, DatabaseError> {
213        let tx = self.provider.tx_ref();
214        let trie_updates = self.trie_updates.as_ref();
215        let cursor: Box<dyn TrieStorageCursor + Send> = if self.is_v2 {
216            Box::new(DatabaseStorageTrieCursor::<_, PackedKeyAdapter>::new(
217                tx.cursor_dup_read::<PackedStoragesTrie>()?,
218                hashed_address,
219            ))
220        } else {
221            Box::new(DatabaseStorageTrieCursor::<_, LegacyKeyAdapter>::new(
222                tx.cursor_dup_read::<tables::StoragesTrie>()?,
223                hashed_address,
224            ))
225        };
226        Ok(InMemoryTrieCursor::new_storage(cursor, trie_updates, hashed_address))
227    }
228}
229
230impl<Provider> HashedCursorFactory for OverlayStateProvider<Provider>
231where
232    Provider: DBProvider,
233{
234    type AccountCursor<'a>
235        = <HashedPostStateCursorFactory<
236        DatabaseHashedCursorFactory<&'a Provider::Tx>,
237        &'a Arc<HashedPostStateSorted>,
238    > as HashedCursorFactory>::AccountCursor<'a>
239    where
240        Self: 'a;
241
242    type StorageCursor<'a>
243        = <HashedPostStateCursorFactory<
244        DatabaseHashedCursorFactory<&'a Provider::Tx>,
245        &'a Arc<HashedPostStateSorted>,
246    > as HashedCursorFactory>::StorageCursor<'a>
247    where
248        Self: 'a;
249
250    fn hashed_account_cursor(&self) -> Result<Self::AccountCursor<'_>, DatabaseError> {
251        let db_hashed_cursor_factory = DatabaseHashedCursorFactory::new(self.provider.tx_ref());
252        let hashed_cursor_factory =
253            HashedPostStateCursorFactory::new(db_hashed_cursor_factory, &self.hashed_post_state);
254        hashed_cursor_factory.hashed_account_cursor()
255    }
256
257    fn hashed_storage_cursor(
258        &self,
259        hashed_address: B256,
260    ) -> Result<Self::StorageCursor<'_>, DatabaseError> {
261        let db_hashed_cursor_factory = DatabaseHashedCursorFactory::new(self.provider.tx_ref());
262        let hashed_cursor_factory =
263            HashedPostStateCursorFactory::new(db_hashed_cursor_factory, &self.hashed_post_state);
264        hashed_cursor_factory.hashed_storage_cursor(hashed_address)
265    }
266}
267
268#[cfg(all(test, feature = "partial-persistence"))]
269mod tests {
270    use super::*;
271    use crate::{
272        test_utils::{create_test_provider_factory, MockNodeTypesWithDB},
273        BlockWriter, ProviderFactory,
274    };
275    use alloy_primitives::U256;
276    use reth_chain_state::{test_utils::TestBlockBuilder, ExecutedBlock};
277    use reth_primitives_traits::Account;
278    use reth_stages_types::{FinishCheckpoint, StageCheckpoint, StageId};
279    use reth_storage_api::StageCheckpointWriter;
280    use reth_storage_overlay::OverlayManager;
281    use reth_trie::{BranchNodeCompact, ComputedTrieData, HashedPostState, HashedStorage, Nibbles};
282
283    fn with_unique_trie_data(
284        block: &ExecutedBlock<EthPrimitives>,
285        id: u8,
286    ) -> ExecutedBlock<EthPrimitives> {
287        let hashed_address = B256::with_last_byte(id);
288        let hashed_slot = B256::with_last_byte(id.saturating_add(32));
289        let hashed_state = HashedPostState::default()
290            .with_accounts([(hashed_address, Some(Account::default()))])
291            .with_storages([(
292                hashed_address,
293                HashedStorage::from_iter(false, [(hashed_slot, U256::from(id))]),
294            )])
295            .into_sorted();
296        let trie_updates = TrieUpdatesSorted::new(
297            vec![(
298                Nibbles::from_nibbles([id]),
299                Some(BranchNodeCompact::new(0, 0, 0, vec![], None)),
300            )],
301            Default::default(),
302        );
303
304        ExecutedBlock::new(
305            Arc::clone(&block.recovered_block),
306            Arc::clone(&block.execution_output),
307            ComputedTrieData::new(Arc::new(hashed_state), Arc::new(trie_updates)),
308        )
309    }
310
311    fn test_blocks() -> Vec<ExecutedBlock<EthPrimitives>> {
312        TestBlockBuilder::eth()
313            .get_executed_blocks(0..5)
314            .enumerate()
315            .map(|(index, block)| with_unique_trie_data(&block, index as u8 + 1))
316            .collect()
317    }
318
319    fn setup_frontiers(
320        state_trie_tip_index: usize,
321        finish_tip_index: usize,
322    ) -> (ProviderFactory<MockNodeTypesWithDB>, Vec<ExecutedBlock<EthPrimitives>>) {
323        let factory = create_test_provider_factory();
324        let blocks = test_blocks();
325        let provider_rw = factory.provider_rw().unwrap();
326        for block in &blocks[..=finish_tip_index] {
327            provider_rw.insert_block(block.recovered_block()).unwrap();
328        }
329        provider_rw
330            .save_stage_checkpoint(
331                StageId::Finish,
332                StageCheckpoint::new(blocks[finish_tip_index].block_number())
333                    .with_finish_stage_checkpoint(FinishCheckpoint {
334                        partial_state_trie: Some(blocks[state_trie_tip_index].block_number()),
335                    }),
336            )
337            .unwrap();
338        provider_rw.commit().unwrap();
339
340        (factory, blocks)
341    }
342
343    fn account_keys(overlay: &Overlay) -> Vec<B256> {
344        overlay.hashed_post_state.accounts.iter().map(|(key, _)| *key).collect()
345    }
346
347    fn account_node_paths(overlay: &Overlay) -> Vec<Nibbles> {
348        overlay.trie_updates.account_nodes_ref().iter().map(|(path, _)| *path).collect()
349    }
350
351    #[test]
352    fn overlay_cache_is_keyed_by_both_durable_frontiers() {
353        let (factory, blocks) = setup_frontiers(1, 3);
354        let manager = OverlayManager::default();
355        for block in &blocks[2..=3] {
356            manager.insert_block(block.clone());
357        }
358        let overlay_factory = OverlayStateProviderFactory::new(
359            factory.clone(),
360            manager.overlay_builder(blocks[3].recovered_block().hash()),
361        );
362
363        let provider = factory.provider().unwrap();
364        let first = overlay_factory.get_overlay(&provider).unwrap();
365        assert_eq!(account_keys(&first), vec![B256::with_last_byte(3), B256::with_last_byte(4)]);
366        drop(provider);
367
368        let provider_rw = factory.provider_rw().unwrap();
369        provider_rw
370            .save_stage_checkpoint(
371                StageId::Finish,
372                StageCheckpoint::new(blocks[3].block_number()).with_finish_stage_checkpoint(
373                    FinishCheckpoint { partial_state_trie: Some(blocks[2].block_number()) },
374                ),
375            )
376            .unwrap();
377        provider_rw.commit().unwrap();
378
379        let provider = factory.provider().unwrap();
380        let second = overlay_factory.get_overlay(&provider).unwrap();
381        assert_eq!(account_keys(&second), vec![B256::with_last_byte(4)]);
382        assert_eq!(account_node_paths(&second), vec![Nibbles::from_nibbles([4])]);
383        assert_eq!(overlay_factory.overlay_cache.len(), 2);
384    }
385}