Skip to main content

reth_node_core/
dirs.rs

1//! reth data directories.
2
3use crate::{args::DatadirArgs, utils::parse_path};
4use reth_chainspec::Chain;
5use std::{
6    convert::Infallible,
7    fmt::{Debug, Display, Formatter},
8    path::{Path, PathBuf},
9    str::FromStr,
10};
11
12/// Constructs a string to be used as a path for configuration and db paths.
13pub fn config_path_prefix(chain: Chain) -> String {
14    chain.to_string()
15}
16
17/// Returns the path to the reth data directory.
18///
19/// Refer to [`dirs_next::data_dir`] for cross-platform behavior.
20pub fn data_dir() -> Option<PathBuf> {
21    dirs_next::data_dir().map(|root| root.join("reth"))
22}
23
24/// Returns the path to the reth database.
25///
26/// Refer to [`dirs_next::data_dir`] for cross-platform behavior.
27pub fn database_path() -> Option<PathBuf> {
28    data_dir().map(|root| root.join("db"))
29}
30
31/// Returns the path to the reth configuration directory.
32///
33/// Refer to [`dirs_next::config_dir`] for cross-platform behavior.
34pub fn config_dir() -> Option<PathBuf> {
35    dirs_next::config_dir().map(|root| root.join("reth"))
36}
37
38/// Returns the path to the reth cache directory.
39///
40/// Refer to [`dirs_next::cache_dir`] for cross-platform behavior.
41pub fn cache_dir() -> Option<PathBuf> {
42    dirs_next::cache_dir().map(|root| root.join("reth"))
43}
44
45/// Returns the path to the reth logs directory.
46///
47/// Refer to [`dirs_next::cache_dir`] for cross-platform behavior.
48pub fn logs_dir() -> Option<PathBuf> {
49    cache_dir().map(|root| root.join("logs"))
50}
51
52/// Returns the path to the reth data dir.
53///
54/// The data dir should contain a subdirectory for each chain, and those chain directories will
55/// include all information for that chain, such as the p2p secret.
56#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
57#[non_exhaustive]
58pub struct DataDirPath;
59
60impl XdgPath for DataDirPath {
61    fn resolve() -> Option<PathBuf> {
62        data_dir()
63    }
64}
65
66/// Returns the path to the reth logs directory.
67///
68/// Refer to [`dirs_next::cache_dir`] for cross-platform behavior.
69#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
70#[non_exhaustive]
71pub struct LogsDir;
72
73impl XdgPath for LogsDir {
74    fn resolve() -> Option<PathBuf> {
75        logs_dir()
76    }
77}
78
79/// A small helper trait for unit structs that represent a standard path following the XDG
80/// path specification.
81pub trait XdgPath {
82    /// Resolve the standard path.
83    fn resolve() -> Option<PathBuf>;
84}
85
86/// A wrapper type that either parses a user-given path or defaults to an
87/// OS-specific path.
88///
89/// The [`FromStr`] implementation parses a string into a path.
90///
91/// # Example
92///
93/// ```
94/// use reth_node_core::dirs::{DataDirPath, PlatformPath};
95/// use std::str::FromStr;
96///
97/// // Resolves to the platform-specific database path
98/// let default: PlatformPath<DataDirPath> = PlatformPath::default();
99/// // Resolves to `$(pwd)/my/path/to/datadir`
100/// let custom: PlatformPath<DataDirPath> = PlatformPath::from_str("my/path/to/datadir").unwrap();
101///
102/// assert_ne!(default.as_ref(), custom.as_ref());
103/// ```
104#[derive(Debug, PartialEq, Eq)]
105pub struct PlatformPath<D>(PathBuf, std::marker::PhantomData<D>);
106
107impl<D> Display for PlatformPath<D> {
108    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
109        write!(f, "{}", self.0.display())
110    }
111}
112
113impl<D> Clone for PlatformPath<D> {
114    fn clone(&self) -> Self {
115        Self(self.0.clone(), std::marker::PhantomData)
116    }
117}
118
119impl<D: XdgPath> Default for PlatformPath<D> {
120    fn default() -> Self {
121        Self(
122            D::resolve().expect("Could not resolve default path. Set one manually."),
123            std::marker::PhantomData,
124        )
125    }
126}
127
128impl<D> FromStr for PlatformPath<D> {
129    type Err = Infallible;
130
131    fn from_str(s: &str) -> Result<Self, Self::Err> {
132        Ok(Self(parse_path(s), std::marker::PhantomData))
133    }
134}
135
136impl<D> AsRef<Path> for PlatformPath<D> {
137    fn as_ref(&self) -> &Path {
138        self.0.as_path()
139    }
140}
141
142impl<D> From<PlatformPath<D>> for PathBuf {
143    fn from(value: PlatformPath<D>) -> Self {
144        value.0
145    }
146}
147
148impl<D> PlatformPath<D> {
149    /// Returns the path joined with another path
150    pub fn join<P: AsRef<Path>>(&self, path: P) -> Self {
151        Self(self.0.join(path), std::marker::PhantomData)
152    }
153}
154
155impl<D> PlatformPath<D> {
156    /// Converts the path to a `ChainPath` with the given `Chain`.
157    pub fn with_chain(&self, chain: Chain, datadir_args: DatadirArgs) -> ChainPath<D> {
158        // extract chain name
159        let platform_path = self.platform_path_from_chain(chain);
160
161        ChainPath::new(platform_path, chain, datadir_args)
162    }
163
164    fn platform_path_from_chain(&self, chain: Chain) -> Self {
165        let chain_name = config_path_prefix(chain);
166        let path = self.0.join(chain_name);
167        Self(path, std::marker::PhantomData)
168    }
169
170    /// Map the inner path to a new type `T`.
171    pub fn map_to<T>(&self) -> PlatformPath<T> {
172        PlatformPath(self.0.clone(), std::marker::PhantomData)
173    }
174}
175
176/// An Optional wrapper type around [`PlatformPath`].
177///
178/// This is useful for when a path is optional, such as the `--data-dir` flag.
179#[derive(Clone, Debug, PartialEq, Eq)]
180pub struct MaybePlatformPath<D>(Option<PlatformPath<D>>);
181
182// === impl MaybePlatformPath ===
183
184impl<D: XdgPath> MaybePlatformPath<D> {
185    /// Returns the path if it is set, otherwise returns the default path for the given chain.
186    pub fn unwrap_or_chain_default(&self, chain: Chain, datadir_args: DatadirArgs) -> ChainPath<D> {
187        ChainPath(
188            self.0
189                .clone()
190                .unwrap_or_else(|| PlatformPath::default().platform_path_from_chain(chain)),
191            chain,
192            datadir_args,
193        )
194    }
195
196    /// Returns the default platform path for the specified [Chain].
197    pub fn chain_default(chain: Chain) -> ChainPath<D> {
198        PlatformPath::default().with_chain(chain, DatadirArgs::default())
199    }
200
201    /// Returns true if a custom path is set
202    pub const fn is_some(&self) -> bool {
203        self.0.is_some()
204    }
205
206    /// Returns the path if it is set, otherwise returns `None`.
207    pub fn as_ref(&self) -> Option<&Path> {
208        self.0.as_ref().map(|p| p.as_ref())
209    }
210
211    /// Returns the path if it is set, otherwise returns the default path, without any chain
212    /// directory.
213    pub fn unwrap_or_default(&self) -> PlatformPath<D> {
214        self.0.clone().unwrap_or_default()
215    }
216}
217
218impl<D: XdgPath> Display for MaybePlatformPath<D> {
219    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
220        if let Some(path) = &self.0 {
221            path.fmt(f)
222        } else {
223            // NOTE: this is a workaround for making it work with clap's `default_value_t` which
224            // computes the default value via `Default -> Display -> FromStr`
225            write!(f, "default")
226        }
227    }
228}
229
230impl<D> Default for MaybePlatformPath<D> {
231    fn default() -> Self {
232        Self(None)
233    }
234}
235
236impl<D> FromStr for MaybePlatformPath<D> {
237    type Err = Infallible;
238
239    fn from_str(s: &str) -> Result<Self, Self::Err> {
240        let p = match s {
241            "default" => {
242                // NOTE: this is a workaround for making it work with clap's `default_value_t` which
243                // computes the default value via `Default -> Display -> FromStr`
244                None
245            }
246            _ => Some(PlatformPath::from_str(s)?),
247        };
248        Ok(Self(p))
249    }
250}
251
252impl<D> From<PathBuf> for MaybePlatformPath<D> {
253    fn from(path: PathBuf) -> Self {
254        Self(Some(PlatformPath(path, std::marker::PhantomData)))
255    }
256}
257
258/// Wrapper type around `PlatformPath` that includes a `Chain`, used for separating reth data for
259/// different networks.
260///
261/// If the chain is either mainnet, sepolia, or holesky, then the path will be:
262///  * mainnet: `<DIR>/mainnet`
263///  * sepolia: `<DIR>/sepolia`
264///  * holesky: `<DIR>/holesky`
265///
266/// Otherwise, the path will be dependent on the chain ID:
267///  * `<DIR>/<CHAIN_ID>`
268#[derive(Clone, Debug, PartialEq, Eq)]
269pub struct ChainPath<D>(PlatformPath<D>, Chain, DatadirArgs);
270
271impl<D> ChainPath<D> {
272    /// Returns a new `ChainPath` given a `PlatformPath` and a `Chain`.
273    pub const fn new(path: PlatformPath<D>, chain: Chain, datadir_args: DatadirArgs) -> Self {
274        Self(path, chain, datadir_args)
275    }
276
277    /// Returns the path to the reth data directory for this chain.
278    ///
279    /// `<DIR>/<CHAIN_ID>`
280    pub fn data_dir(&self) -> &Path {
281        self.0.as_ref()
282    }
283
284    /// Returns the path to the db directory for this chain.
285    ///
286    /// `<DIR>/<CHAIN_ID>/db`
287    pub fn db(&self) -> PathBuf {
288        self.data_dir().join("db")
289    }
290
291    /// Returns the path to the static files directory for this chain.
292    ///
293    /// `<DIR>/<CHAIN_ID>/static_files`
294    pub fn static_files(&self) -> PathBuf {
295        let datadir_args = &self.2;
296        if let Some(static_files_path) = &datadir_args.static_files_path {
297            static_files_path.clone()
298        } else {
299            self.data_dir().join("static_files")
300        }
301    }
302
303    /// Returns the path to the `RocksDB` database directory for this chain.
304    ///
305    /// `<DIR>/<CHAIN_ID>/rocksdb`
306    pub fn rocksdb(&self) -> PathBuf {
307        let datadir_args = &self.2;
308        if let Some(rocksdb_path) = &datadir_args.rocksdb_path {
309            rocksdb_path.clone()
310        } else {
311            self.data_dir().join("rocksdb")
312        }
313    }
314
315    /// Returns the path to the directory for storing pprof dumps for this chain.
316    ///
317    /// `<DIR>/<CHAIN_ID>/pprof`
318    pub fn pprof_dumps(&self) -> PathBuf {
319        let datadir_args = &self.2;
320        if let Some(pprof_dumps_path) = &datadir_args.pprof_dumps_path {
321            pprof_dumps_path.clone()
322        } else {
323            self.data_dir().join("pprof")
324        }
325    }
326
327    /// Returns the path to the reth p2p secret key for this chain.
328    ///
329    /// `<DIR>/<CHAIN_ID>/discovery-secret`
330    pub fn p2p_secret(&self) -> PathBuf {
331        self.data_dir().join("discovery-secret")
332    }
333
334    /// Returns the path to the known peers file for this chain.
335    ///
336    /// `<DIR>/<CHAIN_ID>/known-peers.json`
337    pub fn known_peers(&self) -> PathBuf {
338        self.data_dir().join("known-peers.json")
339    }
340
341    /// Returns the path to the blobstore directory for this chain where blobs of unfinalized
342    /// transactions are stored.
343    ///
344    /// `<DIR>/<CHAIN_ID>/blobstore`
345    pub fn blobstore(&self) -> PathBuf {
346        self.data_dir().join("blobstore")
347    }
348
349    /// Returns the path to the local transactions backup file
350    ///
351    /// `<DIR>/<CHAIN_ID>/txpool-transactions-backup.rlp`
352    pub fn txpool_transactions(&self) -> PathBuf {
353        self.data_dir().join("txpool-transactions-backup.rlp")
354    }
355
356    /// Returns the path to the config file for this chain.
357    ///
358    /// `<DIR>/<CHAIN_ID>/reth.toml`
359    pub fn config(&self) -> PathBuf {
360        self.data_dir().join("reth.toml")
361    }
362
363    /// Returns the path to the jwtsecret file for this chain.
364    ///
365    /// `<DIR>/<CHAIN_ID>/jwt.hex`
366    pub fn jwt(&self) -> PathBuf {
367        self.data_dir().join("jwt.hex")
368    }
369
370    /// Returns the path to the invalid block hooks directory for this chain.
371    ///
372    /// `<DIR>/<CHAIN_ID>/invalid_block_hooks`
373    pub fn invalid_block_hooks(&self) -> PathBuf {
374        self.data_dir().join("invalid_block_hooks")
375    }
376
377    /// Returns the path to the ExEx WAL directory for this chain.
378    pub fn exex_wal(&self) -> PathBuf {
379        self.data_dir().join("exex/wal")
380    }
381}
382
383impl<D> AsRef<Path> for ChainPath<D> {
384    fn as_ref(&self) -> &Path {
385        self.0.as_ref()
386    }
387}
388
389impl<D> Display for ChainPath<D> {
390    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
391        write!(f, "{}", self.0)
392    }
393}
394
395impl<D> From<ChainPath<D>> for PathBuf {
396    fn from(value: ChainPath<D>) -> Self {
397        value.0.into()
398    }
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404
405    #[test]
406    fn test_maybe_data_dir_path() {
407        let path = MaybePlatformPath::<DataDirPath>::default();
408        let path = path.unwrap_or_chain_default(Chain::mainnet(), DatadirArgs::default());
409        assert!(path.as_ref().ends_with("reth/mainnet"), "{path:?}");
410
411        let db_path = path.db();
412        assert!(db_path.ends_with("reth/mainnet/db"), "{db_path:?}");
413
414        let path = MaybePlatformPath::<DataDirPath>::from_str("my/path/to/datadir").unwrap();
415        let path = path.unwrap_or_chain_default(Chain::mainnet(), DatadirArgs::default());
416        assert!(path.as_ref().ends_with("my/path/to/datadir"), "{path:?}");
417    }
418
419    #[test]
420    fn test_maybe_testnet_datadir_path() {
421        let path = MaybePlatformPath::<DataDirPath>::default();
422        let path = path.unwrap_or_chain_default(Chain::holesky(), DatadirArgs::default());
423        assert!(path.as_ref().ends_with("reth/holesky"), "{path:?}");
424
425        let path = MaybePlatformPath::<DataDirPath>::default();
426        let path = path.unwrap_or_chain_default(Chain::sepolia(), DatadirArgs::default());
427        assert!(path.as_ref().ends_with("reth/sepolia"), "{path:?}");
428    }
429}