Skip to main content

reth_storage_overlay/
provider.rs

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