diff --git a/README.md b/README.md index 8d08d8041..9c1c84737 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,33 @@ The framework is compatible with all major desktop and mobile browsers. cargo run --release --bin kaspad -- --testnet ``` +
+ + +Use a configuration file + + + ```bash +cargo run --release --bin kaspad -- --configfile /path/to/configfile.toml +# or +cargo run --release --bin kaspad -- -C /path/to/configfile.toml + ``` + The config file should be a list of \ = \ separated by newlines. + For example: + ``` +testnet=true +utxoindex=false +disable-upnp=true +perf-metrics=true +appdir="some-dir" +testnet-suffix=11 + ``` +note that some arguments have no space between words and some use kebab-case, pass the --help flag to view all possible arguments + + ```bash +cargo run --release --bin kaspad -- --help + ``` +
diff --git a/kaspad/Cargo.toml b/kaspad/Cargo.toml index deaef36a4..36b242871 100644 --- a/kaspad/Cargo.toml +++ b/kaspad/Cargo.toml @@ -41,6 +41,7 @@ kaspa-wrpc-server.workspace = true async-channel.workspace = true clap.workspace = true dhat = { workspace = true, optional = true } +serde.workspace = true dirs.workspace = true futures-util.workspace = true log.workspace = true @@ -51,6 +52,7 @@ tempfile.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["rt", "macros", "rt-multi-thread"] } workflow-log.workspace = true +toml = "0.8.10" [features] heap = ["dhat", "kaspa-alloc/heap"] diff --git a/kaspad/src/args.rs b/kaspad/src/args.rs index 1e29092c7..255612c72 100644 --- a/kaspad/src/args.rs +++ b/kaspad/src/args.rs @@ -1,16 +1,17 @@ use clap::ArgAction; #[allow(unused)] use clap::{arg, command, Arg, Command}; - #[cfg(feature = "devnet-prealloc")] use kaspa_addresses::Address; #[cfg(feature = "devnet-prealloc")] use kaspa_consensus_core::tx::{TransactionOutpoint, UtxoEntry}; #[cfg(feature = "devnet-prealloc")] use kaspa_txscript::pay_to_address_script; -use std::ffi::OsString; +use serde::Deserialize; #[cfg(feature = "devnet-prealloc")] use std::sync::Arc; +use std::{ffi::OsString, fs}; +use toml::from_str; use kaspa_consensus_core::{ config::Config, @@ -22,7 +23,8 @@ use kaspa_core::kaspad_env::version; use kaspa_utils::networking::ContextualNetAddress; use kaspa_wrpc_server::address::WrpcNetAddress; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize)] +#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] pub struct Args { // NOTE: it is best if property names match config file fields pub appdir: Option, @@ -176,6 +178,7 @@ pub fn cli() -> Command { let cmd = Command::new("kaspad") .about(format!("{} (rusty-kaspa) v{}", env!("CARGO_PKG_DESCRIPTION"), version())) .version(env!("CARGO_PKG_VERSION")) + .arg(arg!(-C --configfile "Path of config file.")) .arg(arg!(-b --appdir "Directory to store data.")) .arg(arg!(--logdir "Directory to log output.")) .arg(arg!(--nologfiles "Disable logging to files.")) @@ -365,61 +368,74 @@ impl Args { T: Into + Clone, { let m: clap::ArgMatches = cli().try_get_matches_from(itr)?; - let defaults: Args = Default::default(); + let mut defaults: Args = Default::default(); + + if let Some(config_file) = m.get_one::("configfile") { + let config_str = fs::read_to_string(config_file)?; + defaults = from_str(&config_str).map_err(|toml_error| { + clap::Error::raw( + clap::error::ErrorKind::ValueValidation, + format!("failed parsing config file, reason: {}", toml_error.message()), + ) + })?; + } let args = Args { - appdir: m.get_one::("appdir").cloned(), - logdir: m.get_one::("logdir").cloned(), - no_log_files: m.get_one::("nologfiles").cloned().unwrap_or(defaults.no_log_files), - rpclisten: m.get_one::("rpclisten").cloned(), - rpclisten_borsh: m.get_one::("rpclisten-borsh").cloned(), - rpclisten_json: m.get_one::("rpclisten-json").cloned(), - unsafe_rpc: m.get_one::("unsaferpc").cloned().unwrap_or(defaults.unsafe_rpc), + appdir: m.get_one::("appdir").cloned().or(defaults.appdir), + logdir: m.get_one::("logdir").cloned().or(defaults.logdir), + no_log_files: arg_match_unwrap_or::(&m, "nologfiles", defaults.no_log_files), + rpclisten: m.get_one::("rpclisten").cloned().or(defaults.rpclisten), + rpclisten_borsh: m.get_one::("rpclisten-borsh").cloned().or(defaults.rpclisten_borsh), + rpclisten_json: m.get_one::("rpclisten-json").cloned().or(defaults.rpclisten_json), + unsafe_rpc: arg_match_unwrap_or::(&m, "unsaferpc", defaults.unsafe_rpc), wrpc_verbose: false, log_level: m.get_one::("log_level").cloned().unwrap(), - async_threads: m.get_one::("async_threads").cloned().unwrap_or(defaults.async_threads), + async_threads: arg_match_unwrap_or::(&m, "async_threads", defaults.async_threads), connect_peers: m.get_many::("connect-peers").unwrap_or_default().copied().collect(), add_peers: m.get_many::("add-peers").unwrap_or_default().copied().collect(), - listen: m.get_one::("listen").cloned(), - outbound_target: m.get_one::("outpeers").cloned().unwrap_or(defaults.outbound_target), - inbound_limit: m.get_one::("maxinpeers").cloned().unwrap_or(defaults.inbound_limit), - rpc_max_clients: m.get_one::("rpcmaxclients").cloned().unwrap_or(defaults.rpc_max_clients), - reset_db: m.get_one::("reset-db").cloned().unwrap_or(defaults.reset_db), - enable_unsynced_mining: m.get_one::("enable-unsynced-mining").cloned().unwrap_or(defaults.enable_unsynced_mining), - enable_mainnet_mining: m.get_one::("enable-mainnet-mining").cloned().unwrap_or(defaults.enable_mainnet_mining), - utxoindex: m.get_one::("utxoindex").cloned().unwrap_or(defaults.utxoindex), - testnet: m.get_one::("testnet").cloned().unwrap_or(defaults.testnet), - testnet_suffix: m.get_one::("netsuffix").cloned().unwrap_or(defaults.testnet_suffix), - devnet: m.get_one::("devnet").cloned().unwrap_or(defaults.devnet), - simnet: m.get_one::("simnet").cloned().unwrap_or(defaults.simnet), - archival: m.get_one::("archival").cloned().unwrap_or(defaults.archival), - sanity: m.get_one::("sanity").cloned().unwrap_or(defaults.sanity), - yes: m.get_one::("yes").cloned().unwrap_or(defaults.yes), + listen: m.get_one::("listen").cloned().or(defaults.listen), + outbound_target: arg_match_unwrap_or::(&m, "outpeers", defaults.outbound_target), + inbound_limit: arg_match_unwrap_or::(&m, "maxinpeers", defaults.inbound_limit), + rpc_max_clients: arg_match_unwrap_or::(&m, "rpcmaxclients", defaults.rpc_max_clients), + reset_db: arg_match_unwrap_or::(&m, "reset-db", defaults.reset_db), + enable_unsynced_mining: arg_match_unwrap_or::(&m, "enable-unsynced-mining", defaults.enable_unsynced_mining), + enable_mainnet_mining: arg_match_unwrap_or::(&m, "enable-mainnet-mining", defaults.enable_mainnet_mining), + utxoindex: arg_match_unwrap_or::(&m, "utxoindex", defaults.utxoindex), + testnet: arg_match_unwrap_or::(&m, "testnet", defaults.testnet), + testnet_suffix: arg_match_unwrap_or::(&m, "netsuffix", defaults.testnet_suffix), + devnet: arg_match_unwrap_or::(&m, "devnet", defaults.devnet), + simnet: arg_match_unwrap_or::(&m, "simnet", defaults.simnet), + archival: arg_match_unwrap_or::(&m, "archival", defaults.archival), + sanity: arg_match_unwrap_or::(&m, "sanity", defaults.sanity), + yes: arg_match_unwrap_or::(&m, "yes", defaults.yes), user_agent_comments: m.get_many::("user_agent_comments").unwrap_or_default().cloned().collect(), externalip: m.get_one::("externalip").cloned(), - perf_metrics: m.get_one::("perf-metrics").cloned().unwrap_or(defaults.perf_metrics), - perf_metrics_interval_sec: m - .get_one::("perf-metrics-interval-sec") - .cloned() - .unwrap_or(defaults.perf_metrics_interval_sec), + perf_metrics: arg_match_unwrap_or::(&m, "perf-metrics", defaults.perf_metrics), + perf_metrics_interval_sec: arg_match_unwrap_or::(&m, "perf-metrics-interval-sec", defaults.perf_metrics_interval_sec), // Note: currently used programmatically by benchmarks and not exposed to CLI users block_template_cache_lifetime: defaults.block_template_cache_lifetime, - disable_upnp: m.get_one::("disable-upnp").cloned().unwrap_or(defaults.disable_upnp), - disable_dns_seeding: m.get_one::("nodnsseed").cloned().unwrap_or(defaults.disable_dns_seeding), - disable_grpc: m.get_one::("nogrpc").cloned().unwrap_or(defaults.disable_grpc), - ram_scale: m.get_one::("ram-scale").cloned().unwrap_or(defaults.ram_scale), + disable_upnp: arg_match_unwrap_or::(&m, "disable-upnp", defaults.disable_upnp), + disable_dns_seeding: arg_match_unwrap_or::(&m, "nodnsseed", defaults.disable_dns_seeding), + disable_grpc: arg_match_unwrap_or::(&m, "nogrpc", defaults.disable_grpc), + ram_scale: arg_match_unwrap_or::(&m, "ram-scale", defaults.ram_scale), #[cfg(feature = "devnet-prealloc")] num_prealloc_utxos: m.get_one::("num-prealloc-utxos").cloned(), #[cfg(feature = "devnet-prealloc")] prealloc_address: m.get_one::("prealloc-address").cloned(), #[cfg(feature = "devnet-prealloc")] - prealloc_amount: m.get_one::("prealloc-amount").cloned().unwrap_or(defaults.prealloc_amount), + prealloc_amount: arg_match_unwrap_or::(&m, "prealloc-amount", defaults.prealloc_amount), }; Ok(args) } } +use clap::parser::ValueSource::DefaultValue; +use std::marker::{Send, Sync}; +fn arg_match_unwrap_or(m: &clap::ArgMatches, arg_id: &str, default: T) -> T { + m.get_one::(arg_id).cloned().filter(|_| m.value_source(arg_id) != Some(DefaultValue)).unwrap_or(default) +} + /* -V, --version Display version information and exit diff --git a/rpc/wrpc/server/src/address.rs b/rpc/wrpc/server/src/address.rs index 59f2e0c52..7dac4d75d 100644 --- a/rpc/wrpc/server/src/address.rs +++ b/rpc/wrpc/server/src/address.rs @@ -1,9 +1,11 @@ use crate::service::WrpcEncoding; use kaspa_consensus_core::network::NetworkType; use kaspa_utils::networking::ContextualNetAddress; +use serde::Deserialize; use std::{net::AddrParseError, str::FromStr}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize)] +#[serde(rename = "lowercase")] pub enum WrpcNetAddress { Default, Public,