Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow using a toml config file #429

Merged
merged 5 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,33 @@ The framework is compatible with all major desktop and mobile browsers.
cargo run --release --bin kaspad -- --testnet
```

<details>

<summary>
Use a configuration file
</summary>

```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 \<CLI argument\> = \<value\> 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
```
</details>

<details>

Expand Down
2 changes: 2 additions & 0 deletions kaspad/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"]
Expand Down
90 changes: 53 additions & 37 deletions kaspad/src/args.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<String>,
Expand Down Expand Up @@ -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 <CONFIG_FILE> "Path of config file."))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a config.toml file and tried these params as they are the ones I would use for testnet-11:

testnet=true
utxoindex=true
disable-upnp=true
perf-metrics=true
unsaferpc=true
appdir = "tn11-test"
netsuffix=11
loglevel="info,kaspad_lib::daemon=trace"

appdir seems to be set, but the rest aren't getting set properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, now that I've tested it more thoroughly it appears that defaults take precedence if exist and clap's get_one() always return Some(default) so unwrap_or() didn't work like I'd expect.

I made a kinda hacky fix for it, it's possible that a refactor for the Args struct so that it's auto parsed using clap's Parser derive macro can be a prettier solution but I didn't want make so many changes.

.arg(arg!(-b --appdir <DATA_DIR> "Directory to store data."))
.arg(arg!(--logdir <LOG_DIR> "Directory to log output."))
.arg(arg!(--nologfiles "Disable logging to files."))
Expand Down Expand Up @@ -365,61 +368,74 @@ impl Args {
T: Into<OsString> + 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::<String>("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::<String>("appdir").cloned(),
logdir: m.get_one::<String>("logdir").cloned(),
no_log_files: m.get_one::<bool>("nologfiles").cloned().unwrap_or(defaults.no_log_files),
rpclisten: m.get_one::<ContextualNetAddress>("rpclisten").cloned(),
rpclisten_borsh: m.get_one::<WrpcNetAddress>("rpclisten-borsh").cloned(),
rpclisten_json: m.get_one::<WrpcNetAddress>("rpclisten-json").cloned(),
unsafe_rpc: m.get_one::<bool>("unsaferpc").cloned().unwrap_or(defaults.unsafe_rpc),
appdir: m.get_one::<String>("appdir").cloned().or(defaults.appdir),
logdir: m.get_one::<String>("logdir").cloned().or(defaults.logdir),
no_log_files: arg_match_unwrap_or::<bool>(&m, "nologfiles", defaults.no_log_files),
rpclisten: m.get_one::<ContextualNetAddress>("rpclisten").cloned().or(defaults.rpclisten),
rpclisten_borsh: m.get_one::<WrpcNetAddress>("rpclisten-borsh").cloned().or(defaults.rpclisten_borsh),
rpclisten_json: m.get_one::<WrpcNetAddress>("rpclisten-json").cloned().or(defaults.rpclisten_json),
unsafe_rpc: arg_match_unwrap_or::<bool>(&m, "unsaferpc", defaults.unsafe_rpc),
wrpc_verbose: false,
log_level: m.get_one::<String>("log_level").cloned().unwrap(),
async_threads: m.get_one::<usize>("async_threads").cloned().unwrap_or(defaults.async_threads),
async_threads: arg_match_unwrap_or::<usize>(&m, "async_threads", defaults.async_threads),
connect_peers: m.get_many::<ContextualNetAddress>("connect-peers").unwrap_or_default().copied().collect(),
add_peers: m.get_many::<ContextualNetAddress>("add-peers").unwrap_or_default().copied().collect(),
listen: m.get_one::<ContextualNetAddress>("listen").cloned(),
outbound_target: m.get_one::<usize>("outpeers").cloned().unwrap_or(defaults.outbound_target),
inbound_limit: m.get_one::<usize>("maxinpeers").cloned().unwrap_or(defaults.inbound_limit),
rpc_max_clients: m.get_one::<usize>("rpcmaxclients").cloned().unwrap_or(defaults.rpc_max_clients),
reset_db: m.get_one::<bool>("reset-db").cloned().unwrap_or(defaults.reset_db),
enable_unsynced_mining: m.get_one::<bool>("enable-unsynced-mining").cloned().unwrap_or(defaults.enable_unsynced_mining),
enable_mainnet_mining: m.get_one::<bool>("enable-mainnet-mining").cloned().unwrap_or(defaults.enable_mainnet_mining),
utxoindex: m.get_one::<bool>("utxoindex").cloned().unwrap_or(defaults.utxoindex),
testnet: m.get_one::<bool>("testnet").cloned().unwrap_or(defaults.testnet),
testnet_suffix: m.get_one::<u32>("netsuffix").cloned().unwrap_or(defaults.testnet_suffix),
devnet: m.get_one::<bool>("devnet").cloned().unwrap_or(defaults.devnet),
simnet: m.get_one::<bool>("simnet").cloned().unwrap_or(defaults.simnet),
archival: m.get_one::<bool>("archival").cloned().unwrap_or(defaults.archival),
sanity: m.get_one::<bool>("sanity").cloned().unwrap_or(defaults.sanity),
yes: m.get_one::<bool>("yes").cloned().unwrap_or(defaults.yes),
listen: m.get_one::<ContextualNetAddress>("listen").cloned().or(defaults.listen),
outbound_target: arg_match_unwrap_or::<usize>(&m, "outpeers", defaults.outbound_target),
inbound_limit: arg_match_unwrap_or::<usize>(&m, "maxinpeers", defaults.inbound_limit),
rpc_max_clients: arg_match_unwrap_or::<usize>(&m, "rpcmaxclients", defaults.rpc_max_clients),
reset_db: arg_match_unwrap_or::<bool>(&m, "reset-db", defaults.reset_db),
enable_unsynced_mining: arg_match_unwrap_or::<bool>(&m, "enable-unsynced-mining", defaults.enable_unsynced_mining),
enable_mainnet_mining: arg_match_unwrap_or::<bool>(&m, "enable-mainnet-mining", defaults.enable_mainnet_mining),
utxoindex: arg_match_unwrap_or::<bool>(&m, "utxoindex", defaults.utxoindex),
testnet: arg_match_unwrap_or::<bool>(&m, "testnet", defaults.testnet),
testnet_suffix: arg_match_unwrap_or::<u32>(&m, "netsuffix", defaults.testnet_suffix),
devnet: arg_match_unwrap_or::<bool>(&m, "devnet", defaults.devnet),
simnet: arg_match_unwrap_or::<bool>(&m, "simnet", defaults.simnet),
archival: arg_match_unwrap_or::<bool>(&m, "archival", defaults.archival),
sanity: arg_match_unwrap_or::<bool>(&m, "sanity", defaults.sanity),
yes: arg_match_unwrap_or::<bool>(&m, "yes", defaults.yes),
user_agent_comments: m.get_many::<String>("user_agent_comments").unwrap_or_default().cloned().collect(),
externalip: m.get_one::<ContextualNetAddress>("externalip").cloned(),
perf_metrics: m.get_one::<bool>("perf-metrics").cloned().unwrap_or(defaults.perf_metrics),
perf_metrics_interval_sec: m
.get_one::<u64>("perf-metrics-interval-sec")
.cloned()
.unwrap_or(defaults.perf_metrics_interval_sec),
perf_metrics: arg_match_unwrap_or::<bool>(&m, "perf-metrics", defaults.perf_metrics),
perf_metrics_interval_sec: arg_match_unwrap_or::<u64>(&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::<bool>("disable-upnp").cloned().unwrap_or(defaults.disable_upnp),
disable_dns_seeding: m.get_one::<bool>("nodnsseed").cloned().unwrap_or(defaults.disable_dns_seeding),
disable_grpc: m.get_one::<bool>("nogrpc").cloned().unwrap_or(defaults.disable_grpc),
ram_scale: m.get_one::<f64>("ram-scale").cloned().unwrap_or(defaults.ram_scale),
disable_upnp: arg_match_unwrap_or::<bool>(&m, "disable-upnp", defaults.disable_upnp),
disable_dns_seeding: arg_match_unwrap_or::<bool>(&m, "nodnsseed", defaults.disable_dns_seeding),
disable_grpc: arg_match_unwrap_or::<bool>(&m, "nogrpc", defaults.disable_grpc),
ram_scale: arg_match_unwrap_or::<f64>(&m, "ram-scale", defaults.ram_scale),

#[cfg(feature = "devnet-prealloc")]
num_prealloc_utxos: m.get_one::<u64>("num-prealloc-utxos").cloned(),
#[cfg(feature = "devnet-prealloc")]
prealloc_address: m.get_one::<String>("prealloc-address").cloned(),
#[cfg(feature = "devnet-prealloc")]
prealloc_amount: m.get_one::<u64>("prealloc-amount").cloned().unwrap_or(defaults.prealloc_amount),
prealloc_amount: arg_match_unwrap_or::<u64>(&m, "prealloc-amount", defaults.prealloc_amount),
};
Ok(args)
}
}

use clap::parser::ValueSource::DefaultValue;
use std::marker::{Send, Sync};
fn arg_match_unwrap_or<T: Clone + Send + Sync + 'static>(m: &clap::ArgMatches, arg_id: &str, default: T) -> T {
m.get_one::<T>(arg_id).cloned().filter(|_| m.value_source(arg_id) != Some(DefaultValue)).unwrap_or(default)
}

/*

-V, --version Display version information and exit
Expand Down
4 changes: 3 additions & 1 deletion rpc/wrpc/server/src/address.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Loading