reth_provider/providers/state/
overlay.rs1use 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#[derive(Clone, Metrics)]
32#[metrics(scope = "storage.providers.overlay")]
33pub(crate) struct OverlayStateProviderFactoryMetrics {
34 create_provider_duration: Histogram,
36 database_provider_ro_duration: Histogram,
38 overlay_cache_misses: Counter,
40}
41
42#[derive(Debug, Clone)]
47pub struct OverlayStateProviderFactory<F, N: NodePrimitives = EthPrimitives> {
48 factory: F,
50 overlay_builder: OverlayBuilder<N>,
52 overlay_cache: Arc<DashMap<(BlockHash, BlockHash), Overlay>>,
57 metrics: OverlayStateProviderFactoryMetrics,
59}
60
61impl<F, N: NodePrimitives> OverlayStateProviderFactory<F, N> {
62 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 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 #[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 #[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 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#[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 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}