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,