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