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 clap::Parser;
12use serde::{Deserialize, Serialize};
13
14use cuprate_consensus::ContextConfig;
15use cuprate_helper::{
16    fs::{CUPRATE_CONFIG_DIR, DEFAULT_CONFIG_FILE_NAME},
17    network::Network,
18};
19use cuprate_p2p::block_downloader::BlockDownloaderConfig;
20use cuprate_p2p_core::ClearNet;
21
22use crate::{
23    constants::{DEFAULT_CONFIG_STARTUP_DELAY, DEFAULT_CONFIG_WARNING},
24    logging::eprintln_red,
25};
26
27mod args;
28mod fs;
29mod p2p;
30mod rayon;
31mod rpc;
32mod storage;
33mod tokio;
34mod tracing_config;
35
36#[macro_use]
37mod macros;
38
39use fs::FileSystemConfig;
40use p2p::P2PConfig;
41use rayon::RayonConfig;
42pub use rpc::RpcConfig;
43use storage::StorageConfig;
44use tokio::TokioConfig;
45use tracing_config::TracingConfig;
46
47/// Header to put at the start of the generated config file.
48const HEADER: &str = r"##     ____                      _
49##    / ___|   _ _ __  _ __ __ _| |_ ___
50##   | |  | | | | '_ \| '__/ _` | __/ _ \
51##   | |__| |_| | |_) | | | (_| | ||  __/
52##    \____\__,_| .__/|_|  \__,_|\__\___|
53##              |_|
54##
55## All these config values can be set to
56## their default by commenting them out with '#'.
57##
58## Some values are already commented out,
59## to set the value remove the '#' at the start of the line.
60##
61## For more documentation, see: <https://user.cuprate.org>.
62
63";
64
65/// Reads the args & config file, returning a [`Config`].
66pub fn read_config_and_args() -> Config {
67    let args = args::Args::parse();
68    args.do_quick_requests();
69
70    let config: Config = if let Some(config_file) = &args.config_file {
71        // If a config file was set in the args try to read it and exit if we can't.
72        match Config::read_from_path(config_file) {
73            Ok(config) => config,
74            Err(e) => {
75                eprintln_red(&format!("Failed to read config from file: {e}"));
76                std::process::exit(1);
77            }
78        }
79    } else {
80        // First attempt to read the config file from the current directory.
81        std::env::current_dir()
82            .map(|path| path.join(DEFAULT_CONFIG_FILE_NAME))
83            .map_err(Into::into)
84            .and_then(Config::read_from_path)
85            .inspect_err(|e| tracing::debug!("Failed to read config from current dir: {e}"))
86            // otherwise try the main config directory.
87            .or_else(|_| {
88                let file = CUPRATE_CONFIG_DIR.join(DEFAULT_CONFIG_FILE_NAME);
89                Config::read_from_path(file)
90            })
91            .inspect_err(|e| {
92                tracing::debug!("Failed to read config from config dir: {e}");
93                if !args.skip_config_warning {
94                    eprintln_red(DEFAULT_CONFIG_WARNING);
95                    std::thread::sleep(DEFAULT_CONFIG_STARTUP_DELAY);
96                }
97            })
98            .unwrap_or_default()
99    };
100
101    args.apply_args(config)
102}
103
104config_struct! {
105    /// The config for all of Cuprate.
106    #[derive(Debug, Deserialize, Serialize, PartialEq)]
107    #[serde(deny_unknown_fields, default)]
108    pub struct Config {
109        /// The network cuprated should run on.
110        ///
111        /// Valid values | "Mainnet", "Testnet", "Stagenet"
112        pub network: Network,
113
114        /// Enable/disable fast sync.
115        ///
116        /// Fast sync skips verification of old blocks by
117        /// comparing block hashes to a built-in hash file,
118        /// disabling this will significantly increase sync time.
119        /// New blocks are still fully validated.
120        ///
121        /// Type         | boolean
122        /// Valid values | true, false
123        pub fast_sync: bool,
124
125        #[child = true]
126        /// Configuration for cuprated's logging system, tracing.
127        ///
128        /// Tracing is used for logging to stdout and files.
129        pub tracing: TracingConfig,
130
131        #[child = true]
132        /// Configuration for cuprated's asynchronous runtime system, tokio.
133        ///
134        /// Tokio is used for network operations and the major services inside `cuprated`.
135        pub tokio: TokioConfig,
136
137        #[child = true]
138        /// Configuration for cuprated's thread-pool system, rayon.
139        ///
140        /// Rayon is used for CPU intensive tasks.
141        pub rayon: RayonConfig,
142
143        #[child = true]
144        /// Configuration for cuprated's P2P system.
145        pub p2p: P2PConfig,
146
147        #[child = true]
148        /// Configuration for cuprated's RPC system.
149        pub rpc: RpcConfig,
150
151        #[child = true]
152        /// Configuration for persistent data storage.
153        pub storage: StorageConfig,
154
155        #[child = true]
156        /// Configuration for the file-system.
157        pub fs: FileSystemConfig,
158    }
159}
160
161impl Default for Config {
162    fn default() -> Self {
163        Self {
164            network: Default::default(),
165            fast_sync: true,
166            tracing: Default::default(),
167            tokio: Default::default(),
168            rayon: Default::default(),
169            p2p: Default::default(),
170            rpc: Default::default(),
171            storage: Default::default(),
172            fs: Default::default(),
173        }
174    }
175}
176
177impl Config {
178    /// Returns a default [`Config`], with doc comments.
179    pub fn documented_config() -> String {
180        let str = toml::ser::to_string_pretty(&Self::default()).unwrap();
181        let mut doc = toml_edit::DocumentMut::from_str(&str).unwrap();
182        Self::write_docs(doc.as_table_mut());
183        format!("{HEADER}{doc}")
184    }
185
186    /// Attempts to read a config file in [`toml`] format from the given [`Path`].
187    ///
188    /// # Errors
189    ///
190    /// Will return an [`Err`] if the file cannot be read or if the file is not a valid [`toml`] config.
191    fn read_from_path(file: impl AsRef<Path>) -> Result<Self, anyhow::Error> {
192        let file_text = read_to_string(file.as_ref())?;
193
194        Ok(toml::from_str(&file_text)
195            .inspect(|_| println!("Using config at: {}", file.as_ref().to_string_lossy()))
196            .inspect_err(|e| {
197                eprintln_red(&format!(
198                    "Failed to parse config file at: {}",
199                    file.as_ref().to_string_lossy()
200                ));
201                eprintln_red(&format!("{e}"));
202                std::process::exit(1);
203            })?)
204    }
205
206    /// Returns the current [`Network`] we are running on.
207    pub const fn network(&self) -> Network {
208        self.network
209    }
210
211    /// The [`ClearNet`], [`cuprate_p2p::P2PConfig`].
212    pub fn clearnet_p2p_config(&self) -> cuprate_p2p::P2PConfig<ClearNet> {
213        cuprate_p2p::P2PConfig {
214            network: self.network,
215            seeds: p2p::clear_net_seed_nodes(self.network),
216            outbound_connections: self.p2p.clear_net.outbound_connections,
217            extra_outbound_connections: self.p2p.clear_net.extra_outbound_connections,
218            max_inbound_connections: self.p2p.clear_net.max_inbound_connections,
219            gray_peers_percent: self.p2p.clear_net.gray_peers_percent,
220            p2p_port: self.p2p.clear_net.p2p_port,
221            rpc_port: self.rpc.restricted.port_for_p2p(),
222            address_book_config: self.p2p.clear_net.address_book_config.address_book_config(
223                &self.fs.cache_directory,
224                self.network,
225                None,
226            ),
227        }
228    }
229
230    /// The [`ContextConfig`].
231    pub const fn context_config(&self) -> ContextConfig {
232        match self.network {
233            Network::Mainnet => ContextConfig::main_net(),
234            Network::Stagenet => ContextConfig::stage_net(),
235            Network::Testnet => ContextConfig::test_net(),
236        }
237    }
238
239    /// The [`cuprate_blockchain`] config.
240    pub fn blockchain_config(&self) -> cuprate_blockchain::config::Config {
241        let blockchain = &self.storage.blockchain;
242
243        // We don't set reader threads as we manually make the reader threadpool.
244        cuprate_blockchain::config::ConfigBuilder::default()
245            .network(self.network)
246            .data_directory(self.fs.data_directory.clone())
247            .sync_mode(blockchain.sync_mode)
248            .build()
249    }
250
251    /// The [`cuprate_txpool`] config.
252    pub fn txpool_config(&self) -> cuprate_txpool::config::Config {
253        let txpool = &self.storage.txpool;
254
255        // We don't set reader threads as we manually make the reader threadpool.
256        cuprate_txpool::config::ConfigBuilder::default()
257            .network(self.network)
258            .data_directory(self.fs.data_directory.clone())
259            .sync_mode(txpool.sync_mode)
260            .build()
261    }
262
263    /// The [`BlockDownloaderConfig`].
264    pub fn block_downloader_config(&self) -> BlockDownloaderConfig {
265        self.p2p.block_downloader.clone().into()
266    }
267}
268
269impl fmt::Display for Config {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        writeln!(
272            f,
273            "========== CONFIGURATION ==========\n{self:#?}\n==================================="
274        )
275    }
276}
277
278#[cfg(test)]
279mod test {
280    use pretty_assertions::assert_eq;
281    use toml::from_str;
282
283    use super::*;
284
285    #[test]
286    fn documented_config() {
287        let str = Config::documented_config();
288        let conf: Config = from_str(&str).unwrap();
289
290        assert_eq!(conf, Config::default());
291    }
292}