use super::ConfigurationError;
use config::{Config, ConfigError, Environment};
use multiaddr::Multiaddr;
use std::{
convert::TryInto,
fmt::{Display, Formatter, Result as FormatResult},
net::SocketAddr,
num::{NonZeroU16, TryFromIntError},
path::PathBuf,
str::FromStr,
time::Duration,
};
use tari_storage::lmdb_store::LMDBConfig;
const DB_INIT_DEFAULT_MB: usize = 1000;
const DB_GROW_DEFAULT_MB: usize = 500;
const DB_RESIZE_DEFAULT_MB: usize = 100;
const DB_INIT_MIN_MB: i64 = 100;
const DB_GROW_MIN_MB: i64 = 20;
const DB_RESIZE_MIN_MB: i64 = 10;
#[derive(Debug, Clone)]
pub struct GlobalConfig {
pub network: Network,
pub comms_transport: CommsTransport,
pub listnener_liveness_max_sessions: usize,
pub listener_liveness_allowlist_cidrs: Vec<String>,
pub data_dir: PathBuf,
pub db_type: DatabaseType,
pub db_config: LMDBConfig,
pub orphan_storage_capacity: usize,
pub pruning_horizon: u64,
pub pruned_mode_cleanup_interval: u64,
pub core_threads: usize,
pub blocking_threads: usize,
pub identity_file: PathBuf,
pub public_address: Multiaddr,
pub grpc_enabled: bool,
pub grpc_address: SocketAddr,
pub grpc_wallet_address: SocketAddr,
pub peer_seeds: Vec<String>,
pub peer_db_path: PathBuf,
pub block_sync_strategy: String,
pub enable_mining: bool,
pub num_mining_threads: usize,
pub tor_identity_file: PathBuf,
pub wallet_db_file: PathBuf,
pub wallet_identity_file: PathBuf,
pub wallet_tor_identity_file: PathBuf,
pub wallet_peer_db_path: PathBuf,
pub buffer_size_base_node: usize,
pub buffer_size_base_node_wallet: usize,
pub buffer_rate_limit_base_node: usize,
pub buffer_rate_limit_base_node_wallet: usize,
pub base_node_query_timeout: Duration,
pub transaction_broadcast_monitoring_timeout: Duration,
pub transaction_chain_monitoring_timeout: Duration,
pub transaction_direct_send_timeout: Duration,
pub transaction_broadcast_send_timeout: Duration,
pub prevent_fee_gt_amount: bool,
pub monerod_url: String,
pub monerod_username: String,
pub monerod_password: String,
pub monerod_use_auth: bool,
pub proxy_host_address: SocketAddr,
}
impl GlobalConfig {
pub fn convert_from(mut cfg: Config) -> Result<Self, ConfigurationError> {
let network = cfg
.get_str("base_node.network")
.map_err(|e| ConfigurationError::new("base_node.network", &e.to_string()))?
.parse()?;
cfg.merge(Environment::with_prefix("tari").separator("__"))
.map_err(|e| ConfigurationError::new("environment variable", &e.to_string()))?;
convert_node_config(network, cfg)
}
}
fn convert_node_config(network: Network, cfg: Config) -> Result<GlobalConfig, ConfigurationError> {
let net_str = network.to_string().to_lowercase();
let key = config_string("base_node", &net_str, "data_dir");
let data_dir: PathBuf = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?
.into();
let key = config_string("base_node", &net_str, "db_type");
let db_type = cfg
.get_str(&key)
.map(|s| s.to_lowercase())
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?;
let db_type = match db_type.as_str() {
"memory" => Ok(DatabaseType::Memory),
"lmdb" => Ok(DatabaseType::LMDB(data_dir.join("db"))),
invalid_opt => Err(ConfigurationError::new(
"base_node.db_type",
&format!("Invalid option: {}", invalid_opt),
)),
}?;
let key = config_string("base_node", &net_str, "db_init_size_mb");
let init_size_mb = match cfg.get_int(&key) {
Ok(mb) if mb < DB_INIT_MIN_MB => {
return Err(ConfigurationError::new(
&key,
&format!("DB initial size must be at least {} MB.", DB_INIT_MIN_MB),
))
},
Ok(mb) => mb as usize,
Err(e) => match e {
ConfigError::NotFound(_) => DB_INIT_DEFAULT_MB,
other => return Err(ConfigurationError::new(&key, &other.to_string())),
},
};
let key = config_string("base_node", &net_str, "db_grow_size_mb");
let grow_size_mb = match cfg.get_int(&key) {
Ok(mb) if mb < DB_GROW_MIN_MB => {
return Err(ConfigurationError::new(
&key,
&format!("DB grow size must be at least {} MB.", DB_GROW_MIN_MB),
))
},
Ok(mb) => mb as usize,
Err(e) => match e {
ConfigError::NotFound(_) => DB_GROW_DEFAULT_MB,
other => return Err(ConfigurationError::new(&key, &other.to_string())),
},
};
let key = config_string("base_node", &net_str, "db_resize_threshold_mb");
let resize_threshold_mb = match cfg.get_int(&key) {
Ok(mb) if mb < DB_RESIZE_MIN_MB => {
return Err(ConfigurationError::new(
&key,
&format!("DB resize threshold must be at least {} MB.", DB_RESIZE_MIN_MB),
))
},
Ok(mb) if mb as usize >= grow_size_mb => {
return Err(ConfigurationError::new(
&key,
"DB resize threshold must be less than grow size.",
))
},
Ok(mb) if mb as usize >= init_size_mb => {
return Err(ConfigurationError::new(
&key,
"DB resize threshold must be less than init size.",
))
},
Ok(mb) => mb as usize,
Err(e) => match e {
ConfigError::NotFound(_) => DB_RESIZE_DEFAULT_MB,
other => return Err(ConfigurationError::new(&key, &other.to_string())),
},
};
let db_config = LMDBConfig::new_from_mb(init_size_mb, grow_size_mb, resize_threshold_mb);
let key = config_string("base_node", &net_str, "orphan_storage_capacity");
let orphan_storage_capacity = cfg
.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as usize;
let key = config_string("base_node", &net_str, "pruning_horizon");
let pruning_horizon = cfg
.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as u64;
let key = config_string("base_node", &net_str, "pruned_mode_cleanup_interval");
let pruned_mode_cleanup_interval = cfg
.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as u64;
let key = config_string("base_node", &net_str, "core_threads");
let core_threads = cfg
.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as usize;
let key = config_string("base_node", &net_str, "blocking_threads");
let blocking_threads = cfg
.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as usize;
let key = config_string("base_node", &net_str, "identity_file");
let identity_file = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?
.into();
let key = config_string("base_node", &net_str, "wallet_identity_file");
let wallet_identity_file = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?
.into();
let key = config_string("base_node", &net_str, "wallet_tor_identity_file");
let wallet_tor_identity_file = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?
.into();
let key = config_string("base_node", &net_str, "tor_identity_file");
let tor_identity_file = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?
.into();
let comms_transport = network_transport_config(&cfg, &net_str)?;
let key = config_string("base_node", &net_str, "public_address");
let public_address = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))
.and_then(|addr| {
addr.parse::<Multiaddr>()
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))
})?;
let key = config_string("base_node", &net_str, "grpc_enabled");
let grpc_enabled = cfg
.get_bool(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as bool;
let key = config_string("base_node", &net_str, "grpc_address");
let grpc_address = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))
.and_then(|addr| {
addr.parse::<SocketAddr>()
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))
})?;
let key = config_string("base_node", &net_str, "grpc_wallet_address");
let grpc_wallet_address = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))
.and_then(|addr| {
addr.parse::<SocketAddr>()
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))
})?;
let key = config_string("base_node", &net_str, "peer_seeds");
let peer_seeds = cfg
.get_array(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?;
let peer_seeds = peer_seeds.into_iter().map(|v| v.into_str().unwrap()).collect();
let peer_db_path = data_dir.join("peer_db");
let wallet_peer_db_path = data_dir.join("wallet_peer_db");
let key = config_string("base_node", &net_str, "block_sync_strategy");
let block_sync_strategy = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?;
let key = config_string("base_node", &net_str, "enable_mining");
let enable_mining = cfg
.get_bool(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as bool;
let key = config_string("base_node", &net_str, "num_mining_threads");
let num_mining_threads = cfg
.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as usize;
let key = "wallet.wallet_file".to_string();
let wallet_db_file = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?
.into();
let key = "wallet.base_node_query_timeout";
let base_node_query_timeout = Duration::from_secs(
cfg.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as u64,
);
let key = "wallet.transaction_broadcast_monitoring_timeout";
let transaction_broadcast_monitoring_timeout = Duration::from_secs(
cfg.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as u64,
);
let key = "wallet.transaction_chain_monitoring_timeout";
let transaction_chain_monitoring_timeout = Duration::from_secs(
cfg.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as u64,
);
let key = "wallet.transaction_direct_send_timeout";
let transaction_direct_send_timeout = Duration::from_secs(
cfg.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as u64,
);
let key = "wallet.transaction_broadcast_send_timeout";
let transaction_broadcast_send_timeout = Duration::from_secs(
cfg.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as u64,
);
let key = "wallet.prevent_fee_gt_amount";
let prevent_fee_gt_amount = cfg
.get_bool(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as bool;
let key = "common.liveness_max_sessions";
let liveness_max_sessions = cfg
.get_int(key)
.map_err(|e| ConfigurationError::new(key, &e.to_string()))?
.try_into()
.map_err(|e: TryFromIntError| ConfigurationError::new(&key, &e.to_string()))?;
let key = "common.liveness_allowlist_cidrs";
let liveness_allowlist_cidrs = cfg
.get_array(key)
.map(|values| values.iter().map(ToString::to_string).collect())
.unwrap_or_else(|_| vec!["127.0.0.1/32".to_string()]);
let key = "common.buffer_size_base_node";
let buffer_size_base_node = cfg
.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as usize;
let key = "common.buffer_size_base_node_wallet";
let buffer_size_base_node_wallet = cfg
.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as usize;
let key = "common.buffer_rate_limit_base_node";
let buffer_rate_limit_base_node = cfg
.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as usize;
let key = "common.buffer_rate_limit_base_node_wallet";
let buffer_rate_limit_base_node_wallet =
cfg.get_int(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))? as usize;
let key = config_string("merge_mining_proxy", &net_str, "monerod_url");
let monerod_url = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?;
let key = config_string("merge_mining_proxy", &net_str, "monerod_use_auth");
let monerod_use_auth = cfg
.get_bool(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?;
let key = config_string("merge_mining_proxy", &net_str, "monerod_username");
let monerod_username = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?;
let key = config_string("merge_mining_proxy", &net_str, "monerod_password");
let monerod_password = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))?;
let key = config_string("merge_mining_proxy", &net_str, "proxy_host_address");
let proxy_host_address = cfg
.get_str(&key)
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))
.and_then(|addr| {
addr.parse::<SocketAddr>()
.map_err(|e| ConfigurationError::new(&key, &e.to_string()))
})?;
Ok(GlobalConfig {
network,
comms_transport,
listnener_liveness_max_sessions: liveness_max_sessions,
listener_liveness_allowlist_cidrs: liveness_allowlist_cidrs,
data_dir,
db_type,
db_config,
orphan_storage_capacity,
pruning_horizon,
pruned_mode_cleanup_interval,
core_threads,
blocking_threads,
identity_file,
public_address,
grpc_enabled,
grpc_address,
grpc_wallet_address,
peer_seeds,
peer_db_path,
block_sync_strategy,
enable_mining,
num_mining_threads,
tor_identity_file,
wallet_identity_file,
wallet_db_file,
wallet_tor_identity_file,
wallet_peer_db_path,
buffer_size_base_node,
buffer_size_base_node_wallet,
buffer_rate_limit_base_node,
buffer_rate_limit_base_node_wallet,
base_node_query_timeout,
transaction_broadcast_monitoring_timeout,
transaction_chain_monitoring_timeout,
transaction_direct_send_timeout,
transaction_broadcast_send_timeout,
prevent_fee_gt_amount,
proxy_host_address,
monerod_url,
monerod_username,
monerod_password,
monerod_use_auth,
})
}
fn network_transport_config(cfg: &Config, network: &str) -> Result<CommsTransport, ConfigurationError> {
let get_conf_str = |key| {
cfg.get_str(key)
.map_err(|err| ConfigurationError::new(key, &err.to_string()))
};
let get_conf_multiaddr = |key| {
let path_str = get_conf_str(key)?;
path_str
.parse::<Multiaddr>()
.map_err(|err| ConfigurationError::new(key, &err.to_string()))
};
let transport_key = config_string("base_node", network, "transport");
let transport = get_conf_str(&transport_key)?;
match transport.to_lowercase().as_str() {
"tcp" => {
let key = config_string("base_node", network, "tcp_listener_address");
let listener_address = get_conf_multiaddr(&key)?;
let key = config_string("base_node", network, "tcp_tor_socks_address");
let tor_socks_address = get_conf_multiaddr(&key).ok();
let key = config_string("base_node", network, "tcp_tor_socks_auth");
let tor_socks_auth = get_conf_str(&key).ok().and_then(|auth_str| auth_str.parse().ok());
Ok(CommsTransport::Tcp {
listener_address,
tor_socks_auth,
tor_socks_address,
})
},
"tor" => {
let key = config_string("base_node", network, "tor_control_address");
let control_server_address = get_conf_multiaddr(&key)?;
let key = config_string("base_node", network, "tor_control_auth");
let auth_str = get_conf_str(&key)?;
let auth = auth_str
.parse()
.map_err(|err: String| ConfigurationError::new(&key, &err))?;
let key = config_string("base_node", network, "tor_forward_address");
let forward_address = get_conf_multiaddr(&key)?;
let key = config_string("base_node", network, "tor_onion_port");
let onion_port = cfg
.get::<NonZeroU16>(&key)
.map_err(|err| ConfigurationError::new(&key, &err.to_string()))?;
let key = config_string("base_node", network, "tor_socks_address_override");
let socks_address_override = match get_conf_str(&key).ok() {
Some(addr) => Some(
addr.parse::<Multiaddr>()
.map_err(|err| ConfigurationError::new(&key, &err.to_string()))?,
),
None => None,
};
Ok(CommsTransport::TorHiddenService {
control_server_address,
auth,
socks_address_override,
forward_address,
onion_port,
})
},
"socks5" => {
let key = config_string("base_node", network, "socks5_proxy_address");
let proxy_address = get_conf_multiaddr(&key)?;
let key = config_string("base_node", network, "socks5_auth");
let auth_str = get_conf_str(&key)?;
let auth = auth_str
.parse()
.map_err(|err: String| ConfigurationError::new(&key, &err))?;
let key = config_string("base_node", network, "socks5_listener_address");
let listener_address = get_conf_multiaddr(&key)?;
Ok(CommsTransport::Socks5 {
proxy_address,
listener_address,
auth,
})
},
t => Err(ConfigurationError::new(
&transport_key,
&format!("Invalid transport type '{}'", t),
)),
}
}
fn config_string(prefix: &str, network: &str, key: &str) -> String {
format!("{}.{}.{}", prefix, network, key)
}
#[derive(Clone, Debug, PartialEq, Copy)]
pub enum Network {
MainNet,
Rincewind,
}
impl FromStr for Network {
type Err = ConfigurationError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.to_lowercase().as_str() {
"rincewind" => Ok(Self::Rincewind),
"mainnet" => Ok(Self::MainNet),
invalid => Err(ConfigurationError::new(
"network",
&format!("Invalid network option: {}", invalid),
)),
}
}
}
impl Display for Network {
fn fmt(&self, f: &mut Formatter) -> FormatResult {
let msg = match self {
Self::MainNet => "mainnet",
Self::Rincewind => "rincewind",
};
f.write_str(msg)
}
}
#[derive(Debug, Clone)]
pub enum DatabaseType {
LMDB(PathBuf),
Memory,
}
#[derive(Debug, Clone)]
pub enum TorControlAuthentication {
None,
Password(String),
}
fn parse_key_value(s: &str, split_chr: char) -> (String, Option<&str>) {
let mut parts = s.splitn(2, split_chr);
(
parts
.next()
.expect("splitn always emits at least one part")
.to_lowercase(),
parts.next(),
)
}
impl FromStr for TorControlAuthentication {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (auth_type, maybe_value) = parse_key_value(s, '=');
match auth_type.as_str() {
"none" => Ok(TorControlAuthentication::None),
"password" => {
let password = maybe_value.ok_or_else(|| {
"Invalid format for 'password' tor authentication type. It should be in the format \
'password=xxxxxx'."
.to_string()
})?;
Ok(TorControlAuthentication::Password(password.to_string()))
},
s => Err(format!("Invalid tor auth type '{}'", s)),
}
}
}
#[derive(Debug, Clone)]
pub enum SocksAuthentication {
None,
UsernamePassword(String, String),
}
impl FromStr for SocksAuthentication {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (auth_type, maybe_value) = parse_key_value(s, '=');
match auth_type.as_str() {
"none" => Ok(SocksAuthentication::None),
"username_password" => {
let (username, password) = maybe_value
.and_then(|value| {
let (un, pwd) = parse_key_value(value, ':');
pwd.map(|p| (un, p))
})
.ok_or_else(|| {
"Invalid format for 'username-password' socks authentication type. It should be in the format \
'username_password=my_username:xxxxxx'."
.to_string()
})?;
Ok(SocksAuthentication::UsernamePassword(username, password.to_string()))
},
s => Err(format!("Invalid tor auth type '{}'", s)),
}
}
}
#[derive(Debug, Clone)]
pub enum CommsTransport {
Tcp {
listener_address: Multiaddr,
tor_socks_address: Option<Multiaddr>,
tor_socks_auth: Option<SocksAuthentication>,
},
TorHiddenService {
control_server_address: Multiaddr,
socks_address_override: Option<Multiaddr>,
forward_address: Multiaddr,
auth: TorControlAuthentication,
onion_port: NonZeroU16,
},
Socks5 {
proxy_address: Multiaddr,
auth: SocksAuthentication,
listener_address: Multiaddr,
},
}