reth_provider/providers/
consistent_view.rs

1use crate::{BlockNumReader, DatabaseProviderFactory, HeaderProvider};
2use alloy_primitives::B256;
3use reth_errors::ProviderError;
4use reth_primitives::GotExpected;
5use reth_storage_api::{BlockReader, DBProvider, StateCommitmentProvider};
6use reth_storage_errors::provider::ProviderResult;
7
8use reth_trie::HashedPostState;
9use reth_trie_db::{DatabaseHashedPostState, StateCommitment};
10
11pub use reth_storage_errors::provider::ConsistentViewError;
12
13/// A consistent view over state in the database.
14///
15/// View gets initialized with the latest or provided tip.
16/// Upon every attempt to create a database provider, the view will
17/// perform a consistency check of current tip against the initial one.
18///
19/// ## Usage
20///
21/// The view should only be used outside of staged-sync.
22/// Otherwise, any attempt to create a provider will result in [`ConsistentViewError::Syncing`].
23///
24/// When using the view, the consumer should either
25/// 1) have a failover for when the state changes and handle [`ConsistentViewError::Inconsistent`]
26///    appropriately.
27/// 2) be sure that the state does not change.
28#[derive(Clone, Debug)]
29pub struct ConsistentDbView<Factory> {
30    factory: Factory,
31    tip: Option<B256>,
32}
33
34impl<Factory> ConsistentDbView<Factory>
35where
36    Factory: DatabaseProviderFactory<Provider: BlockReader> + StateCommitmentProvider,
37{
38    /// Creates new consistent database view.
39    pub const fn new(factory: Factory, tip: Option<B256>) -> Self {
40        Self { factory, tip }
41    }
42
43    /// Creates new consistent database view with latest tip.
44    pub fn new_with_latest_tip(provider: Factory) -> ProviderResult<Self> {
45        let provider_ro = provider.database_provider_ro()?;
46        let last_num = provider_ro.last_block_number()?;
47        let tip = provider_ro.sealed_header(last_num)?.map(|h| h.hash());
48        Ok(Self::new(provider, tip))
49    }
50
51    /// Retrieve revert hashed state down to the given block hash.
52    pub fn revert_state(&self, block_hash: B256) -> ProviderResult<HashedPostState> {
53        let provider = self.provider_ro()?;
54        let block_number = provider
55            .block_number(block_hash)?
56            .ok_or(ProviderError::BlockHashNotFound(block_hash))?;
57        if block_number == provider.best_block_number()? &&
58            block_number == provider.last_block_number()?
59        {
60            Ok(HashedPostState::default())
61        } else {
62            Ok(HashedPostState::from_reverts::<
63                <Factory::StateCommitment as StateCommitment>::KeyHasher,
64            >(provider.tx_ref(), block_number + 1)?)
65        }
66    }
67
68    /// Creates new read-only provider and performs consistency checks on the current tip.
69    pub fn provider_ro(&self) -> ProviderResult<Factory::Provider> {
70        // Create a new provider.
71        let provider_ro = self.factory.database_provider_ro()?;
72
73        // Check that the latest stored header number matches the number
74        // that consistent view was initialized with.
75        // The mismatch can happen if a new block was appended while
76        // the view was being used.
77        // We compare block hashes instead of block numbers to account for reorgs.
78        let last_num = provider_ro.last_block_number()?;
79        let tip = provider_ro.sealed_header(last_num)?.map(|h| h.hash());
80        if self.tip != tip {
81            return Err(ConsistentViewError::Inconsistent {
82                tip: GotExpected { got: tip, expected: self.tip },
83            }
84            .into())
85        }
86
87        // Check that the best block number is the same as the latest stored header.
88        // This ensures that the consistent view cannot be used for initializing new providers
89        // if the node fell back to the staged sync.
90        let best_block_number = provider_ro.best_block_number()?;
91        if last_num != best_block_number {
92            return Err(ConsistentViewError::Syncing {
93                best_block: GotExpected { got: best_block_number, expected: last_num },
94            }
95            .into())
96        }
97
98        Ok(provider_ro)
99    }
100}