diff --git a/crates/interfaces/src/provider.rs b/crates/interfaces/src/provider.rs index 513e3efe9d..f5211072df 100644 --- a/crates/interfaces/src/provider.rs +++ b/crates/interfaces/src/provider.rs @@ -153,3 +153,20 @@ pub struct RootMismatch { /// The target block hash. pub block_hash: BlockHash, } + +/// Consistent database view error. +#[derive(Error, Debug)] +pub enum ConsistentViewError { + /// Error thrown on attempt to initialize provider while node is still syncing. + #[error("node is syncing. best block: {0}")] + Syncing(BlockNumber), + /// Error thrown on inconsistent database view. + #[error("inconsistent database state: {tip:?}")] + InconsistentView { + /// The tip diff. + tip: GotExpected>, + }, + /// Underlying provider error. + #[error(transparent)] + Provider(#[from] ProviderError), +} diff --git a/crates/storage/provider/src/providers/consistent_view.rs b/crates/storage/provider/src/providers/consistent_view.rs new file mode 100644 index 0000000000..1175ca53df --- /dev/null +++ b/crates/storage/provider/src/providers/consistent_view.rs @@ -0,0 +1,71 @@ +use crate::{BlockNumReader, DatabaseProviderFactory, DatabaseProviderRO, ProviderError}; +use reth_db::{cursor::DbCursorRO, database::Database, tables, transaction::DbTx}; +use reth_interfaces::provider::{ConsistentViewError, ProviderResult}; +use reth_primitives::{GotExpected, B256}; +use std::marker::PhantomData; + +/// A consistent view over state in the database. +/// +/// View gets initialized with the latest or provided tip. +/// Upon every attempt to create a database provider, the view will +/// perform a consistency check of current tip against the initial one. +/// +/// ## Usage +/// +/// The view should only be used outside of staged-sync. +/// Otherwise, any attempt to create a provider will result in [ConsistentViewError::Syncing]. +#[derive(Clone, Debug)] +pub struct ConsistentDbView { + database: PhantomData, + provider: Provider, + tip: Option, +} + +impl ConsistentDbView +where + DB: Database, + Provider: DatabaseProviderFactory, +{ + /// Creates new consistent database view. + pub fn new(provider: Provider) -> Self { + Self { database: PhantomData, provider, tip: None } + } + + /// Initializes the view with provided tip. + pub fn with_tip(mut self, tip: B256) -> Self { + self.tip = Some(tip); + self + } + + /// Initializes the view with latest tip. + pub fn with_latest_tip(mut self) -> ProviderResult { + let provider = self.provider.database_provider_ro()?; + let tip = provider.tx_ref().cursor_read::()?.last()?; + self.tip = tip.map(|(_, hash)| hash); + Ok(self) + } + + /// Creates new read-only provider and performs consistency checks on the current tip. + pub fn provider_ro(&self) -> Result, ConsistentViewError> { + let provider_ro = self.provider.database_provider_ro()?; + let last_entry = provider_ro + .tx_ref() + .cursor_read::() + .and_then(|mut cursor| cursor.last()) + .map_err(ProviderError::Database)?; + + let tip = last_entry.map(|(_, hash)| hash); + if self.tip != tip { + return Err(ConsistentViewError::InconsistentView { + tip: GotExpected { got: tip, expected: self.tip }, + }) + } + + let best_block_number = provider_ro.best_block_number()?; + if last_entry.map(|(number, _)| number).unwrap_or_default() != best_block_number { + return Err(ConsistentViewError::Syncing(best_block_number)) + } + + Ok(provider_ro) + } +} diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 0fbde23f13..d9218f48dd 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -1,14 +1,20 @@ use crate::{ AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, - BlockchainTreePendingStateProvider, BundleStateDataProvider, CanonChainTracker, + BlockSource, BlockchainTreePendingStateProvider, BundleStateDataProvider, CanonChainTracker, CanonStateNotifications, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, DatabaseProviderFactory, EvmEnvProvider, HeaderProvider, ProviderError, PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StateProviderBox, StateProviderFactory, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; -use reth_db::{database::Database, models::StoredBlockBodyIndices}; +use reth_db::{ + database::Database, + models::{AccountBeforeTx, StoredBlockBodyIndices}, +}; use reth_interfaces::{ - blockchain_tree::{BlockchainTreeEngine, BlockchainTreeViewer}, + blockchain_tree::{ + error::InsertBlockError, BlockValidationKind, BlockchainTreeEngine, BlockchainTreeViewer, + CanonicalOutcome, InsertPayloadOk, + }, consensus::ForkchoiceState, provider::ProviderResult, RethError, RethResult, @@ -31,27 +37,29 @@ use std::{ }; use tracing::trace; +mod database; +pub use database::*; + +mod static_file; +pub use static_file::{ + StaticFileJarProvider, StaticFileProvider, StaticFileProviderRW, StaticFileProviderRWRefMut, + StaticFileWriter, +}; + +mod state; pub use state::{ historical::{HistoricalStateProvider, HistoricalStateProviderRef}, latest::{LatestStateProvider, LatestStateProviderRef}, }; mod bundle_state_provider; -mod chain_info; -mod database; -mod static_file; -pub use static_file::{ - StaticFileJarProvider, StaticFileProvider, StaticFileProviderRW, StaticFileProviderRWRefMut, - StaticFileWriter, -}; -mod state; -use crate::{providers::chain_info::ChainInfoTracker, traits::BlockSource}; pub use bundle_state_provider::BundleStateProvider; -pub use database::*; -use reth_db::models::AccountBeforeTx; -use reth_interfaces::blockchain_tree::{ - error::InsertBlockError, BlockValidationKind, CanonicalOutcome, InsertPayloadOk, -}; + +mod chain_info; +use chain_info::ChainInfoTracker; + +mod consistent_view; +pub use consistent_view::ConsistentDbView; /// The main type for interacting with the blockchain. ///