1use std::{
3 fs::{read_to_string, File},
4 io,
5 path::Path,
6 time::Duration,
7};
8
9use clap::Parser;
10use serde::{Deserialize, Serialize};
11
12use cuprate_consensus::ContextConfig;
13use cuprate_helper::{
14 fs::{CUPRATE_CONFIG_DIR, DEFAULT_CONFIG_FILE_NAME},
15 network::Network,
16};
17use cuprate_p2p::block_downloader::BlockDownloaderConfig;
18use cuprate_p2p_core::{ClearNet, ClearNetServerCfg};
19
20use crate::{
21 constants::{DEFAULT_CONFIG_STARTUP_DELAY, DEFAULT_CONFIG_WARNING},
22 logging::eprintln_red,
23};
24
25mod args;
26mod fs;
27mod p2p;
28mod rayon;
29mod storage;
30mod tokio;
31mod tracing_config;
32
33use fs::FileSystemConfig;
34use p2p::P2PConfig;
35use rayon::RayonConfig;
36use storage::StorageConfig;
37use tokio::TokioConfig;
38use tracing_config::TracingConfig;
39
40pub fn read_config_and_args() -> Config {
42 let args = args::Args::parse();
43 args.do_quick_requests();
44
45 let config: Config = if let Some(config_file) = &args.config_file {
46 match Config::read_from_path(config_file) {
48 Ok(config) => config,
49 Err(e) => {
50 eprintln_red(&format!("Failed to read config from file: {e}"));
51 std::process::exit(1);
52 }
53 }
54 } else {
55 std::env::current_dir()
57 .map(|path| path.join(DEFAULT_CONFIG_FILE_NAME))
58 .map_err(Into::into)
59 .and_then(Config::read_from_path)
60 .inspect_err(|e| tracing::debug!("Failed to read config from current dir: {e}"))
61 .or_else(|_| {
63 let file = CUPRATE_CONFIG_DIR.join(DEFAULT_CONFIG_FILE_NAME);
64 Config::read_from_path(file)
65 })
66 .inspect_err(|e| {
67 tracing::debug!("Failed to read config from config dir: {e}");
68 if !args.skip_config_warning {
69 eprintln_red(DEFAULT_CONFIG_WARNING);
70 std::thread::sleep(DEFAULT_CONFIG_STARTUP_DELAY);
71 }
72 })
73 .unwrap_or_default()
74 };
75
76 args.apply_args(config)
77}
78
79#[derive(Debug, Default, Deserialize, Serialize, PartialEq)]
81#[serde(deny_unknown_fields, default)]
82pub struct Config {
83 network: Network,
85
86 pub no_fast_sync: bool,
87
88 pub tracing: TracingConfig,
90
91 pub tokio: TokioConfig,
92
93 pub rayon: RayonConfig,
94
95 p2p: P2PConfig,
97
98 pub storage: StorageConfig,
100
101 pub fs: FileSystemConfig,
102}
103
104impl Config {
105 fn read_from_path(file: impl AsRef<Path>) -> Result<Self, anyhow::Error> {
111 let file_text = read_to_string(file.as_ref())?;
112
113 Ok(toml::from_str(&file_text)
114 .inspect(|_| println!("Using config at: {}", file.as_ref().to_string_lossy()))
115 .inspect_err(|e| {
116 eprintln_red(&format!(
117 "Failed to parse config file at: {}",
118 file.as_ref().to_string_lossy()
119 ));
120 eprintln_red(&format!("{e}"));
121 std::process::exit(1);
122 })?)
123 }
124
125 pub const fn network(&self) -> Network {
127 self.network
128 }
129
130 pub fn clearnet_p2p_config(&self) -> cuprate_p2p::P2PConfig<ClearNet> {
132 cuprate_p2p::P2PConfig {
133 network: self.network,
134 seeds: p2p::clear_net_seed_nodes(self.network),
135 outbound_connections: self.p2p.clear_net.general.outbound_connections,
136 extra_outbound_connections: self.p2p.clear_net.general.extra_outbound_connections,
137 max_inbound_connections: self.p2p.clear_net.general.max_inbound_connections,
138 gray_peers_percent: self.p2p.clear_net.general.gray_peers_percent,
139 server_config: Some(ClearNetServerCfg {
140 ip: self.p2p.clear_net.listen_on,
141 }),
142 p2p_port: self.p2p.clear_net.general.p2p_port,
143 rpc_port: 0,
145 address_book_config: self
146 .p2p
147 .clear_net
148 .general
149 .address_book_config(&self.fs.cache_directory, self.network),
150 }
151 }
152
153 pub const fn context_config(&self) -> ContextConfig {
155 match self.network {
156 Network::Mainnet => ContextConfig::main_net(),
157 Network::Stagenet => ContextConfig::stage_net(),
158 Network::Testnet => ContextConfig::test_net(),
159 }
160 }
161
162 pub fn blockchain_config(&self) -> cuprate_blockchain::config::Config {
164 let blockchain = &self.storage.blockchain;
165
166 cuprate_blockchain::config::ConfigBuilder::default()
168 .network(self.network)
169 .data_directory(self.fs.data_directory.clone())
170 .sync_mode(blockchain.shared.sync_mode)
171 .build()
172 }
173
174 pub fn txpool_config(&self) -> cuprate_txpool::config::Config {
176 let txpool = &self.storage.txpool;
177
178 cuprate_txpool::config::ConfigBuilder::default()
180 .network(self.network)
181 .data_directory(self.fs.data_directory.clone())
182 .sync_mode(txpool.shared.sync_mode)
183 .build()
184 }
185
186 pub fn block_downloader_config(&self) -> BlockDownloaderConfig {
188 self.p2p.block_downloader.clone().into()
189 }
190}
191
192#[cfg(test)]
193mod test {
194 use toml::from_str;
195
196 use crate::constants::EXAMPLE_CONFIG;
197
198 use super::*;
199
200 #[test]
202 fn config_latest() {
203 let config: Config = from_str(EXAMPLE_CONFIG).unwrap();
204 assert_eq!(config, Config::default());
205 }
206
207 #[test]
209 fn config_backwards_compat() {
210 #[expect(
212 clippy::single_element_loop,
213 reason = "Remove after adding other versions"
214 )]
215 for version in ["0.0.1"] {
216 let path = format!("config/{version}.toml");
217 println!("Testing config serde backwards compat: {path}");
218 let string = read_to_string(path).unwrap();
219 from_str::<Config>(&string).unwrap();
220 }
221 }
222}