Feature/config refactor (#3498)

* revamping mixnode connfig

* wip

* native client config revamping

* wip

* building socks5

* using const for mixnnode config template

* compiling updated gateway

* nym-api

* nym-sdk

* everything compiling once more

but definitely not compatible with CI and older versions (yet)

* creating full directory structure on init

* renamed paths to storage_paths and fixed mixnode template

* mixnode config migration

* gateway config migration

* nym-api config migration

* native client config migration

* socks5 client config migration

* NR config migration

* removed deprecations (that will be resolved in the following PRs) + fixed clippy

* nym-connect clippy

* nym-connect config updates

* outfox fixes

* defined socks5 lib config

* clippy

* fixed wasm client build

* removed explicit packet_type argument when starting base client

it's known implicitly from the previously passed config struct

* Empty commit

* fixed re-using gateway information when client configs are re-initialised

* fixed borrowing id value in nym-connect

* post-rebase fixes

* updated 'old_config' versions

---------

Co-authored-by: Tommy Verrall <tommy@nymtech.net>
This commit is contained in:
Jędrzej Stuczyński
2023-06-07 17:06:35 +01:00
committed by GitHub
parent bc5198768e
commit 9a68702d4d
146 changed files with 6320 additions and 4865 deletions
+5 -15
View File
@@ -1,11 +1,10 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::Config;
use crate::commands::try_load_current_config;
use crate::node::node_description::NodeDescription;
use clap::Args;
use colored::Colorize;
use nym_config::NymConfig;
use std::io;
use std::io::Write;
@@ -39,15 +38,9 @@ fn read_user_input() -> String {
buf.trim().to_string()
}
pub(crate) fn execute(args: Describe) {
pub(crate) fn execute(args: Describe) -> anyhow::Result<()> {
// ensure that the mixnode has in fact been initialized
match Config::load_from_file(&args.id) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", &args.id);
return;
}
};
let config = try_load_current_config(&args.id)?;
let example_url = "https://mixnode.yourdomain.com".bright_cyan();
let example_location = "City: London, Country: UK";
@@ -81,9 +74,6 @@ pub(crate) fn execute(args: Describe) {
};
// save the struct
NodeDescription::save_to_file(
&node_description,
Config::default_config_directory(&args.id),
)
.unwrap()
NodeDescription::save_to_file(&node_description, config.storage_paths.node_description)?;
Ok(())
}
+30 -27
View File
@@ -2,15 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
use super::OverrideConfig;
use crate::config::Config;
use crate::commands::override_config;
use crate::config::{
default_config_directory, default_config_filepath, default_data_directory, Config,
};
use crate::node::MixNode;
use crate::{commands::override_config, config::persistence::pathfinder::MixNodePathfinder};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_config::NymConfig;
use nym_crypto::asymmetric::{encryption, identity};
use nym_validator_client::nyxd;
use std::net::IpAddr;
use std::{fs, io};
#[derive(Args, Clone)]
pub(crate) struct Init {
@@ -22,10 +23,6 @@ pub(crate) struct Init {
#[clap(long)]
host: IpAddr,
/// The wallet address you will use to bond this mixnode, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9
#[clap(long)]
wallet_address: nyxd::AccountId,
/// The port on which the mixnode will be listening for mix packets
#[clap(long)]
mix_port: Option<u16>,
@@ -38,10 +35,6 @@ pub(crate) struct Init {
#[clap(long)]
http_api_port: Option<u16>,
/// The custom host that will be reported to the directory server
#[clap(long)]
announce_host: Option<String>,
/// Comma separated list of nym-api endpoints of the validators
// the alias here is included for backwards compatibility (1.1.4 and before)
#[clap(long, alias = "validators", value_delimiter = ',')]
@@ -56,29 +49,33 @@ impl From<Init> for OverrideConfig {
OverrideConfig {
id: init_config.id,
host: Some(init_config.host),
wallet_address: Some(init_config.wallet_address),
mix_port: init_config.mix_port,
verloc_port: init_config.verloc_port,
http_api_port: init_config.http_api_port,
announce_host: init_config.announce_host,
nym_apis: init_config.nym_apis,
}
}
}
fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_data_directory(id))?;
fs::create_dir_all(default_config_directory(id))
}
pub(crate) fn execute(args: &Init) {
let override_config_fields = OverrideConfig::from(args.clone());
let id = &override_config_fields.id;
let id = override_config_fields.id.clone();
eprintln!("Initialising mixnode {id}...");
let already_init = if Config::default_config_file_path(id).exists() {
let already_init = if default_config_filepath(&id).exists() {
eprintln!("Mixnode \"{id}\" was already initialised before! Config information will be overwritten (but keys will be kept)!");
true
} else {
init_paths(&id).expect("failed to initialise storage paths");
false
};
let mut config = Config::new(id);
let mut config = Config::new(&id);
config = override_config(config, override_config_fields);
// if node was already initialised, don't generate new keys
@@ -87,12 +84,12 @@ pub(crate) fn execute(args: &Init) {
let identity_keys = identity::KeyPair::new(&mut rng);
let sphinx_keys = encryption::KeyPair::new(&mut rng);
let pathfinder = MixNodePathfinder::new_from_config(&config);
nym_pemstore::store_keypair(
&identity_keys,
&nym_pemstore::KeyPairPath::new(
pathfinder.private_identity_key().to_owned(),
pathfinder.public_identity_key().to_owned(),
config.storage_paths.private_identity_key(),
config.storage_paths.public_identity_key(),
),
)
.expect("Failed to save identity keys");
@@ -100,19 +97,25 @@ pub(crate) fn execute(args: &Init) {
nym_pemstore::store_keypair(
&sphinx_keys,
&nym_pemstore::KeyPairPath::new(
pathfinder.private_encryption_key().to_owned(),
pathfinder.public_encryption_key().to_owned(),
config.storage_paths.private_encryption_key(),
config.storage_paths.public_encryption_key(),
),
)
.expect("Failed to save sphinx keys");
eprintln!("Saved mixnet identity and sphinx keypairs");
}
let config_save_location = config.get_config_file_save_location();
config
.save_to_file(None)
.expect("Failed to save the config file");
eprintln!("Saved configuration file to {config_save_location:?}");
let config_save_location = config.default_location();
config.save_to_default_location().unwrap_or_else(|_| {
panic!(
"Failed to save the config file to {}",
config_save_location.display()
)
});
eprintln!(
"Saved configuration file to {}",
config_save_location.display()
);
eprintln!("Mixnode configuration completed.\n\n\n");
MixNode::new(config).print_node_details(args.output)
+52 -32
View File
@@ -1,7 +1,9 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_21::ConfigV1_1_21;
use crate::{config::Config, Cli};
use anyhow::anyhow;
use clap::CommandFactory;
use clap::Subcommand;
use colored::Colorize;
@@ -10,7 +12,6 @@ use nym_bin_common::version_checker;
use nym_config::defaults::var_names::{BECH32_PREFIX, NYM_API};
use nym_config::OptionalSet;
use nym_crypto::bech32_address_validation;
use nym_validator_client::nyxd;
use std::net::IpAddr;
use std::process;
@@ -52,45 +53,31 @@ pub(crate) enum Commands {
struct OverrideConfig {
id: String,
host: Option<IpAddr>,
wallet_address: Option<nyxd::AccountId>,
mix_port: Option<u16>,
verloc_port: Option<u16>,
http_api_port: Option<u16>,
announce_host: Option<String>,
nym_apis: Option<Vec<url::Url>>,
}
pub(crate) async fn execute(args: Cli) {
pub(crate) async fn execute(args: Cli) -> anyhow::Result<()> {
let bin_name = "nym-mixnode";
match args.command {
Commands::Describe(m) => describe::execute(m),
Commands::Describe(m) => describe::execute(m)?,
Commands::Init(m) => init::execute(&m),
Commands::Run(m) => run::execute(&m).await,
Commands::Sign(m) => sign::execute(&m),
Commands::Upgrade(m) => upgrade::execute(&m),
Commands::NodeDetails(m) => node_details::execute(&m),
Commands::Run(m) => run::execute(&m).await?,
Commands::Sign(m) => sign::execute(&m)?,
Commands::Upgrade(m) => upgrade::execute(&m)?,
Commands::NodeDetails(m) => node_details::execute(&m)?,
Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name),
}
Ok(())
}
fn override_config(mut config: Config, args: OverrideConfig) -> Config {
// special case that I'm not sure could be easily handled with the trait
let mut was_host_overridden = false;
if let Some(host) = args.host {
config = config.with_listening_address(host);
was_host_overridden = true;
}
if let Some(announce_host) = args.announce_host {
config = config.with_announce_address(announce_host);
} else if was_host_overridden {
// make sure our 'announce-host' always defaults to 'host'
config = config.announce_address_from_listening_address()
}
fn override_config(config: Config, args: OverrideConfig) -> Config {
config
.with_optional(Config::with_listening_address, args.host)
.with_optional(Config::with_mix_port, args.mix_port)
.with_optional(Config::with_verloc_port, args.verloc_port)
.with_optional(Config::with_http_api_port, args.http_api_port)
@@ -100,13 +87,6 @@ fn override_config(mut config: Config, args: OverrideConfig) -> Config {
NYM_API,
nym_config::parse_urls,
)
.with_optional(
|cfg, wallet_address| {
validate_bech32_address_or_exit(wallet_address.as_ref());
cfg.with_wallet_address(wallet_address)
},
args.wallet_address,
)
}
/// Ensures that a given bech32 address is valid, or exits
@@ -135,7 +115,7 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) {
// network version. It might do so in the future.
pub(crate) fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_version();
let config_version = &cfg.mixnode.version;
if binary_version == config_version {
true
} else {
@@ -149,3 +129,43 @@ pub(crate) fn version_check(cfg: &Config) -> bool {
}
}
}
fn try_upgrade_v1_1_21_config(id: &str) -> std::io::Result<()> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// 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 mixnode is using <= v1.1.21 config template.");
info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into();
updated.save_to_default_location()
}
fn try_load_current_config(id: &str) -> anyhow::Result<Config> {
try_upgrade_v1_1_21_config(id)?;
Config::read_from_default_path(id).map_err(|err| {
let error_msg =
format!(
"Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})",
);
error!("{error_msg}");
anyhow!(error_msg)
})
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
}
+5 -15
View File
@@ -1,11 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::Config;
use crate::commands::try_load_current_config;
use crate::node::MixNode;
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_config::NymConfig;
#[derive(Args)]
pub(crate) struct NodeDetails {
@@ -17,18 +16,9 @@ pub(crate) struct NodeDetails {
output: OutputFormat,
}
pub(crate) fn execute(args: &NodeDetails) {
let config = match Config::load_from_file(&args.id) {
Ok(cfg) => cfg,
Err(err) => {
error!(
"Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})",
args.id,
err,
);
return;
}
};
pub(crate) fn execute(args: &NodeDetails) -> anyhow::Result<()> {
let config = try_load_current_config(&args.id)?;
MixNode::new(config).print_node_details(args.output)
MixNode::new(config).print_node_details(args.output);
Ok(())
}
+11 -31
View File
@@ -2,12 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
use super::OverrideConfig;
use crate::commands::{override_config, version_check};
use crate::config::Config;
use crate::commands::{override_config, try_load_current_config, version_check};
use crate::node::MixNode;
use anyhow::bail;
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_config::NymConfig;
use nym_config::helpers::SPECIAL_ADDRESSES;
use nym_validator_client::nyxd;
use std::net::IpAddr;
@@ -37,10 +37,6 @@ pub(crate) struct Run {
#[clap(long)]
http_api_port: Option<u16>,
/// The host that will be reported to the directory server
#[clap(long)]
announce_host: Option<String>,
/// Comma separated list of nym-api endpoints of the validators
// the alias here is included for backwards compatibility (1.1.4 and before)
#[clap(long, alias = "validators", value_delimiter = ',')]
@@ -55,11 +51,9 @@ impl From<Run> for OverrideConfig {
OverrideConfig {
id: run_config.id,
host: run_config.host,
wallet_address: run_config.wallet_address,
mix_port: run_config.mix_port,
verloc_port: run_config.verloc_port,
http_api_port: run_config.http_api_port,
announce_host: run_config.announce_host,
nym_apis: run_config.nym_apis,
}
}
@@ -70,39 +64,24 @@ fn show_binding_warning(address: &str) {
eprintln!(
"\nYou are trying to bind to {address} - you might not be accessible to other nodes\n\
You can ignore this note if you're running setup on a local network \n\
or have set a custom 'announce-host'"
or have used different host when bonding your node"
);
eprintln!("\n\n");
}
fn special_addresses() -> Vec<&'static str> {
vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]
}
pub(crate) async fn execute(args: &Run) {
pub(crate) async fn execute(args: &Run) -> anyhow::Result<()> {
eprintln!("Starting mixnode {}...", args.id);
let mut config = match Config::load_from_file(&args.id) {
Ok(cfg) => cfg,
Err(err) => {
error!(
"Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})",
args.id,
err
);
return;
}
};
let mut config = try_load_current_config(&args.id)?;
config = override_config(config, OverrideConfig::from(args.clone()));
if !version_check(&config) {
error!("failed the local version check");
return;
bail!("failed the local version check")
}
if special_addresses().contains(&&*config.get_listening_address().to_string()) {
show_binding_warning(&config.get_listening_address().to_string());
if SPECIAL_ADDRESSES.contains(&config.mixnode.listening_address) {
show_binding_warning(&config.mixnode.listening_address.to_string());
}
let mut mixnode = MixNode::new(config);
@@ -112,5 +91,6 @@ pub(crate) async fn execute(args: &Run) {
Select the correct version and install it to your machine. You will need to provide the following: \n ");
mixnode.print_node_details(args.output);
mixnode.run().await
mixnode.run().await;
Ok(())
}
+10 -21
View File
@@ -1,18 +1,16 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::convert::TryFrom;
use crate::commands::validate_bech32_address_or_exit;
use crate::config::{persistence::pathfinder::MixNodePathfinder, Config};
use crate::commands::{try_load_current_config, validate_bech32_address_or_exit};
use crate::node::MixNode;
use anyhow::{bail, Result};
use clap::{ArgGroup, Args};
use nym_bin_common::output_format::OutputFormat;
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity;
use nym_types::helpers::ConsoleSigningOutput;
use nym_validator_client::nyxd;
use std::convert::TryFrom;
#[cfg(feature = "cpucycles")]
use tracing::error;
@@ -116,32 +114,22 @@ fn print_signed_contract_msg(
println!("{}", output.format(&sign_output));
}
pub(crate) fn execute(args: &Sign) {
let config = match Config::load_from_file(&args.id) {
Ok(cfg) => cfg,
Err(err) => {
error!(
"Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})",
args.id,
);
return;
}
};
pub(crate) fn execute(args: &Sign) -> anyhow::Result<()> {
let config = try_load_current_config(&args.id)?;
if !version_check(&config) {
error!("Failed the local version check");
return;
error!("failed the local version check");
bail!("failed the local version check")
}
let signed_target = match SignedTarget::try_from(args.clone()) {
Ok(s) => s,
Err(err) => {
error!("{err}");
return;
bail!(err);
}
};
let pathfinder = MixNodePathfinder::new_from_config(&config);
let identity_keypair = MixNode::load_identity_keys(&pathfinder);
let identity_keypair = MixNode::load_identity_keys(&config);
match signed_target {
SignedTarget::Text(text) => {
@@ -154,4 +142,5 @@ pub(crate) fn execute(args: &Sign) {
print_signed_contract_msg(identity_keypair.private_key(), &raw_msg, args.output)
}
}
Ok(())
}
+18 -13
View File
@@ -1,10 +1,10 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{missing_string_value, Config};
use crate::commands::try_upgrade_v1_1_21_config;
use crate::config::Config;
use clap::Args;
use nym_bin_common::version_checker::Version;
use nym_config::NymConfig;
use std::fmt::Display;
use std::process;
@@ -46,7 +46,7 @@ fn unsupported_upgrade(config_version: &Version, package_version: &Version) -> !
}
fn parse_config_version(config: &Config) -> Version {
let version = Version::parse(config.get_version()).unwrap_or_else(|err| {
let version = Version::parse(&config.mixnode.version).unwrap_or_else(|err| {
eprintln!("failed to parse client version! - {err}");
process::exit(1)
});
@@ -89,13 +89,15 @@ fn minor_0_12_upgrade(
print_start_upgrade(config_version, &to_version);
let upgraded_config = config.with_custom_version(to_version.to_string().as_ref());
let upgraded_config = config.with_custom_version(to_version.to_string());
upgraded_config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {err}");
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
upgraded_config
.save_to_default_location()
.unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {err}");
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
print_successful_upgrade(config_version, to_version);
@@ -122,18 +124,21 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) {
}
}
pub(crate) fn execute(args: &Upgrade) {
pub(crate) fn execute(args: &Upgrade) -> anyhow::Result<()> {
try_upgrade_v1_1_21_config(&args.id)?;
let package_version = parse_package_version();
let existing_config = Config::load_from_file(&args.id).unwrap_or_else(|err| {
let existing_config = Config::read_from_default_path(&args.id).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {err}");
process::exit(1)
});
if existing_config.get_version() == missing_string_value::<String>() {
if existing_config.mixnode.version.is_empty() {
eprintln!("the existing configuration file does not seem to contain version number.");
process::exit(1);
}
do_upgrade(existing_config, args, package_version)
do_upgrade(existing_config, args, package_version);
Ok(())
}
+108 -322
View File
@@ -1,24 +1,31 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::template::config_template;
use nym_config::defaults::mainnet::NYM_API;
use crate::config::persistence::paths::MixNodePaths;
use crate::config::template::CONFIG_TEMPLATE;
use nym_bin_common::logging::LoggingSettings;
use nym_config::defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
mainnet, DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT,
DEFAULT_VERLOC_LISTENING_PORT,
};
use nym_config::NymConfig;
use nym_validator_client::nyxd;
use serde::{Deserialize, Deserializer, Serialize};
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use nym_config::helpers::inaddr_any;
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, NYM_DIR,
};
use serde::{Deserialize, Serialize};
use std::io;
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use url::Url;
pub(crate) mod old_config_v1_1_21;
pub mod persistence;
mod template;
pub(crate) const MISSING_VALUE: &str = "MISSING VALUE";
const DEFAULT_MIXNODES_DIR: &str = "mixnodes";
// 'RTT MEASUREMENT'
const DEFAULT_PACKETS_PER_NODE: usize = 100;
@@ -37,125 +44,84 @@ const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_milli
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
pub fn missing_string_value<T: From<String>>() -> T {
MISSING_VALUE.to_string().into()
/// Derive default path to mixnodes's config directory.
/// It should get resolved to `$HOME/.nym/mixnodes/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_MIXNODES_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
}
fn bind_all_address() -> IpAddr {
"0.0.0.0".parse().unwrap()
/// Derive default path to mixnodes's config file.
/// It should get resolved to `$HOME/.nym/mixnodes/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
}
fn default_mix_port() -> u16 {
DEFAULT_MIX_LISTENING_PORT
/// Derive default path to mixnodes's data directory where files, such as keys, are stored.
/// It should get resolved to `$HOME/.nym/mixnodes/<id>/data`
pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_MIXNODES_DIR)
.join(id)
.join(DEFAULT_DATA_DIR)
}
fn default_verloc_port() -> u16 {
DEFAULT_VERLOC_LISTENING_PORT
}
fn default_http_api_port() -> u16 {
DEFAULT_HTTP_API_LISTENING_PORT
}
// basically a migration helper that deserialises string representation of a maybe socket addr (like "1.1.1.1:1234")
// into just the ipaddr (like "1.1.1.1")
pub(super) fn de_ipaddr_from_maybe_str_socks_addr<'de, D>(
deserializer: D,
) -> Result<IpAddr, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if let Ok(socket_addr) = SocketAddr::from_str(&s) {
Ok(socket_addr.ip())
} else {
IpAddr::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
mixnode: MixNode,
pub mixnode: MixNode,
pub storage_paths: MixNodePaths,
#[serde(default)]
verloc: Verloc,
pub verloc: Verloc,
#[serde(default)]
logging: Logging,
pub logging: LoggingSettings,
#[serde(default)]
debug: Debug,
pub debug: Debug,
}
impl NymConfig for Config {
impl NymConfigTemplate for Config {
fn template() -> &'static str {
config_template()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("mixnodes")
}
fn try_default_root_directory() -> Option<PathBuf> {
dirs::home_dir().map(|path| path.join(".nym").join("mixnodes"))
}
fn root_directory(&self) -> PathBuf {
self.mixnode.nym_root_directory.clone()
}
fn config_directory(&self) -> PathBuf {
self.mixnode
.nym_root_directory
.join(&self.mixnode.id)
.join("config")
}
fn data_directory(&self) -> PathBuf {
self.mixnode
.nym_root_directory
.join(&self.mixnode.id)
.join("data")
CONFIG_TEMPLATE
}
}
impl Config {
pub fn new<S: Into<String>>(id: S) -> Self {
Config::default().with_id(id)
pub fn new<S: AsRef<str>>(id: S) -> Self {
Config {
mixnode: MixNode::new_default(id.as_ref()),
storage_paths: MixNodePaths::new_default(id.as_ref()),
verloc: Default::default(),
logging: Default::default(),
debug: Default::default(),
}
}
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn default_location(&self) -> PathBuf {
default_config_filepath(&self.mixnode.id)
}
pub fn save_to_default_location(&self) -> io::Result<()> {
let config_save_location: PathBuf = self.default_location();
save_formatted_config_to_file(self, config_save_location)
}
// builder methods
pub fn with_id<S: Into<String>>(mut self, id: S) -> Self {
let id = id.into();
if self
.mixnode
.private_identity_key_file
.as_os_str()
.is_empty()
{
self.mixnode.private_identity_key_file =
self::MixNode::default_private_identity_key_file(&id);
}
if self.mixnode.public_identity_key_file.as_os_str().is_empty() {
self.mixnode.public_identity_key_file =
self::MixNode::default_public_identity_key_file(&id);
}
if self.mixnode.private_sphinx_key_file.as_os_str().is_empty() {
self.mixnode.private_sphinx_key_file =
self::MixNode::default_private_sphinx_key_file(&id);
}
if self.mixnode.public_sphinx_key_file.as_os_str().is_empty() {
self.mixnode.public_sphinx_key_file =
self::MixNode::default_public_sphinx_key_file(&id);
}
self.mixnode.id = id;
self
}
pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec<Url>) -> Self {
self.mixnode.nym_api_urls = nym_api_urls;
self
@@ -166,11 +132,6 @@ impl Config {
self
}
pub fn with_announce_address<S: Into<String>>(mut self, announce_address: S) -> Self {
self.mixnode.announce_address = announce_address.into();
self
}
pub fn with_mix_port(mut self, port: u16) -> Self {
self.mixnode.mix_port = port;
self
@@ -186,261 +147,86 @@ impl Config {
self
}
pub fn announce_address_from_listening_address(mut self) -> Self {
self.mixnode.announce_address = self.mixnode.listening_address.to_string();
pub fn with_custom_version<S: Into<String>>(mut self, version: S) -> Self {
self.mixnode.version = version.into();
self
}
pub fn with_custom_version(mut self, version: &str) -> Self {
self.mixnode.version = version.to_string();
self
}
pub fn with_wallet_address(mut self, wallet_address: nyxd::AccountId) -> Self {
self.mixnode.wallet_address = Some(wallet_address);
self
}
// getters
pub fn get_id(&self) -> String {
self.mixnode.id.clone()
}
pub fn get_config_file_save_location(&self) -> PathBuf {
self.config_directory().join(Self::config_file_name())
}
pub fn get_private_identity_key_file(&self) -> PathBuf {
self.mixnode.private_identity_key_file.clone()
}
pub fn get_public_identity_key_file(&self) -> PathBuf {
self.mixnode.public_identity_key_file.clone()
}
pub fn get_private_sphinx_key_file(&self) -> PathBuf {
self.mixnode.private_sphinx_key_file.clone()
}
pub fn get_public_sphinx_key_file(&self) -> PathBuf {
self.mixnode.public_sphinx_key_file.clone()
}
pub fn get_nym_api_endpoints(&self) -> Vec<Url> {
self.mixnode.nym_api_urls.clone()
}
pub fn get_node_stats_logging_delay(&self) -> Duration {
self.debug.node_stats_logging_delay
}
pub fn get_node_stats_updating_delay(&self) -> Duration {
self.debug.node_stats_updating_delay
}
pub fn get_listening_address(&self) -> IpAddr {
self.mixnode.listening_address
}
pub fn get_announce_address(&self) -> String {
self.mixnode.announce_address.clone()
}
pub fn get_mix_port(&self) -> u16 {
self.mixnode.mix_port
}
pub fn get_verloc_port(&self) -> u16 {
self.mixnode.verloc_port
}
pub fn get_http_api_port(&self) -> u16 {
self.mixnode.http_api_port
}
pub fn get_packet_forwarding_initial_backoff(&self) -> Duration {
self.debug.packet_forwarding_initial_backoff
}
pub fn get_packet_forwarding_maximum_backoff(&self) -> Duration {
self.debug.packet_forwarding_maximum_backoff
}
pub fn get_initial_connection_timeout(&self) -> Duration {
self.debug.initial_connection_timeout
}
pub fn get_maximum_connection_buffer_size(&self) -> usize {
self.debug.maximum_connection_buffer_size
}
pub fn get_use_legacy_sphinx_framing(&self) -> bool {
self.debug.use_legacy_framed_packet_version
}
pub fn get_version(&self) -> &str {
&self.mixnode.version
}
pub fn get_measurement_packets_per_node(&self) -> usize {
self.verloc.packets_per_node
}
pub fn get_measurement_packet_timeout(&self) -> Duration {
self.verloc.packet_timeout
}
pub fn get_measurement_connection_timeout(&self) -> Duration {
self.verloc.connection_timeout
}
pub fn get_measurement_delay_between_packets(&self) -> Duration {
self.verloc.delay_between_packets
}
pub fn get_measurement_tested_nodes_batch_size(&self) -> usize {
self.verloc.tested_nodes_batch_size
}
pub fn get_measurement_testing_interval(&self) -> Duration {
self.verloc.testing_interval
}
pub fn get_measurement_retry_timeout(&self) -> Duration {
self.verloc.retry_timeout
}
pub fn get_wallet_address(&self) -> Option<nyxd::AccountId> {
self.mixnode.wallet_address.clone()
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
struct MixNode {
pub struct MixNode {
/// Version of the mixnode for which this configuration was created.
#[serde(default = "missing_string_value")]
version: String,
pub version: String,
/// ID specifies the human readable ID of this particular mixnode.
id: String,
pub id: String,
/// Address to which this mixnode will bind to and will be listening for packets.
#[serde(deserialize_with = "de_ipaddr_from_maybe_str_socks_addr")]
listening_address: IpAddr,
/// Optional address announced to the validator 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: String,
pub listening_address: IpAddr,
/// Port used for listening for all mixnet traffic.
/// (default: 1789)
#[serde(default = "default_mix_port")]
mix_port: u16,
pub mix_port: u16,
/// Port used for listening for verloc traffic.
/// (default: 1790)
#[serde(default = "default_verloc_port")]
verloc_port: u16,
pub verloc_port: u16,
/// Port used for listening for http requests.
/// (default: 8000)
#[serde(default = "default_http_api_port")]
http_api_port: u16,
/// Path to file containing private identity key.
#[serde(default = "missing_string_value")]
private_identity_key_file: PathBuf,
/// Path to file containing public identity key.
#[serde(default = "missing_string_value")]
public_identity_key_file: PathBuf,
/// Path to file containing private sphinx key.
private_sphinx_key_file: PathBuf,
/// Path to file containing public sphinx key.
public_sphinx_key_file: PathBuf,
pub http_api_port: u16,
/// Addresses to nym APIs from which the node gets the view of the network.
nym_api_urls: Vec<Url>,
/// nym_home_directory specifies absolute path to the home nym MixNodes directory.
/// It is expected to use default value and hence .toml file should not redefine this field.
nym_root_directory: PathBuf,
/// The Cosmos wallet address that will control this mixnode
// the only reason this is an Option is because of the lack of existence of a sane default value
wallet_address: Option<nyxd::AccountId>,
pub nym_api_urls: Vec<Url>,
}
impl MixNode {
fn default_private_identity_key_file(id: &str) -> PathBuf {
Config::default_data_directory(id).join("private_identity.pem")
}
fn default_public_identity_key_file(id: &str) -> PathBuf {
Config::default_data_directory(id).join("public_identity.pem")
}
fn default_private_sphinx_key_file(id: &str) -> PathBuf {
Config::default_data_directory(id).join("private_sphinx.pem")
}
fn default_public_sphinx_key_file(id: &str) -> PathBuf {
Config::default_data_directory(id).join("public_sphinx.pem")
}
}
impl Default for MixNode {
fn default() -> Self {
pub fn new_default<S: Into<String>>(id: S) -> Self {
MixNode {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
listening_address: bind_all_address(),
announce_address: "127.0.0.1".to_string(),
id: id.into(),
listening_address: inaddr_any(),
mix_port: DEFAULT_MIX_LISTENING_PORT,
verloc_port: DEFAULT_VERLOC_LISTENING_PORT,
http_api_port: DEFAULT_HTTP_API_LISTENING_PORT,
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
private_sphinx_key_file: Default::default(),
public_sphinx_key_file: Default::default(),
nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")],
nym_root_directory: Config::default_root_directory(),
wallet_address: None,
nym_api_urls: vec![Url::from_str(mainnet::NYM_API).expect("Invalid default API URL")],
}
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
struct Logging {}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
struct Verloc {
pub struct Verloc {
/// Specifies number of echo packets sent to each node during a measurement run.
packets_per_node: usize,
pub packets_per_node: usize,
/// Specifies maximum amount of time to wait for the connection to get established.
connection_timeout: Duration,
#[serde(with = "humantime_serde")]
pub connection_timeout: Duration,
/// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test.
packet_timeout: Duration,
#[serde(with = "humantime_serde")]
pub packet_timeout: Duration,
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
delay_between_packets: Duration,
#[serde(with = "humantime_serde")]
pub delay_between_packets: Duration,
/// Specifies number of nodes being tested at once.
tested_nodes_batch_size: usize,
pub tested_nodes_batch_size: usize,
/// Specifies delay between subsequent test runs.
testing_interval: Duration,
#[serde(with = "humantime_serde")]
pub testing_interval: Duration,
/// Specifies delay between attempting to run the measurement again if the previous run failed
/// due to being unable to get the list of nodes.
retry_timeout: Duration,
#[serde(with = "humantime_serde")]
pub retry_timeout: Duration,
}
impl Default for Verloc {
@@ -459,37 +245,37 @@ impl Default for Verloc {
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
struct Debug {
pub struct Debug {
/// Delay between each subsequent node statistics being logged to the console
#[serde(with = "humantime_serde")]
node_stats_logging_delay: Duration,
pub node_stats_logging_delay: Duration,
/// Delay between each subsequent node statistics being updated
#[serde(with = "humantime_serde")]
node_stats_updating_delay: Duration,
pub node_stats_updating_delay: Duration,
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
packet_forwarding_initial_backoff: Duration,
pub packet_forwarding_initial_backoff: Duration,
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
packet_forwarding_maximum_backoff: Duration,
pub packet_forwarding_maximum_backoff: Duration,
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
#[serde(with = "humantime_serde")]
initial_connection_timeout: Duration,
pub initial_connection_timeout: Duration,
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
maximum_connection_buffer_size: usize,
pub maximum_connection_buffer_size: usize,
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
// it's set to true by default. The reason for that decision is to preserve compatibility with the
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
// It shall be disabled in the subsequent releases.
use_legacy_framed_packet_version: bool,
pub use_legacy_framed_packet_version: bool,
}
impl Default for Debug {
+249
View File
@@ -0,0 +1,249 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::paths::{KeysPaths, MixNodePaths};
use crate::config::{Config, Debug, MixNode, Verloc};
use nym_bin_common::logging::LoggingSettings;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use nym_validator_client::nyxd;
use serde::{Deserialize, Deserializer, Serialize};
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
use url::Url;
const DEFAULT_MIX_LISTENING_PORT: u16 = 1789;
const DEFAULT_VERLOC_LISTENING_PORT: u16 = 1790;
const DEFAULT_HTTP_API_LISTENING_PORT: u16 = 8000;
const NYM_API: &str = "https://validator.nymtech.net/api/";
const DESCRIPTION_FILE: &str = "description.toml";
// 'RTT MEASUREMENT'
const DEFAULT_PACKETS_PER_NODE: usize = 100;
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50);
const DEFAULT_BATCH_SIZE: usize = 50;
const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12);
const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30);
// 'DEBUG'
const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000);
const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000);
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
pub(super) fn de_ipaddr_from_maybe_str_socks_addr<'de, D>(
deserializer: D,
) -> Result<IpAddr, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if let Ok(socket_addr) = SocketAddr::from_str(&s) {
Ok(socket_addr.ip())
} else {
IpAddr::from_str(&s).map_err(serde::de::Error::custom)
}
}
fn bind_all_address() -> IpAddr {
"0.0.0.0".parse().unwrap()
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_21 {
mixnode: MixNodeV1_1_21,
#[serde(default)]
verloc: VerlocV1_1_21,
#[serde(default)]
logging: LoggingV1_1_21,
#[serde(default)]
debug: DebugV1_1_21,
}
impl From<ConfigV1_1_21> for Config {
fn from(value: ConfigV1_1_21) -> Self {
let node_description =
ConfigV1_1_21::default_config_directory(&value.mixnode.id).join(DESCRIPTION_FILE);
Config {
mixnode: MixNode {
version: value.mixnode.version,
id: value.mixnode.id,
listening_address: value.mixnode.listening_address,
mix_port: value.mixnode.mix_port,
verloc_port: value.mixnode.verloc_port,
http_api_port: value.mixnode.http_api_port,
nym_api_urls: value.mixnode.nym_api_urls,
},
storage_paths: MixNodePaths {
keys: KeysPaths {
private_identity_key_file: value.mixnode.private_identity_key_file,
public_identity_key_file: value.mixnode.public_identity_key_file,
private_sphinx_key_file: value.mixnode.private_sphinx_key_file,
public_sphinx_key_file: value.mixnode.public_sphinx_key_file,
},
node_description,
},
verloc: value.verloc.into(),
logging: value.logging.into(),
debug: value.debug.into(),
}
}
}
impl MigrationNymConfig for ConfigV1_1_21 {
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("mixnodes")
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
struct MixNodeV1_1_21 {
version: String,
id: String,
#[serde(deserialize_with = "de_ipaddr_from_maybe_str_socks_addr")]
listening_address: IpAddr,
announce_address: String,
mix_port: u16,
verloc_port: u16,
http_api_port: u16,
private_identity_key_file: PathBuf,
public_identity_key_file: PathBuf,
private_sphinx_key_file: PathBuf,
public_sphinx_key_file: PathBuf,
nym_api_urls: Vec<Url>,
nym_root_directory: PathBuf,
wallet_address: Option<nyxd::AccountId>,
}
impl Default for MixNodeV1_1_21 {
fn default() -> Self {
MixNodeV1_1_21 {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
listening_address: bind_all_address(),
announce_address: "127.0.0.1".to_string(),
mix_port: DEFAULT_MIX_LISTENING_PORT,
verloc_port: DEFAULT_VERLOC_LISTENING_PORT,
http_api_port: DEFAULT_HTTP_API_LISTENING_PORT,
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
private_sphinx_key_file: Default::default(),
public_sphinx_key_file: Default::default(),
nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")],
nym_root_directory: ConfigV1_1_21::default_root_directory(),
wallet_address: None,
}
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
struct LoggingV1_1_21 {}
impl From<LoggingV1_1_21> for LoggingSettings {
fn from(_value: LoggingV1_1_21) -> Self {
LoggingSettings {}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
struct VerlocV1_1_21 {
packets_per_node: usize,
connection_timeout: Duration,
packet_timeout: Duration,
delay_between_packets: Duration,
tested_nodes_batch_size: usize,
testing_interval: Duration,
retry_timeout: Duration,
}
impl From<VerlocV1_1_21> for Verloc {
fn from(value: VerlocV1_1_21) -> Self {
Verloc {
packets_per_node: value.packets_per_node,
connection_timeout: value.connection_timeout,
packet_timeout: value.packet_timeout,
delay_between_packets: value.delay_between_packets,
tested_nodes_batch_size: value.tested_nodes_batch_size,
testing_interval: value.testing_interval,
retry_timeout: value.retry_timeout,
}
}
}
impl Default for VerlocV1_1_21 {
fn default() -> Self {
VerlocV1_1_21 {
packets_per_node: DEFAULT_PACKETS_PER_NODE,
connection_timeout: DEFAULT_CONNECTION_TIMEOUT,
packet_timeout: DEFAULT_PACKET_TIMEOUT,
delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS,
tested_nodes_batch_size: DEFAULT_BATCH_SIZE,
testing_interval: DEFAULT_TESTING_INTERVAL,
retry_timeout: DEFAULT_RETRY_TIMEOUT,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
struct DebugV1_1_21 {
#[serde(with = "humantime_serde")]
node_stats_logging_delay: Duration,
#[serde(with = "humantime_serde")]
node_stats_updating_delay: Duration,
#[serde(with = "humantime_serde")]
packet_forwarding_initial_backoff: Duration,
#[serde(with = "humantime_serde")]
packet_forwarding_maximum_backoff: Duration,
#[serde(with = "humantime_serde")]
initial_connection_timeout: Duration,
maximum_connection_buffer_size: usize,
use_legacy_framed_packet_version: bool,
}
impl From<DebugV1_1_21> for Debug {
fn from(value: DebugV1_1_21) -> Self {
Debug {
node_stats_logging_delay: value.node_stats_logging_delay,
node_stats_updating_delay: value.node_stats_updating_delay,
packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff,
packet_forwarding_maximum_backoff: value.packet_forwarding_maximum_backoff,
initial_connection_timeout: value.initial_connection_timeout,
maximum_connection_buffer_size: value.maximum_connection_buffer_size,
use_legacy_framed_packet_version: value.use_legacy_framed_packet_version,
}
}
}
impl Default for DebugV1_1_21 {
fn default() -> Self {
DebugV1_1_21 {
node_stats_logging_delay: DEFAULT_NODE_STATS_LOGGING_DELAY,
node_stats_updating_delay: DEFAULT_NODE_STATS_UPDATING_DELAY,
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
use_legacy_framed_packet_version: true,
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod pathfinder;
pub mod paths;
@@ -1,40 +0,0 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::Config;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct MixNodePathfinder {
identity_private_key: PathBuf,
identity_public_key: PathBuf,
private_sphinx_key: PathBuf,
public_sphinx_key: PathBuf,
}
impl MixNodePathfinder {
pub fn new_from_config(config: &Config) -> Self {
MixNodePathfinder {
identity_private_key: config.get_private_identity_key_file(),
identity_public_key: config.get_public_identity_key_file(),
private_sphinx_key: config.get_private_sphinx_key_file(),
public_sphinx_key: config.get_public_sphinx_key_file(),
}
}
pub fn private_identity_key(&self) -> &Path {
&self.identity_private_key
}
pub fn public_identity_key(&self) -> &Path {
&self.identity_public_key
}
pub fn private_encryption_key(&self) -> &Path {
&self.private_sphinx_key
}
pub fn public_encryption_key(&self) -> &Path {
&self.public_sphinx_key
}
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{default_config_directory, default_data_directory};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem";
pub const DEFAULT_PRIVATE_SPHINX_KEY_FILENAME: &str = "private_sphinx.pem";
pub const DEFAULT_PUBLIC_SPHINX_KEY_FILENAME: &str = "public_sphinx.pem";
pub const DEFAULT_DESCRIPTION_FILENAME: &str = "description.toml";
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MixNodePaths {
pub keys: KeysPaths,
pub node_description: PathBuf,
}
impl MixNodePaths {
pub fn new_default<P: AsRef<Path>>(id: P) -> Self {
MixNodePaths {
keys: KeysPaths::new_default(id.as_ref()),
node_description: default_config_directory(id).join(DEFAULT_DESCRIPTION_FILENAME),
}
}
pub fn private_identity_key(&self) -> &Path {
self.keys.private_identity_key()
}
pub fn public_identity_key(&self) -> &Path {
self.keys.public_identity_key()
}
pub fn private_encryption_key(&self) -> &Path {
self.keys.private_encryption_key()
}
pub fn public_encryption_key(&self) -> &Path {
self.keys.public_encryption_key()
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct KeysPaths {
/// Path to file containing private identity key.
pub private_identity_key_file: PathBuf,
/// Path to file containing public identity key.
pub public_identity_key_file: PathBuf,
/// Path to file containing private sphinx key.
pub private_sphinx_key_file: PathBuf,
/// Path to file containing public sphinx key.
pub public_sphinx_key_file: PathBuf,
}
impl KeysPaths {
pub fn new_default<P: AsRef<Path>>(id: P) -> Self {
let data_dir = default_data_directory(id);
KeysPaths {
private_identity_key_file: data_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME),
public_identity_key_file: data_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME),
private_sphinx_key_file: data_dir.join(DEFAULT_PRIVATE_SPHINX_KEY_FILENAME),
public_sphinx_key_file: data_dir.join(DEFAULT_PUBLIC_SPHINX_KEY_FILENAME),
}
}
pub fn private_identity_key(&self) -> &Path {
&self.private_identity_key_file
}
pub fn public_identity_key(&self) -> &Path {
&self.public_identity_key_file
}
pub fn private_encryption_key(&self) -> &Path {
&self.private_sphinx_key_file
}
pub fn public_encryption_key(&self) -> &Path {
&self.public_sphinx_key_file
}
}
+19 -32
View File
@@ -1,12 +1,11 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) fn config_template() -> &'static str {
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs in verloc.
r#"
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs.
pub(crate) const CONFIG_TEMPLATE: &str = r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
@@ -22,25 +21,6 @@ id = '{{ mixnode.id }}'
# Socket address to which this mixnode will bind to and will be listening for packets.
listening_address = '{{ mixnode.listening_address }}'
# Path to file containing private identity key.
private_identity_key_file = '{{ mixnode.private_identity_key_file }}'
# Path to file containing public identity key.
public_identity_key_file = '{{ mixnode.public_identity_key_file }}'
# Path to file containing private identity key.
private_sphinx_key_file = '{{ mixnode.private_sphinx_key_file }}'
# Path to file containing public sphinx key.
public_sphinx_key_file = '{{ mixnode.public_sphinx_key_file }}'
##### additional mixnode config options #####
# Optional 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 = '{{ mixnode.announce_address }}'
# Port used for listening for all mixnet traffic.
# (default: 1789)
mix_port = {{ mixnode.mix_port }}
@@ -60,14 +40,22 @@ nym_api_urls = [
{{/each}}
]
# Nym wallet address on the blockchain that should control this mixnode
wallet_address = '{{ mixnode.wallet_address }}'
[storage_paths]
##### advanced configuration options #####
# Path to file containing private identity key.
keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}'
# Absolute path to the home Nym Clients directory.
nym_root_directory = '{{ mixnode.nym_root_directory }}'
# Path to file containing public identity key.
keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}'
# Path to file containing private identity key.
keys.private_sphinx_key_file = '{{ storage_paths.keys.private_sphinx_key_file }}'
# Path to file containing public sphinx key.
keys.public_sphinx_key_file = '{{ storage_paths.keys.public_sphinx_key_file }}'
# Path to file containing description of this node.
node_description = '{{ storage_paths.node_description }}'
##### logging configuration options #####
@@ -75,5 +63,4 @@ nym_root_directory = '{{ mixnode.nym_root_directory }}'
# TODO
"#
}
"#;
+4 -2
View File
@@ -48,7 +48,7 @@ fn test_function() {
}
#[tokio::main]
async fn main() {
async fn main() -> anyhow::Result<()> {
cfg_if::cfg_if! {
if #[cfg(feature = "cpucycles")] {
setup_tracing!("mixnode");
@@ -63,12 +63,14 @@ async fn main() {
let args = Cli::parse();
setup_env(args.config_env_file.as_ref());
commands::execute(args).await;
commands::execute(args).await?;
cfg_if::cfg_if! {
if #[cfg(feature = "cpucycles")] {
opentelemetry::global::shutdown_tracer_provider();
}}
Ok(())
}
#[cfg(test)]
+51 -65
View File
@@ -1,14 +1,13 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::pathfinder::MixNodePathfinder;
use crate::config::Config;
use crate::node::http::{
description::description,
hardware::hardware,
not_found,
stats::stats,
verloc::{verloc as verlocRoute, VerlocState},
verloc::{verloc as verloc_route, VerlocState},
};
use crate::node::listener::connection_handler::packet_processing::PacketProcessor;
use crate::node::listener::connection_handler::ConnectionHandler;
@@ -18,7 +17,6 @@ use crate::node::node_statistics::SharedNodeStats;
use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSender};
use nym_bin_common::output_format::OutputFormat;
use nym_bin_common::version_checker::parse_version;
use nym_config::NymConfig;
use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnode_common::verloc::{self, AtomicVerlocResult, VerlocMeasurer};
use nym_task::{TaskClient, TaskManager};
@@ -27,6 +25,7 @@ use rand::thread_rng;
use std::net::SocketAddr;
use std::process;
use std::sync::Arc;
#[cfg(feature = "cpucycles")]
use tracing::{error, info, warn};
@@ -46,38 +45,35 @@ pub struct MixNode {
impl MixNode {
pub fn new(config: Config) -> Self {
let pathfinder = MixNodePathfinder::new_from_config(&config);
MixNode {
descriptor: Self::load_node_description(&config),
identity_keypair: Arc::new(Self::load_identity_keys(&pathfinder)),
sphinx_keypair: Arc::new(Self::load_sphinx_keys(&pathfinder)),
identity_keypair: Arc::new(Self::load_identity_keys(&config)),
sphinx_keypair: Arc::new(Self::load_sphinx_keys(&config)),
config,
}
}
fn load_node_description(config: &Config) -> NodeDescription {
NodeDescription::load_from_file(Config::default_config_directory(&config.get_id()))
.unwrap_or_default()
NodeDescription::load_from_file(&config.storage_paths.node_description).unwrap_or_default()
}
/// Loads identity keys stored on disk
pub(crate) fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair {
pub(crate) fn load_identity_keys(config: &Config) -> identity::KeyPair {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
pathfinder.private_identity_key().to_owned(),
pathfinder.public_identity_key().to_owned(),
config.storage_paths.keys.private_identity_key(),
config.storage_paths.keys.public_identity_key(),
))
.expect("Failed to read stored identity key files");
identity_keypair
}
/// Loads Sphinx keys stored on disk
fn load_sphinx_keys(pathfinder: &MixNodePathfinder) -> encryption::KeyPair {
fn load_sphinx_keys(config: &Config) -> encryption::KeyPair {
let sphinx_keypair: encryption::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
pathfinder.private_encryption_key().to_owned(),
pathfinder.public_encryption_key().to_owned(),
config.storage_paths.keys.private_encryption_key(),
config.storage_paths.keys.public_encryption_key(),
))
.expect("Failed to read stored sphinx key files");
sphinx_keypair
@@ -88,13 +84,11 @@ impl MixNode {
let node_details = nym_types::mixnode::MixnodeNodeDetailsResponse {
identity_key: self.identity_keypair.public_key().to_base58_string(),
sphinx_key: self.sphinx_keypair.public_key().to_base58_string(),
announce_address: self.config.get_announce_address(),
bind_address: self.config.get_listening_address().to_string(),
version: self.config.get_version().to_string(),
mix_port: self.config.get_mix_port(),
http_api_port: self.config.get_http_api_port(),
verloc_port: self.config.get_verloc_port(),
wallet_address: self.config.get_wallet_address().map(|x| x.to_string()),
bind_address: self.config.mixnode.listening_address,
version: self.config.mixnode.version.clone(),
mix_port: self.config.mixnode.mix_port,
http_api_port: self.config.mixnode.http_api_port,
verloc_port: self.config.mixnode.verloc_port,
};
println!("{}", output.format(&node_details));
@@ -105,13 +99,16 @@ impl MixNode {
atomic_verloc_result: AtomicVerlocResult,
node_stats_pointer: SharedNodeStats,
) {
info!("Starting HTTP API on http://localhost:8000");
info!(
"Starting HTTP API on http://{}:{}",
self.config.mixnode.listening_address, self.config.mixnode.http_api_port
);
let mut config = rocket::config::Config::release_default();
// bind to the same address as we are using for mixnodes
config.address = self.config.get_listening_address();
config.port = self.config.get_http_api_port();
config.address = self.config.mixnode.listening_address;
config.port = self.config.mixnode.http_api_port;
let verloc_state = VerlocState::new(atomic_verloc_result);
let descriptor = self.descriptor.clone();
@@ -119,7 +116,7 @@ impl MixNode {
tokio::spawn(async move {
rocket::build()
.configure(config)
.mount("/", routes![verlocRoute, description, stats, hardware])
.mount("/", routes![verloc_route, description, stats, hardware])
.register("/", catchers![not_found])
.manage(verloc_state)
.manage(descriptor)
@@ -135,8 +132,8 @@ impl MixNode {
) -> (SharedNodeStats, node_statistics::UpdateSender) {
info!("Starting node stats controller...");
let controller = node_statistics::Controller::new(
self.config.get_node_stats_logging_delay(),
self.config.get_node_stats_updating_delay(),
self.config.debug.node_stats_logging_delay,
self.config.debug.node_stats_updating_delay,
shutdown,
);
let node_stats_pointer = controller.get_node_stats_data_pointer();
@@ -159,8 +156,8 @@ impl MixNode {
let connection_handler = ConnectionHandler::new(packet_processor, delay_forwarding_channel);
let listening_address = SocketAddr::new(
self.config.get_listening_address(),
self.config.get_mix_port(),
self.config.mixnode.listening_address,
self.config.mixnode.mix_port,
);
Listener::new(listening_address, shutdown).start(connection_handler);
@@ -174,11 +171,11 @@ impl MixNode {
info!("Starting packet delay-forwarder...");
let client_config = nym_mixnet_client::Config::new(
self.config.get_packet_forwarding_initial_backoff(),
self.config.get_packet_forwarding_maximum_backoff(),
self.config.get_initial_connection_timeout(),
self.config.get_maximum_connection_buffer_size(),
self.config.get_use_legacy_sphinx_framing(),
self.config.debug.packet_forwarding_initial_backoff,
self.config.debug.packet_forwarding_maximum_backoff,
self.config.debug.initial_connection_timeout,
self.config.debug.maximum_connection_buffer_size,
self.config.debug.use_legacy_framed_packet_version,
);
let mut packet_forwarder = DelayForwarder::new(
@@ -199,8 +196,8 @@ impl MixNode {
// this is a sanity check to make sure we didn't mess up with the minimum version at some point
// and whether the user has run update if they're using old config
// if this code exists in the node, it MUST BE compatible
let config_version =
parse_version(self.config.get_version()).expect("malformed version in the config file");
let config_version = parse_version(&self.config.mixnode.version)
.expect("malformed version in the config file");
let minimum_version = parse_version(verloc::MINIMUM_NODE_VERSION).unwrap();
if config_version < minimum_version {
error!("You seem to have not updated your mixnode configuration file - please run `upgrade` before attempting again");
@@ -210,19 +207,19 @@ impl MixNode {
// use the same binding address with the HARDCODED port for time being (I don't like that approach personally)
let listening_address = SocketAddr::new(
self.config.get_listening_address(),
self.config.get_verloc_port(),
self.config.mixnode.listening_address,
self.config.mixnode.verloc_port,
);
let config = verloc::ConfigBuilder::new()
.listening_address(listening_address)
.packets_per_node(self.config.get_measurement_packets_per_node())
.connection_timeout(self.config.get_measurement_connection_timeout())
.packet_timeout(self.config.get_measurement_packet_timeout())
.delay_between_packets(self.config.get_measurement_delay_between_packets())
.tested_nodes_batch_size(self.config.get_measurement_tested_nodes_batch_size())
.testing_interval(self.config.get_measurement_testing_interval())
.retry_timeout(self.config.get_measurement_retry_timeout())
.packets_per_node(self.config.verloc.packets_per_node)
.connection_timeout(self.config.verloc.connection_timeout)
.packet_timeout(self.config.verloc.packet_timeout)
.delay_between_packets(self.config.verloc.delay_between_packets)
.tested_nodes_batch_size(self.config.verloc.tested_nodes_batch_size)
.testing_interval(self.config.verloc.testing_interval)
.retry_timeout(self.config.verloc.retry_timeout)
.nym_api_urls(self.config.get_nym_api_endpoints())
.build();
@@ -242,8 +239,7 @@ impl MixNode {
nym_validator_client::NymApiClient::new(nym_api.clone())
}
// TODO: ask DH whether this function still makes sense in ^0.10
async fn check_if_same_ip_node_exists(&mut self) -> Option<String> {
async fn check_if_bonded(&self) -> bool {
// TODO: if anything, this should be getting data directly from the contract
// as opposed to the validator API
let validator_client = self.random_api_client();
@@ -258,12 +254,10 @@ impl MixNode {
}
};
let our_host = self.config.get_announce_address();
existing_nodes
.iter()
.find(|node| node.bond_information.mix_node.host == our_host)
.map(|node| node.bond_information.mix_node.identity_key.clone())
existing_nodes.iter().any(|node| {
node.bond_information.mix_node.identity_key
== self.identity_keypair.public_key().to_base58_string()
})
}
async fn wait_for_interrupt(&self, shutdown: TaskManager) {
@@ -274,16 +268,8 @@ impl MixNode {
pub async fn run(&mut self) {
info!("Starting nym mixnode");
if let Some(duplicate_node_key) = self.check_if_same_ip_node_exists().await {
if duplicate_node_key == self.identity_keypair.public_key().to_base58_string() {
warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing");
} else {
log::error!(
"Our announce-host is identical to an existing node's announce-host! (its key is {:?})",
duplicate_node_key
);
return;
}
if self.check_if_bonded().await {
warn!("You seem to have bonded your mixnode before starting it - that's highly unrecommended as in the future it might result in slashing");
}
let shutdown = TaskManager::default();
+14 -14
View File
@@ -1,10 +1,8 @@
use serde::Deserialize;
use serde::Serialize;
use std::path::PathBuf;
use std::path::Path;
use std::{fs, io};
pub(crate) const DESCRIPTION_FILE: &str = "description.toml";
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct NodeDescription {
pub(crate) name: String,
@@ -25,24 +23,26 @@ impl Default for NodeDescription {
}
impl NodeDescription {
pub(crate) fn load_from_file(config_path: PathBuf) -> io::Result<NodeDescription> {
let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE]
.iter()
.collect();
let toml = fs::read_to_string(description_file_path)?;
pub(crate) fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<NodeDescription> {
// let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE]
// .iter()
// .collect();
// let toml = fs::read_to_string(description_file_path)?;
// toml::from_str(&toml).map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
let toml = fs::read_to_string(path)?;
toml::from_str(&toml).map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
}
pub(crate) fn save_to_file(
pub(crate) fn save_to_file<P: AsRef<Path>>(
description: &NodeDescription,
config_path: PathBuf,
path: P,
) -> io::Result<()> {
let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE]
.iter()
.collect();
// let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE]
// .iter()
// .collect();
let description_toml =
toml::to_string(description).expect("could not encode description to toml");
fs::write(description_file_path, description_toml)?;
fs::write(path, description_toml)?;
Ok(())
}
}