Skip to main content

reth_provider/providers/database/
builder.rs

1//! Helper builder entrypoint to instantiate a [`ProviderFactory`].
2
3use crate::{
4    providers::{NodeTypesForProvider, RocksDBProvider, StaticFileProvider},
5    ProviderFactory,
6};
7use reth_db::{
8    mdbx::{DatabaseArguments, MaxReadTransactionDuration},
9    open_db_read_only, DatabaseEnv,
10};
11use reth_node_types::NodeTypesWithDBAdapter;
12use std::{
13    marker::PhantomData,
14    path::{Path, PathBuf},
15    sync::Arc,
16};
17
18/// Helper type to create a [`ProviderFactory`].
19///
20/// See [`ProviderFactoryBuilder::open_read_only`] for usage examples.
21#[derive(Debug)]
22pub struct ProviderFactoryBuilder<N> {
23    _types: PhantomData<N>,
24}
25
26impl<N> ProviderFactoryBuilder<N> {
27    /// Maps the [`reth_node_types::NodeTypes`] of this builder.
28    pub fn types<T>(self) -> ProviderFactoryBuilder<T> {
29        ProviderFactoryBuilder::default()
30    }
31
32    /// Opens the database with the given chainspec and [`ReadOnlyConfig`].
33    ///
34    /// # Open a monitored instance
35    ///
36    /// This is recommended when the new read-only instance is used with an active node.
37    ///
38    /// ```no_run
39    /// use reth_chainspec::MAINNET;
40    /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder};
41    ///
42    /// fn demo<N: NodeTypesForProvider<ChainSpec = reth_chainspec::ChainSpec>>(
43    ///     runtime: reth_tasks::Runtime,
44    /// ) {
45    ///     let provider_factory = ProviderFactoryBuilder::<N>::default()
46    ///         .open_read_only(MAINNET.clone(), "datadir", runtime)
47    ///         .unwrap();
48    /// }
49    /// ```
50    ///
51    /// # Open an unmonitored instance
52    ///
53    /// This is recommended when no changes to the static files are expected (e.g. no active node)
54    ///
55    /// ```no_run
56    /// use reth_chainspec::MAINNET;
57    /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder, ReadOnlyConfig};
58    ///
59    /// fn demo<N: NodeTypesForProvider<ChainSpec = reth_chainspec::ChainSpec>>(
60    ///     runtime: reth_tasks::Runtime,
61    /// ) {
62    ///     let provider_factory = ProviderFactoryBuilder::<N>::default()
63    ///         .open_read_only(
64    ///             MAINNET.clone(),
65    ///             ReadOnlyConfig::from_datadir("datadir").no_watch(),
66    ///             runtime,
67    ///         )
68    ///         .unwrap();
69    /// }
70    /// ```
71    ///
72    /// # Open an instance with disabled read-transaction timeout
73    ///
74    /// By default, read transactions are automatically terminated after a timeout to prevent
75    /// database free list growth. However, if the database is static (no writes occurring), this
76    /// safety mechanism can be disabled using
77    /// [`ReadOnlyConfig::disable_long_read_transaction_safety`].
78    ///
79    /// ```no_run
80    /// use reth_chainspec::MAINNET;
81    /// use reth_provider::providers::{NodeTypesForProvider, ProviderFactoryBuilder, ReadOnlyConfig};
82    ///
83    /// fn demo<N: NodeTypesForProvider<ChainSpec = reth_chainspec::ChainSpec>>(
84    ///     runtime: reth_tasks::Runtime,
85    /// ) {
86    ///     let provider_factory = ProviderFactoryBuilder::<N>::default()
87    ///         .open_read_only(
88    ///             MAINNET.clone(),
89    ///             ReadOnlyConfig::from_datadir("datadir").disable_long_read_transaction_safety(),
90    ///             runtime,
91    ///         )
92    ///         .unwrap();
93    /// }
94    /// ```
95    pub fn open_read_only(
96        self,
97        chainspec: Arc<N::ChainSpec>,
98        config: impl Into<ReadOnlyConfig>,
99        runtime: reth_tasks::Runtime,
100    ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, DatabaseEnv>>>
101    where
102        N: NodeTypesForProvider,
103    {
104        let ReadOnlyConfig { db_dir, db_args, static_files_dir, rocksdb_dir, watch_static_files } =
105            config.into();
106        let db = open_db_read_only(db_dir, db_args)?;
107        let static_file_provider =
108            StaticFileProvider::read_only(static_files_dir, watch_static_files)?;
109        let rocksdb_provider = RocksDBProvider::builder(&rocksdb_dir)
110            .with_default_tables()
111            .with_read_only(true)
112            .build()?;
113        ProviderFactory::new(db, chainspec, static_file_provider, rocksdb_provider, runtime)
114            .map_err(Into::into)
115    }
116}
117
118impl<N> Default for ProviderFactoryBuilder<N> {
119    fn default() -> Self {
120        Self { _types: Default::default() }
121    }
122}
123
124/// Settings for how to open the database, static files, and `RocksDB`.
125///
126/// The default derivation from a path assumes the path is the datadir:
127/// [`ReadOnlyConfig::from_datadir`]
128#[derive(Debug, Clone)]
129pub struct ReadOnlyConfig {
130    /// The path to the database directory.
131    pub db_dir: PathBuf,
132    /// How to open the database
133    pub db_args: DatabaseArguments,
134    /// The path to the static file dir
135    pub static_files_dir: PathBuf,
136    /// The path to the `RocksDB` directory
137    pub rocksdb_dir: PathBuf,
138    /// Whether the static files should be watched for changes.
139    pub watch_static_files: bool,
140}
141
142impl ReadOnlyConfig {
143    /// Derives the [`ReadOnlyConfig`] from the datadir.
144    ///
145    /// By default this assumes the following datadir layout:
146    ///
147    /// ```text
148    ///  -`datadir`
149    ///    |__db
150    ///    |__rocksdb
151    ///    |__static_files
152    /// ```
153    ///
154    /// By default this watches the static file directory for changes, see also
155    /// [`StaticFileProvider::read_only`]
156    pub fn from_datadir(datadir: impl AsRef<Path>) -> Self {
157        let datadir = datadir.as_ref();
158        Self {
159            db_dir: datadir.join("db"),
160            db_args: Default::default(),
161            static_files_dir: datadir.join("static_files"),
162            rocksdb_dir: datadir.join("rocksdb"),
163            watch_static_files: true,
164        }
165    }
166
167    /// Disables long-lived read transaction safety guarantees.
168    ///
169    /// Caution: Keeping database transaction open indefinitely can cause the free list to grow if
170    /// changes to the database are made.
171    pub const fn disable_long_read_transaction_safety(mut self) -> Self {
172        self.db_args.max_read_transaction_duration(Some(MaxReadTransactionDuration::Unbounded));
173        self
174    }
175
176    /// Derives the [`ReadOnlyConfig`] from the database dir.
177    ///
178    /// By default this assumes the following datadir layout:
179    ///
180    /// ```text
181    ///    - db
182    ///    - rocksdb
183    ///    - static_files
184    /// ```
185    ///
186    /// By default this watches the static file directory for changes, see also
187    /// [`StaticFileProvider::read_only`]
188    ///
189    /// # Panics
190    ///
191    /// If the path does not exist
192    pub fn from_db_dir(db_dir: impl AsRef<Path>) -> Self {
193        let db_dir = db_dir.as_ref();
194        let datadir = std::fs::canonicalize(db_dir).unwrap().parent().unwrap().to_path_buf();
195        let static_files_dir = datadir.join("static_files");
196        let rocksdb_dir = datadir.join("rocksdb");
197        Self::from_dirs(db_dir, static_files_dir, rocksdb_dir)
198    }
199
200    /// Creates the config for the given paths.
201    ///
202    ///
203    /// By default this watches the static file directory for changes, see also
204    /// [`StaticFileProvider::read_only`]
205    pub fn from_dirs(
206        db_dir: impl AsRef<Path>,
207        static_files_dir: impl AsRef<Path>,
208        rocksdb_dir: impl AsRef<Path>,
209    ) -> Self {
210        Self {
211            db_dir: db_dir.as_ref().into(),
212            db_args: Default::default(),
213            static_files_dir: static_files_dir.as_ref().into(),
214            rocksdb_dir: rocksdb_dir.as_ref().into(),
215            watch_static_files: true,
216        }
217    }
218
219    /// Configures the db arguments used when opening the database.
220    pub fn with_db_args(mut self, db_args: impl Into<DatabaseArguments>) -> Self {
221        self.db_args = db_args.into();
222        self
223    }
224
225    /// Configures the db directory.
226    pub fn with_db_dir(mut self, db_dir: impl Into<PathBuf>) -> Self {
227        self.db_dir = db_dir.into();
228        self
229    }
230
231    /// Configures the static file directory.
232    pub fn with_static_file_dir(mut self, static_file_dir: impl Into<PathBuf>) -> Self {
233        self.static_files_dir = static_file_dir.into();
234        self
235    }
236
237    /// Whether the static file directory should be watches for changes, see also
238    /// [`StaticFileProvider::read_only`]
239    pub const fn set_watch_static_files(&mut self, watch_static_files: bool) {
240        self.watch_static_files = watch_static_files;
241    }
242
243    /// Don't watch the static files for changes.
244    ///
245    /// This is only recommended if this is used without a running node instance that modifies
246    /// static files.
247    pub const fn no_watch(mut self) -> Self {
248        self.set_watch_static_files(false);
249        self
250    }
251}
252
253impl<T> From<T> for ReadOnlyConfig
254where
255    T: AsRef<Path>,
256{
257    fn from(value: T) -> Self {
258        Self::from_datadir(value.as_ref())
259    }
260}