reth_node_core/
dirs.rs

1//! reth data directories.
2
3use crate::{args::DatadirArgs, utils::parse_path};
4use reth_chainspec::Chain;
5use std::{
6    env::VarError,
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 supports shell expansions and common patterns such as `~` for the
90/// home directory.
91///
92/// # Example
93///
94/// ```
95/// use reth_node_core::dirs::{DataDirPath, PlatformPath};
96/// use std::str::FromStr;
97///
98/// // Resolves to the platform-specific database path
99/// let default: PlatformPath<DataDirPath> = PlatformPath::default();
100/// // Resolves to `$(pwd)/my/path/to/datadir`
101/// let custom: PlatformPath<DataDirPath> = PlatformPath::from_str("my/path/to/datadir").unwrap();
102///
103/// assert_ne!(default.as_ref(), custom.as_ref());
104/// ```
105#[derive(Debug, PartialEq, Eq)]
106pub struct PlatformPath<D>(PathBuf, std::marker::PhantomData<D>);
107
108impl<D> Display for PlatformPath<D> {
109    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
110        write!(f, "{}", self.0.display())
111    }
112}
113
114impl<D> Clone for PlatformPath<D> {
115    fn clone(&self) -> Self {
116        Self(self.0.clone(), std::marker::PhantomData)
117    }
118}
119
120impl<D: XdgPath> Default for PlatformPath<D> {
121    fn default() -> Self {
122        Self(
123            D::resolve().expect("Could not resolve default path. Set one manually."),
124            std::marker::PhantomData,
125        )
126    }
127}
128
129impl<D> FromStr for PlatformPath<D> {
130    type Err = shellexpand::LookupError<VarError>;
131
132    fn from_str(s: &str) -> Result<Self, Self::Err> {
133        Ok(Self(parse_path(s)?, std::marker::PhantomData))
134    }
135}
136
137impl<D> AsRef<Path> for PlatformPath<D> {
138    fn as_ref(&self) -> &Path {
139        self.0.as_path()
140    }
141}
142
143impl<D> From<PlatformPath<D>> for PathBuf {
144    fn from(value: PlatformPath<D>) -> Self {
145        value.0
146    }
147}
148
149impl<D> PlatformPath<D> {
150    /// Returns the path joined with another path
151    pub fn join<P: AsRef<Path>>(&self, path: P) -> Self {
152        Self(self.0.join(path), std::marker::PhantomData)
153    }
154}
155
156impl<D> PlatformPath<D> {
157    /// Converts the path to a `ChainPath` with the given `Chain`.
158    pub fn with_chain(&self, chain: Chain, datadir_args: DatadirArgs) -> ChainPath<D> {
159        // extract chain name
160        let platform_path = self.platform_path_from_chain(chain);
161
162        ChainPath::new(platform_path, chain, datadir_args)
163    }
164
165    fn platform_path_from_chain(&self, chain: Chain) -> Self {
166        let chain_name = config_path_prefix(chain);
167        let path = self.0.join(chain_name);
168        Self(path, std::marker::PhantomData)
169    }
170
171    /// Map the inner path to a new type `T`.
172    pub fn map_to<T>(&self) -> PlatformPath<T> {
173        PlatformPath(self.0.clone(), std::marker::PhantomData)
174    }
175}
176
177/// An Optional wrapper type around [`PlatformPath`].
178///
179/// This is useful for when a path is optional, such as the `--data-dir` flag.
180#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct MaybePlatformPath<D>(Option<PlatformPath<D>>);
182
183// === impl MaybePlatformPath ===
184
185impl<D: XdgPath> MaybePlatformPath<D> {
186    /// Returns the path if it is set, otherwise returns the default path for the given chain.
187    pub fn unwrap_or_chain_default(&self, chain: Chain, datadir_args: DatadirArgs) -> ChainPath<D> {
188        ChainPath(
189            self.0
190                .clone()
191                .unwrap_or_else(|| PlatformPath::default().platform_path_from_chain(chain)),
192            chain,
193            datadir_args,
194        )
195    }
196
197    /// Returns the default platform path for the specified [Chain].
198    pub fn chain_default(chain: Chain) -> ChainPath<D> {
199        PlatformPath::default().with_chain(chain, DatadirArgs::default())
200    }
201
202    /// Returns true if a custom path is set
203    pub const fn is_some(&self) -> bool {
204        self.0.is_some()
205    }
206
207    /// Returns the path if it is set, otherwise returns `None`.
208    pub fn as_ref(&self) -> Option<&Path> {
209        self.0.as_ref().map(|p| p.as_ref())
210    }
211
212    /// Returns the path if it is set, otherwise returns the default path, without any chain
213    /// directory.
214    pub fn unwrap_or_default(&self) -> PlatformPath<D> {
215        self.0.clone().unwrap_or_default()
216    }
217}
218
219impl<D: XdgPath> Display for MaybePlatformPath<D> {
220    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
221        if let Some(path) = &self.0 {
222            path.fmt(f)
223        } else {
224            // NOTE: this is a workaround for making it work with clap's `default_value_t` which
225            // computes the default value via `Default -> Display -> FromStr`
226            write!(f, "default")
227        }
228    }
229}
230
231impl<D> Default for MaybePlatformPath<D> {
232    fn default() -> Self {
233        Self(None)
234    }
235}
236
237impl<D> FromStr for MaybePlatformPath<D> {
238    type Err = shellexpand::LookupError<VarError>;
239
240    fn from_str(s: &str) -> Result<Self, Self::Err> {
241        let p = match s {
242            "default" => {
243                // NOTE: this is a workaround for making it work with clap's `default_value_t` which
244                // computes the default value via `Default -> Display -> FromStr`
245                None
246            }
247            _ => Some(PlatformPath::from_str(s)?),
248        };
249        Ok(Self(p))
250    }
251}
252
253impl<D> From<PathBuf> for MaybePlatformPath<D> {
254    fn from(path: PathBuf) -> Self {
255        Self(Some(PlatformPath(path, std::marker::PhantomData)))
256    }
257}
258
259/// Wrapper type around `PlatformPath` that includes a `Chain`, used for separating reth data for
260/// different networks.
261///
262/// If the chain is either mainnet, sepolia, or holesky, then the path will be:
263///  * mainnet: `<DIR>/mainnet`
264///  * sepolia: `<DIR>/sepolia`
265///  * holesky: `<DIR>/holesky`
266///
267/// Otherwise, the path will be dependent on the chain ID:
268///  * `<DIR>/<CHAIN_ID>`
269#[derive(Clone, Debug, PartialEq, Eq)]
270pub struct ChainPath<D>(PlatformPath<D>, Chain, DatadirArgs);
271
272impl<D> ChainPath<D> {
273    /// Returns a new `ChainPath` given a `PlatformPath` and a `Chain`.
274    pub const fn new(path: PlatformPath<D>, chain: Chain, datadir_args: DatadirArgs) -> Self {
275        Self(path, chain, datadir_args)
276    }
277
278    /// Returns the path to the reth data directory for this chain.
279    ///
280    /// `<DIR>/<CHAIN_ID>`
281    pub fn data_dir(&self) -> &Path {
282        self.0.as_ref()
283    }
284
285    /// Returns the path to the db directory for this chain.
286    ///
287    /// `<DIR>/<CHAIN_ID>/db`
288    pub fn db(&self) -> PathBuf {
289        self.data_dir().join("db")
290    }
291
292    /// Returns the path to the static files directory for this chain.
293    ///
294    /// `<DIR>/<CHAIN_ID>/static_files`
295    pub fn static_files(&self) -> PathBuf {
296        let datadir_args = &self.2;
297        if let Some(static_files_path) = &datadir_args.static_files_path {
298            static_files_path.clone()
299        } else {
300            self.data_dir().join("static_files")
301        }
302    }
303
304    /// Returns the path to the reth p2p secret key for this chain.
305    ///
306    /// `<DIR>/<CHAIN_ID>/discovery-secret`
307    pub fn p2p_secret(&self) -> PathBuf {
308        self.data_dir().join("discovery-secret")
309    }
310
311    /// Returns the path to the known peers file for this chain.
312    ///
313    /// `<DIR>/<CHAIN_ID>/known-peers.json`
314    pub fn known_peers(&self) -> PathBuf {
315        self.data_dir().join("known-peers.json")
316    }
317
318    /// Returns the path to the blobstore directory for this chain where blobs of unfinalized
319    /// transactions are stored.
320    ///
321    /// `<DIR>/<CHAIN_ID>/blobstore`
322    pub fn blobstore(&self) -> PathBuf {
323        self.data_dir().join("blobstore")
324    }
325
326    /// Returns the path to the local transactions backup file
327    ///
328    /// `<DIR>/<CHAIN_ID>/txpool-transactions-backup.rlp`
329    pub fn txpool_transactions(&self) -> PathBuf {
330        self.data_dir().join("txpool-transactions-backup.rlp")
331    }
332
333    /// Returns the path to the config file for this chain.
334    ///
335    /// `<DIR>/<CHAIN_ID>/reth.toml`
336    pub fn config(&self) -> PathBuf {
337        self.data_dir().join("reth.toml")
338    }
339
340    /// Returns the path to the jwtsecret file for this chain.
341    ///
342    /// `<DIR>/<CHAIN_ID>/jwt.hex`
343    pub fn jwt(&self) -> PathBuf {
344        self.data_dir().join("jwt.hex")
345    }
346
347    /// Returns the path to the invalid block hooks directory for this chain.
348    ///
349    /// `<DIR>/<CHAIN_ID>/invalid_block_hooks`
350    pub fn invalid_block_hooks(&self) -> PathBuf {
351        self.data_dir().join("invalid_block_hooks")
352    }
353
354    /// Returns the path to the ExEx WAL directory for this chain.
355    pub fn exex_wal(&self) -> PathBuf {
356        self.data_dir().join("exex/wal")
357    }
358}
359
360impl<D> AsRef<Path> for ChainPath<D> {
361    fn as_ref(&self) -> &Path {
362        self.0.as_ref()
363    }
364}
365
366impl<D> Display for ChainPath<D> {
367    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
368        write!(f, "{}", self.0)
369    }
370}
371
372impl<D> From<ChainPath<D>> for PathBuf {
373    fn from(value: ChainPath<D>) -> Self {
374        value.0.into()
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[test]
383    fn test_maybe_data_dir_path() {
384        let path = MaybePlatformPath::<DataDirPath>::default();
385        let path = path.unwrap_or_chain_default(Chain::mainnet(), DatadirArgs::default());
386        assert!(path.as_ref().ends_with("reth/mainnet"), "{path:?}");
387
388        let db_path = path.db();
389        assert!(db_path.ends_with("reth/mainnet/db"), "{db_path:?}");
390
391        let path = MaybePlatformPath::<DataDirPath>::from_str("my/path/to/datadir").unwrap();
392        let path = path.unwrap_or_chain_default(Chain::mainnet(), DatadirArgs::default());
393        assert!(path.as_ref().ends_with("my/path/to/datadir"), "{path:?}");
394    }
395
396    #[test]
397    fn test_maybe_testnet_datadir_path() {
398        let path = MaybePlatformPath::<DataDirPath>::default();
399        let path = path.unwrap_or_chain_default(Chain::holesky(), DatadirArgs::default());
400        assert!(path.as_ref().ends_with("reth/holesky"), "{path:?}");
401
402        let path = MaybePlatformPath::<DataDirPath>::default();
403        let path = path.unwrap_or_chain_default(Chain::sepolia(), DatadirArgs::default());
404        assert!(path.as_ref().ends_with("reth/sepolia"), "{path:?}");
405    }
406}