ckb_app_config/
app_config.rs

1//! # CKB AppConfig
2//!
3//! Because the limitation of toml library,
4//! we must put nested config struct in the tail to make it serializable,
5//! details <https://docs.rs/toml/0.5.0/toml/ser/index.html>
6
7use ckb_types::global::DATA_DIR;
8use path_clean::PathClean;
9use std::fs;
10use std::path::{Path, PathBuf};
11
12use serde::{Deserialize, Serialize};
13
14use ckb_chain_spec::ChainSpec;
15pub use ckb_logger_config::Config as LogConfig;
16pub use ckb_metrics_config::Config as MetricsConfig;
17use ckb_resource::Resource;
18
19use super::configs::*;
20#[cfg(feature = "with_sentry")]
21use super::sentry_config::SentryConfig;
22use super::{cli, legacy, ExitCode};
23
24/// The parsed config file.
25///
26/// CKB process reads `ckb.toml` or `ckb-miner.toml`, depending what subcommand to be executed.
27pub enum AppConfig {
28    /// The parsed `ckb.toml.`
29    CKB(Box<CKBAppConfig>),
30    /// The parsed `ckb-miner.toml.`
31    Miner(Box<MinerAppConfig>),
32}
33
34/// The main config file for the most subcommands. Usually it is the `ckb.toml` in the CKB root
35/// directory.
36///
37/// **Attention:** Changing the order of fields will break integration test, see module doc.
38#[derive(Clone, Debug, Serialize)]
39#[serde(deny_unknown_fields)]
40pub struct CKBAppConfig {
41    /// The binary name.
42    #[serde(skip)]
43    pub bin_name: String,
44    /// The root directory.
45    #[serde(skip)]
46    pub root_dir: PathBuf,
47    /// The data directory.
48    pub data_dir: PathBuf,
49    /// freezer files path
50    #[serde(default)]
51    pub ancient: PathBuf,
52    /// The directory to store temporary files.
53    pub tmp_dir: Option<PathBuf>,
54    /// Logger config options.
55    pub logger: LogConfig,
56    /// Sentry config options.
57    #[cfg(feature = "with_sentry")]
58    #[serde(default)]
59    pub sentry: SentryConfig,
60    /// Metrics options.
61    ///
62    /// Developers can collect metrics for performance tuning and troubleshooting.
63    #[serde(default)]
64    pub metrics: MetricsConfig,
65    /// Memory tracker options.
66    ///
67    /// Developers can enable memory tracker to analyze the process memory usage.
68    #[serde(default)]
69    pub memory_tracker: MemoryTrackerConfig,
70    /// Chain config options.
71    pub chain: ChainConfig,
72
73    /// Block assembler options.
74    pub block_assembler: Option<BlockAssemblerConfig>,
75    /// Database config options.
76    #[serde(default)]
77    pub db: DBConfig,
78    /// Network config options.
79    pub network: NetworkConfig,
80    /// RPC config options.
81    pub rpc: RpcConfig,
82    /// Tx pool config options.
83    pub tx_pool: TxPoolConfig,
84    /// Store config options.
85    #[serde(default)]
86    pub store: StoreConfig,
87    /// P2P alert config options.
88    pub alert_signature: Option<NetworkAlertConfig>,
89    /// Notify config options.
90    #[serde(default)]
91    pub notify: NotifyConfig,
92    /// Indexer config options.
93    #[serde(default)]
94    pub indexer: IndexerConfig,
95    /// Fee estimator config options.
96    #[serde(default)]
97    pub fee_estimator: FeeEstimatorConfig,
98}
99
100/// The miner config file for `ckb miner`. Usually it is the `ckb-miner.toml` in the CKB root
101/// directory.
102///
103/// **Attention:** Changing the order of fields will break integration test, see module doc.
104#[derive(Clone, Debug, Serialize)]
105#[serde(deny_unknown_fields)]
106pub struct MinerAppConfig {
107    /// The binary name.
108    #[serde(skip)]
109    pub bin_name: String,
110    /// The root directory.
111    #[serde(skip)]
112    pub root_dir: PathBuf,
113    /// The data directory.
114    pub data_dir: PathBuf,
115    /// Chain config options.
116    pub chain: ChainConfig,
117    /// Logger config options.
118    pub logger: LogConfig,
119    /// Sentry config options.
120    #[cfg(feature = "with_sentry")]
121    pub sentry: SentryConfig,
122    /// Metrics options.
123    ///
124    /// Developers can collect metrics for performance tuning and troubleshooting.
125    #[serde(default)]
126    pub metrics: MetricsConfig,
127    /// Memory tracker options.
128    ///
129    /// Developers can enable memory tracker to analyze the process memory usage.
130    #[serde(default)]
131    pub memory_tracker: MemoryTrackerConfig,
132
133    /// The miner config options.
134    pub miner: MinerConfig,
135}
136
137/// The chain config options.
138#[derive(Clone, Debug, Serialize, Deserialize)]
139#[serde(deny_unknown_fields)]
140pub struct ChainConfig {
141    /// Specifies the chain spec.
142    pub spec: Resource,
143}
144
145impl AppConfig {
146    /// Reads the config file for the subcommand.
147    ///
148    /// This will reads the `ckb-miner.toml` in the CKB directory for `ckb miner`, and `ckb.toml`
149    /// for all other subcommands.
150    pub fn load_for_subcommand<P: AsRef<Path>>(
151        root_dir: P,
152        subcommand_name: &str,
153    ) -> Result<AppConfig, ExitCode> {
154        match subcommand_name {
155            cli::CMD_MINER => {
156                let resource = ensure_ckb_dir(Resource::miner_config(root_dir.as_ref()))?;
157                let config = MinerAppConfig::load_from_slice(&resource.get()?)?;
158
159                Ok(AppConfig::with_miner(
160                    config.derive_options(root_dir.as_ref())?,
161                ))
162            }
163            _ => {
164                let resource = ensure_ckb_dir(Resource::ckb_config(root_dir.as_ref()))?;
165                let config = CKBAppConfig::load_from_slice(&resource.get()?)?;
166
167                Ok(AppConfig::with_ckb(
168                    config.derive_options(root_dir.as_ref(), subcommand_name)?,
169                ))
170            }
171        }
172    }
173
174    /// Gets logger options.
175    pub fn logger(&self) -> &LogConfig {
176        match self {
177            AppConfig::CKB(config) => &config.logger,
178            AppConfig::Miner(config) => &config.logger,
179        }
180    }
181
182    /// Gets sentry options.
183    #[cfg(feature = "with_sentry")]
184    pub fn sentry(&self) -> &SentryConfig {
185        match self {
186            AppConfig::CKB(config) => &config.sentry,
187            AppConfig::Miner(config) => &config.sentry,
188        }
189    }
190
191    /// Gets metrics options.
192    pub fn metrics(&self) -> &MetricsConfig {
193        match self {
194            AppConfig::CKB(config) => &config.metrics,
195            AppConfig::Miner(config) => &config.metrics,
196        }
197    }
198
199    /// Gets memory tracker options.
200    pub fn memory_tracker(&self) -> &MemoryTrackerConfig {
201        match self {
202            AppConfig::CKB(config) => &config.memory_tracker,
203            AppConfig::Miner(config) => &config.memory_tracker,
204        }
205    }
206
207    /// Gets chain spec.
208    pub fn chain_spec(&self) -> Result<ChainSpec, ExitCode> {
209        let spec_resource = match self {
210            AppConfig::CKB(config) => &config.chain.spec,
211            AppConfig::Miner(config) => &config.chain.spec,
212        };
213        ChainSpec::load_from(spec_resource).map_err(|err| {
214            eprintln!("{err}");
215            ExitCode::Config
216        })
217    }
218
219    /// Unpacks the parsed ckb.toml config file.
220    ///
221    /// Panics when this is a parsed ckb-miner.toml.
222    pub fn into_ckb(self) -> Result<Box<CKBAppConfig>, ExitCode> {
223        match self {
224            AppConfig::CKB(config) => Ok(config),
225            _ => {
226                eprintln!("Unmatched config file");
227                Err(ExitCode::Failure)
228            }
229        }
230    }
231
232    /// Unpacks the parsed ckb-miner.toml config file.
233    ///
234    /// Panics when this is a parsed ckb.toml.
235    pub fn into_miner(self) -> Result<Box<MinerAppConfig>, ExitCode> {
236        match self {
237            AppConfig::Miner(config) => Ok(config),
238            _ => {
239                eprintln!("Unmatched config file");
240                Err(ExitCode::Failure)
241            }
242        }
243    }
244
245    /// Set the binary name with full path.
246    pub fn set_bin_name(&mut self, bin_name: String) {
247        match self {
248            AppConfig::CKB(config) => config.bin_name = bin_name,
249            AppConfig::Miner(config) => config.bin_name = bin_name,
250        }
251    }
252}
253
254impl AppConfig {
255    fn with_ckb(config: CKBAppConfig) -> AppConfig {
256        AppConfig::CKB(Box::new(config))
257    }
258    fn with_miner(config: MinerAppConfig) -> AppConfig {
259        AppConfig::Miner(Box::new(config))
260    }
261}
262
263impl CKBAppConfig {
264    /// Load a new instance from a file
265    pub fn load_from_slice(slice: &[u8]) -> Result<Self, ExitCode> {
266        let legacy_config: legacy::CKBAppConfig = toml::from_slice(slice)?;
267        for field in legacy_config.deprecated_fields() {
268            eprintln!(
269                "WARN: the option \"{}\" in configuration files is deprecated since v{}.",
270                field.path, field.since
271            );
272        }
273        Ok(legacy_config.into())
274    }
275
276    fn derive_options(mut self, root_dir: &Path, subcommand_name: &str) -> Result<Self, ExitCode> {
277        self.root_dir = root_dir.to_path_buf();
278
279        self.data_dir = canonicalize_data_dir(self.data_dir, root_dir);
280
281        DATA_DIR
282            .set(self.data_dir.clone())
283            .expect("DATA_DIR is empty");
284
285        self.db.adjust(root_dir, &self.data_dir, "db");
286        self.ancient = mkdir(path_specified_or_else(&self.ancient, || {
287            self.data_dir.join("ancient")
288        }))?;
289
290        self.network.path = self.data_dir.join("network");
291        if self.tmp_dir.is_none() {
292            self.tmp_dir = Some(self.data_dir.join("tmp"));
293        }
294        self.logger.log_dir = self.data_dir.join("logs");
295        self.logger.file = Path::new(&(subcommand_name.to_string() + ".log")).to_path_buf();
296
297        let tx_pool_path = mkdir(self.data_dir.join("tx_pool"))?;
298        self.tx_pool.adjust(root_dir, tx_pool_path);
299
300        let indexer_path = mkdir(self.data_dir.join("indexer"))?;
301        self.indexer.adjust(root_dir, indexer_path);
302
303        if subcommand_name == cli::CMD_RESET_DATA {
304            return Ok(self);
305        }
306
307        self.data_dir = mkdir(self.data_dir)?;
308        self.db.path = mkdir(self.db.path)?;
309        self.network.path = mkdir(self.network.path)?;
310        if let Some(tmp_dir) = self.tmp_dir {
311            self.tmp_dir = Some(mkdir(tmp_dir)?);
312        }
313        if self.logger.log_to_file {
314            mkdir(self.logger.log_dir.clone())?;
315            touch(self.logger.log_dir.join(&self.logger.file))?;
316        }
317        self.chain.spec.absolutize(root_dir);
318
319        Ok(self)
320    }
321}
322
323impl MinerAppConfig {
324    /// Load a new instance from a file.
325    pub fn load_from_slice(slice: &[u8]) -> Result<Self, ExitCode> {
326        let legacy_config: legacy::MinerAppConfig = toml::from_slice(slice)?;
327        for field in legacy_config.deprecated_fields() {
328            eprintln!(
329                "WARN: the option \"{}\" in configuration files is deprecated since v{}.",
330                field.path, field.since
331            );
332        }
333        Ok(legacy_config.into())
334    }
335
336    fn derive_options(mut self, root_dir: &Path) -> Result<Self, ExitCode> {
337        self.root_dir = root_dir.to_path_buf();
338
339        self.data_dir = mkdir(canonicalize_data_dir(self.data_dir, root_dir))?;
340        self.logger.log_dir = self.data_dir.join("logs");
341        self.logger.file = Path::new("miner.log").to_path_buf();
342        if self.logger.log_to_file {
343            mkdir(self.logger.log_dir.clone())?;
344            touch(self.logger.log_dir.join(&self.logger.file))?;
345        }
346        self.chain.spec.absolutize(root_dir);
347
348        Ok(self)
349    }
350}
351
352fn canonicalize_data_dir(data_dir: PathBuf, root_dir: &Path) -> PathBuf {
353    if data_dir.is_absolute() {
354        data_dir
355    } else {
356        root_dir.join(data_dir)
357    }
358}
359
360fn mkdir(dir: PathBuf) -> Result<PathBuf, ExitCode> {
361    fs::create_dir_all(dir.clean())?;
362    // std::fs::canonicalize will bring windows compatibility problems
363    Ok(dir)
364}
365
366fn touch(path: PathBuf) -> Result<PathBuf, ExitCode> {
367    fs::OpenOptions::new()
368        .create(true)
369        .append(true)
370        .open(&path)?;
371
372    Ok(path)
373}
374
375fn ensure_ckb_dir(r: Resource) -> Result<Resource, ExitCode> {
376    if r.exists() {
377        Ok(r)
378    } else {
379        eprintln!("Not a CKB directory; initialize one with `ckb init`.");
380        Err(ExitCode::Config)
381    }
382}
383
384fn path_specified_or_else<P: AsRef<Path>, F: FnOnce() -> PathBuf>(
385    path: P,
386    default_path: F,
387) -> PathBuf {
388    let path_ref = path.as_ref();
389    if path_ref.to_str().is_none() || path_ref.to_str() == Some("") {
390        default_path()
391    } else {
392        path_ref.to_path_buf()
393    }
394}