diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs index ab966c41ba..932d7515a6 100644 --- a/common/config/src/lib.rs +++ b/common/config/src/lib.rs @@ -73,7 +73,7 @@ where P: AsRef, { let path = path.as_ref(); - log::debug!("trying to save config file to {}", path.display()); + log::info!("saving config file to {}", path.display()); if let Some(parent) = path.parent() { create_dir_all(parent)?; diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 236c9d9f1f..13e8f21024 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -3,6 +3,7 @@ use crate::var_names; use crate::{DenomDetails, ValidatorDetails}; +use std::str::FromStr; pub const NETWORK_NAME: &str = "mainnet"; @@ -60,6 +61,12 @@ pub fn read_var_if_not_default(var: &str) -> Option { } } +pub fn read_parsed_var_if_not_default(var: &str) -> Option> { + read_var_if_not_default(var) + .as_deref() + .map(FromStr::from_str) +} + pub fn export_to_env() { set_var_to_default(var_names::CONFIGURED, "true"); set_var_to_default(var_names::NETWORK_NAME, NETWORK_NAME); diff --git a/nym-api/src/coconut/comm.rs b/nym-api/src/coconut/comm.rs index a18d10588e..5bceaa7627 100644 --- a/nym-api/src/coconut/comm.rs +++ b/nym-api/src/coconut/comm.rs @@ -3,12 +3,12 @@ use crate::coconut::error::Result; use crate::nyxd; -use nym_coconut_dkg_common::types::EpochId; +use crate::support::nyxd::ClientInner; +use nym_coconut_dkg_common::types::{Epoch, EpochId}; use nym_coconut_interface::VerificationKey; use nym_credentials::coconut::utils::obtain_aggregate_verification_key; use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; -use nym_validator_client::DirectSigningHttpRpcNyxdClient; use std::cmp::min; use std::collections::HashMap; use std::ops::Deref; @@ -40,9 +40,7 @@ impl CachedEpoch { self.valid_until > OffsetDateTime::now_utc() } - async fn update(&mut self, client: &DirectSigningHttpRpcNyxdClient) -> Result<()> { - let epoch = client.get_current_epoch().await?; - + async fn update(&mut self, epoch: Epoch) -> Result<()> { let now = OffsetDateTime::now_utc(); let state_end = OffsetDateTime::from_unix_timestamp(epoch.finish_timestamp.seconds() as i64).unwrap(); @@ -84,8 +82,13 @@ impl APICommunicationChannel for QueryCommunicationChannel { // update cache drop(guard); let mut guard = self.cached_epoch.write().await; - let client = self.nyxd_client.0.read().await; - guard.update(&client).await?; + + let epoch = match self.nyxd_client.read().await.deref() { + ClientInner::Query(client) => client.get_current_epoch().await?, + ClientInner::Signing(client) => client.get_current_epoch().await?, + }; + + guard.update(epoch).await?; return Ok(guard.current_epoch_id); } @@ -96,8 +99,11 @@ impl APICommunicationChannel for QueryCommunicationChannel { } let mut guard = self.epoch_keys.write().await; - let client = self.nyxd_client.0.read().await; - let coconut_api_clients = all_coconut_api_clients(client.deref(), epoch_id).await?; + let coconut_api_clients = match self.nyxd_client.read().await.deref() { + ClientInner::Query(client) => all_coconut_api_clients(client, epoch_id).await?, + ClientInner::Signing(client) => all_coconut_api_clients(client, epoch_id).await?, + }; + let vk = obtain_aggregate_verification_key(&coconut_api_clients).await?; guard.insert(epoch_id, vk.clone()); diff --git a/nym-api/src/coconut/dkg/controller.rs b/nym-api/src/coconut/dkg/controller.rs index b85597a51a..89275400ca 100644 --- a/nym-api/src/coconut/dkg/controller.rs +++ b/nym-api/src/coconut/dkg/controller.rs @@ -13,7 +13,7 @@ use crate::coconut::dkg::{ use crate::coconut::keypair::KeyPair as CoconutKeyPair; use crate::nyxd; use crate::support::config; -use anyhow::Result; +use anyhow::{bail, Result}; use nym_coconut_dkg_common::types::EpochState; use nym_dkg::bte::keys::KeyPair as DkgKeyPair; use nym_task::{TaskClient, TaskManager}; @@ -53,6 +53,10 @@ impl DkgController { coconut_keypair: CoconutKeyPair, rng: R, ) -> Result { + let Some(announce_address) = &config.announce_address else { + bail!("can't start a DKG controller without specifying an announce address!") + }; + let dkg_keypair = nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( &config.storage_paths.decryption_key_path, &config.storage_paths.public_key_with_proof_path, @@ -76,7 +80,7 @@ impl DkgController { state: State::new( config.storage_paths.dkg_persistent_state_path.clone(), persistent_state, - config.announce_address.clone(), + announce_address.clone(), dkg_keypair, coconut_keypair, ), diff --git a/nym-api/src/main.rs b/nym-api/src/main.rs index e63e22167f..e28d0ad37e 100644 --- a/nym-api/src/main.rs +++ b/nym-api/src/main.rs @@ -9,7 +9,7 @@ use crate::network::models::NetworkDetails; use crate::node_describe_cache::DescribedNodes; use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater; use crate::support::caching::cache::SharedCache; -use crate::support::cli::{self, Commands}; +use crate::support::cli; use crate::support::config::Config; use crate::support::storage; use crate::support::storage::NymApiStorage; @@ -21,11 +21,11 @@ use clap::Parser; use coconut::dkg::controller::DkgController; use node_status_api::NodeStatusCache; use nym_bin_common::logging::setup_logging; +use nym_config::defaults::NymNetworkDetails; use nym_contract_cache::cache::NymContractCache; use nym_sphinx::receiver::SphinxMessageReceiver; use nym_task::TaskManager; use rand::rngs::OsRng; -use std::error::Error; use support::{http, nyxd}; mod circulating_supply_api; @@ -46,7 +46,7 @@ struct ShutdownHandles { } #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), anyhow::Error> { println!("Starting nym api..."); cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] { @@ -59,24 +59,13 @@ async fn main() -> Result<(), Box> { trace!("{:#?}", args); setup_env(args.config_env_file.as_ref()); - - let command = args.command.unwrap_or(Commands::Run(Box::new(args.run))); - - match command { - Commands::BuildInfo(m) => { - cli::build_info::execute(m); - Ok(()) - } - Commands::Run(m) => cli::run::execute(*m).await, - } + args.execute().await } -async fn start_nym_api_tasks( - config: Config, -) -> Result> { +async fn start_nym_api_tasks(config: Config) -> anyhow::Result { let nyxd_client = nyxd::Client::new(&config); let connected_nyxd = config.get_nyxd_url(); - let nym_network_details = config.get_network_details(); + let nym_network_details = NymNetworkDetails::new_from_env(); let network_details = NetworkDetails::new(connected_nyxd.to_string(), nym_network_details); let coconut_keypair = coconut::keypair::KeyPair::new(); diff --git a/nym-api/src/nym_contract_cache/cache/refresher.rs b/nym-api/src/nym_contract_cache/cache/refresher.rs index bc99ba299f..ef94eec15d 100644 --- a/nym-api/src/nym_contract_cache/cache/refresher.rs +++ b/nym-api/src/nym_contract_cache/cache/refresher.rs @@ -47,44 +47,52 @@ impl NymContractCacheRefresher { } async fn get_nym_contracts_info(&self) -> Result { + use crate::query_guard; + let mut updated = HashMap::new(); let client_guard = self.nyxd_client.read().await; - let mixnet = client_guard.mixnet_contract_address(); - let vesting = client_guard.vesting_contract_address(); - let name_service = client_guard.name_service_contract_address(); - let service_provider = client_guard.service_provider_contract_address(); - let coconut_bandwidth = client_guard.coconut_bandwidth_contract_address(); - let coconut_dkg = client_guard.dkg_contract_address(); - let group = client_guard.group_contract_address(); - let multisig = client_guard.multisig_contract_address(); + let mixnet = query_guard!(client_guard, mixnet_contract_address()); + let vesting = query_guard!(client_guard, vesting_contract_address()); + let name_service = query_guard!(client_guard, name_service_contract_address()); + let service_provider = query_guard!(client_guard, service_provider_contract_address()); + let coconut_bandwidth = query_guard!(client_guard, coconut_bandwidth_contract_address()); + let coconut_dkg = query_guard!(client_guard, dkg_contract_address()); + let group = query_guard!(client_guard, group_contract_address()); + let multisig = query_guard!(client_guard, multisig_contract_address()); // get cw2 versions - let mixnet_cw2_future = client_guard.get_mixnet_contract_cw2_version(); - let vesting_cw2_future = client_guard.get_vesting_contract_cw2_version(); - let service_provider_cw2_future = client_guard.get_name_service_contract_cw2_version(); - let name_service_cw2_future = client_guard.get_name_service_contract_cw2_version(); + let mixnet_cw2_future = query_guard!(client_guard, get_mixnet_contract_cw2_version()); + let vesting_cw2_future = query_guard!(client_guard, get_vesting_contract_cw2_version()); + let service_provider_cw2_future = + query_guard!(client_guard, get_name_service_contract_cw2_version()); + let name_service_cw2_future = + query_guard!(client_guard, get_name_service_contract_cw2_version()); // group and multisig contract save that information in their storage but don't expose it via queries // so a temporary workaround... let multisig_cw2 = if let Some(multisig_contract) = multisig { - client_guard - .query_contract_raw(multisig_contract, b"contract_info".to_vec()) - .await - .map(|r| serde_json::from_slice(&r).ok()) - .ok() - .flatten() + query_guard!( + client_guard, + query_contract_raw(multisig_contract, b"contract_info".to_vec()) + .await + .map(|r| serde_json::from_slice(&r).ok()) + .ok() + .flatten() + ) } else { None }; let group_cw2 = if let Some(group_contract) = group { - client_guard - .query_contract_raw(group_contract, b"contract_info".to_vec()) - .await - .map(|r| serde_json::from_slice(&r).ok()) - .ok() - .flatten() + query_guard!( + client_guard, + query_contract_raw(group_contract, b"contract_info".to_vec()) + .await + .map(|r| serde_json::from_slice(&r).ok()) + .ok() + .flatten() + ) } else { None }; @@ -98,10 +106,12 @@ impl NymContractCacheRefresher { .await; // get detailed build info - let mixnet_detailed_future = client_guard.get_mixnet_contract_version(); - let vesting_detailed_future = client_guard.get_vesting_contract_version(); - let service_provider_detailed_future = client_guard.get_sp_contract_version(); - let name_service_detailed_future = client_guard.get_name_service_contract_version(); + let mixnet_detailed_future = query_guard!(client_guard, get_mixnet_contract_version()); + let vesting_detailed_future = query_guard!(client_guard, get_vesting_contract_version()); + let service_provider_detailed_future = + query_guard!(client_guard, get_sp_contract_version()); + let name_service_detailed_future = + query_guard!(client_guard, get_name_service_contract_version()); let mut build_info = join_all(vec![ mixnet_detailed_future, diff --git a/nym-api/src/support/cli/build_info.rs b/nym-api/src/support/cli/build_info.rs index 566d59aa15..5189dbc0f6 100644 --- a/nym-api/src/support/cli/build_info.rs +++ b/nym-api/src/support/cli/build_info.rs @@ -10,6 +10,7 @@ pub(crate) struct Args { output: OutputFormat, } -pub(crate) fn execute(args: Args) { - println!("{}", args.output.format(&bin_info_owned!())) +pub(crate) fn execute(args: Args) -> anyhow::Result<()> { + println!("{}", args.output.format(&bin_info_owned!())); + Ok(()) } diff --git a/nym-api/src/support/cli/init.rs b/nym-api/src/support/cli/init.rs new file mode 100644 index 0000000000..10ce41b6f9 --- /dev/null +++ b/nym-api/src/support/cli/init.rs @@ -0,0 +1,79 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::support::config::default_config_filepath; +use crate::support::config::helpers::initialise_new; +use anyhow::bail; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + /// Id of the nym-api we want to initialise. if unspecified, a default value will be used. + /// default: "default" + #[clap(long, default_value = "default")] + pub(crate) id: String, + + /// Specifies whether network monitoring is enabled on this API + /// default: false + #[clap(short = 'm', long)] + pub(crate) enable_monitor: bool, + + /// Specifies whether network rewarding is enabled on this API + /// default: false + #[clap(short = 'r', long, requires = "enable_monitor", requires = "mnemonic")] + pub(crate) enable_rewarding: bool, + + /// Endpoint to nyxd instance used for contract information. + /// default: http://localhost:26657 + #[clap(long)] + pub(crate) nyxd_validator: Option, + + /// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions + /// default: None + #[clap(long)] + pub(crate) mnemonic: Option, + + /// Flag to indicate whether credential signer authority is enabled on this API + /// default: false + #[clap( + long, + requires = "mnemonic", + requires = "announce_address", + alias = "enable_coconut" + )] + pub(crate) enable_zk_nym: bool, + + /// Announced address that is going to be put in the DKG contract where zk-nym clients will connect + /// to obtain their credentials + /// default: None + #[clap(long)] + pub(crate) announce_address: Option, + + /// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement + #[clap(long, requires = "enable_monitor")] + pub(crate) monitor_credentials_mode: bool, + // #[clap(short, long, default_value_t = OutputFormat::default())] + // output: OutputFormat, +} + +pub(crate) async fn execute(args: Args) -> anyhow::Result<()> { + eprintln!("initialising nym-api..."); + + // let output = args.output; + + let config_path = default_config_filepath(&args.id); + if config_path.exists() { + // don't bother with attempting to override some of the data and preserving the rest of it + // if the config exists. + // this situation should never occur under normal circumstances, so it's up to the user to deal with it + bail!("there already exists a configuration file at '{}'. If you intend to replace it, you need to manually remove it first. Make sure to make backup of any keys and datastores first.", config_path.display()) + } + + let config = initialise_new(&args.id)?; + // args take precedence over env + config + .override_with_env() + .override_with_args(args) + .try_save()?; + + Ok(()) +} diff --git a/nym-api/src/support/cli/mod.rs b/nym-api/src/support/cli/mod.rs index 2427b5e7c8..2ea7738673 100644 --- a/nym-api/src/support/cli/mod.rs +++ b/nym-api/src/support/cli/mod.rs @@ -1,31 +1,22 @@ // Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use super::config::Config; -use crate::support::config::default_config_filepath; -use crate::support::config::helpers::{initialise_new, try_load_current_config}; -use ::nym_config::defaults::var_names::{MIXNET_CONTRACT_ADDRESS, VESTING_CONTRACT_ADDRESS}; -use anyhow::{bail, Result}; +use anyhow::Result; use clap::{Parser, Subcommand}; -use lazy_static::lazy_static; use nym_bin_common::bin_info; -use nym_config::defaults::var_names::NYXD; -use nym_config::OptionalSet; +use std::sync::OnceLock; pub(crate) mod build_info; +pub(crate) mod init; pub(crate) mod run; -lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); -} - // Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { - &PRETTY_BUILD_INFORMATION + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } #[derive(Parser, Debug)] -#[command(args_conflicts_with_subcommands = true)] #[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] pub(crate) struct Cli { /// Path pointing to an env file that configures the Nym API. @@ -33,109 +24,27 @@ pub(crate) struct Cli { pub(crate) config_env_file: Option, #[clap(subcommand)] - pub(crate) command: Option, + pub(crate) command: Commands, +} - // this shouldn't really be here, but we don't want to break backwards compat - #[clap(flatten)] - pub(crate) run: run::Args, +impl Cli { + pub(crate) async fn execute(self) -> Result<(), anyhow::Error> { + match self.command { + Commands::Init(args) => init::execute(args).await, + Commands::Run(args) => run::execute(args).await, + Commands::BuildInfo(args) => build_info::execute(args), + } + } } #[derive(Subcommand, Debug)] pub(crate) enum Commands { + /// Initialise a Nym Api instance with persistent config.toml file. + Init(init::Args), + /// Run the Nym Api with provided configuration optionally overriding set parameters - Run(Box), + Run(run::Args), /// Show build information of this binary BuildInfo(build_info::Args), } - -pub(crate) fn override_config(config: Config, args: run::Args) -> Config { - config - .with_optional_env( - Config::with_custom_nyxd_validator, - args.nyxd_validator, - NYXD, - ) - .with_optional_env( - Config::with_custom_mixnet_contract, - args.mixnet_contract, - MIXNET_CONTRACT_ADDRESS, - ) - .with_optional_env( - Config::with_custom_vesting_contract, - args.vesting_contract, - VESTING_CONTRACT_ADDRESS, - ) - .with_optional(Config::with_mnemonic, args.mnemonic) - .with_optional( - Config::with_minimum_interval_monitor_threshold, - args.monitor_threshold, - ) - .with_optional( - Config::with_min_mixnode_reliability, - args.min_mixnode_reliability, - ) - .with_optional( - Config::with_min_gateway_reliability, - args.min_gateway_reliability, - ) - .with_optional(Config::with_network_monitor_enabled, args.enable_monitor) - .with_optional(Config::with_rewarding_enabled, args.enable_rewarding) - .with_optional(Config::with_ephemera_enabled, args.enable_ephemera) - .with_optional( - Config::with_disabled_credentials_mode, - args.enabled_credentials_mode.map(|b| !b), - ) - .with_optional(Config::with_announce_address, args.announce_address) - .with_optional(Config::with_coconut_signer_enabled, args.enable_coconut) - .with_optional(Config::with_ephemera_ip, args.ephemera_args.ephemera_ip) - .with_optional( - Config::with_ephemera_protocol_port, - args.ephemera_args.ephemera_protocol_port, - ) - .with_optional( - Config::with_ephemera_websocket_port, - args.ephemera_args.ephemera_websocket_port, - ) - .with_optional( - Config::with_ephemera_http_api_port, - args.ephemera_args.ephemera_http_api_port, - ) -} - -pub(crate) fn build_config(args: run::Args) -> Result { - let id = match &args.id { - Some(id) => id.clone(), - None => { - error!("--id argument must be provided to run nym-api"); - bail!("--id argument must be provided to run nym-api") - } - }; - - // try to load config from the file, if it doesn't exist, use default values - let config = match try_load_current_config(&id) { - Ok(cfg) => cfg, - Err(err) => { - let config_path = default_config_filepath(&id); - warn!( - "Could not load the configuration file from {}: {err}. Either the file did not exist or was malformed. Using the default values instead", - config_path.display() - ); - - initialise_new(&id)? - } - }; - - let mut config = override_config(config, args); - // since we have no proper `init`, we have to do id check here: - let made_new_keys = config - .base - .storage_paths - .generate_identity_if_missing(&config.base.id)?; - - if made_new_keys { - config.save_to_default_location()? - } - - Ok(config) -} diff --git a/nym-api/src/support/cli/run.rs b/nym-api/src/support/cli/run.rs index cb98eab5c4..549438d095 100644 --- a/nym-api/src/support/cli/run.rs +++ b/nym-api/src/support/cli/run.rs @@ -2,114 +2,68 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::start_nym_api_tasks; -use crate::support::cli::build_config; -use nym_validator_client::nyxd; -use std::error::Error; - -// explicitly defined custom parser (as opposed to just using -// #[arg(value_parser = clap::value_parser!(u8).range(0..100))] -// for better error message -fn threshold_in_range(s: &str) -> Result { - let threshold: usize = s - .parse() - .map_err(|_| format!("`{s}` isn't a valid threshold number"))?; - if threshold > 100 { - Err(format!("{threshold} is not within the range 0-100")) - } else { - Ok(threshold as u8) - } -} +use crate::support::config::helpers::try_load_current_config; +use anyhow::bail; #[derive(clap::Args, Debug)] pub(crate) struct Args { - /// Id of the nym-api we want to run - #[clap(long)] - // ugh. we had to make it optional in case somebody wanted to run `build-info` - pub(crate) id: Option, + /// Id of the nym-api we want to run.if unspecified, a default value will be used. + /// default: "default" + #[clap(long, default_value = "default")] + pub(crate) id: String, /// Specifies whether network monitoring is enabled on this API + /// default: None - config value will be used instead #[clap(short = 'm', long)] pub(crate) enable_monitor: Option, /// Specifies whether network rewarding is enabled on this API + /// default: None - config value will be used instead #[clap(short = 'r', long, requires = "enable_monitor", requires = "mnemonic")] pub(crate) enable_rewarding: Option, - /// Specifies whether ephemera is used to aggregate monitor data on this API - #[clap(short = 'e', long, requires = "enable_monitor")] - pub(crate) enable_ephemera: Option, - - /// Endpoint to nyxd instance from which the monitor will grab nodes to test + /// Endpoint to nyxd instance used for contract information. + /// default: None - config value will be used instead #[clap(long)] pub(crate) nyxd_validator: Option, - /// Address of the mixnet contract managing the network - #[clap(long)] - pub(crate) mixnet_contract: Option, - - /// Address of the vesting contract holding locked tokens - #[clap(long)] - pub(crate) vesting_contract: Option, - - /// Mnemonic of the network monitor used for rewarding operators - // even though we're currently converting the mnemonic to string (and then back to the concrete type) - // at least we're getting immediate validation when passing the arguments + /// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions + /// default: None - config value will be used instead #[clap(long)] pub(crate) mnemonic: Option, - /// Specifies whether a config file based on provided arguments should be saved to a file - #[clap(short = 'w', long)] - pub(crate) save_config: bool, - - /// Specifies the minimum percentage of monitor test run data present in order to distribute rewards for given interval. - #[clap(long, value_parser = threshold_in_range)] - pub(crate) monitor_threshold: Option, - - /// Mixnodes with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set. - #[clap(long, value_parser = threshold_in_range)] - pub(crate) min_mixnode_reliability: Option, - - /// Gateways with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set. - #[clap(long, value_parser = threshold_in_range)] - pub(crate) min_gateway_reliability: Option, - - /// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement - #[clap(long)] - pub(crate) enabled_credentials_mode: Option, - - /// Announced address where coconut clients will connect. - #[clap(long, hide = true)] - pub(crate) announce_address: Option, - /// Flag to indicate whether coconut signer authority is enabled on this API + /// default: None - config value will be used instead #[clap( long, requires = "mnemonic", requires = "announce_address", - hide = true + alias = "enable_coconut" )] - pub(crate) enable_coconut: Option, + pub(crate) enable_zk_nym: Option, - /// Ephemera configuration arguments. - #[command(flatten)] - pub(crate) ephemera_args: ephemera::cli::init::Cmd, + /// Announced address that is going to be put in the DKG contract where zk-nym clients will connect + /// to obtain their credentials + /// default: None - config value will be used instead + #[clap(long)] + pub(crate) announce_address: Option, + + /// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement + /// default: None - config value will be used instead + #[clap(long)] + pub(crate) monitor_credentials_mode: Option, + // /// Ephemera configuration arguments. + // #[command(flatten)] + // pub(crate) ephemera_args: ephemera::cli::init::Cmd, } -pub(crate) async fn execute(args: Args) -> Result<(), Box> { - let save_to_file = args.save_config; - let config = build_config(args)?; +pub(crate) async fn execute(args: Args) -> anyhow::Result<()> { + // args take precedence over env + let config = try_load_current_config(&args.id)? + .override_with_env() + .override_with_args(args); - // if we just wanted to write data to the config, exit, don't start any tasks - if save_to_file { - info!("Saving the configuration to a file"); - config.save_to_default_location()?; - config - .get_ephemera_args() - .cmd - .clone() - .execute(Some(&config.get_id())); - return Ok(()); - } + config.validate()?; let shutdown_handlers = start_nym_api_tasks(config).await?; @@ -120,5 +74,10 @@ pub(crate) async fn execute(args: Args) -> Result<(), Box // SPDX-License-Identifier: GPL-3.0-only -use crate::support::config::old_config_v1_1_21::ConfigV1_1_21; -use crate::support::config::old_config_v1_1_27::ConfigV1_1_27; -use crate::support::config::{default_config_directory, default_data_directory, Config}; -use anyhow::Result; +use crate::support::config; +use crate::support::config::{ + default_config_directory, default_data_directory, upgrade_helpers, Config, +}; +use anyhow::{Context, Result}; +use nym_crypto::asymmetric::identity; +use rand_07::rngs::OsRng; use std::{fs, io}; -fn try_upgrade_v1_1_21_config(id: &str) -> Result<()> { - use nym_config::legacy_helpers::nym_config::MigrationNymConfig; +fn init_identity_keys(config: &config::NymApiPaths) -> Result<()> { + let keypaths = nym_pemstore::KeyPairPath::new( + &config.private_identity_key_file, + &config.public_identity_key_file, + ); - // explicitly load it as v1.1.21 (which is incompatible with the current, i.e. 1.1.22+) - let Ok(old_config) = ConfigV1_1_21::load_from_file(id) else { - // if we failed to load it, there might have been nothing to upgrade - // or maybe it was an even older file. in either way. just ignore it and carry on with our day - return Ok(()); - }; - info!("It seems the nym-api is using <= v1.1.21 config template."); - info!("It is going to get updated to the current specification."); - - let updated: Config = old_config.into(); - Ok(updated.save_to_default_location()?) -} - -fn try_upgrade_v1_1_27_config(id: &str) -> Result<()> { - use nym_config::legacy_helpers::nym_config::MigrationNymConfig; - - // explicitly load it as v1.1.27 (which is incompatible with the current, i.e. 1.1.28+) - let Ok(old_config) = ConfigV1_1_27::load_from_file(id) else { - // if we failed to load it, there might have been nothing to upgrade - // or maybe it was an even older file. in either way. just ignore it and carry on with our day - return Ok(()); - }; - info!("It seems the nym-api is using <= v1.1.27 config template."); - info!("It is going to get updated to the current specification."); - - let updated: Config = old_config.into(); - Ok(updated.save_to_default_location()?) + let mut rng = OsRng; + let keypair = identity::KeyPair::new(&mut rng); + nym_pemstore::store_keypair(&keypair, &keypaths) + .context("failed to store identity keys of the nym api")?; + Ok(()) } fn init_paths(id: &str) -> io::Result<()> { @@ -46,14 +30,21 @@ fn init_paths(id: &str) -> io::Result<()> { pub(crate) fn initialise_new(id: &str) -> Result { let config = Config::new(id); + + // create base storage paths init_paths(id)?; + + // create identity keys + init_identity_keys(&config.base.storage_paths)?; + + // create DKG BTE keys crate::coconut::dkg::controller::init_keypair(&config.coconut_signer)?; Ok(config) } pub(crate) fn try_load_current_config(id: &str) -> Result { - try_upgrade_v1_1_21_config(id)?; - try_upgrade_v1_1_27_config(id)?; - - Ok(Config::read_from_default_path(id)?) + upgrade_helpers::try_upgrade_config(id)?; + Config::read_from_default_path(id).context( + "failed to load config.toml from the default path - are you sure you run `init` before?", + ) } diff --git a/nym-api/src/support/config/mod.rs b/nym-api/src/support/config/mod.rs index d4052be515..8d71b43fd6 100644 --- a/nym-api/src/support/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -4,13 +4,16 @@ use crate::support::config::persistence::{ CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, NymApiPaths, }; +use crate::support::config::r#override::OverrideConfig; use crate::support::config::template::CONFIG_TEMPLATE; -use nym_config::defaults::{mainnet, NymNetworkDetails}; +use anyhow::bail; +use nym_config::defaults::mainnet::read_parsed_var_if_not_default; +use nym_config::defaults::var_names::{CONFIGURED, NYXD}; +use nym_config::serde_helpers::de_maybe_stringified; use nym_config::{ must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, DEFAULT_NYM_APIS_DIR, NYM_DIR, }; -use nym_validator_client::nyxd; use serde::{Deserialize, Serialize}; use std::io; use std::path::{Path, PathBuf}; @@ -19,14 +22,13 @@ use url::Url; use zeroize::{Zeroize, ZeroizeOnDrop}; pub(crate) mod helpers; -pub(crate) mod old_config_v1_1_21; -pub(crate) mod old_config_v1_1_27; +mod r#override; mod persistence; mod template; +mod upgrade_helpers; pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; -pub const DEFAULT_NYM_API_PORT: u16 = 8080; pub const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10); @@ -86,6 +88,10 @@ pub fn default_data_directory>(id: P) -> PathBuf { #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct Config { + // additional metadata holding on-disk location of this config file + #[serde(skip)] + pub(crate) save_path: Option, + pub base: Base, // TODO: perhaps introduce separate 'path finder' field for all the paths and directories like we have with other configs @@ -104,16 +110,6 @@ pub struct Config { pub ephemera: Ephemera, } -impl<'a> From<&'a Config> for NymNetworkDetails { - fn from(value: &'a Config) -> Self { - // we get the current environmental details and then overwrite whatever is appropriate with - // the values from the config - NymNetworkDetails::new_from_env() - .with_mixnet_contract(Some(value.get_mixnet_contract_address().as_ref())) - .with_vesting_contract(Some(value.get_vesting_contract_address().as_ref())) - } -} - impl NymConfigTemplate for Config { fn template(&self) -> &'static str { CONFIG_TEMPLATE @@ -123,6 +119,7 @@ impl NymConfigTemplate for Config { impl Config { pub fn new>(id: S) -> Self { Config { + save_path: None, base: Base::new_default(id.as_ref()), network_monitor: NetworkMonitor::new_default(id.as_ref()), node_status_api: NodeStatusAPI::new_default(id.as_ref()), @@ -134,12 +131,78 @@ impl Config { } } + pub fn validate(&self) -> anyhow::Result<()> { + let can_sign = self.base.mnemonic.is_some(); + + if !can_sign && self.rewarding.enabled { + bail!("can't enable rewarding without providing a mnemonic") + } + + if !can_sign && self.coconut_signer.enabled { + bail!("can't enable coconut signer without providing a mnemonic") + } + + if !can_sign && self.ephemera.enabled { + bail!("can't enable ephemera without providing a mnemonic") + } + + Ok(()) + } + + pub fn override_with_args>(mut self, args: O) -> Self { + let args = args.into(); + + if let Some(enabled_monitor) = args.enable_monitor { + self.network_monitor.enabled = enabled_monitor; + } + if let Some(enable_rewarding) = args.enable_rewarding { + self.rewarding.enabled = enable_rewarding; + } + if let Some(nyxd_upstream) = args.nyxd_validator { + self.base.local_validator = nyxd_upstream; + } + if let Some(mnemonic) = args.mnemonic { + self.base.mnemonic = Some(mnemonic) + } + if let Some(enable_zk_nym) = args.enable_zk_nym { + self.coconut_signer.enabled = enable_zk_nym + } + if let Some(announce_address) = args.announce_address { + self.coconut_signer.announce_address = Some(announce_address) + } + if let Some(monitor_credentials_mode) = args.monitor_credentials_mode { + self.network_monitor.debug.disabled_credentials_mode = !monitor_credentials_mode + } + + self + } + + pub fn override_with_env(mut self) -> Self { + if std::env::var(CONFIGURED).is_ok() { + // currently the only value that can be overridden is 'nyxd' + if let Some(Ok(custom_nyxd)) = read_parsed_var_if_not_default(NYXD) { + self.base.local_validator = custom_nyxd + } + } + self + } + + // simple wrapper that reads config file and assigns path location + fn read_from_path>(path: P) -> io::Result { + let path = path.as_ref(); + let mut loaded: Config = read_config_from_toml_file(path)?; + loaded.save_path = Some(path.to_path_buf()); + debug!("loaded config file from {}", path.display()); + Ok(loaded) + } + + #[allow(dead_code)] pub fn read_from_toml_file>(path: P) -> io::Result { - read_config_from_toml_file(path) + Self::read_from_path(path) } pub fn read_from_default_path>(id: P) -> io::Result { - Self::read_from_toml_file(default_config_filepath(id)) + Self::read_from_path(default_config_filepath(id)) } pub fn default_location(&self) -> PathBuf { @@ -151,93 +214,13 @@ impl Config { save_formatted_config_to_file(self, config_save_location) } - pub fn get_network_details(&self) -> NymNetworkDetails { - self.into() - } - - pub fn with_network_monitor_enabled(mut self, enabled: bool) -> Self { - self.network_monitor.enabled = enabled; - self - } - - pub fn with_disabled_credentials_mode(mut self, disabled_credentials_mode: bool) -> Self { - self.network_monitor.debug.disabled_credentials_mode = disabled_credentials_mode; - self - } - - pub fn with_rewarding_enabled(mut self, enabled: bool) -> Self { - self.rewarding.enabled = enabled; - self - } - - pub fn with_coconut_signer_enabled(mut self, enabled: bool) -> Self { - self.coconut_signer.enabled = enabled; - self - } - - pub fn with_ephemera_enabled(mut self, enabled: bool) -> Self { - self.ephemera.enabled = enabled; - self - } - - pub fn with_custom_nyxd_validator(mut self, validator: Url) -> Self { - self.base.local_validator = validator; - self - } - - pub fn with_announce_address(mut self, announce_address: Url) -> Self { - self.coconut_signer.announce_address = announce_address; - self - } - - pub fn with_custom_mixnet_contract(mut self, mixnet_contract: nyxd::AccountId) -> Self { - self.base.mixnet_contract_address = mixnet_contract; - self - } - - pub fn with_custom_vesting_contract(mut self, vesting_contract: nyxd::AccountId) -> Self { - self.base.vesting_contract_address = vesting_contract; - self - } - - pub fn with_mnemonic(mut self, mnemonic: bip39::Mnemonic) -> Self { - self.base.mnemonic = mnemonic; - self - } - - pub fn with_minimum_interval_monitor_threshold(mut self, threshold: u8) -> Self { - self.rewarding.debug.minimum_interval_monitor_threshold = threshold; - self - } - - pub fn with_min_mixnode_reliability(mut self, min_mixnode_reliability: u8) -> Self { - self.network_monitor.debug.min_mixnode_reliability = min_mixnode_reliability; - self - } - - pub fn with_min_gateway_reliability(mut self, min_gateway_reliability: u8) -> Self { - self.network_monitor.debug.min_gateway_reliability = min_gateway_reliability; - self - } - - pub fn with_ephemera_ip(mut self, ip: String) -> Self { - self.ephemera.args.cmd.ephemera_ip = Some(ip); - self - } - - pub fn with_ephemera_protocol_port(mut self, port: u16) -> Self { - self.ephemera.args.cmd.ephemera_protocol_port = Some(port); - self - } - - pub fn with_ephemera_websocket_port(mut self, port: u16) -> Self { - self.ephemera.args.cmd.ephemera_websocket_port = Some(port); - self - } - - pub fn with_ephemera_http_api_port(mut self, port: u16) -> Self { - self.ephemera.args.cmd.ephemera_http_api_port = Some(port); - self + pub fn try_save(&self) -> io::Result<()> { + if let Some(save_location) = &self.save_path { + save_formatted_config_to_file(self, save_location) + } else { + debug!("config file save location is unknown. falling back to the default"); + self.save_to_default_location() + } } pub fn get_id(&self) -> String { @@ -248,16 +231,8 @@ impl Config { self.base.local_validator.clone() } - pub fn get_mixnet_contract_address(&self) -> nyxd::AccountId { - self.base.mixnet_contract_address.clone() - } - - pub fn get_vesting_contract_address(&self) -> nyxd::AccountId { - self.base.vesting_contract_address.clone() - } - - pub fn get_mnemonic(&self) -> bip39::Mnemonic { - self.base.mnemonic.clone() + pub fn get_mnemonic(&self) -> Option<&bip39::Mnemonic> { + self.base.mnemonic.as_ref() } pub fn get_ephemera_args(&self) -> &crate::ephemera::Args { @@ -278,23 +253,13 @@ pub struct Base { #[zeroize(skip)] pub local_validator: Url, - /// Address of the validator contract managing the network - #[zeroize(skip)] - pub mixnet_contract_address: nyxd::AccountId, - - /// Address of the vesting contract holding locked tokens - #[zeroize(skip)] - pub vesting_contract_address: nyxd::AccountId, - /// Mnemonic used for rewarding and/or multisig operations // TODO: similarly to the note in gateway, this should get moved to a separate file - mnemonic: bip39::Mnemonic, + #[serde(deserialize_with = "de_maybe_stringified")] + mnemonic: Option, /// Storage paths to the common nym-api files #[zeroize(skip)] - // ideally we wouldn't be using default here, but I really really don't want to be writing - // another big config migration - #[serde(default)] pub storage_paths: NymApiPaths, } @@ -310,10 +275,7 @@ impl Base { storage_paths: NymApiPaths::new_default(&id), id, local_validator: default_validator, - mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS.parse().unwrap(), - vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS.parse().unwrap(), - // this this doesn't make any sense since you really have a mnemonic beforehand... - mnemonic: bip39::Mnemonic::generate(24).unwrap(), + mnemonic: None, } } } @@ -561,7 +523,8 @@ pub struct CoconutSigner { /// Specifies whether rewarding service is enabled in this process. pub enabled: bool, - pub announce_address: Url, + #[serde(deserialize_with = "de_maybe_stringified")] + pub announce_address: Option, pub storage_paths: CoconutSignerPaths, @@ -571,17 +534,9 @@ pub struct CoconutSigner { impl CoconutSigner { pub fn new_default>(id: P) -> Self { - let default_validator: Url = DEFAULT_LOCAL_VALIDATOR - .parse() - .expect("default local validator is malformed!"); - let mut default_announce_address = default_validator; - default_announce_address - .set_port(Some(DEFAULT_NYM_API_PORT)) - .expect("default local validator is malformed!"); - CoconutSigner { enabled: false, - announce_address: default_announce_address, + announce_address: None, storage_paths: CoconutSignerPaths::new_default(id), debug: Default::default(), } diff --git a/nym-api/src/support/config/old_config_v1_1_21.rs b/nym-api/src/support/config/old_config_v1_1_21.rs deleted file mode 100644 index 39058c3844..0000000000 --- a/nym-api/src/support/config/old_config_v1_1_21.rs +++ /dev/null @@ -1,374 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::support::config::persistence::{ - CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, -}; -use crate::support::config::{ - Base, CirculatingSupplyCacher, CirculatingSupplyCacherDebug, CoconutSigner, CoconutSignerDebug, - Config, NetworkMonitor, NetworkMonitorDebug, NodeStatusAPI, NodeStatusAPIDebug, Rewarding, - RewardingDebug, TopologyCacher, TopologyCacherDebug, -}; -use nym_config::legacy_helpers::nym_config::MigrationNymConfig; -use nym_validator_client::nyxd; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use std::time::Duration; -use url::Url; - -const DEFAULT_NYM_API_PORT: u16 = 8080; -const MIXNET_CONTRACT_ADDRESS: &str = - "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr"; -const VESTING_CONTRACT_ADDRESS: &str = - "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; - -const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; - -const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10); - -const DEFAULT_GATEWAY_SENDING_RATE: usize = 200; -const DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS: usize = 50; -const DEFAULT_PACKET_DELIVERY_TIMEOUT: Duration = Duration::from_secs(20); -const DEFAULT_MONITOR_RUN_INTERVAL: Duration = Duration::from_secs(15 * 60); -const DEFAULT_GATEWAY_PING_INTERVAL: Duration = Duration::from_secs(60); -// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause -// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the -// bandwidth bridging protocol, we can come back to a smaller timeout value -const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60); -// This timeout value should be big enough to accommodate an initial bandwidth acquirement -const DEFAULT_GATEWAY_CONNECTION_TIMEOUT: Duration = Duration::from_secs(2 * 60); - -const DEFAULT_TEST_ROUTES: usize = 3; -const DEFAULT_MINIMUM_TEST_ROUTES: usize = 1; -const DEFAULT_ROUTE_TEST_PACKETS: usize = 1000; -const DEFAULT_PER_NODE_TEST_PACKETS: usize = 3; - -const DEFAULT_TOPOLOGY_CACHE_INTERVAL: Duration = Duration::from_secs(30); -const DEFAULT_NODE_STATUS_CACHE_INTERVAL: Duration = Duration::from_secs(120); -const DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL: Duration = Duration::from_secs(3600); -const DEFAULT_MONITOR_THRESHOLD: u8 = 60; -const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50; -const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20; - -#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct ConfigV1_1_21 { - #[serde(default)] - base: BaseV1_1_21, - - #[serde(default)] - network_monitor: NetworkMonitorV1_1_21, - - #[serde(default)] - node_status_api: NodeStatusAPIV1_1_21, - - #[serde(default)] - topology_cacher: TopologyCacherV1_1_21, - - #[serde(default)] - circulating_supply_cacher: CirculatingSupplyCacherV1_1_21, - - #[serde(default)] - rewarding: RewardingV1_1_21, - - #[serde(default)] - coconut_signer: CoconutSignerV1_1_21, -} - -impl From for Config { - fn from(value: ConfigV1_1_21) -> Self { - // this value was never properly saved (probably a bug) - // so explicitly set it to the default - - let dkg_persistent_state_path = - CoconutSignerV1_1_21::default_dkg_persistent_state_path(&value.base.id); - - Config { - base: Base { - id: value.base.id, - local_validator: value.base.local_validator, - mixnet_contract_address: value.base.mixnet_contract_address, - vesting_contract_address: value.base.vesting_contract_address, - mnemonic: value.base.mnemonic, - storage_paths: Default::default(), - }, - network_monitor: NetworkMonitor { - enabled: value.network_monitor.enabled, - storage_paths: NetworkMonitorPaths { - credentials_database_path: value.network_monitor.credentials_database_path, - }, - debug: NetworkMonitorDebug { - min_mixnode_reliability: value.network_monitor.min_mixnode_reliability, - min_gateway_reliability: value.network_monitor.min_gateway_reliability, - disabled_credentials_mode: value.network_monitor.disabled_credentials_mode, - run_interval: value.network_monitor.run_interval, - gateway_ping_interval: value.network_monitor.gateway_ping_interval, - gateway_sending_rate: value.network_monitor.gateway_sending_rate, - max_concurrent_gateway_clients: value - .network_monitor - .max_concurrent_gateway_clients, - gateway_response_timeout: value.network_monitor.gateway_response_timeout, - gateway_connection_timeout: value.network_monitor.gateway_connection_timeout, - packet_delivery_timeout: value.network_monitor.packet_delivery_timeout, - test_routes: value.network_monitor.test_routes, - minimum_test_routes: value.network_monitor.minimum_test_routes, - route_test_packets: value.network_monitor.route_test_packets, - per_node_test_packets: value.network_monitor.per_node_test_packets, - }, - }, - node_status_api: NodeStatusAPI { - storage_paths: NodeStatusAPIPaths { - database_path: value.node_status_api.database_path, - }, - debug: NodeStatusAPIDebug { - caching_interval: value.node_status_api.caching_interval, - }, - }, - topology_cacher: TopologyCacher { - debug: TopologyCacherDebug { - caching_interval: value.topology_cacher.caching_interval, - ..Default::default() - }, - }, - circulating_supply_cacher: CirculatingSupplyCacher { - enabled: value.circulating_supply_cacher.enabled, - debug: CirculatingSupplyCacherDebug { - caching_interval: value.circulating_supply_cacher.caching_interval, - }, - }, - rewarding: Rewarding { - enabled: value.rewarding.enabled, - debug: RewardingDebug { - minimum_interval_monitor_threshold: value - .rewarding - .minimum_interval_monitor_threshold, - }, - }, - coconut_signer: CoconutSigner { - enabled: value.coconut_signer.enabled, - announce_address: value.base.announce_address, - storage_paths: CoconutSignerPaths { - dkg_persistent_state_path, - verification_key_path: value.coconut_signer.verification_key_path, - secret_key_path: value.coconut_signer.secret_key_path, - decryption_key_path: value.coconut_signer.decryption_key_path, - public_key_with_proof_path: value.coconut_signer.public_key_with_proof_path, - }, - debug: CoconutSignerDebug { - dkg_contract_polling_rate: value.coconut_signer.dkg_contract_polling_rate, - }, - }, - ephemera: Default::default(), - } - } -} - -impl MigrationNymConfig for ConfigV1_1_21 { - fn default_root_directory() -> PathBuf { - dirs::home_dir() - .expect("Failed to evaluate $HOME value") - .join(".nym") - .join("nym-api") - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct BaseV1_1_21 { - /// ID specifies the human readable ID of this particular nym-api. - id: String, - - local_validator: Url, - - /// Address announced to the directory server for the clients to connect to. - // It is useful, say, in NAT scenarios or wanting to more easily update actual IP address - // later on by using name resolvable with a DNS query, such as `nymtech.net`. - announce_address: Url, - - /// Address of the validator contract managing the network - mixnet_contract_address: nyxd::AccountId, - - /// Address of the vesting contract holding locked tokens - vesting_contract_address: nyxd::AccountId, - - /// Mnemonic used for rewarding and/or multisig operations - mnemonic: bip39::Mnemonic, -} - -impl Default for BaseV1_1_21 { - fn default() -> Self { - let default_validator: Url = DEFAULT_LOCAL_VALIDATOR - .parse() - .expect("default local validator is malformed!"); - let mut default_announce_address = default_validator.clone(); - default_announce_address - .set_port(Some(DEFAULT_NYM_API_PORT)) - .expect("default local validator is malformed!"); - - BaseV1_1_21 { - id: String::default(), - local_validator: default_validator, - announce_address: default_announce_address, - mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.parse().unwrap(), - vesting_contract_address: VESTING_CONTRACT_ADDRESS.parse().unwrap(), - mnemonic: bip39::Mnemonic::generate(24).unwrap(), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct NetworkMonitorV1_1_21 { - min_mixnode_reliability: u8, // defaults to 50 - min_gateway_reliability: u8, // defaults to 20 - enabled: bool, - #[serde(default)] - disabled_credentials_mode: bool, - #[serde(with = "humantime_serde")] - run_interval: Duration, - #[serde(with = "humantime_serde")] - gateway_ping_interval: Duration, - gateway_sending_rate: usize, - max_concurrent_gateway_clients: usize, - #[serde(with = "humantime_serde")] - gateway_response_timeout: Duration, - #[serde(with = "humantime_serde")] - gateway_connection_timeout: Duration, - #[serde(with = "humantime_serde")] - packet_delivery_timeout: Duration, - credentials_database_path: PathBuf, - test_routes: usize, - minimum_test_routes: usize, - route_test_packets: usize, - per_node_test_packets: usize, -} - -impl Default for NetworkMonitorV1_1_21 { - fn default() -> Self { - NetworkMonitorV1_1_21 { - min_mixnode_reliability: DEFAULT_MIN_MIXNODE_RELIABILITY, - min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY, - enabled: false, - disabled_credentials_mode: true, - run_interval: DEFAULT_MONITOR_RUN_INTERVAL, - gateway_ping_interval: DEFAULT_GATEWAY_PING_INTERVAL, - gateway_sending_rate: DEFAULT_GATEWAY_SENDING_RATE, - max_concurrent_gateway_clients: DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS, - gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, - gateway_connection_timeout: DEFAULT_GATEWAY_CONNECTION_TIMEOUT, - packet_delivery_timeout: DEFAULT_PACKET_DELIVERY_TIMEOUT, - credentials_database_path: Default::default(), - test_routes: DEFAULT_TEST_ROUTES, - minimum_test_routes: DEFAULT_MINIMUM_TEST_ROUTES, - route_test_packets: DEFAULT_ROUTE_TEST_PACKETS, - per_node_test_packets: DEFAULT_PER_NODE_TEST_PACKETS, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct NodeStatusAPIV1_1_21 { - database_path: PathBuf, - #[serde(with = "humantime_serde")] - caching_interval: Duration, -} - -impl Default for NodeStatusAPIV1_1_21 { - fn default() -> Self { - NodeStatusAPIV1_1_21 { - database_path: Default::default(), - caching_interval: DEFAULT_NODE_STATUS_CACHE_INTERVAL, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct TopologyCacherV1_1_21 { - #[serde(with = "humantime_serde")] - caching_interval: Duration, -} - -impl Default for TopologyCacherV1_1_21 { - fn default() -> Self { - TopologyCacherV1_1_21 { - caching_interval: DEFAULT_TOPOLOGY_CACHE_INTERVAL, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct CirculatingSupplyCacherV1_1_21 { - enabled: bool, - - #[serde(with = "humantime_serde")] - caching_interval: Duration, -} - -impl Default for CirculatingSupplyCacherV1_1_21 { - fn default() -> Self { - CirculatingSupplyCacherV1_1_21 { - enabled: true, - caching_interval: DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct RewardingV1_1_21 { - enabled: bool, - minimum_interval_monitor_threshold: u8, -} - -impl Default for RewardingV1_1_21 { - fn default() -> Self { - RewardingV1_1_21 { - enabled: false, - minimum_interval_monitor_threshold: DEFAULT_MONITOR_THRESHOLD, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct CoconutSignerV1_1_21 { - enabled: bool, - dkg_persistent_state_path: PathBuf, - verification_key_path: PathBuf, - secret_key_path: PathBuf, - decryption_key_path: PathBuf, - public_key_with_proof_path: PathBuf, - dkg_contract_polling_rate: Duration, -} - -impl CoconutSignerV1_1_21 { - pub const DKG_PERSISTENT_STATE_FILE: &'static str = "dkg_persistent_state.json"; - - fn default_dkg_persistent_state_path(id: &str) -> PathBuf { - ConfigV1_1_21::default_data_directory(id).join(Self::DKG_PERSISTENT_STATE_FILE) - } -} - -impl Default for CoconutSignerV1_1_21 { - fn default() -> Self { - Self { - enabled: Default::default(), - dkg_persistent_state_path: Default::default(), - verification_key_path: Default::default(), - secret_key_path: Default::default(), - decryption_key_path: Default::default(), - public_key_with_proof_path: Default::default(), - dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE, - } - } -} diff --git a/nym-api/src/support/config/old_config_v1_1_27.rs b/nym-api/src/support/config/old_config_v1_1_27.rs deleted file mode 100644 index 550c14c1bf..0000000000 --- a/nym-api/src/support/config/old_config_v1_1_27.rs +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::support::config::persistence::{ - CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, -}; -use crate::support::config::{ - Base, CirculatingSupplyCacher, CoconutSigner, CoconutSignerDebug, Config, Ephemera, - NetworkMonitor, NetworkMonitorDebug, NodeStatusAPI, NodeStatusAPIDebug, Rewarding, - TopologyCacher, -}; -use nym_config::legacy_helpers::nym_config::MigrationNymConfig; -use nym_validator_client::nyxd; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use url::Url; - -const DEFAULT_NYM_API_PORT: u16 = 8080; -const MIXNET_CONTRACT_ADDRESS: &str = - "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr"; -const VESTING_CONTRACT_ADDRESS: &str = - "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; - -const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; - -#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct ConfigV1_1_27 { - #[serde(default)] - base: BaseV1_1_27, - - #[serde(default)] - network_monitor: NetworkMonitorV1_1_27, - - #[serde(default)] - node_status_api: NodeStatusAPIV1_1_27, - - #[serde(default)] - topology_cacher: TopologyCacher, - - #[serde(default)] - circulating_supply_cacher: CirculatingSupplyCacher, - - #[serde(default)] - rewarding: Rewarding, - - #[serde(default)] - coconut_signer: CoconutSignerV1_1_27, -} - -impl From for Config { - fn from(value: ConfigV1_1_27) -> Self { - // this value was never properly saved (probably a bug) - // so explicitly set it to the default - - Config { - base: Base { - id: value.base.id.clone(), - local_validator: value.base.local_validator, - mixnet_contract_address: value.base.mixnet_contract_address, - vesting_contract_address: value.base.vesting_contract_address, - mnemonic: value.base.mnemonic, - storage_paths: Default::default(), - }, - network_monitor: NetworkMonitor { - enabled: value.network_monitor.enabled, - storage_paths: NetworkMonitorPaths { - credentials_database_path: value - .network_monitor - .storage_paths - .credentials_database_path, - }, - debug: NetworkMonitorDebug { - min_mixnode_reliability: value.network_monitor.debug.min_mixnode_reliability, - min_gateway_reliability: value.network_monitor.debug.min_gateway_reliability, - disabled_credentials_mode: value - .network_monitor - .debug - .disabled_credentials_mode, - run_interval: value.network_monitor.debug.run_interval, - gateway_ping_interval: value.network_monitor.debug.gateway_ping_interval, - gateway_sending_rate: value.network_monitor.debug.gateway_sending_rate, - max_concurrent_gateway_clients: value - .network_monitor - .debug - .max_concurrent_gateway_clients, - gateway_response_timeout: value.network_monitor.debug.gateway_response_timeout, - gateway_connection_timeout: value - .network_monitor - .debug - .gateway_connection_timeout, - packet_delivery_timeout: value.network_monitor.debug.packet_delivery_timeout, - test_routes: value.network_monitor.debug.test_routes, - minimum_test_routes: value.network_monitor.debug.minimum_test_routes, - route_test_packets: value.network_monitor.debug.route_test_packets, - per_node_test_packets: value.network_monitor.debug.per_node_test_packets, - }, - }, - node_status_api: NodeStatusAPI { - storage_paths: NodeStatusAPIPaths { - database_path: value.node_status_api.storage_paths.database_path, - }, - debug: NodeStatusAPIDebug { - caching_interval: value.node_status_api.debug.caching_interval, - }, - }, - topology_cacher: value.topology_cacher, - circulating_supply_cacher: value.circulating_supply_cacher, - rewarding: value.rewarding, - coconut_signer: CoconutSigner { - enabled: value.coconut_signer.enabled, - announce_address: value.base.announce_address, - storage_paths: CoconutSignerPaths { - dkg_persistent_state_path: value - .coconut_signer - .storage_paths - .dkg_persistent_state_path, - verification_key_path: value.coconut_signer.storage_paths.verification_key_path, - secret_key_path: value.coconut_signer.storage_paths.secret_key_path, - decryption_key_path: value.coconut_signer.storage_paths.decryption_key_path, - public_key_with_proof_path: value - .coconut_signer - .storage_paths - .public_key_with_proof_path, - }, - debug: CoconutSignerDebug { - dkg_contract_polling_rate: value.coconut_signer.debug.dkg_contract_polling_rate, - }, - }, - ephemera: Ephemera::new_default(&value.base.id), - } - } -} - -impl MigrationNymConfig for ConfigV1_1_27 { - fn default_root_directory() -> PathBuf { - dirs::home_dir() - .expect("Failed to evaluate $HOME value") - .join(".nym") - .join("nym-api") - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct BaseV1_1_27 { - /// ID specifies the human readable ID of this particular nym-api. - id: String, - - local_validator: Url, - - /// Address announced to the directory server for the clients to connect to. - // It is useful, say, in NAT scenarios or wanting to more easily update actual IP address - // later on by using name resolvable with a DNS query, such as `nymtech.net`. - announce_address: Url, - - /// Address of the validator contract managing the network - mixnet_contract_address: nyxd::AccountId, - - /// Address of the vesting contract holding locked tokens - vesting_contract_address: nyxd::AccountId, - - /// Mnemonic used for rewarding and/or multisig operations - mnemonic: bip39::Mnemonic, -} - -impl Default for BaseV1_1_27 { - fn default() -> Self { - let default_validator: Url = DEFAULT_LOCAL_VALIDATOR - .parse() - .expect("default local validator is malformed!"); - let mut default_announce_address = default_validator.clone(); - default_announce_address - .set_port(Some(DEFAULT_NYM_API_PORT)) - .expect("default local validator is malformed!"); - - BaseV1_1_27 { - id: String::default(), - local_validator: default_validator, - announce_address: default_announce_address, - mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.parse().unwrap(), - vesting_contract_address: VESTING_CONTRACT_ADDRESS.parse().unwrap(), - mnemonic: bip39::Mnemonic::generate(24).unwrap(), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct NetworkMonitorV1_1_27 { - /// Specifies whether network monitoring service is enabled in this process. - pub enabled: bool, - - pub storage_paths: NetworkMonitorPaths, - - #[serde(default)] - pub debug: NetworkMonitorDebug, -} - -impl Default for NetworkMonitorV1_1_27 { - fn default() -> Self { - NetworkMonitorV1_1_27 { - enabled: false, - storage_paths: NetworkMonitorPaths { - credentials_database_path: Default::default(), - }, - debug: Default::default(), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct NodeStatusAPIV1_1_27 { - // pub enabled: bool, - pub storage_paths: NodeStatusAPIPaths, - - #[serde(default)] - pub debug: NodeStatusAPIDebug, -} - -impl Default for NodeStatusAPIV1_1_27 { - fn default() -> Self { - NodeStatusAPIV1_1_27 { - storage_paths: NodeStatusAPIPaths { - database_path: Default::default(), - }, - debug: Default::default(), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct CoconutSignerV1_1_27 { - /// Specifies whether rewarding service is enabled in this process. - pub enabled: bool, - - pub announce_address: Url, - - pub storage_paths: CoconutSignerPaths, - - #[serde(default)] - pub debug: CoconutSignerDebug, -} - -impl Default for CoconutSignerV1_1_27 { - fn default() -> Self { - let announce_address: Url = DEFAULT_LOCAL_VALIDATOR - .parse() - .expect("default local validator is malformed!"); - CoconutSignerV1_1_27 { - enabled: Default::default(), - announce_address, - storage_paths: CoconutSignerPaths { - dkg_persistent_state_path: Default::default(), - verification_key_path: Default::default(), - secret_key_path: Default::default(), - decryption_key_path: Default::default(), - public_key_with_proof_path: Default::default(), - }, - debug: Default::default(), - } - } -} diff --git a/nym-api/src/support/config/override.rs b/nym-api/src/support/config/override.rs new file mode 100644 index 0000000000..dea892d2a9 --- /dev/null +++ b/nym-api/src/support/config/override.rs @@ -0,0 +1,57 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::support::cli::{init, run}; + +// Configuration that can be overridden. +pub(crate) struct OverrideConfig { + /// Specifies whether network monitoring is enabled on this API + pub(crate) enable_monitor: Option, + + /// Specifies whether network rewarding is enabled on this API + pub(crate) enable_rewarding: Option, + + /// Endpoint to nyxd instance used for contract information. + pub(crate) nyxd_validator: Option, + + /// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions + pub(crate) mnemonic: Option, + + /// Flag to indicate whether coconut signer authority is enabled on this API + pub(crate) enable_zk_nym: Option, + + /// Announced address that is going to be put in the DKG contract where zk-nym clients will connect + /// to obtain their credentials + pub(crate) announce_address: Option, + + /// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement + pub(crate) monitor_credentials_mode: Option, +} + +impl From for OverrideConfig { + fn from(args: init::Args) -> Self { + OverrideConfig { + enable_monitor: Some(args.enable_monitor), + enable_rewarding: Some(args.enable_rewarding), + nyxd_validator: args.nyxd_validator, + mnemonic: args.mnemonic, + enable_zk_nym: Some(args.enable_zk_nym), + announce_address: args.announce_address, + monitor_credentials_mode: Some(args.monitor_credentials_mode), + } + } +} + +impl From for OverrideConfig { + fn from(args: run::Args) -> Self { + OverrideConfig { + enable_monitor: args.enable_monitor, + enable_rewarding: args.enable_rewarding, + nyxd_validator: args.nyxd_validator, + mnemonic: args.mnemonic, + enable_zk_nym: args.enable_zk_nym, + announce_address: args.announce_address, + monitor_credentials_mode: args.monitor_credentials_mode, + } + } +} diff --git a/nym-api/src/support/config/persistence.rs b/nym-api/src/support/config/persistence.rs index 24a16a4bae..9a9b253776 100644 --- a/nym-api/src/support/config/persistence.rs +++ b/nym-api/src/support/config/persistence.rs @@ -2,10 +2,8 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::support::config::default_data_directory; -use anyhow::{anyhow, Context}; -use nym_config::serde_helpers::de_maybe_stringified; +use anyhow::Context; use nym_crypto::asymmetric::identity; -use rand_07::rngs::OsRng; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; @@ -111,12 +109,10 @@ impl CoconutSignerPaths { #[serde(default)] pub struct NymApiPaths { /// Path to file containing private identity key of the nym-api. - #[serde(deserialize_with = "de_maybe_stringified")] - pub private_identity_key_file: Option, + pub private_identity_key_file: PathBuf, /// Path to file containing public identity key of the nym-api. - #[serde(deserialize_with = "de_maybe_stringified")] - pub public_identity_key_file: Option, + pub public_identity_key_file: PathBuf, } impl NymApiPaths { @@ -124,48 +120,19 @@ impl NymApiPaths { let data_dir = default_data_directory(id); NymApiPaths { - private_identity_key_file: Some(data_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME)), - public_identity_key_file: Some(data_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME)), - } - } - - pub fn generate_identity_if_missing>(&mut self, id: P) -> anyhow::Result { - if self.private_identity_key_file.is_none() { - log::warn!("identity key paths are not set - going to generate a fresh pair!"); - - let data_dir = default_data_directory(id); - self.private_identity_key_file = - Some(data_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME)); - self.public_identity_key_file = - Some(data_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME)); - - let keypaths = nym_pemstore::KeyPairPath::new( - self.private_identity_key_file.as_ref().unwrap(), - self.public_identity_key_file.as_ref().unwrap(), - ); - - let mut rng = OsRng; - let keypair = identity::KeyPair::new(&mut rng); - - nym_pemstore::store_keypair(&keypair, &keypaths) - .context("failed to store identity keys of the nym api")?; - Ok(true) - } else { - Ok(false) + private_identity_key_file: data_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME), + public_identity_key_file: data_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME), } } pub fn load_identity(&self) -> anyhow::Result { - // if somebody has set their private key but removed public key, the panic is totally on them. let keypaths = nym_pemstore::KeyPairPath::new( - self.private_identity_key_file - .as_ref() - .ok_or(anyhow!("private key path is not specified"))?, - self.public_identity_key_file - .as_ref() - .ok_or(anyhow!("public key path is not specified"))?, + &self.private_identity_key_file, + &self.public_identity_key_file, ); - nym_pemstore::load_keypair(&keypaths).context("failed to load identity keys of the nym api") + nym_pemstore::load_keypair(&keypaths).context(format!( + "failed to load identity keys of the nym api. paths: {keypaths:?}" + )) } } diff --git a/nym-api/src/support/config/template.rs b/nym-api/src/support/config/template.rs index 21d8487762..a859d94ad1 100644 --- a/nym-api/src/support/config/template.rs +++ b/nym-api/src/support/config/template.rs @@ -15,21 +15,15 @@ id = '{{ base.id }}' # Validator server to which the API will be getting information about the network. local_validator = '{{ base.local_validator }}' -# Address of the validator contract managing the network. -mixnet_contract_address = '{{ base.mixnet_contract_address }}' - -# Address of the vesting contract holding locked tokens -vesting_contract_address = '{{ base.vesting_contract_address }}' - # Mnemonic used for rewarding and validator interaction mnemonic = '{{ base.mnemonic }}' [base.storage_paths] # Path to file containing private identity key of the nym-api. -keys.private_identity_key_file = '{{ base.storage_paths.private_identity_key_file }}' +private_identity_key_file = '{{ base.storage_paths.private_identity_key_file }}' # Path to file containing public identity key of the nym-api. -keys.public_identity_key_file = '{{ base.storage_paths.public_identity_key_file }}' +public_identity_key_file = '{{ base.storage_paths.public_identity_key_file }}' ##### network monitor config options ##### diff --git a/nym-api/src/support/config/upgrade_helpers.rs b/nym-api/src/support/config/upgrade_helpers.rs new file mode 100644 index 0000000000..d68e923306 --- /dev/null +++ b/nym-api/src/support/config/upgrade_helpers.rs @@ -0,0 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub(crate) fn try_upgrade_config(_id: &str) -> anyhow::Result<()> { + Ok(()) +} diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index 8a99a0ff0a..452f93569f 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -16,7 +16,7 @@ use nym_coconut_dkg_common::{ types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId}, verification_key::{ContractVKShare, VerificationKeyShare}, }; -use nym_config::defaults::ChainDetails; +use nym_config::defaults::{ChainDetails, NymNetworkDetails}; use nym_contracts_common::dealings::ContractSafeBytes; use nym_ephemera_common::msg::QueryMsg as EphemeraQueryMsg; use nym_ephemera_common::types::JsonPeerInfo; @@ -46,77 +46,122 @@ use nym_validator_client::nyxd::{ hash::{Hash, SHA256_HASH_SIZE}, AccountId, Coin, TendermintTime, }; -use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient}; +use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient}; use nym_vesting_contract_common::AccountVestingCoins; use serde::Deserialize; use std::sync::Arc; use tokio::sync::{RwLock, RwLockReadGuard}; -pub(crate) struct Client(pub(crate) Arc>); +#[macro_export] +macro_rules! query_guard { + ($guard:expr, $($op:tt)*) => {{ + match &*$guard { + $crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*, + $crate::support::nyxd::ClientInner::Query(client) => client.$($op)*, + } + }}; +} -impl Clone for Client { - fn clone(&self) -> Self { - Client(Arc::clone(&self.0)) - } +macro_rules! nyxd_query { + ($self:expr, $($op:tt)*) => {{ + let guard = $self.inner.read().await; + match &*guard { + $crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*, + $crate::support::nyxd::ClientInner::Query(client) => client.$($op)*, + } + }}; +} + +macro_rules! nyxd_signing_shared { + ($self:expr, $($op:tt)*) => {{ + let guard = $self.inner.read().await; + match &*guard { + $crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*, + $crate::support::nyxd::ClientInner::Query(_) => panic!("attempted to use a signing method on a query client"), + } + }}; +} + +macro_rules! nyxd_signing { + ($self:expr, $($op:tt)*) => {{ + let guard = $self.inner.write().await; + match &*guard { + $crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*, + $crate::support::nyxd::ClientInner::Query(_) => panic!("attempted to use a signing method on a query client"), + } + }}; +} + +#[derive(Clone)] +pub(crate) struct Client { + inner: Arc>, +} + +pub enum ClientInner { + Signing(DirectSigningHttpRpcNyxdClient), + Query(QueryHttpRpcNyxdClient), } impl Client { pub(crate) fn new(config: &Config) -> Self { - let details = config.get_network_details(); + let details = NymNetworkDetails::new_from_env(); let nyxd_url = config.get_nyxd_url(); let client_config = nyxd::Config::try_from_nym_network_details(&details) .expect("failed to construct valid validator client config with the provided network"); - let mnemonic = config.get_mnemonic(); + let inner = if let Some(mnemonic) = config.get_mnemonic() { + ClientInner::Signing( + DirectSigningHttpRpcNyxdClient::connect_with_mnemonic( + client_config, + nyxd_url.as_str(), + mnemonic.clone(), + ) + .expect("Failed to connect to nyxd!"), + ) + } else { + ClientInner::Query( + QueryHttpRpcNyxdClient::connect(client_config, nyxd_url.as_str()) + .expect("Failed to connect to nyxd!"), + ) + }; - let inner = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic( - client_config, - nyxd_url.as_str(), - mnemonic, - ) - .expect("Failed to connect to nyxd!"); - - Client(Arc::new(RwLock::new(inner))) + Client { + inner: Arc::new(RwLock::new(inner)), + } } - pub(crate) async fn read(&self) -> RwLockReadGuard<'_, DirectSigningHttpRpcNyxdClient> { - self.0.read().await + pub(crate) async fn read(&self) -> RwLockReadGuard<'_, ClientInner> { + self.inner.read().await } pub(crate) async fn client_address(&self) -> AccountId { - self.0.read().await.address() + nyxd_signing_shared!(self, address()) } pub(crate) async fn balance>(&self, denom: S) -> Result { - let guard = self.0.read().await; + let address = self.client_address().await; let denom = denom.into(); - let address = guard.address(); - match self - .0 - .read() - .await - .get_balance(&address, denom.clone()) - .await? - { + let balance = nyxd_query!(self, get_balance(&address, denom.clone()).await?); + + match balance { None => Ok(Coin::new(0, denom)), Some(coin) => Ok(coin), } } pub(crate) async fn chain_details(&self) -> ChainDetails { - self.0.read().await.current_chain_details().clone() + nyxd_query!(self, current_chain_details().clone()) } pub(crate) async fn get_rewarding_validator_address(&self) -> Result { - let cosmwasm_addr = self - .0 - .read() - .await - .get_mixnet_contract_state() - .await? - .rewarding_validator_address - .into_string(); + let cosmwasm_addr = nyxd_query!( + self, + get_mixnet_contract_state() + .await? + .rewarding_validator_address + .into_string() + ); // this should never fail otherwise it implies either // 1) our mixnet contract state is invalid @@ -132,7 +177,7 @@ impl Client { // a helper function for the future to obtain the current block timestamp #[allow(dead_code)] pub(crate) async fn current_block_timestamp(&self) -> Result { - let time = self.0.read().await.get_current_block_timestamp().await?; + let time = nyxd_query!(self, get_current_block_timestamp().await?); Ok(time) } @@ -148,7 +193,7 @@ impl Client { &self, height: u32, ) -> Result, NyxdError> { - let hash = match self.0.read().await.get_block_hash(height).await? { + let hash = match nyxd_query!(self, get_block_hash(height).await?) { Hash::Sha256(hash) => Some(hash), Hash::None => None, }; @@ -157,44 +202,46 @@ impl Client { } pub(crate) async fn get_mixnodes(&self) -> Result, NyxdError> { - self.0.read().await.get_all_mixnodes_detailed().await + nyxd_query!(self, get_all_mixnodes_detailed().await) } pub(crate) async fn get_gateways(&self) -> Result, NyxdError> { - self.0.read().await.get_all_gateways().await + nyxd_query!(self, get_all_gateways().await) } pub(crate) async fn get_current_interval(&self) -> Result { - self.0.read().await.get_current_interval_details().await + nyxd_query!(self, get_current_interval_details().await) } pub(crate) async fn get_current_epoch_status(&self) -> Result { - self.0.read().await.get_current_epoch_status().await + nyxd_query!(self, get_current_epoch_status().await) } pub(crate) async fn get_current_rewarding_parameters( &self, ) -> Result { - self.0.read().await.get_rewarding_parameters().await + nyxd_query!(self, get_rewarding_parameters().await) } pub(crate) async fn get_rewarded_set_mixnodes( &self, ) -> Result, NyxdError> { - self.0.read().await.get_all_rewarded_set_mixnodes().await + nyxd_query!(self, get_all_rewarded_set_mixnodes().await) } pub(crate) async fn get_current_vesting_account_storage_key(&self) -> Result { - let guard = self.0.read().await; + let guard = self.inner.read().await; // the expect is fine as we always construct the client with the vesting contract explicitly set - let vesting_contract = guard - .vesting_contract_address() - .expect("vesting contract address is not available"); + let vesting_contract = query_guard!( + guard, + vesting_contract_address().expect("vesting contract address is not available") + ); // TODO: I don't like the usage of the hardcoded value here - let res = guard - .query_contract_raw(vesting_contract, b"key".to_vec()) - .await?; + let res = query_guard!( + guard, + query_contract_raw(vesting_contract, b"key".to_vec()).await? + ); if res.is_empty() { return Ok(0); } @@ -205,22 +252,22 @@ impl Client { pub(crate) async fn get_all_vesting_coins( &self, ) -> Result, NyxdError> { - self.0.read().await.get_all_accounts_vesting_coins().await + nyxd_query!(self, get_all_accounts_vesting_coins().await) } pub(crate) async fn get_all_family_members( &self, ) -> Result, NyxdError> { - self.0.read().await.get_all_family_members().await + nyxd_query!(self, get_all_family_members().await) } pub(crate) async fn get_pending_events_count(&self) -> Result { - let pending = self.0.read().await.get_number_of_pending_events().await?; + let pending = nyxd_query!(self, get_number_of_pending_events().await?); Ok(pending.epoch_events + pending.interval_events) } pub(crate) async fn begin_epoch_transition(&self) -> Result<(), NyxdError> { - self.0.write().await.begin_epoch_transition(None).await?; + nyxd_signing!(self, begin_epoch_transition(None).await?); Ok(()) } @@ -247,26 +294,29 @@ impl Client { } } - // "technically" we don't need a write access to the client, - // but we REALLY don't want to accidentally send any transactions while we're sending rewarding messages - // as that would have messed up sequence numbers - let guard = self.0.write().await; - // the expect is fine as we always construct the client with the mixnet contract explicitly set - let mixnet_contract = guard - .mixnet_contract_address() - .expect("mixnet contract address is not available"); + let mixnet_contract = nyxd_query!( + self, + mixnet_contract_address() + .expect("mixnet contract address is not available") + .clone() + ); let msgs = generate_reward_messages(nodes); - guard - .execute_multiple( - mixnet_contract, + // "technically" we don't need a write access to the client, + // but we REALLY don't want to accidentally send any transactions while we're sending rewarding messages + // as that would have messed up sequence numbers + nyxd_signing!( + self, + execute_multiple( + &mixnet_contract, msgs, Default::default(), format!("rewarding {} mixnodes", nodes.len()), ) - .await?; + .await? + ); Ok(()) } @@ -275,20 +325,15 @@ impl Client { new_rewarded_set: Vec, expected_active_set_size: u32, ) -> Result<(), NyxdError> { - self.0 - .write() - .await - .advance_current_epoch(new_rewarded_set, expected_active_set_size, None) - .await?; + nyxd_signing!( + self, + advance_current_epoch(new_rewarded_set, expected_active_set_size, None).await? + ); Ok(()) } pub(crate) async fn reconcile_epoch_events(&self, limit: Option) -> Result<(), NyxdError> { - self.0 - .write() - .await - .reconcile_epoch_events(limit, None) - .await?; + nyxd_signing!(self, reconcile_epoch_events(limit, None).await?); Ok(()) } } @@ -300,7 +345,7 @@ impl crate::coconut::client::Client for Client { } async fn get_tx(&self, tx_hash: Hash) -> crate::coconut::error::Result { - self.0.read().await.get_tx(tx_hash).await.map_err(|source| { + nyxd_query!(self, get_tx(tx_hash).await).map_err(|source| { CoconutError::TxRetrievalFailure { tx_hash: tx_hash.to_string(), source, @@ -312,78 +357,69 @@ impl crate::coconut::client::Client for Client { &self, proposal_id: u64, ) -> crate::coconut::error::Result { - Ok(self.0.read().await.query_proposal(proposal_id).await?) + Ok(nyxd_query!(self, query_proposal(proposal_id).await?)) } async fn list_proposals(&self) -> crate::coconut::error::Result> { - Ok(self.0.read().await.get_all_proposals().await?) + Ok(nyxd_query!(self, get_all_proposals().await?)) } async fn get_spent_credential( &self, blinded_serial_number: String, ) -> crate::coconut::error::Result { - Ok(self - .0 - .read() - .await - .get_spent_credential(blinded_serial_number) - .await?) + Ok(nyxd_query!( + self, + get_spent_credential(blinded_serial_number).await? + )) } async fn get_current_epoch(&self) -> crate::coconut::error::Result { - Ok(self.0.read().await.get_current_epoch().await?) + Ok(nyxd_query!(self, get_current_epoch().await?)) } async fn group_member(&self, addr: String) -> crate::coconut::error::Result { - Ok(self.0.read().await.member(addr, None).await?) + Ok(nyxd_query!(self, member(addr, None).await?)) } async fn get_current_epoch_threshold( &self, ) -> crate::coconut::error::Result> { - Ok(self.0.read().await.get_current_epoch_threshold().await?) + Ok(nyxd_query!(self, get_current_epoch_threshold().await?)) } async fn get_initial_dealers( &self, ) -> crate::coconut::error::Result> { - Ok(self.0.read().await.get_initial_dealers().await?) + Ok(nyxd_query!(self, get_initial_dealers().await?)) } async fn get_self_registered_dealer_details( &self, ) -> crate::coconut::error::Result { let self_address = &self.address().await; - Ok(self.0.read().await.get_dealer_details(self_address).await?) + Ok(nyxd_query!(self, get_dealer_details(self_address).await?)) } async fn get_current_dealers(&self) -> crate::coconut::error::Result> { - Ok(self.0.read().await.get_all_current_dealers().await?) + Ok(nyxd_query!(self, get_all_current_dealers().await?)) } async fn get_dealings( &self, idx: usize, ) -> crate::coconut::error::Result> { - Ok(self - .0 - .read() - .await - .get_all_epoch_dealings(idx as u64) - .await?) + Ok(nyxd_query!(self, get_all_epoch_dealings(idx as u64).await?)) } async fn get_verification_key_shares( &self, epoch_id: EpochId, ) -> crate::coconut::error::Result> { - Ok(self - .0 - .read() - .await - .get_all_verification_key_shares(epoch_id) - .await?) + Ok(nyxd_query!( + self, + get_all_verification_key_shares(epoch_id).await? + )) } async fn vote_proposal( @@ -392,25 +428,17 @@ impl crate::coconut::client::Client for Client { vote_yes: bool, fee: Option, ) -> Result<(), CoconutError> { - self.0 - .write() - .await - .vote_proposal(proposal_id, vote_yes, fee) - .await?; + nyxd_signing!(self, vote_proposal(proposal_id, vote_yes, fee).await?); Ok(()) } async fn execute_proposal(&self, proposal_id: u64) -> crate::coconut::error::Result<()> { - self.0 - .write() - .await - .execute_proposal(proposal_id, None) - .await?; + nyxd_signing!(self, execute_proposal(proposal_id, None).await?); Ok(()) } async fn advance_epoch_state(&self) -> crate::coconut::error::Result<()> { - self.0.write().await.advance_dkg_epoch_state(None).await?; + nyxd_signing!(self, advance_dkg_epoch_state(None).await?); Ok(()) } @@ -420,12 +448,10 @@ impl crate::coconut::client::Client for Client { announce_address: String, resharing: bool, ) -> Result { - Ok(self - .0 - .write() - .await - .register_dealer(bte_key, announce_address, resharing, None) - .await?) + Ok(nyxd_signing!( + self, + register_dealer(bte_key, announce_address, resharing, None).await? + )) } async fn submit_dealing( @@ -433,12 +459,10 @@ impl crate::coconut::client::Client for Client { dealing_bytes: ContractSafeBytes, resharing: bool, ) -> Result { - Ok(self - .0 - .write() - .await - .submit_dealing_bytes(dealing_bytes, resharing, None) - .await?) + Ok(nyxd_signing!( + self, + submit_dealing_bytes(dealing_bytes, resharing, None).await? + )) } async fn submit_verification_key_share( @@ -446,31 +470,27 @@ impl crate::coconut::client::Client for Client { share: VerificationKeyShare, resharing: bool, ) -> crate::coconut::error::Result { - Ok(self - .0 - .write() - .await - .submit_verification_key_share(share, resharing, None) - .await?) + Ok(nyxd_signing!( + self, + submit_verification_key_share(share, resharing, None).await? + )) } } #[async_trait] impl crate::ephemera::client::Client for Client { async fn get_ephemera_peers(&self) -> crate::ephemera::error::Result> { - Ok(self.0.read().await.get_all_ephemera_peers().await?) + Ok(nyxd_query!(self, get_all_ephemera_peers().await?)) } async fn register_ephemera_peer( &self, peer_info: JsonPeerInfo, ) -> crate::ephemera::error::Result { - Ok(self - .0 - .write() - .await - .register_as_peer(peer_info, None) - .await?) + Ok(nyxd_signing!( + self, + register_as_peer(peer_info, None).await? + )) } } @@ -480,7 +500,7 @@ impl DkgQueryClient for Client { where for<'a> T: Deserialize<'a>, { - self.0.read().await.query_dkg_contract(query).await + nyxd_query!(self, query_dkg_contract(query).await) } } @@ -493,7 +513,7 @@ impl EphemeraQueryClient for Client { where for<'a> T: Deserialize<'a>, { - self.0.read().await.query_ephemera_contract(query).await + nyxd_query!(self, query_ephemera_contract(query).await) } } @@ -506,11 +526,7 @@ impl SpDirectoryQueryClient for Client { where for<'a> T: Deserialize<'a>, { - self.0 - .read() - .await - .query_service_provider_contract(query) - .await + nyxd_query!(self, query_service_provider_contract(query).await) } } @@ -523,6 +539,6 @@ impl NameServiceQueryClient for Client { where for<'a> T: Deserialize<'a>, { - self.0.read().await.query_name_service_contract(query).await + nyxd_query!(self, query_name_service_contract(query).await) } }