cuprated/
config.rs

1//! cuprated config
2use 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
40/// Reads the args & config file, returning a [`Config`].
41pub 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        // If a config file was set in the args try to read it and exit if we can't.
47        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        // First attempt to read the config file from the current directory.
56        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            // otherwise try the main config directory.
62            .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/// The config for all of Cuprate.
80#[derive(Debug, Default, Deserialize, Serialize, PartialEq)]
81#[serde(deny_unknown_fields, default)]
82pub struct Config {
83    /// The network we should run on.
84    network: Network,
85
86    pub no_fast_sync: bool,
87
88    /// [`tracing`] config.
89    pub tracing: TracingConfig,
90
91    pub tokio: TokioConfig,
92
93    pub rayon: RayonConfig,
94
95    /// The P2P network config.
96    p2p: P2PConfig,
97
98    /// The storage config.
99    pub storage: StorageConfig,
100
101    pub fs: FileSystemConfig,
102}
103
104impl Config {
105    /// Attempts to read a config file in [`toml`] format from the given [`Path`].
106    ///
107    /// # Errors
108    ///
109    /// Will return an [`Err`] if the file cannot be read or if the file is not a valid [`toml`] config.
110    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    /// Returns the current [`Network`] we are running on.
126    pub const fn network(&self) -> Network {
127        self.network
128    }
129
130    /// The [`ClearNet`], [`cuprate_p2p::P2PConfig`].
131    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            // TODO: set this if a public RPC server is set.
144            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    /// The [`ContextConfig`].
154    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    /// The [`cuprate_blockchain`] config.
163    pub fn blockchain_config(&self) -> cuprate_blockchain::config::Config {
164        let blockchain = &self.storage.blockchain;
165
166        // We don't set reader threads as we manually make the reader threadpool.
167        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    /// The [`cuprate_txpool`] config.
175    pub fn txpool_config(&self) -> cuprate_txpool::config::Config {
176        let txpool = &self.storage.txpool;
177
178        // We don't set reader threads as we manually make the reader threadpool.
179        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    /// The [`BlockDownloaderConfig`].
187    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    /// Tests the latest config is the `Default`.
201    #[test]
202    fn config_latest() {
203        let config: Config = from_str(EXAMPLE_CONFIG).unwrap();
204        assert_eq!(config, Config::default());
205    }
206
207    /// Tests backwards compatibility.
208    #[test]
209    fn config_backwards_compat() {
210        // (De)serialization tests.
211        #[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}