1use std::{
3 fmt,
4 fs::{read_to_string, File},
5 io,
6 path::Path,
7 str::FromStr,
8 time::Duration,
9};
10
11use arti_client::KeystoreSelector;
12use clap::Parser;
13use safelog::DisplayRedacted;
14use serde::{Deserialize, Serialize};
15
16use cuprate_consensus::ContextConfig;
17use cuprate_helper::{
18 fs::{CUPRATE_CONFIG_DIR, DEFAULT_CONFIG_FILE_NAME},
19 network::Network,
20};
21use cuprate_p2p::block_downloader::BlockDownloaderConfig;
22use cuprate_p2p_core::{ClearNet, Tor};
23use cuprate_wire::OnionAddr;
24
25use crate::{
26 constants::{DEFAULT_CONFIG_STARTUP_DELAY, DEFAULT_CONFIG_WARNING},
27 logging::eprintln_red,
28 tor::{TorContext, TorMode},
29};
30
31mod args;
32mod default;
33mod fs;
34mod p2p;
35mod rayon;
36mod rpc;
37mod storage;
38mod tokio;
39mod tor;
40mod tracing_config;
41
42#[macro_use]
43mod macros;
44
45use fs::FileSystemConfig;
46pub use p2p::{p2p_port, P2PConfig};
47use rayon::RayonConfig;
48pub use rpc::{restricted_rpc_port, unrestricted_rpc_port, RpcConfig};
49pub use storage::{StorageConfig, TxpoolConfig};
50use tokio::TokioConfig;
51use tor::TorConfig;
52use tracing_config::TracingConfig;
53
54const HEADER: &str = r"## ____ _
56## / ___| _ _ __ _ __ __ _| |_ ___
57## | | | | | | '_ \| '__/ _` | __/ _ \
58## | |__| |_| | |_) | | | (_| | || __/
59## \____\__,_| .__/|_| \__,_|\__\___|
60## |_|
61##
62## All these config values can be set to
63## their default by commenting them out with '#'.
64##
65## Some values are already commented out,
66## to set the value remove the '#' at the start of the line.
67##
68## For more documentation, see: <https://user.cuprate.org>.
69
70";
71
72pub fn read_config_and_args() -> Config {
74 let args = args::Args::parse();
75 args.do_quick_requests();
76
77 let config: Config = if let Some(config_file) = &args.config_file {
78 match Config::read_from_path(config_file) {
80 Ok(config) => config,
81 Err(e) => {
82 eprintln_red(&format!("Failed to read config from file: {e}"));
83 std::process::exit(1);
84 }
85 }
86 } else {
87 std::env::current_dir()
89 .map(|path| path.join(DEFAULT_CONFIG_FILE_NAME))
90 .map_err(Into::into)
91 .and_then(Config::read_from_path)
92 .inspect_err(|e| tracing::debug!("Failed to read config from current dir: {e}"))
93 .or_else(|_| {
95 let file = CUPRATE_CONFIG_DIR.join(DEFAULT_CONFIG_FILE_NAME);
96 Config::read_from_path(file)
97 })
98 .inspect_err(|e| {
99 tracing::debug!("Failed to read config from config dir: {e}");
100 if !args.skip_config_warning {
101 eprintln_red(DEFAULT_CONFIG_WARNING);
102 std::thread::sleep(DEFAULT_CONFIG_STARTUP_DELAY);
103 }
104 })
105 .unwrap_or_default()
106 };
107
108 args.apply_args(config)
109}
110
111config_struct! {
112 #[derive(Debug, Deserialize, Serialize, PartialEq)]
114 #[serde(deny_unknown_fields, default)]
115 pub struct Config {
116 pub network: Network,
120
121 pub fast_sync: bool,
131
132 #[child = true]
133 pub tracing: TracingConfig,
137
138 #[child = true]
139 pub tokio: TokioConfig,
143
144 #[child = true]
145 pub rayon: RayonConfig,
149
150 #[child = true]
151 pub p2p: P2PConfig,
153
154 #[child = true]
155 pub tor: TorConfig,
157
158 #[child = true]
159 pub rpc: RpcConfig,
161
162 #[child = true]
163 pub storage: StorageConfig,
165
166 #[child = true]
167 pub fs: FileSystemConfig,
169 }
170}
171
172impl Default for Config {
173 fn default() -> Self {
174 Self {
175 network: Default::default(),
176 fast_sync: true,
177 tracing: Default::default(),
178 tokio: Default::default(),
179 tor: Default::default(),
180 rayon: Default::default(),
181 p2p: Default::default(),
182 rpc: Default::default(),
183 storage: Default::default(),
184 fs: Default::default(),
185 }
186 }
187}
188
189impl Config {
190 pub fn documented_config() -> String {
192 let str = toml::ser::to_string_pretty(&Self::default()).unwrap();
193 let mut doc = toml_edit::DocumentMut::from_str(&str).unwrap();
194 Self::write_docs(doc.as_table_mut());
195 format!("{HEADER}{doc}")
196 }
197
198 fn read_from_path(file: impl AsRef<Path>) -> Result<Self, anyhow::Error> {
204 let file_text = read_to_string(file.as_ref())?;
205
206 Ok(toml::from_str(&file_text)
207 .inspect(|_| println!("Using config at: {}", file.as_ref().to_string_lossy()))
208 .inspect_err(|e| {
209 eprintln_red(&format!(
210 "Failed to parse config file at: {}",
211 file.as_ref().to_string_lossy()
212 ));
213 eprintln_red(&format!("{e}"));
214 std::process::exit(1);
215 })?)
216 }
217
218 pub const fn network(&self) -> Network {
220 self.network
221 }
222
223 pub fn clearnet_p2p_config(&self) -> cuprate_p2p::P2PConfig<ClearNet> {
225 cuprate_p2p::P2PConfig {
226 network: self.network,
227 seeds: p2p::clear_net_seed_nodes(self.network),
228 outbound_connections: self.p2p.clear_net.outbound_connections,
229 extra_outbound_connections: self.p2p.clear_net.extra_outbound_connections,
230 max_inbound_connections: self.p2p.clear_net.max_inbound_connections,
231 gray_peers_percent: self.p2p.clear_net.gray_peers_percent,
232 p2p_port: p2p_port(self.p2p.clear_net.p2p_port, self.network),
233 rpc_port: self.rpc.restricted.port_for_p2p(self.network),
234 address_book_config: self.p2p.clear_net.address_book_config.address_book_config(
235 &self.fs.cache_directory,
236 self.network,
237 None,
238 ),
239 }
240 }
241
242 pub fn tor_p2p_config(&self, ctx: &TorContext) -> cuprate_p2p::P2PConfig<Tor> {
244 let inbound_enabled = self.p2p.tor_net.inbound_onion;
245
246 let tor_p2p_port = p2p_port(self.p2p.tor_net.p2p_port, self.network);
247
248 let our_onion_address = match ctx.mode {
249 TorMode::Off => None,
250 TorMode::Daemon => inbound_enabled.then(||
251 OnionAddr::new(
252 &self.tor.daemon.anonymous_inbound,
253 tor_p2p_port
254 ).expect("Unable to parse supplied `anonymous_inbound` onion address. Please make sure the address is correct.")),
255 TorMode::Arti => inbound_enabled.then(|| {
256 let addr = ctx.arti_onion_service
257 .as_ref()
258 .unwrap()
259 .generate_identity_key(KeystoreSelector::Primary)
260 .unwrap()
261 .display_unredacted()
262 .to_string();
263
264 OnionAddr::new(&addr, tor_p2p_port).unwrap()
265 })
266 };
267
268 cuprate_p2p::P2PConfig {
269 network: self.network,
270 seeds: p2p::tor_net_seed_nodes(self.network),
271 outbound_connections: self.p2p.tor_net.outbound_connections,
272 extra_outbound_connections: self.p2p.tor_net.extra_outbound_connections,
273 max_inbound_connections: self.p2p.tor_net.max_inbound_connections,
274 gray_peers_percent: self.p2p.tor_net.gray_peers_percent,
275 p2p_port: tor_p2p_port,
276 rpc_port: 0,
277 address_book_config: self.p2p.tor_net.address_book_config.address_book_config(
278 &self.fs.cache_directory,
279 self.network,
280 our_onion_address,
281 ),
282 }
283 }
284
285 pub const fn context_config(&self) -> ContextConfig {
287 match self.network {
288 Network::Mainnet => ContextConfig::main_net(),
289 Network::Stagenet => ContextConfig::stage_net(),
290 Network::Testnet => ContextConfig::test_net(),
291 }
292 }
293
294 pub fn blockchain_config(&self) -> cuprate_blockchain::config::Config {
296 let blockchain = &self.storage.blockchain;
297
298 cuprate_blockchain::config::ConfigBuilder::default()
300 .network(self.network)
301 .data_directory(self.fs.data_directory.clone())
302 .sync_mode(blockchain.sync_mode)
303 .build()
304 }
305
306 pub fn txpool_config(&self) -> cuprate_txpool::config::Config {
308 let txpool = &self.storage.txpool;
309
310 cuprate_txpool::config::ConfigBuilder::default()
312 .network(self.network)
313 .data_directory(self.fs.data_directory.clone())
314 .sync_mode(txpool.sync_mode)
315 .build()
316 }
317
318 pub fn block_downloader_config(&self) -> BlockDownloaderConfig {
320 self.p2p.block_downloader.clone().into()
321 }
322}
323
324impl fmt::Display for Config {
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 writeln!(
327 f,
328 "========== CONFIGURATION ==========\n{self:#?}\n==================================="
329 )
330 }
331}
332
333#[cfg(test)]
334mod test {
335 use pretty_assertions::assert_eq;
336 use toml::from_str;
337
338 use super::*;
339
340 #[test]
341 fn documented_config() {
342 let str = Config::documented_config();
343 let conf: Config = from_str(&str).unwrap();
344
345 assert_eq!(conf, Config::default());
346 }
347}