reth_provider/providers/database/
builder.rs

1//! Helper builder entrypoint to instantiate a [`ProviderFactory`].
2//!
3//! This also includes general purpose staging types that provide builder style functions that lead
4//! up to the intended build target.
5
6use crate::{providers::StaticFileProvider, ProviderFactory};
7use reth_db::{
8    mdbx::{DatabaseArguments, MaxReadTransactionDuration},
9    open_db_read_only, DatabaseEnv,
10};
11use reth_db_api::{database_metrics::DatabaseMetrics, Database};
12use reth_node_types::{NodeTypes, NodeTypesWithDBAdapter};
13use std::{
14    marker::PhantomData,
15    path::{Path, PathBuf},
16    sync::Arc,
17};
18
19/// Helper type to create a [`ProviderFactory`].
20///
21/// This type is the entry point for a stage based builder.
22///
23/// The intended staging is:
24///  1. Configure the database: [`ProviderFactoryBuilder::db`]
25///  2. Configure the chainspec: `chainspec`
26///  3. Configure the [`StaticFileProvider`]: `static_file`
27#[derive(Debug)]
28pub struct ProviderFactoryBuilder<N> {
29    _types: PhantomData<N>,
30}
31
32impl<N> ProviderFactoryBuilder<N> {
33    /// Maps the [`NodeTypes`] of this builder.
34    pub fn types<T>(self) -> ProviderFactoryBuilder<T> {
35        ProviderFactoryBuilder::default()
36    }
37
38    /// Configures the database.
39    pub fn db<DB>(self, db: DB) -> TypesAnd1<N, DB> {
40        TypesAnd1::new(db)
41    }
42
43    /// Opens the database with the given chainspec and [`ReadOnlyConfig`].
44    ///
45    /// # Open a monitored instance
46    ///
47    /// This is recommended when the new read-only instance is used with an active node.
48    ///
49    /// ```no_run
50    /// use reth_chainspec::MAINNET;
51    /// use reth_node_types::NodeTypes;
52    /// use reth_provider::providers::ProviderFactoryBuilder;
53    ///
54    /// fn demo<N: NodeTypes<ChainSpec = reth_chainspec::ChainSpec>>() {
55    ///     let provider_factory = ProviderFactoryBuilder::<N>::default()
56    ///         .open_read_only(MAINNET.clone(), "datadir")
57    ///         .unwrap();
58    /// }
59    /// ```
60    ///
61    /// # Open an unmonitored instance
62    ///
63    /// This is recommended when no changes to the static files are expected (e.g. no active node)
64    ///
65    /// ```no_run
66    /// use reth_chainspec::MAINNET;
67    /// use reth_node_types::NodeTypes;
68    ///
69    /// use reth_provider::providers::{ProviderFactoryBuilder, ReadOnlyConfig};
70    ///
71    /// fn demo<N: NodeTypes<ChainSpec = reth_chainspec::ChainSpec>>() {
72    ///     let provider_factory = ProviderFactoryBuilder::<N>::default()
73    ///         .open_read_only(MAINNET.clone(), ReadOnlyConfig::from_datadir("datadir").no_watch())
74    ///         .unwrap();
75    /// }
76    /// ```
77    ///
78    /// # Open an instance with disabled read-transaction timeout
79    ///
80    /// By default, read transactions are automatically terminated after a timeout to prevent
81    /// database free list growth. However, if the database is static (no writes occurring), this
82    /// safety mechanism can be disabled using
83    /// [`ReadOnlyConfig::disable_long_read_transaction_safety`].
84    ///
85    /// ```no_run
86    /// use reth_chainspec::MAINNET;
87    /// use reth_node_types::NodeTypes;
88    ///
89    /// use reth_provider::providers::{ProviderFactoryBuilder, ReadOnlyConfig};
90    ///
91    /// fn demo<N: NodeTypes<ChainSpec = reth_chainspec::ChainSpec>>() {
92    ///     let provider_factory = ProviderFactoryBuilder::<N>::default()
93    ///         .open_read_only(
94    ///             MAINNET.clone(),
95    ///             ReadOnlyConfig::from_datadir("datadir").disable_long_read_transaction_safety(),
96    ///         )
97    ///         .unwrap();
98    /// }
99    /// ```
100    pub fn open_read_only(
101        self,
102        chainspec: Arc<N::ChainSpec>,
103        config: impl Into<ReadOnlyConfig>,
104    ) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>>
105    where
106        N: NodeTypes,
107    {
108        let ReadOnlyConfig { db_dir, db_args, static_files_dir, watch_static_files } =
109            config.into();
110        Ok(self
111            .db(Arc::new(open_db_read_only(db_dir, db_args)?))
112            .chainspec(chainspec)
113            .static_file(StaticFileProvider::read_only(static_files_dir, watch_static_files)?)
114            .build_provider_factory())
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 and static files.
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    /// Whether the static files should be watched for changes.
137    pub watch_static_files: bool,
138}
139
140impl ReadOnlyConfig {
141    /// Derives the [`ReadOnlyConfig`] from the datadir.
142    ///
143    /// By default this assumes the following datadir layout:
144    ///
145    /// ```text
146    ///  -`datadir`
147    ///    |__db
148    ///    |__static_files
149    /// ```
150    ///
151    /// By default this watches the static file directory for changes, see also
152    /// [`StaticFileProvider::read_only`]
153    pub fn from_datadir(datadir: impl AsRef<Path>) -> Self {
154        let datadir = datadir.as_ref();
155        Self::from_dirs(datadir.join("db"), datadir.join("static_files"))
156    }
157
158    /// Disables long-lived read transaction safety guarantees.
159    ///
160    /// Caution: Keeping database transaction open indefinitely can cause the free list to grow if
161    /// changes to the database are made.
162    pub const fn disable_long_read_transaction_safety(mut self) -> Self {
163        self.db_args.max_read_transaction_duration(Some(MaxReadTransactionDuration::Unbounded));
164        self
165    }
166
167    /// Derives the [`ReadOnlyConfig`] from the database dir.
168    ///
169    /// By default this assumes the following datadir layout:
170    ///
171    /// ```text
172    ///    - db
173    ///    -static_files
174    /// ```
175    ///
176    /// By default this watches the static file directory for changes, see also
177    /// [`StaticFileProvider::read_only`]
178    ///
179    /// # Panics
180    ///
181    /// If the path does not exist
182    pub fn from_db_dir(db_dir: impl AsRef<Path>) -> Self {
183        let db_dir = db_dir.as_ref();
184        let static_files_dir = std::fs::canonicalize(db_dir)
185            .unwrap()
186            .parent()
187            .unwrap()
188            .to_path_buf()
189            .join("static_files");
190        Self::from_dirs(db_dir, static_files_dir)
191    }
192
193    /// Creates the config for the given paths.
194    ///
195    ///
196    /// By default this watches the static file directory for changes, see also
197    /// [`StaticFileProvider::read_only`]
198    pub fn from_dirs(db_dir: impl AsRef<Path>, static_files_dir: impl AsRef<Path>) -> Self {
199        Self {
200            static_files_dir: static_files_dir.as_ref().into(),
201            db_dir: db_dir.as_ref().into(),
202            db_args: Default::default(),
203            watch_static_files: true,
204        }
205    }
206
207    /// Configures the db arguments used when opening the database.
208    pub fn with_db_args(mut self, db_args: impl Into<DatabaseArguments>) -> Self {
209        self.db_args = db_args.into();
210        self
211    }
212
213    /// Configures the db directory.
214    pub fn with_db_dir(mut self, db_dir: impl Into<PathBuf>) -> Self {
215        self.db_dir = db_dir.into();
216        self
217    }
218
219    /// Configures the static file directory.
220    pub fn with_static_file_dir(mut self, static_file_dir: impl Into<PathBuf>) -> Self {
221        self.static_files_dir = static_file_dir.into();
222        self
223    }
224
225    /// Whether the static file directory should be watches for changes, see also
226    /// [`StaticFileProvider::read_only`]
227    pub const fn set_watch_static_files(&mut self, watch_static_files: bool) {
228        self.watch_static_files = watch_static_files;
229    }
230
231    /// Don't watch the static files for changes.
232    ///
233    /// This is only recommended if this is used without a running node instance that modifies
234    /// static files.
235    pub const fn no_watch(mut self) -> Self {
236        self.set_watch_static_files(false);
237        self
238    }
239}
240
241impl<T> From<T> for ReadOnlyConfig
242where
243    T: AsRef<Path>,
244{
245    fn from(value: T) -> Self {
246        Self::from_datadir(value.as_ref())
247    }
248}
249
250/// This is staging type that contains the configured types and _one_ value.
251#[derive(Debug)]
252pub struct TypesAnd1<N, Val1> {
253    _types: PhantomData<N>,
254    val_1: Val1,
255}
256
257impl<N, Val1> TypesAnd1<N, Val1> {
258    /// Creates a new instance with the given types and one value.
259    pub fn new(val_1: Val1) -> Self {
260        Self { _types: Default::default(), val_1 }
261    }
262
263    /// Configures the chainspec.
264    pub fn chainspec<C>(self, chainspec: Arc<C>) -> TypesAnd2<N, Val1, Arc<C>> {
265        TypesAnd2::new(self.val_1, chainspec)
266    }
267}
268
269/// This is staging type that contains the configured types and _two_ values.
270#[derive(Debug)]
271pub struct TypesAnd2<N, Val1, Val2> {
272    _types: PhantomData<N>,
273    val_1: Val1,
274    val_2: Val2,
275}
276
277impl<N, Val1, Val2> TypesAnd2<N, Val1, Val2> {
278    /// Creates a new instance with the given types and two values.
279    pub fn new(val_1: Val1, val_2: Val2) -> Self {
280        Self { _types: Default::default(), val_1, val_2 }
281    }
282
283    /// Returns the first value.
284    pub const fn val_1(&self) -> &Val1 {
285        &self.val_1
286    }
287
288    /// Returns the second value.
289    pub const fn val_2(&self) -> &Val2 {
290        &self.val_2
291    }
292
293    /// Configures the [`StaticFileProvider`].
294    pub fn static_file(
295        self,
296        static_file_provider: StaticFileProvider<N::Primitives>,
297    ) -> TypesAnd3<N, Val1, Val2, StaticFileProvider<N::Primitives>>
298    where
299        N: NodeTypes,
300    {
301        TypesAnd3::new(self.val_1, self.val_2, static_file_provider)
302    }
303}
304
305/// This is staging type that contains the configured types and _three_ values.
306#[derive(Debug)]
307pub struct TypesAnd3<N, Val1, Val2, Val3> {
308    _types: PhantomData<N>,
309    val_1: Val1,
310    val_2: Val2,
311    val_3: Val3,
312}
313
314impl<N, Val1, Val2, Val3> TypesAnd3<N, Val1, Val2, Val3> {
315    /// Creates a new instance with the given types and three values.
316    pub fn new(val_1: Val1, val_2: Val2, val_3: Val3) -> Self {
317        Self { _types: Default::default(), val_1, val_2, val_3 }
318    }
319}
320
321impl<N, DB> TypesAnd3<N, DB, Arc<N::ChainSpec>, StaticFileProvider<N::Primitives>>
322where
323    N: NodeTypes,
324    DB: Database + DatabaseMetrics + Clone + Unpin + 'static,
325{
326    /// Creates the [`ProviderFactory`].
327    pub fn build_provider_factory(self) -> ProviderFactory<NodeTypesWithDBAdapter<N, DB>> {
328        let Self { _types, val_1, val_2, val_3 } = self;
329        ProviderFactory::new(val_1, val_2, val_3)
330    }
331}