use ckb_types::global::DATA_DIR;
use path_clean::PathClean;
use std::fs;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use ckb_chain_spec::ChainSpec;
pub use ckb_logger_config::Config as LogConfig;
pub use ckb_metrics_config::Config as MetricsConfig;
use ckb_resource::Resource;
use super::configs::*;
#[cfg(feature = "with_sentry")]
use super::sentry_config::SentryConfig;
use super::{cli, legacy, ExitCode};
pub enum AppConfig {
CKB(Box<CKBAppConfig>),
Miner(Box<MinerAppConfig>),
}
#[derive(Clone, Debug, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CKBAppConfig {
#[serde(skip)]
pub bin_name: String,
#[serde(skip)]
pub root_dir: PathBuf,
pub data_dir: PathBuf,
#[serde(default)]
pub ancient: PathBuf,
pub tmp_dir: Option<PathBuf>,
pub logger: LogConfig,
#[cfg(feature = "with_sentry")]
#[serde(default)]
pub sentry: SentryConfig,
#[serde(default)]
pub metrics: MetricsConfig,
#[serde(default)]
pub memory_tracker: MemoryTrackerConfig,
pub chain: ChainConfig,
pub block_assembler: Option<BlockAssemblerConfig>,
#[serde(default)]
pub db: DBConfig,
pub network: NetworkConfig,
pub rpc: RpcConfig,
pub tx_pool: TxPoolConfig,
#[serde(default)]
pub store: StoreConfig,
pub alert_signature: Option<NetworkAlertConfig>,
#[serde(default)]
pub notify: NotifyConfig,
#[serde(default)]
pub indexer: IndexerConfig,
#[serde(default)]
pub fee_estimator: FeeEstimatorConfig,
}
#[derive(Clone, Debug, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MinerAppConfig {
#[serde(skip)]
pub bin_name: String,
#[serde(skip)]
pub root_dir: PathBuf,
pub data_dir: PathBuf,
pub chain: ChainConfig,
pub logger: LogConfig,
#[cfg(feature = "with_sentry")]
pub sentry: SentryConfig,
#[serde(default)]
pub metrics: MetricsConfig,
#[serde(default)]
pub memory_tracker: MemoryTrackerConfig,
pub miner: MinerConfig,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChainConfig {
pub spec: Resource,
}
impl AppConfig {
pub fn load_for_subcommand<P: AsRef<Path>>(
root_dir: P,
subcommand_name: &str,
) -> Result<AppConfig, ExitCode> {
match subcommand_name {
cli::CMD_MINER => {
let resource = ensure_ckb_dir(Resource::miner_config(root_dir.as_ref()))?;
let config = MinerAppConfig::load_from_slice(&resource.get()?)?;
Ok(AppConfig::with_miner(
config.derive_options(root_dir.as_ref())?,
))
}
_ => {
let resource = ensure_ckb_dir(Resource::ckb_config(root_dir.as_ref()))?;
let config = CKBAppConfig::load_from_slice(&resource.get()?)?;
Ok(AppConfig::with_ckb(
config.derive_options(root_dir.as_ref(), subcommand_name)?,
))
}
}
}
pub fn logger(&self) -> &LogConfig {
match self {
AppConfig::CKB(config) => &config.logger,
AppConfig::Miner(config) => &config.logger,
}
}
#[cfg(feature = "with_sentry")]
pub fn sentry(&self) -> &SentryConfig {
match self {
AppConfig::CKB(config) => &config.sentry,
AppConfig::Miner(config) => &config.sentry,
}
}
pub fn metrics(&self) -> &MetricsConfig {
match self {
AppConfig::CKB(config) => &config.metrics,
AppConfig::Miner(config) => &config.metrics,
}
}
pub fn memory_tracker(&self) -> &MemoryTrackerConfig {
match self {
AppConfig::CKB(config) => &config.memory_tracker,
AppConfig::Miner(config) => &config.memory_tracker,
}
}
pub fn chain_spec(&self) -> Result<ChainSpec, ExitCode> {
let spec_resource = match self {
AppConfig::CKB(config) => &config.chain.spec,
AppConfig::Miner(config) => &config.chain.spec,
};
ChainSpec::load_from(spec_resource).map_err(|err| {
eprintln!("{err}");
ExitCode::Config
})
}
pub fn into_ckb(self) -> Result<Box<CKBAppConfig>, ExitCode> {
match self {
AppConfig::CKB(config) => Ok(config),
_ => {
eprintln!("Unmatched config file");
Err(ExitCode::Failure)
}
}
}
pub fn into_miner(self) -> Result<Box<MinerAppConfig>, ExitCode> {
match self {
AppConfig::Miner(config) => Ok(config),
_ => {
eprintln!("Unmatched config file");
Err(ExitCode::Failure)
}
}
}
pub fn set_bin_name(&mut self, bin_name: String) {
match self {
AppConfig::CKB(config) => config.bin_name = bin_name,
AppConfig::Miner(config) => config.bin_name = bin_name,
}
}
}
impl AppConfig {
fn with_ckb(config: CKBAppConfig) -> AppConfig {
AppConfig::CKB(Box::new(config))
}
fn with_miner(config: MinerAppConfig) -> AppConfig {
AppConfig::Miner(Box::new(config))
}
}
impl CKBAppConfig {
pub fn load_from_slice(slice: &[u8]) -> Result<Self, ExitCode> {
let legacy_config: legacy::CKBAppConfig = toml::from_slice(slice)?;
for field in legacy_config.deprecated_fields() {
eprintln!(
"WARN: the option \"{}\" in configuration files is deprecated since v{}.",
field.path, field.since
);
}
Ok(legacy_config.into())
}
fn derive_options(mut self, root_dir: &Path, subcommand_name: &str) -> Result<Self, ExitCode> {
self.root_dir = root_dir.to_path_buf();
self.data_dir = canonicalize_data_dir(self.data_dir, root_dir);
DATA_DIR
.set(self.data_dir.clone())
.expect("DATA_DIR is empty");
self.db.adjust(root_dir, &self.data_dir, "db");
self.ancient = mkdir(path_specified_or_else(&self.ancient, || {
self.data_dir.join("ancient")
}))?;
self.network.path = self.data_dir.join("network");
if self.tmp_dir.is_none() {
self.tmp_dir = Some(self.data_dir.join("tmp"));
}
self.logger.log_dir = self.data_dir.join("logs");
self.logger.file = Path::new(&(subcommand_name.to_string() + ".log")).to_path_buf();
let tx_pool_path = mkdir(self.data_dir.join("tx_pool"))?;
self.tx_pool.adjust(root_dir, tx_pool_path);
let indexer_path = mkdir(self.data_dir.join("indexer"))?;
self.indexer.adjust(root_dir, indexer_path);
if subcommand_name == cli::CMD_RESET_DATA {
return Ok(self);
}
self.data_dir = mkdir(self.data_dir)?;
self.db.path = mkdir(self.db.path)?;
self.network.path = mkdir(self.network.path)?;
if let Some(tmp_dir) = self.tmp_dir {
self.tmp_dir = Some(mkdir(tmp_dir)?);
}
if self.logger.log_to_file {
mkdir(self.logger.log_dir.clone())?;
touch(self.logger.log_dir.join(&self.logger.file))?;
}
self.chain.spec.absolutize(root_dir);
Ok(self)
}
}
impl MinerAppConfig {
pub fn load_from_slice(slice: &[u8]) -> Result<Self, ExitCode> {
let legacy_config: legacy::MinerAppConfig = toml::from_slice(slice)?;
for field in legacy_config.deprecated_fields() {
eprintln!(
"WARN: the option \"{}\" in configuration files is deprecated since v{}.",
field.path, field.since
);
}
Ok(legacy_config.into())
}
fn derive_options(mut self, root_dir: &Path) -> Result<Self, ExitCode> {
self.root_dir = root_dir.to_path_buf();
self.data_dir = mkdir(canonicalize_data_dir(self.data_dir, root_dir))?;
self.logger.log_dir = self.data_dir.join("logs");
self.logger.file = Path::new("miner.log").to_path_buf();
if self.logger.log_to_file {
mkdir(self.logger.log_dir.clone())?;
touch(self.logger.log_dir.join(&self.logger.file))?;
}
self.chain.spec.absolutize(root_dir);
Ok(self)
}
}
fn canonicalize_data_dir(data_dir: PathBuf, root_dir: &Path) -> PathBuf {
if data_dir.is_absolute() {
data_dir
} else {
root_dir.join(data_dir)
}
}
fn mkdir(dir: PathBuf) -> Result<PathBuf, ExitCode> {
fs::create_dir_all(dir.clean())?;
Ok(dir)
}
fn touch(path: PathBuf) -> Result<PathBuf, ExitCode> {
fs::OpenOptions::new()
.create(true)
.append(true)
.open(&path)?;
Ok(path)
}
fn ensure_ckb_dir(r: Resource) -> Result<Resource, ExitCode> {
if r.exists() {
Ok(r)
} else {
eprintln!("Not a CKB directory; initialize one with `ckb init`.");
Err(ExitCode::Config)
}
}
fn path_specified_or_else<P: AsRef<Path>, F: FnOnce() -> PathBuf>(
path: P,
default_path: F,
) -> PathBuf {
let path_ref = path.as_ref();
if path_ref.to_str().is_none() || path_ref.to_str() == Some("") {
default_path()
} else {
path_ref.to_path_buf()
}
}