1pub 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
19pub const FOREST_DATA_DIR_ENV: &str = "FOREST_PATH";
23
24pub fn chain_path(config: &Config) -> PathBuf {
26 PathBuf::from(&config.client.data_dir).join(config.chain().to_string())
27}
28
29pub 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 let toml = std::fs::read_to_string(path.to_path_buf())?;
49 (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 if let Some(data_dir) = data_dir_from_env() {
60 config.client.data_dir = data_dir;
61 }
62 Ok((path, config))
63}
64
65fn 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
74pub fn default_data_dir() -> PathBuf {
80 data_dir_from_env().unwrap_or_else(|| crate::cli_shared::cli::Client::default().data_dir)
81}
82
83pub 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 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 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 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 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 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;