Skip to main content

forest/cli_shared/
mod.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4pub mod cli;
5pub mod logger;
6
7use crate::cli_shared::cli::{Config, ConfigPath, find_config_path};
8use crate::networks::NetworkChain;
9use crate::utils::io::read_toml;
10use std::path::PathBuf;
11
12cfg_if::cfg_if! {
13    if #[cfg(feature = "rustalloc")] {
14    } else if #[cfg(feature = "jemalloc")] {
15        pub use tikv_jemallocator;
16    }
17}
18
19/// Environment variable that overrides the Forest data directory, taking
20/// precedence over both the configuration file and the built-in default. Named
21/// after Lotus' `LOTUS_PATH` to ease switching between implementations.
22pub const FOREST_DATA_DIR_ENV: &str = "FOREST_PATH";
23
24/// Gets chain data directory
25pub fn chain_path(config: &Config) -> PathBuf {
26    PathBuf::from(&config.client.data_dir).join(config.chain().to_string())
27}
28
29/// Deletes the chain database and F3 data for the configured chain.
30pub fn delete_chain_data(config: &Config) -> anyhow::Result<()> {
31    for dir in [chain_path(config), crate::f3::get_f3_root(config)] {
32        if dir.is_dir() {
33            tracing::info!("Removing {}", dir.display());
34            fs_extra::dir::remove(&dir)
35                .map_err(|e| anyhow::anyhow!("failed to remove {}: {e}", dir.display()))?;
36        }
37    }
38    Ok(())
39}
40
41pub fn read_config(
42    config_path_opt: Option<&PathBuf>,
43    chain_opt: Option<NetworkChain>,
44) -> anyhow::Result<(Option<ConfigPath>, Config)> {
45    let (path, mut config) = match find_config_path(config_path_opt) {
46        Some(path) => {
47            // Read from config file
48            let toml = std::fs::read_to_string(path.to_path_buf())?;
49            // Parse and return the configuration file
50            (Some(path), read_toml(&toml)?)
51        }
52        None => (None, Config::default()),
53    };
54    if let Some(chain) = chain_opt {
55        config.chain = chain;
56    }
57    // The `FOREST_PATH` environment variable takes precedence over the data
58    // directory set in the configuration file (or the default one).
59    if let Some(data_dir) = data_dir_from_env() {
60        config.client.data_dir = data_dir;
61    }
62    Ok((path, config))
63}
64
65/// Returns the data directory set via the [`FOREST_DATA_DIR_ENV`] environment
66/// variable, if it is present and non-empty.
67fn data_dir_from_env() -> Option<PathBuf> {
68    match std::env::var(FOREST_DATA_DIR_ENV) {
69        Ok(s) if !s.trim().is_empty() => Some(PathBuf::from(s)),
70        _ => None,
71    }
72}
73
74/// Returns the effective Forest data directory: the [`FOREST_DATA_DIR_ENV`]
75/// environment variable if set, otherwise the built-in default. Unlike
76/// [`read_config`], this does not consult a configuration file and is meant for
77/// contexts (e.g. the RPC client) that need the data directory without loading
78/// the full configuration.
79pub fn default_data_dir() -> PathBuf {
80    data_dir_from_env().unwrap_or_else(|| crate::cli_shared::cli::Client::default().data_dir)
81}
82
83/// Returns the path to the RPC admin token within the effective data directory
84/// (see [`default_data_dir`]). This is where a daemon started with the same
85/// environment saves the token, so clients can read it back from here.
86pub fn default_token_path() -> PathBuf {
87    default_data_dir().join(crate::cli_shared::cli::Client::RPC_TOKEN_FILENAME)
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn delete_chain_data_removes_chain_and_f3_but_keeps_keystore() {
96        let tmp = tempfile::TempDir::new().unwrap();
97
98        let mut config = Config::default();
99        config.client.data_dir = tmp.path().to_path_buf();
100        config.chain = NetworkChain::Calibnet;
101
102        // Lay out a representative data directory: a versioned database, F3
103        // sidecar data, and a wallet keystore.
104        let chain_dir = chain_path(&config);
105        let f3_dir = crate::f3::get_f3_root(&config);
106        let keystore = config.client.data_dir.join(crate::KEYSTORE_NAME);
107        std::fs::create_dir_all(chain_dir.join("0.1.0")).unwrap();
108        std::fs::create_dir_all(&f3_dir).unwrap();
109        std::fs::write(&keystore, b"wallet-keys").unwrap();
110
111        delete_chain_data(&config).unwrap();
112
113        assert!(!chain_dir.exists(), "chain database should be deleted");
114        assert!(!f3_dir.exists(), "F3 data should be deleted");
115        assert!(keystore.exists(), "keystore must be preserved");
116    }
117
118    #[test]
119    fn read_config_default() {
120        let (config_path, config) = read_config(None, None).unwrap();
121
122        assert!(config_path.is_none());
123        assert_eq!(config.chain(), &NetworkChain::Mainnet);
124    }
125
126    #[test]
127    fn read_config_calibnet_override() {
128        let (config_path, config) = read_config(None, Some(NetworkChain::Calibnet)).unwrap();
129
130        assert!(config_path.is_none());
131        assert_eq!(config.chain(), &NetworkChain::Calibnet);
132    }
133
134    #[test]
135    fn read_config_butterflynet_override() {
136        let (config_path, config) = read_config(None, Some(NetworkChain::Butterflynet)).unwrap();
137
138        assert!(config_path.is_none());
139        assert_eq!(config.chain(), &NetworkChain::Butterflynet);
140    }
141
142    /// Runs `f` with [`FOREST_DATA_DIR_ENV`] set to `value`, restoring the
143    /// environment afterwards.
144    fn with_data_dir_env<T>(value: &str, f: impl FnOnce() -> T) -> T {
145        unsafe { std::env::set_var(FOREST_DATA_DIR_ENV, value) };
146        let result = f();
147        unsafe { std::env::remove_var(FOREST_DATA_DIR_ENV) };
148        result
149    }
150
151    #[test]
152    #[serial_test::serial]
153    fn read_config_data_dir_env_override() {
154        let data_dir = "/tmp/forest-path-env-override-test";
155        let (_, config) = with_data_dir_env(data_dir, || read_config(None, None).unwrap());
156
157        // The env variable takes precedence over the default data directory.
158        assert_eq!(config.client.data_dir, std::path::Path::new(data_dir));
159    }
160
161    #[test]
162    #[serial_test::serial]
163    fn default_data_dir_honors_env_override() {
164        let data_dir = "/tmp/forest-path-default-data-dir-test";
165        let resolved = with_data_dir_env(data_dir, default_data_dir);
166        assert_eq!(resolved, std::path::Path::new(data_dir));
167
168        // Without the env variable, it falls back to the default data directory.
169        assert_eq!(default_data_dir(), Config::default().client.data_dir);
170    }
171
172    #[test]
173    #[serial_test::serial]
174    fn read_config_data_dir_env_empty_is_ignored() {
175        let (_, config) = with_data_dir_env("", || read_config(None, None).unwrap());
176
177        // An empty env variable falls back to the default data directory.
178        assert_eq!(config.client.data_dir, Config::default().client.data_dir);
179    }
180
181    #[test]
182    #[serial_test::serial]
183    fn read_config_with_path() {
184        let default_config = Config::default();
185        let temp_dir = tempfile::tempdir().expect("couldn't create temp dir");
186        let config_file = temp_dir.path().join("config.toml");
187        let serialized_config = toml::to_string(&default_config).unwrap();
188        std::fs::write(&config_file, serialized_config).unwrap();
189
190        let (config_path, config) = read_config(Some(&config_file), None).unwrap();
191
192        assert_eq!(config_path.unwrap(), ConfigPath::Cli(config_file));
193        assert_eq!(config.chain(), &NetworkChain::Mainnet);
194        assert_eq!(config, default_config);
195    }
196}
197
198pub mod snapshot;