cuprated/
config.rs

1//! cuprated config
2use 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
54/// Header to put at the start of the generated config file.
55const 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
72/// Reads the args & config file, returning a [`Config`].
73pub 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        // If a config file was set in the args try to read it and exit if we can't.
79        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        // First attempt to read the config file from the current directory.
88        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            // otherwise try the main config directory.
94            .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    /// The config for all of Cuprate.
113    #[derive(Debug, Deserialize, Serialize, PartialEq)]
114    #[serde(deny_unknown_fields, default)]
115    pub struct Config {
116        /// The network cuprated should run on.
117        ///
118        /// Valid values | "Mainnet", "Testnet", "Stagenet"
119        pub network: Network,
120
121        /// Enable/disable fast sync.
122        ///
123        /// Fast sync skips verification of old blocks by
124        /// comparing block hashes to a built-in hash file,
125        /// disabling this will significantly increase sync time.
126        /// New blocks are still fully validated.
127        ///
128        /// Type         | boolean
129        /// Valid values | true, false
130        pub fast_sync: bool,
131
132        #[child = true]
133        /// Configuration for cuprated's logging system, tracing.
134        ///
135        /// Tracing is used for logging to stdout and files.
136        pub tracing: TracingConfig,
137
138        #[child = true]
139        /// Configuration for cuprated's asynchronous runtime system, tokio.
140        ///
141        /// Tokio is used for network operations and the major services inside `cuprated`.
142        pub tokio: TokioConfig,
143
144        #[child = true]
145        /// Configuration for cuprated's thread-pool system, rayon.
146        ///
147        /// Rayon is used for CPU intensive tasks.
148        pub rayon: RayonConfig,
149
150        #[child = true]
151        /// Configuration for cuprated's P2P system.
152        pub p2p: P2PConfig,
153
154        #[child = true]
155        /// Configuration for cuprated's Tor component
156        pub tor: TorConfig,
157
158        #[child = true]
159        /// Configuration for cuprated's RPC system.
160        pub rpc: RpcConfig,
161
162        #[child = true]
163        /// Configuration for persistent data storage.
164        pub storage: StorageConfig,
165
166        #[child = true]
167        /// Configuration for the file-system.
168        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    /// Returns a default [`Config`], with doc comments.
191    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    /// Attempts to read a config file in [`toml`] format from the given [`Path`].
199    ///
200    /// # Errors
201    ///
202    /// Will return an [`Err`] if the file cannot be read or if the file is not a valid [`toml`] config.
203    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    /// Returns the current [`Network`] we are running on.
219    pub const fn network(&self) -> Network {
220        self.network
221    }
222
223    /// The [`ClearNet`], [`cuprate_p2p::P2PConfig`].
224    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    /// The [`Tor`], [`cuprate_p2p::P2PConfig`].
243    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    /// The [`ContextConfig`].
286    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    /// The [`cuprate_blockchain`] config.
295    pub fn blockchain_config(&self) -> cuprate_blockchain::config::Config {
296        let blockchain = &self.storage.blockchain;
297
298        // We don't set reader threads as we manually make the reader threadpool.
299        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    /// The [`cuprate_txpool`] config.
307    pub fn txpool_config(&self) -> cuprate_txpool::config::Config {
308        let txpool = &self.storage.txpool;
309
310        // We don't set reader threads as we manually make the reader threadpool.
311        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    /// The [`BlockDownloaderConfig`].
319    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}