From b3f883e82bdfb7431f74818d5fe831377b85e03b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 24 May 2023 14:56:42 +0100 Subject: [PATCH] revamping mixnode connfig --- .../key_pathfinder.rs | 0 .../{persistence => disk_persistence}/mod.rs | 0 common/config/Cargo.toml | 9 +- common/config/src/helpers.rs | 143 +++++ common/config/src/lib.rs | 366 ++++++------ common/network-defaults/src/eth_contract.rs | 549 ------------------ mixnode/src/commands/describe.rs | 9 +- mixnode/src/commands/init.rs | 36 +- mixnode/src/commands/mod.rs | 29 +- mixnode/src/commands/node_details.rs | 3 +- mixnode/src/commands/run.rs | 18 +- mixnode/src/commands/sign.rs | 12 +- mixnode/src/commands/upgrade.rs | 23 +- mixnode/src/config/mod.rs | 410 +++---------- mixnode/src/config/persistence/pathfinder.rs | 83 ++- mixnode/src/node/mod.rs | 116 ++-- mixnode/src/node/node_description.rs | 28 +- 17 files changed, 586 insertions(+), 1248 deletions(-) rename common/client-core/src/config/{persistence => disk_persistence}/key_pathfinder.rs (100%) rename common/client-core/src/config/{persistence => disk_persistence}/mod.rs (100%) create mode 100644 common/config/src/helpers.rs delete mode 100644 common/network-defaults/src/eth_contract.rs diff --git a/common/client-core/src/config/persistence/key_pathfinder.rs b/common/client-core/src/config/disk_persistence/key_pathfinder.rs similarity index 100% rename from common/client-core/src/config/persistence/key_pathfinder.rs rename to common/client-core/src/config/disk_persistence/key_pathfinder.rs diff --git a/common/client-core/src/config/persistence/mod.rs b/common/client-core/src/config/disk_persistence/mod.rs similarity index 100% rename from common/client-core/src/config/persistence/mod.rs rename to common/client-core/src/config/disk_persistence/mod.rs diff --git a/common/config/Cargo.toml b/common/config/Cargo.toml index 3add3dc1b4..ed894e808b 100644 --- a/common/config/Cargo.toml +++ b/common/config/Cargo.toml @@ -7,11 +7,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cfg-if = "1.0.0" -handlebars = "3.0.1" +dirs = { version = "5.0.1", optional = true } +handlebars = "3.5.5" log = { workspace = true } serde = { workspace = true, features = ["derive"] } -toml = "0.5.6" +toml = "0.7.4" url = "2.2" nym-network-defaults = { path = "../network-defaults" } + +[features] +default = ["dirs"] \ No newline at end of file diff --git a/common/config/src/helpers.rs b/common/config/src/helpers.rs new file mode 100644 index 0000000000..df2717bb57 --- /dev/null +++ b/common/config/src/helpers.rs @@ -0,0 +1,143 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_network_defaults::mainnet::read_var_if_not_default; +use nym_network_defaults::var_names::CONFIGURED; +use std::any::type_name; +use std::fmt::Debug; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::str::FromStr; + +pub const MISSING_VALUE: &str = "MISSING VALUE"; + +/// Helper for providing default value for templated config fields. +pub fn missing_string_value>() -> T { + MISSING_VALUE.to_string().into() +} + +/// Helper for providing default INADDR_ANY IpAddr, i.e. `0.0.0.0` +pub fn inaddr_any() -> IpAddr { + IpAddr::V4(Ipv4Addr::UNSPECIFIED) +} + +/// Helper for providing default IN6ADDR_ANY_INIT IpAddr, i.e. `::` +pub fn in6addr_any_init() -> IpAddr { + IpAddr::V6(Ipv6Addr::UNSPECIFIED) +} + +/// Helper for providing binding warnings if node tries to bind to any of those +pub const SPECIAL_ADDRESSES: &[IpAddr] = &[ + IpAddr::V4(Ipv4Addr::LOCALHOST), + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + IpAddr::V4(Ipv4Addr::BROADCAST), + IpAddr::V6(Ipv6Addr::LOCALHOST), + IpAddr::V6(Ipv6Addr::UNSPECIFIED), +]; + +// TODO: is it really part of 'Config'? +pub trait OptionalSet { + /// If the value is available (i.e. `Some`), the provided closure is applied. + /// Otherwise `self` is returned with no modifications. + fn with_optional(self, f: F, val: Option) -> Self + where + F: Fn(Self, T) -> Self, + Self: Sized, + { + if let Some(val) = val { + f(self, val) + } else { + self + } + } + + /// If the value is available (i.e. `Some`) it is validated and then the provided closure is applied. + /// Otherwise `self` is returned with no modifications. + fn with_validated_optional( + self, + f: F, + value: Option, + validate: V, + ) -> Result + where + F: Fn(Self, T) -> Self, + V: Fn(&T) -> Result<(), E>, + Self: Sized, + { + if let Some(val) = value { + validate(&val)?; + Ok(f(self, val)) + } else { + Ok(self) + } + } + + /// If the value is available (i.e. `Some`), the provided closure is applied. + /// Otherwise, if the environment was configured and the corresponding variable was set, + /// the value is parsed using the `FromStr` implementation and the closure is applied on that instead. + /// Finally, if none of those were available, `self` is returned with no modifications. + fn with_optional_env(self, f: F, val: Option, env_var: &str) -> Self + where + F: Fn(Self, T) -> Self, + T: FromStr, + ::Err: Debug, + Self: Sized, + { + if let Some(val) = val { + return f(self, val); + } else if std::env::var(CONFIGURED).is_ok() { + if let Some(raw) = read_var_if_not_default(env_var) { + return f( + self, + raw.parse().unwrap_or_else(|err| { + panic!( + "failed to parse value of {raw} into type {}. the error was {:?}", + type_name::(), + err + ) + }), + ); + } + } + self + } + + /// If the value is available (i.e. `Some`), the provided closure is applied. + /// Otherwise, if the environment was configured and the corresponding variable was set, + /// the value is parsed using the provided parser and the closure is applied on that instead. + /// Finally, if none of those were available, `self` is returned with no modifications. + fn with_optional_custom_env( + self, + f: F, + val: Option, + env_var: &str, + parser: G, + ) -> Self + where + F: Fn(Self, T) -> Self, + G: Fn(&str) -> T, + Self: Sized, + { + if let Some(val) = val { + return f(self, val); + } else if std::env::var(CONFIGURED).is_ok() { + if let Some(raw) = read_var_if_not_default(env_var) { + return f(self, parser(&raw)); + } + } + self + } +} + +// this function is only used for parsing values from the network defaults and thus the "expect" there are fine +pub fn parse_urls(raw: &str) -> Vec { + raw.split(',') + .map(|raw_url| { + raw_url + .trim() + .parse() + .expect("one of the provided urls was invalid") + }) + .collect() +} + +impl OptionalSet for T {} diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs index 030a991bfc..12c7b2a7b1 100644 --- a/common/config/src/lib.rs +++ b/common/config/src/lib.rs @@ -1,219 +1,201 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use handlebars::Handlebars; -use nym_network_defaults::mainnet::read_var_if_not_default; -use nym_network_defaults::var_names::CONFIGURED; +use handlebars::{Handlebars, TemplateRenderError}; use serde::de::DeserializeOwned; use serde::Serialize; -use std::any::type_name; -use std::fmt::Debug; -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; +use std::fs::File; +use std::io::Write; use std::path::{Path, PathBuf}; -use std::str::FromStr; use std::{fs, io}; -pub mod defaults; +pub use helpers::{parse_urls, OptionalSet}; +pub use toml::de::Error as TomlDeError; -pub const CONFIG_DIR: &str = "config"; -pub const DATA_DIR: &str = "data"; +pub mod defaults; +pub mod helpers; + +pub const NYM_DIR: &str = ".nym"; +pub const DEFAULT_CONFIG_DIR: &str = "config"; +pub const DEFAULT_DATA_DIR: &str = "data"; +pub const DEFAULT_CONFIG_FILENAME: &str = "config.toml"; + +// TODO: move it elsewhere because it's only really for clients +#[deprecated] pub const CRED_DB_FILE_NAME: &str = "credentials_database.db"; -pub trait NymConfig: Default + Serialize + DeserializeOwned { +#[cfg(feature = "dirs")] +pub fn must_get_home() -> PathBuf { + dirs::home_dir().expect("Failed to evaluate $HOME value") +} + +#[cfg(feature = "dirs")] +pub fn may_get_home() -> Option { + dirs::home_dir() +} + +pub trait NymConfigTemplate: Serialize { fn template() -> &'static str; - fn config_file_name() -> String { - "config.toml".to_string() + fn format_to_string(&self) -> String { + // it is responsibility of whoever is implementing the trait to ensure the template is valid + Handlebars::new() + .render_template(Self::template(), &self) + .unwrap() } - fn default_root_directory() -> PathBuf; - - // default, most probable, implementations; can be easily overridden where required - fn default_config_directory(id: &str) -> PathBuf { - Self::default_config_directory_with_root(Self::default_root_directory(), id) - } - - fn default_config_directory_with_root>(root: P, id: &str) -> PathBuf { - root.as_ref().join(id).join(CONFIG_DIR) - } - - fn default_data_directory(id: &str) -> PathBuf { - Self::default_data_directory_with_root(Self::default_root_directory(), id) - } - - fn default_data_directory_with_root>(root: P, id: &str) -> PathBuf { - root.as_ref().join(id).join(DATA_DIR) - } - - fn default_config_file_path(id: &str) -> PathBuf { - Self::default_config_directory(id).join(Self::config_file_name()) - } - - fn default_config_file_path_with_root>(root: P, id: &str) -> PathBuf { - Self::default_config_directory_with_root(root, id).join(Self::config_file_name()) - } - - // We provide a second set of functions that tries to not panic. - - fn try_default_root_directory() -> Option; - - fn try_default_config_directory(id: &str) -> Option { - Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR)) - } - - fn try_default_data_directory(id: &str) -> Option { - Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR)) - } - - fn try_default_config_file_path(id: &str) -> Option { - Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name())) - } - - fn root_directory(&self) -> PathBuf; - fn config_directory(&self) -> PathBuf; - fn data_directory(&self) -> PathBuf; - - fn save_to_file(&self, custom_location: Option) -> io::Result<()> { - let reg = Handlebars::new(); - // it's whoever is implementing the trait responsibility to make sure you can execute your own template on your data - let templated_config = reg.render_template(Self::template(), self).unwrap(); - - // make sure the whole directory structure actually exists - match custom_location.clone() { - Some(loc) => { - if let Some(parent_dir) = loc.parent() { - fs::create_dir_all(parent_dir) - } else { - Ok(()) - } - } - None => fs::create_dir_all(self.config_directory()), - }?; - - let location = custom_location - .unwrap_or_else(|| self.config_directory().join(Self::config_file_name())); - log::info!("Configuration file will be saved to {:?}", location); - - cfg_if::cfg_if! { - if #[cfg(unix)] { - fs::write(location.clone(), templated_config)?; - let mut perms = fs::metadata(location.clone())?.permissions(); - perms.set_mode(0o600); - fs::set_permissions(location, perms)?; + fn format_to_writer(&self, writer: W) -> io::Result<()> { + if let Err(err) = + Handlebars::new().render_template_to_write(Self::template(), &self, writer) + { + if let TemplateRenderError::IOError(err, _) = err { + return Err(err); } else { - fs::write(location, templated_config)?; + // it is responsibility of whoever is implementing the trait to ensure the template is valid + panic!("invalid template") } } Ok(()) } - - fn load_from_file(id: &str) -> io::Result { - let file = Self::default_config_file_path(id); - Self::load_from_filepath(file) - } - - fn load_from_filepath>(filepath: P) -> io::Result { - log::trace!("Loading from file: {:#?}", filepath.as_ref().to_owned()); - let config_contents = fs::read_to_string(filepath)?; - - toml::from_str(&config_contents) - .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) - } } -// this function is only used for parsing values from the network defaults and thus the "expect" there are fine -pub fn parse_urls(raw: &str) -> Vec { - raw.split(',') - .map(|raw_url| { - raw_url - .trim() - .parse() - .expect("one of the provided nym api urls is invalid") - }) - .collect() +pub fn save_formatted_config_to_file(config: &C, path: P) -> io::Result<()> +where + C: NymConfigTemplate, + P: AsRef, +{ + log::trace!("trying to save config file to {}", path.as_ref().display()); + let file = File::create(path.as_ref())?; + + // TODO: check for whether any of our configs stores anything sensitive + // and change that to 0o644 instead + #[cfg(target_family = "unix")] + { + use std::os::unix::fs::PermissionsExt; + + let mut perms = fs::metadata(path.as_ref())?.permissions(); + perms.set_mode(0o600); + fs::set_permissions(path, perms)?; + } + + config.format_to_writer(file) } -pub trait OptionalSet { - fn with_optional(self, f: F, val: Option) -> Self - where - F: Fn(Self, T) -> Self, - Self: Sized, - { - if let Some(val) = val { - f(self, val) - } else { - self - } - } - - fn with_validated_optional( - self, - f: F, - value: Option, - validate: V, - ) -> Result - where - F: Fn(Self, T) -> Self, - V: Fn(&T) -> Result<(), E>, - Self: Sized, - { - if let Some(val) = value { - validate(&val)?; - Ok(f(self, val)) - } else { - Ok(self) - } - } - - fn with_optional_env(self, f: F, val: Option, env_var: &str) -> Self - where - F: Fn(Self, T) -> Self, - T: FromStr, - ::Err: Debug, - Self: Sized, - { - if let Some(val) = val { - return f(self, val); - } else if std::env::var(CONFIGURED).is_ok() { - if let Some(raw) = read_var_if_not_default(env_var) { - return f( - self, - raw.parse().unwrap_or_else(|err| { - panic!( - "failed to parse value of {raw} into type {}. the error was {:?}", - type_name::(), - err - ) - }), - ); - } - } - self - } - - fn with_optional_custom_env( - self, - f: F, - val: Option, - env_var: &str, - parser: G, - ) -> Self - where - F: Fn(Self, T) -> Self, - G: Fn(&str) -> T, - Self: Sized, - { - if let Some(val) = val { - return f(self, val); - } else if std::env::var(CONFIGURED).is_ok() { - if let Some(raw) = read_var_if_not_default(env_var) { - return f(self, parser(&raw)); - } - } - self - } +pub fn deserialize_config_from_toml_str(raw: &str) -> Result +where + C: DeserializeOwned, +{ + toml::from_str(raw) } -impl OptionalSet for T where T: NymConfig {} +pub fn read_config_from_toml_file(path: P) -> io::Result +where + C: DeserializeOwned, + P: AsRef, +{ + log::trace!( + "trying to read config file from {}", + path.as_ref().display() + ); + let content = fs::read_to_string(path)?; + + // TODO: should we be preserving original error type instead? + deserialize_config_from_toml_str(&content) + .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) +} + +// +// +// +// pub trait NymConfig: Default + Serialize + DeserializeOwned { +// fn template() -> &'static str; +// +// fn config_file_name() -> String { +// "config.toml".to_string() +// } +// +// fn default_root_directory() -> PathBuf; +// +// // default, most probable, implementations; can be easily overridden where required +// fn default_config_directory(id: &str) -> PathBuf { +// Self::default_root_directory() +// .join(id) +// .join(DEFAULT_CONFIG_DIR) +// } +// +// fn default_data_directory(id: &str) -> PathBuf { +// Self::default_root_directory() +// .join(id) +// .join(DEFAULT_DATA_DIR) +// } +// +// fn default_config_file_path(id: &str) -> PathBuf { +// Self::default_config_directory(id).join(Self::config_file_name()) +// } +// +// // We provide a second set of functions that tries to not panic. +// +// fn try_default_root_directory() -> Option; +// +// fn try_default_config_directory(id: &str) -> Option { +// Self::try_default_root_directory().map(|d| d.join(id).join(DEFAULT_CONFIG_DIR)) +// } +// +// fn try_default_data_directory(id: &str) -> Option { +// Self::try_default_root_directory().map(|d| d.join(id).join(DEFAULT_DATA_DIR)) +// } +// +// fn try_default_config_file_path(id: &str) -> Option { +// Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name())) +// } +// +// fn root_directory(&self) -> PathBuf; +// fn config_directory(&self) -> PathBuf; +// fn data_directory(&self) -> PathBuf; +// +// fn save_to_file(&self, custom_location: Option) -> io::Result<()> { +// Ok(()) +// // let reg = Handlebars::new(); +// // // it's whoever is implementing the trait responsibility to make sure you can execute your own template on your data +// // let templated_config = reg.render_template(Self::template(), self).unwrap(); +// // +// // // make sure the whole directory structure actually exists +// // match custom_location.clone() { +// // Some(loc) => { +// // if let Some(parent_dir) = loc.parent() { +// // fs::create_dir_all(parent_dir) +// // } else { +// // Ok(()) +// // } +// // } +// // None => fs::create_dir_all(self.config_directory()), +// // }?; +// // +// // let location = custom_location +// // .unwrap_or_else(|| self.config_directory().join(Self::config_file_name())); +// // log::info!("Configuration file will be saved to {:?}", location); +// // +// // cfg_if::cfg_if! { +// // if #[cfg(unix)] { +// // fs::write(location.clone(), templated_config)?; +// // let mut perms = fs::metadata(location.clone())?.permissions(); +// // perms.set_mode(0o600); +// // fs::set_permissions(location, perms)?; +// // } else { +// // fs::write(location, templated_config)?; +// // } +// // } +// // +// // Ok(()) +// } +// +// fn load_from_file(id: &str) -> io::Result { +// let file = Self::default_config_file_path(id); +// log::trace!("Loading from file: {:#?}", file); +// let config_contents = fs::read_to_string(file)?; +// +// toml::from_str(&config_contents) +// .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err)) +// } +// } diff --git a/common/network-defaults/src/eth_contract.rs b/common/network-defaults/src/eth_contract.rs deleted file mode 100644 index 53b9190caa..0000000000 --- a/common/network-defaults/src/eth_contract.rs +++ /dev/null @@ -1,549 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -// This should be modified whenever an updated Ethereum contract is uploaded -pub const ETH_JSON_ABI: &str = r#" -[ - { - "inputs": [ - { - "internalType": "contract CosmosERC20", - "name": "_erc20", - "type": "address" - }, - { - "internalType": "contract Gravity", - "name": "_gravityBridge", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "Bandwidth", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "VerificationKey", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "SignedVerificationKey", - "type": "bytes" - }, - { - "indexed": false, - "internalType": "string", - "name": "CosmosRecipient", - "type": "string" - } - ], - "name": "BBCredentialPurchased", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "bool", - "name": "Enabled", - "type": "bool" - } - ], - "name": "CredentialGenerationSwitch", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "NewBytesPerToken", - "type": "uint256" - } - ], - "name": "RatioChanged", - "type": "event" - }, - { - "inputs": [], - "name": "BytesPerToken", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "bandwidthFromToken", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_newBytesPerTokenAmount", - "type": "uint256" - } - ], - "name": "changeRatio", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "credentialGenerationEnabled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "_generation", - "type": "bool" - } - ], - "name": "credentialGenerationSwitch", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "erc20", - "outputs": [ - { - "internalType": "contract CosmosERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_verificationKey", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_signedVerificationKey", - "type": "bytes" - }, - { - "internalType": "string", - "name": "_cosmosRecipient", - "type": "string" - } - ], - "name": "generateBasicBandwidthCredential", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "gravityBridge", - "outputs": [ - { - "internalType": "contract Gravity", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ] - "#; - -pub const ETH_ERC20_JSON_ABI: &str = r#" -[ - { - "inputs": [ - { - "internalType": "string", - "name": "name_", - "type": "string" - }, - { - "internalType": "string", - "name": "symbol_", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } - ] - "#; diff --git a/mixnode/src/commands/describe.rs b/mixnode/src/commands/describe.rs index 160e5de633..ee870ed5d7 100644 --- a/mixnode/src/commands/describe.rs +++ b/mixnode/src/commands/describe.rs @@ -5,7 +5,6 @@ use crate::config::Config; use crate::node::node_description::NodeDescription; use clap::Args; use colored::Colorize; -use nym_config::NymConfig; use std::io; use std::io::Write; @@ -41,7 +40,7 @@ fn read_user_input() -> String { pub(crate) fn execute(args: Describe) { // ensure that the mixnode has in fact been initialized - match Config::load_from_file(&args.id) { + let config = match Config::read_from_default_path(&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); @@ -81,9 +80,5 @@ 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.pathfinder.node_description).unwrap() } diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index 2b7c7bd364..ae0227edb0 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -2,14 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use super::OverrideConfig; -use crate::config::Config; +use crate::commands::override_config; +use crate::config::{default_config_filepath, 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; #[derive(Args, Clone)] @@ -22,10 +20,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, @@ -38,10 +32,6 @@ pub(crate) struct Init { #[clap(long)] http_api_port: Option, - /// The custom host that will be reported to the directory server - #[clap(long)] - announce_host: Option, - /// 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,11 +46,9 @@ impl From 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, } } @@ -68,17 +56,17 @@ impl From for OverrideConfig { 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 { 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 +75,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.pathfinder.private_identity_key(), + config.pathfinder.public_identity_key(), ), ) .expect("Failed to save identity keys"); @@ -100,17 +88,17 @@ 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.pathfinder.private_encryption_key(), + config.pathfinder.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(); + let config_save_location = default_config_filepath(id); config - .save_to_file(None) + .save_to_default_location() .expect("Failed to save the config file"); eprintln!("Saved configuration file to {config_save_location:?}"); eprintln!("Mixnode configuration completed.\n\n\n"); diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index 09fc34ebdf..8e316a54b8 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -10,7 +10,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,11 +51,9 @@ pub(crate) enum Commands { struct OverrideConfig { id: String, host: Option, - wallet_address: Option, mix_port: Option, verloc_port: Option, http_api_port: Option, - announce_host: Option, nym_apis: Option>, } @@ -75,22 +72,9 @@ pub(crate) async fn execute(args: Cli) { } } -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 +84,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 +112,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 { diff --git a/mixnode/src/commands/node_details.rs b/mixnode/src/commands/node_details.rs index a4b185206c..cf58b942cc 100644 --- a/mixnode/src/commands/node_details.rs +++ b/mixnode/src/commands/node_details.rs @@ -5,7 +5,6 @@ use crate::config::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 { @@ -18,7 +17,7 @@ pub(crate) struct NodeDetails { } pub(crate) fn execute(args: &NodeDetails) { - let config = match Config::load_from_file(&args.id) { + let config = match Config::read_from_default_path(&args.id) { Ok(cfg) => cfg, Err(err) => { error!( diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index 97afd5b86f..616e128048 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -7,7 +7,7 @@ use crate::config::Config; use crate::node::MixNode; 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, - /// The host that will be reported to the directory server - #[clap(long)] - announce_host: Option, - /// 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 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, } } @@ -75,14 +69,10 @@ fn show_binding_warning(address: &str) { 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) { eprintln!("Starting mixnode {}...", args.id); - let mut config = match Config::load_from_file(&args.id) { + let mut config = match Config::read_from_default_path(&args.id) { Ok(cfg) => cfg, Err(err) => { error!( @@ -101,8 +91,8 @@ pub(crate) async fn execute(args: &Run) { return; } - 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); diff --git a/mixnode/src/commands/sign.rs b/mixnode/src/commands/sign.rs index 29111b7354..d061e1ca93 100644 --- a/mixnode/src/commands/sign.rs +++ b/mixnode/src/commands/sign.rs @@ -1,18 +1,17 @@ // Copyright 2020-2023 - Nym Technologies SA // 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::config::Config; 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; @@ -117,7 +116,7 @@ fn print_signed_contract_msg( } pub(crate) fn execute(args: &Sign) { - let config = match Config::load_from_file(&args.id) { + let config = match Config::read_from_default_path(&args.id) { Ok(cfg) => cfg, Err(err) => { error!( @@ -140,8 +139,7 @@ pub(crate) fn execute(args: &Sign) { return; } }; - 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) => { diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs index ea636cdf14..2df435c8f2 100644 --- a/mixnode/src/commands/upgrade.rs +++ b/mixnode/src/commands/upgrade.rs @@ -1,10 +1,9 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::{missing_string_value, 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 +45,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 +88,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); @@ -125,12 +126,12 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) { pub(crate) fn execute(args: &Upgrade) { 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::() { + if existing_config.mixnode.version.is_empty() { eprintln!("the existing configuration file does not seem to contain version number."); process::exit(1); } diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 5320a382f3..d49c402639 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -1,16 +1,21 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::config::persistence::pathfinder::MixNodePathfinder; use crate::config::template::config_template; -use nym_config::defaults::mainnet::NYM_API; 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; @@ -18,7 +23,7 @@ use url::Url; 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 +42,75 @@ 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 { - MISSING_VALUE.to_string().into() +/// Derive default path to mixnodes's config file. +/// It should get resolved to `$HOME/.nym/mixnodes//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_MIXNODES_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) + .join(DEFAULT_CONFIG_FILENAME) } -fn bind_all_address() -> IpAddr { - "0.0.0.0".parse().unwrap() +/// Derive default path to mixnodes's data directory where files, such as keys, are stored. +/// It should get resolved to `$HOME/.nym/mixnodes//data` +pub fn default_data_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_MIXNODES_DIR) + .join(id) + .join(DEFAULT_DATA_DIR) } -fn default_mix_port() -> u16 { - DEFAULT_MIX_LISTENING_PORT -} - -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 -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 pathfinder: MixNodePathfinder, #[serde(default)] - verloc: Verloc, + pub verloc: Verloc, + #[serde(default)] - logging: Logging, + pub logging: Logging, + #[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 { - 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") - } } impl Config { - pub fn new>(id: S) -> Self { - Config::default().with_id(id) + pub fn new>(id: S) -> Self { + Config { + mixnode: MixNode::new_default(id.as_ref()), + pathfinder: MixNodePathfinder::new_default(id.as_ref()), + verloc: Default::default(), + logging: Default::default(), + debug: Default::default(), + } + } + + pub fn read_from_toml_file>(path: P) -> io::Result { + read_config_from_toml_file(path) + } + + pub fn read_from_default_path>(id: P) -> io::Result { + Self::read_from_toml_file(default_config_filepath(id)) + } + + pub fn save_to_default_location(&self) -> io::Result<()> { + let config_save_location: PathBuf = default_config_filepath(&self.mixnode.id); + save_formatted_config_to_file(self, config_save_location) } // builder methods - pub fn with_id>(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) -> Self { self.mixnode.nym_api_urls = nym_api_urls; self @@ -166,11 +121,6 @@ impl Config { self } - pub fn with_announce_address>(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 +136,85 @@ 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>(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 { 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 { - 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, - - /// 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, + pub nym_api_urls: Vec, } 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>(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 {} +pub 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, + pub connection_timeout: Duration, /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. - packet_timeout: Duration, + pub packet_timeout: Duration, /// Specifies delay between subsequent test packets being sent (after receiving a reply). - delay_between_packets: Duration, + 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, + 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, + pub retry_timeout: Duration, } impl Default for Verloc { @@ -459,37 +233,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 { diff --git a/mixnode/src/config/persistence/pathfinder.rs b/mixnode/src/config/persistence/pathfinder.rs index 1740263859..3498372060 100644 --- a/mixnode/src/config/persistence/pathfinder.rs +++ b/mixnode/src/config/persistence/pathfinder.rs @@ -1,40 +1,91 @@ -// Copyright 2020 - Nym Technologies SA +// Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::Config; +use serde::{Deserialize, Serialize}; + +use crate::config::{default_config_filepath, default_data_directory}; use std::path::{Path, PathBuf}; -#[derive(Debug)] +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 MixNodePathfinder { - identity_private_key: PathBuf, - identity_public_key: PathBuf, - private_sphinx_key: PathBuf, - public_sphinx_key: PathBuf, + pub keys: KeysPathfinder, + + pub node_description: PathBuf, } impl MixNodePathfinder { - pub fn new_from_config(config: &Config) -> Self { + pub fn new_default>(id: P) -> 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(), + keys: KeysPathfinder::new_default(id.as_ref()), + node_description: default_config_filepath(id).join(DEFAULT_DESCRIPTION_FILENAME), } } pub fn private_identity_key(&self) -> &Path { - &self.identity_private_key + self.keys.private_identity_key() } pub fn public_identity_key(&self) -> &Path { - &self.identity_public_key + self.keys.public_identity_key() } pub fn private_encryption_key(&self) -> &Path { - &self.private_sphinx_key + self.keys.private_encryption_key() } pub fn public_encryption_key(&self) -> &Path { - &self.public_sphinx_key + self.keys.public_encryption_key() + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +pub struct KeysPathfinder { + /// 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 KeysPathfinder { + pub fn new_default>(id: P) -> Self { + let data_dir = default_data_directory(id); + + KeysPathfinder { + 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 } } diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index a1c11d202d..6fb617d6ec 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -1,14 +1,13 @@ -// Copyright 2020 - Nym Technologies SA +// Copyright 2020-2023 - Nym Technologies SA // 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.pathfinder.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.pathfinder.keys.private_identity_key(), + config.pathfinder.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.pathfinder.keys.private_encryption_key(), + config.pathfinder.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 { + 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(); diff --git a/mixnode/src/node/node_description.rs b/mixnode/src/node/node_description.rs index 4622c4be12..0c31a3e004 100644 --- a/mixnode/src/node/node_description.rs +++ b/mixnode/src/node/node_description.rs @@ -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 { - 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>(path: P) -> io::Result { + // 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>( 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(()) } }