revamping mixnode connfig

This commit is contained in:
Jędrzej Stuczyński
2023-05-24 14:56:42 +01:00
parent 9ae4fd04ac
commit b3f883e82b
17 changed files with 586 additions and 1248 deletions
+6 -3
View File
@@ -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"]
+143
View File
@@ -0,0 +1,143 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// 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: From<String>>() -> 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<F, T>(self, f: F, val: Option<T>) -> 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<F, T, V, E>(
self,
f: F,
value: Option<T>,
validate: V,
) -> Result<Self, E>
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<F, T>(self, f: F, val: Option<T>, env_var: &str) -> Self
where
F: Fn(Self, T) -> Self,
T: FromStr,
<T as 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::<T>(),
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<F, T, G>(
self,
f: F,
val: Option<T>,
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<url::Url> {
raw.split(',')
.map(|raw_url| {
raw_url
.trim()
.parse()
.expect("one of the provided urls was invalid")
})
.collect()
}
impl<T> OptionalSet for T {}
+174 -192
View File
@@ -1,219 +1,201 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// 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<PathBuf> {
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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<PathBuf>;
fn try_default_config_directory(id: &str) -> Option<PathBuf> {
Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR))
}
fn try_default_data_directory(id: &str) -> Option<PathBuf> {
Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR))
}
fn try_default_config_file_path(id: &str) -> Option<PathBuf> {
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<PathBuf>) -> 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<W: Write>(&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<Self> {
let file = Self::default_config_file_path(id);
Self::load_from_filepath(file)
}
fn load_from_filepath<P: AsRef<Path>>(filepath: P) -> io::Result<Self> {
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<url::Url> {
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<C, P>(config: &C, path: P) -> io::Result<()>
where
C: NymConfigTemplate,
P: AsRef<Path>,
{
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<F, T>(self, f: F, val: Option<T>) -> Self
where
F: Fn(Self, T) -> Self,
Self: Sized,
{
if let Some(val) = val {
f(self, val)
} else {
self
}
}
fn with_validated_optional<F, T, V, E>(
self,
f: F,
value: Option<T>,
validate: V,
) -> Result<Self, E>
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<F, T>(self, f: F, val: Option<T>, env_var: &str) -> Self
where
F: Fn(Self, T) -> Self,
T: FromStr,
<T as 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::<T>(),
err
)
}),
);
}
}
self
}
fn with_optional_custom_env<F, T, G>(
self,
f: F,
val: Option<T>,
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<C>(raw: &str) -> Result<C, TomlDeError>
where
C: DeserializeOwned,
{
toml::from_str(raw)
}
impl<T> OptionalSet for T where T: NymConfig {}
pub fn read_config_from_toml_file<C, P>(path: P) -> io::Result<C>
where
C: DeserializeOwned,
P: AsRef<Path>,
{
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<PathBuf>;
//
// fn try_default_config_directory(id: &str) -> Option<PathBuf> {
// Self::try_default_root_directory().map(|d| d.join(id).join(DEFAULT_CONFIG_DIR))
// }
//
// fn try_default_data_directory(id: &str) -> Option<PathBuf> {
// Self::try_default_root_directory().map(|d| d.join(id).join(DEFAULT_DATA_DIR))
// }
//
// fn try_default_config_file_path(id: &str) -> Option<PathBuf> {
// 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<PathBuf>) -> 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<Self> {
// 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))
// }
// }
-549
View File
@@ -1,549 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// 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"
}
]
"#;
+2 -7
View File
@@ -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()
}
+12 -24
View File
@@ -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<u16>,
@@ -38,10 +32,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,11 +46,9 @@ 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,
}
}
@@ -68,17 +56,17 @@ impl From<Init> 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");
+3 -26
View File
@@ -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<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>>,
}
@@ -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 {
+1 -2
View File
@@ -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!(
+4 -14
View File
@@ -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<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,
}
}
@@ -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);
+5 -7
View File
@@ -1,18 +1,17 @@
// 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::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) => {
+12 -11
View File
@@ -1,10 +1,9 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// 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::<String>() {
if existing_config.mixnode.version.is_empty() {
eprintln!("the existing configuration file does not seem to contain version number.");
process::exit(1);
}
+92 -318
View File
@@ -1,16 +1,21 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// 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: From<String>>() -> T {
MISSING_VALUE.to_string().into()
/// 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 {
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/<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_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<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 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<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")
}
}
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()),
pathfinder: MixNodePathfinder::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 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<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 +121,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 +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<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 {}
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 {
+67 -16
View File
@@ -1,40 +1,91 @@
// 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::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<P: AsRef<Path>>(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<P: AsRef<Path>>(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
}
}
+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.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<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(())
}
}