diff --git a/Cargo.lock b/Cargo.lock index cb73f1c662..abb9707966 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1507,7 +1507,16 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", ] [[package]] @@ -1521,6 +1530,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -1811,7 +1832,7 @@ dependencies = [ "atomic", "pear", "serde", - "toml", + "toml 0.5.11", "uncased", "version_check", ] @@ -3305,7 +3326,7 @@ dependencies = [ "cw-utils", "cw3", "cw4", - "dirs", + "dirs 4.0.0", "futures", "getset", "humantime-serde", @@ -3357,6 +3378,7 @@ dependencies = [ "tokio-stream", "ts-rs", "url", + "zeroize", ] [[package]] @@ -3486,7 +3508,7 @@ dependencies = [ "tap", "thiserror", "time 0.3.21", - "toml", + "toml 0.5.11", "url", ] @@ -3495,7 +3517,7 @@ name = "nym-client" version = "1.1.20" dependencies = [ "clap 4.2.7", - "dirs", + "dirs 4.0.0", "futures", "lazy_static", "log", @@ -3532,7 +3554,7 @@ version = "1.1.14" dependencies = [ "async-trait", "dashmap 5.4.0", - "dirs", + "dirs 4.0.0", "futures", "gloo-timers", "humantime-serde", @@ -3639,12 +3661,12 @@ dependencies = [ name = "nym-config" version = "0.1.0" dependencies = [ - "cfg-if", + "dirs 5.0.1", "handlebars", "log", "nym-network-defaults", "serde", - "toml", + "toml 0.7.4", "url", ] @@ -3668,6 +3690,7 @@ dependencies = [ "log", "nym-bandwidth-controller", "nym-bin-common", + "nym-client-core", "nym-config", "nym-credential-storage", "nym-credentials", @@ -3773,7 +3796,7 @@ dependencies = [ "clap 4.2.7", "colored", "dashmap 4.0.2", - "dirs", + "dirs 4.0.0", "dotenvy", "futures", "humantime-serde", @@ -3923,7 +3946,7 @@ dependencies = [ "colored", "cpu-cycles", "cupid", - "dirs", + "dirs 4.0.0", "futures", "humantime-serde", "lazy_static", @@ -3952,7 +3975,7 @@ dependencies = [ "sysinfo", "tokio", "tokio-util", - "toml", + "toml 0.5.11", "tracing", "url", ] @@ -4030,7 +4053,7 @@ dependencies = [ "async-trait", "bs58", "clap 4.2.7", - "dirs", + "dirs 4.0.0", "futures", "humantime-serde", "ipnetwork 0.20.0", @@ -4071,7 +4094,7 @@ dependencies = [ name = "nym-network-statistics" version = "1.1.20" dependencies = [ - "dirs", + "dirs 4.0.0", "log", "nym-bin-common", "nym-statistics-common", @@ -4176,7 +4199,7 @@ dependencies = [ "tap", "thiserror", "tokio", - "toml", + "toml 0.5.11", "url", ] @@ -4243,7 +4266,7 @@ dependencies = [ name = "nym-socks5-client-core" version = "0.1.0" dependencies = [ - "dirs", + "dirs 4.0.0", "futures", "log", "nym-bandwidth-controller", @@ -4284,6 +4307,7 @@ dependencies = [ "openssl", "rand 0.7.3", "safer-ffi", + "serde", "tokio", ] @@ -4826,6 +4850,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_str_bytes" version = "6.5.0" @@ -6221,6 +6251,15 @@ dependencies = [ "syn 2.0.16", ] +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6860,7 +6899,7 @@ dependencies = [ "serde", "serde_json", "tendermint", - "toml", + "toml 0.5.11", "url", ] @@ -7205,6 +7244,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.9.2" @@ -8044,6 +8117,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" diff --git a/clients/credential/Cargo.toml b/clients/credential/Cargo.toml index 3a47f059b0..8d161359ec 100644 --- a/clients/credential/Cargo.toml +++ b/clients/credential/Cargo.toml @@ -12,7 +12,9 @@ serde = { workspace = true, features = ["derive"] } thiserror = "1.0" tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime + nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } +nym-client-core = { path = "../../common/client-core" } nym-config = { path = "../../common/config" } nym-credentials = { path = "../../common/credentials" } nym-credential-storage = { path = "../../common/credential-storage" } diff --git a/clients/credential/src/main.rs b/clients/credential/src/main.rs index 3fb0adcad8..e37a825403 100644 --- a/clients/credential/src/main.rs +++ b/clients/credential/src/main.rs @@ -1,4 +1,4 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 mod commands; @@ -9,13 +9,14 @@ use commands::*; use error::Result; use log::*; use nym_bin_common::completions::fig_generate; -use nym_config::{CRED_DB_FILE_NAME, DATA_DIR}; +use nym_config::DEFAULT_DATA_DIR; use nym_network_defaults::{setup_env, NymNetworkDetails}; use std::process::exit; use std::time::{Duration, SystemTime}; use clap::{CommandFactory, Parser}; use nym_bin_common::logging::setup_logging; +use nym_client_core::config::disk_persistence::CommonClientPaths; use nym_validator_client::nyxd::traits::DkgQueryClient; use nym_validator_client::nyxd::{Coin, CosmWasmClient}; use nym_validator_client::Config; @@ -71,10 +72,11 @@ async fn main() -> Result<()> { match args.command { Command::Run(r) => { - let db_path = r - .client_home_directory - .join(DATA_DIR) - .join(CRED_DB_FILE_NAME); + // we assume the structure of /data + let data_dir = r.client_home_directory.join(DEFAULT_DATA_DIR); + let paths = CommonClientPaths::new_default(data_dir); + let db_path = paths.credentials_database; + let shared_storage = nym_credential_storage::initialise_persistent_storage(db_path).await; let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?; diff --git a/clients/native/src/client/config/mod.rs b/clients/native/src/client/config/mod.rs index 79d160d58e..2f6e47391d 100644 --- a/clients/native/src/client/config/mod.rs +++ b/clients/native/src/client/config/mod.rs @@ -1,23 +1,57 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::client::config::template::config_template; -use nym_client_core::config::ClientCoreConfigTrait; +use crate::client::config::persistence::ClientPaths; +use crate::client::config::template::CONFIG_TEMPLATE; +use nym_bin_common::logging::LoggingSettings; use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; -use nym_config::{NymConfig, OptionalSet}; +use nym_config::{ + must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, + OptionalSet, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, +}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; +use std::io; use std::net::{IpAddr, Ipv4Addr}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::str::FromStr; -pub use nym_client_core::config::Config as BaseConfig; -pub use nym_client_core::config::MISSING_VALUE; +pub use nym_client_core::config::Config as BaseClientConfig; pub use nym_client_core::config::{DebugConfig, GatewayEndpointConfig}; pub mod old_config_v1_1_13; +pub mod old_config_v1_1_20; +mod persistence; mod template; +const DEFAULT_CLIENTS_DIR: &str = "clients"; + +/// Derive default path to clients's config directory. +/// It should get resolved to `$HOME/.nym/mixnodes//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_CLIENTS_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) +} + +/// Derive default path to client's config file. +/// It should get resolved to `$HOME/.nym/clients//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) +} + +/// Derive default path to client's data directory where files, such as keys, are stored. +/// It should get resolved to `$HOME/.nym/clients//data` +pub fn default_data_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_CLIENTS_DIR) + .join(id) + .join(DEFAULT_DATA_DIR) +} + #[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)] #[serde(deny_unknown_fields)] pub enum SocketType { @@ -26,74 +60,57 @@ pub enum SocketType { } impl SocketType { - pub fn from_string>(val: S) -> Self { - let mut upper = val.into(); - upper.make_ascii_uppercase(); - match upper.as_ref() { - "WEBSOCKET" | "WS" => SocketType::WebSocket, - _ => SocketType::None, - } - } - pub fn is_websocket(&self) -> bool { matches!(self, SocketType::WebSocket) } } -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] +#[derive(Debug, Deserialize, PartialEq, Serialize)] pub struct Config { #[serde(flatten)] - base: BaseConfig, + pub base: BaseClientConfig, - socket: Socket, + pub socket: Socket, + + // pub paths: CommonClientPathfinder, + pub storage_paths: ClientPaths, + + pub logging: LoggingSettings, } -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("clients") - } - - fn try_default_root_directory() -> Option { - dirs::home_dir().map(|path| path.join(".nym").join("clients")) - } - - fn root_directory(&self) -> PathBuf { - self.base.get_nym_root_directory() - } - - fn config_directory(&self) -> PathBuf { - self.root_directory() - .join(self.base.get_id()) - .join("config") - } - - fn data_directory(&self) -> PathBuf { - self.root_directory().join(self.base.get_id()).join("data") - } -} - -impl ClientCoreConfigTrait for Config { - fn get_gateway_endpoint(&self) -> &nym_client_core::config::GatewayEndpointConfig { - self.base.get_gateway_endpoint() + CONFIG_TEMPLATE } } impl Config { - pub fn new>(id: S) -> Self { + pub fn new>(id: S) -> Self { Config { - base: BaseConfig::new(id), + base: BaseClientConfig::new(id.as_ref()), + storage_paths: ClientPaths::new_default(default_data_directory(id.as_ref())), + logging: Default::default(), socket: 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 default_location(&self) -> PathBuf { + default_config_filepath(&self.base.client.id) + } + + pub fn save_to_default_location(&self) -> io::Result<()> { + let config_save_location: PathBuf = self.default_location(); + save_formatted_config_to_file(self, config_save_location) + } + pub fn validate(&self) -> bool { // no other sections have explicit requirements (yet) self.base.validate() @@ -123,39 +140,10 @@ impl Config { self } - // getters - pub fn get_config_file_save_location(&self) -> PathBuf { - self.config_directory().join(Self::config_file_name()) - } - - pub fn get_base(&self) -> &BaseConfig { - &self.base - } - - pub fn get_base_mut(&mut self) -> &mut BaseConfig { - &mut self.base - } - - pub fn get_debug_settings(&self) -> &DebugConfig { - self.get_base().get_debug_config() - } - - pub fn get_socket_type(&self) -> SocketType { - self.socket.socket_type - } - - pub fn get_listening_ip(&self) -> IpAddr { - self.socket.host - } - - pub fn get_listening_port(&self) -> u16 { - self.socket.listening_port - } - // poor man's 'builder' method pub fn with_base(mut self, f: F, val: T) -> Self where - F: Fn(BaseConfig, T) -> BaseConfig, + F: Fn(BaseClientConfig, T) -> BaseClientConfig, { self.base = f(self.base, val); self @@ -165,7 +153,7 @@ impl Config { // (plz, lets refactor it) pub fn with_optional_ext(mut self, f: F, val: Option) -> Self where - F: Fn(BaseConfig, T) -> BaseConfig, + F: Fn(BaseClientConfig, T) -> BaseClientConfig, { self.base = self.base.with_optional(f, val); self @@ -173,7 +161,7 @@ impl Config { pub fn with_optional_env_ext(mut self, f: F, val: Option, env_var: &str) -> Self where - F: Fn(BaseConfig, T) -> BaseConfig, + F: Fn(BaseClientConfig, T) -> BaseClientConfig, T: FromStr, ::Err: Debug, { @@ -189,7 +177,7 @@ impl Config { parser: G, ) -> Self where - F: Fn(BaseConfig, T) -> BaseConfig, + F: Fn(BaseClientConfig, T) -> BaseClientConfig, G: Fn(&str) -> T, { self.base = self.base.with_optional_custom_env(f, val, env_var, parser); @@ -197,19 +185,21 @@ impl Config { } } +// define_optional_set_inner!(Config, base, BaseClientConfig); + #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] #[serde(default, deny_unknown_fields)] pub struct Socket { - socket_type: SocketType, - host: IpAddr, - listening_port: u16, + pub socket_type: SocketType, + pub host: IpAddr, + pub listening_port: u16, } impl Default for Socket { fn default() -> Self { Socket { socket_type: SocketType::WebSocket, - host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + host: IpAddr::V4(Ipv4Addr::LOCALHOST), listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT, } } diff --git a/clients/native/src/client/config/old_config_v1_1_13.rs b/clients/native/src/client/config/old_config_v1_1_13.rs index 9e1b5bff81..507517ab3b 100644 --- a/clients/native/src/client/config/old_config_v1_1_13.rs +++ b/clients/native/src/client/config/old_config_v1_1_13.rs @@ -1,58 +1,33 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::client::config::{Config, Socket}; +use crate::client::config::old_config_v1_1_20::{ConfigV1_1_20, SocketV1_1_20}; use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13; -use nym_config::NymConfig; +use nym_config::legacy_helpers::nym_config::MigrationNymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct OldConfigV1_1_13 { #[serde(flatten)] - base: OldBaseConfigV1_1_13, + pub base: OldBaseConfigV1_1_13, - socket: Socket, + pub socket: SocketV1_1_20, } -impl NymConfig for OldConfigV1_1_13 { - fn template() -> &'static str { - // not intended to be used - unimplemented!() - } - +impl MigrationNymConfig for OldConfigV1_1_13 { fn default_root_directory() -> PathBuf { dirs::home_dir() .expect("Failed to evaluate $HOME value") .join(".nym") .join("clients") } - - fn try_default_root_directory() -> Option { - dirs::home_dir().map(|path| path.join(".nym").join("clients")) - } - - fn root_directory(&self) -> PathBuf { - self.base.client.nym_root_directory.clone() - } - - fn config_directory(&self) -> PathBuf { - self.root_directory() - .join(&self.base.client.id) - .join("config") - } - - fn data_directory(&self) -> PathBuf { - self.root_directory() - .join(&self.base.client.id) - .join("data") - } } -impl From for Config { +impl From for ConfigV1_1_20 { fn from(value: OldConfigV1_1_13) -> Self { - Config { + ConfigV1_1_20 { base: value.base.into(), socket: value.socket, } diff --git a/clients/native/src/client/config/old_config_v1_1_20.rs b/clients/native/src/client/config/old_config_v1_1_20.rs new file mode 100644 index 0000000000..c68f3871c3 --- /dev/null +++ b/clients/native/src/client/config/old_config_v1_1_20.rs @@ -0,0 +1,112 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::client::config::persistence::ClientPaths; +use crate::client::config::{Config, Socket, SocketType}; +use nym_bin_common::logging::LoggingSettings; +use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths; +use nym_client_core::config::disk_persistence::CommonClientPaths; +use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20; +use nym_client_core::config::{Client, Config as BaseConfig}; +use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; +use nym_config::legacy_helpers::nym_config::MigrationNymConfig; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::net::{IpAddr, Ipv4Addr}; +use std::path::PathBuf; + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)] +#[serde(deny_unknown_fields)] +pub enum SocketTypeV1_1_20 { + WebSocket, + None, +} + +impl From for SocketType { + fn from(value: SocketTypeV1_1_20) -> Self { + match value { + SocketTypeV1_1_20::WebSocket => SocketType::WebSocket, + SocketTypeV1_1_20::None => SocketType::None, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_20 { + #[serde(flatten)] + pub base: BaseConfigV1_1_20, + + pub socket: SocketV1_1_20, +} + +impl From for Config { + fn from(value: ConfigV1_1_20) -> Self { + Config { + base: BaseConfig { + client: Client { + version: value.base.client.version, + id: value.base.client.id, + disabled_credentials_mode: value.base.client.disabled_credentials_mode, + nyxd_urls: value.base.client.nyxd_urls, + nym_api_urls: value.base.client.nym_api_urls, + gateway_endpoint: value.base.client.gateway_endpoint.into(), + }, + debug: value.base.debug.into(), + }, + socket: value.socket.into(), + storage_paths: ClientPaths { + common_paths: CommonClientPaths { + keys: ClientKeysPaths { + private_identity_key_file: value.base.client.private_identity_key_file, + public_identity_key_file: value.base.client.public_identity_key_file, + private_encryption_key_file: value.base.client.private_encryption_key_file, + public_encryption_key_file: value.base.client.public_encryption_key_file, + gateway_shared_key_file: value.base.client.gateway_shared_key_file, + ack_key_file: value.base.client.ack_key_file, + }, + credentials_database: value.base.client.database_path, + reply_surb_database: value.base.client.reply_surb_database_path, + }, + }, + logging: LoggingSettings::default(), + } + } +} + +impl MigrationNymConfig for ConfigV1_1_20 { + fn default_root_directory() -> PathBuf { + dirs::home_dir() + .expect("Failed to evaluate $HOME value") + .join(".nym") + .join("clients") + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct SocketV1_1_20 { + socket_type: SocketTypeV1_1_20, + host: IpAddr, + listening_port: u16, +} + +impl From for Socket { + fn from(value: SocketV1_1_20) -> Self { + Socket { + socket_type: value.socket_type.into(), + host: value.host, + listening_port: value.listening_port, + } + } +} + +impl Default for SocketV1_1_20 { + fn default() -> Self { + SocketV1_1_20 { + socket_type: SocketTypeV1_1_20::WebSocket, + host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT, + } + } +} diff --git a/clients/native/src/client/config/persistence.rs b/clients/native/src/client/config/persistence.rs new file mode 100644 index 0000000000..d84c956d8f --- /dev/null +++ b/clients/native/src/client/config/persistence.rs @@ -0,0 +1,20 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_client_core::config::disk_persistence::CommonClientPaths; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)] +pub struct ClientPaths { + #[serde(flatten)] + pub common_paths: CommonClientPaths, +} + +impl ClientPaths { + pub fn new_default>(base_data_directory: P) -> Self { + ClientPaths { + common_paths: CommonClientPaths::new_default(base_data_directory), + } + } +} diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index 1c9a19f061..e3406a0101 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -1,12 +1,11 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub(crate) fn config_template() -> &'static str { - // While using normal toml marshalling would have been way simpler with less overhead, - // I think it's useful to have comments attached to the saved config file to explain behaviour of - // particular fields. - // Note: any changes to the template must be reflected in the appropriate structs. - r#" +// While using normal toml marshalling would have been way simpler with less overhead, +// I think it's useful to have comments attached to the saved config file to explain behaviour of +// particular fields. +// Note: any changes to the template must be reflected in the appropriate structs. +pub(crate) const CONFIG_TEMPLATE: &str = r#" # This is a TOML config file. # For more information, see https://github.com/toml-lang/toml @@ -37,39 +36,35 @@ nym_api_urls = [ {{/each}} ] +[storage_paths] + # Path to file containing private identity key. -private_identity_key_file = '{{ client.private_identity_key_file }}' +keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' # Path to file containing public identity key. -public_identity_key_file = '{{ client.public_identity_key_file }}' +keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}' # Path to file containing private encryption key. -private_encryption_key_file = '{{ client.private_encryption_key_file }}' +keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}' # Path to file containing public encryption key. -public_encryption_key_file = '{{ client.public_encryption_key_file }}' - -# Path to the database containing bandwidth credentials -database_path = '{{ client.database_path }}' - -# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. -reply_surb_database_path = '{{ client.reply_surb_database_path }}' - -##### additional client config options ##### +keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}' # A gateway specific, optional, base58 stringified shared key used for # communication with particular gateway. -gateway_shared_key_file = '{{ client.gateway_shared_key_file }}' +keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}' # Path to file containing key used for encrypting and decrypting the content of an # acknowledgement so that nobody besides the client knows which packet it refers to. -ack_key_file = '{{ client.ack_key_file }}' - -##### advanced configuration options ##### +keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}' -# Absolute path to the home Nym Clients directory. -nym_root_directory = '{{ client.nym_root_directory }}' +# Path to the database containing bandwidth credentials +credentials_database = '{{ storage_paths.credentials_database }}' +# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. +reply_surb_database = '{{ storage_paths.reply_surb_database }}' + +# DEPRECATED [client.gateway_endpoint] # ID of the gateway from which the client should be fetching messages. gateway_id = '{{ client.gateway_endpoint.gateway_id }}' @@ -120,5 +115,5 @@ average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}' [debug.cover_traffic] loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}' -"# -} + +"#; diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index 5c612f8fdf..3aa4798881 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2021-2022 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::client::config::Config; @@ -17,7 +17,6 @@ use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::client::received_buffer::{ ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver, }; -use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use nym_credential_storage::persistent_storage::PersistentStorage; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::params::PacketType; @@ -50,11 +49,11 @@ impl SocketClient { config: &Config, ) -> BandwidthController, PersistentStorage> { let storage = nym_credential_storage::initialise_persistent_storage( - config.get_base().get_database_path(), + &config.storage_paths.common_paths.credentials_database, ) .await; - create_bandwidth_controller(config.get_base(), storage) + create_bandwidth_controller(&config.base, storage) } fn start_websocket_listener( @@ -93,7 +92,7 @@ impl SocketClient { Some(packet_type), ); - websocket::Listener::new(config.get_listening_ip(), config.get_listening_port()) + websocket::Listener::new(config.socket.host, config.socket.listening_port) .start(websocket_handler, shutdown); } @@ -107,26 +106,25 @@ impl SocketClient { } fn key_store(&self) -> OnDiskKeys { - let pathfinder = ClientKeyPathfinder::new_from_config(self.config.get_base()); - OnDiskKeys::new(pathfinder) + OnDiskKeys::new(self.config.storage_paths.common_paths.keys.clone()) } // TODO: see if this could also be shared with socks5 client / nym-sdk maybe async fn create_base_client_builder(&self) -> Result { // don't create bandwidth controller if credentials are disabled - let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() { + let bandwidth_controller = if self.config.base.client.disabled_credentials_mode { None } else { Some(Self::create_bandwidth_controller(&self.config).await) }; let base_client = BaseClientBuilder::new_from_base_config( - self.config.get_base(), + &self.config.base, self.key_store(), bandwidth_controller, non_wasm_helpers::setup_fs_reply_surb_backend( - self.config.get_base().get_reply_surb_database_path(), - &self.config.get_debug_settings().reply_surbs, + &self.config.storage_paths.common_paths.reply_surb_database, + &self.config.base.debug.reply_surbs, ) .await?, ); @@ -135,13 +133,13 @@ impl SocketClient { } pub async fn start_socket(self) -> Result { - if !self.config.get_socket_type().is_websocket() { + if !self.config.socket.socket_type.is_websocket() { return Err(ClientError::InvalidSocketMode); } let base_builder = self.create_base_client_builder().await?; - let packet_type = self.config.get_base().get_packet_type(); - let mut started_client = base_builder.start_base(packet_type).await?; + let packet_type = self.config.base.debug.traffic.packet_type; + let mut started_client = base_builder.start_base().await?; let self_address = started_client.address; let client_input = started_client.client_input.register_producer(); let client_output = started_client.client_output.register_consumer(); @@ -164,13 +162,13 @@ impl SocketClient { } pub async fn start_direct(self) -> Result { - if self.config.get_socket_type().is_websocket() { + if self.config.socket.socket_type.is_websocket() { return Err(ClientError::InvalidSocketMode); } let base_builder = self.create_base_client_builder().await?; - let packet_type = self.config.get_base().get_packet_type(); - let mut started_client = base_builder.start_base(packet_type).await?; + let packet_type = self.config.base.debug.traffic.packet_type; + let mut started_client = base_builder.start_base().await?; let address = started_client.address; let client_input = started_client.client_input.register_producer(); let client_output = started_client.client_output.register_consumer(); diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index 293dbe93f4..c037329c38 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -1,7 +1,10 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::commands::try_upgrade_v1_1_13_config; +use crate::client::config::{ + default_config_directory, default_config_filepath, default_data_directory, +}; +use crate::commands::try_load_current_config; use crate::{ client::config::Config, commands::{override_config, OverrideConfig}, @@ -10,12 +13,12 @@ use crate::{ use clap::Args; use nym_bin_common::output_format::OutputFormat; use nym_client_core::client::key_manager::persistence::OnDiskKeys; -use nym_config::NymConfig; use nym_crypto::asymmetric::identity; use nym_sphinx::addressing::clients::Recipient; use serde::Serialize; use std::fmt::Display; use std::net::IpAddr; +use std::{fs, io}; use tap::TapFallible; #[derive(Args, Clone)] @@ -97,15 +100,15 @@ impl From for OverrideConfig { pub struct InitResults { #[serde(flatten)] client_core: nym_client_core::init::InitResults, - client_listening_port: String, + client_listening_port: u16, client_address: String, } impl InitResults { fn new(config: &Config, address: &Recipient) -> Self { Self { - client_core: nym_client_core::init::InitResults::new(config.get_base(), address), - client_listening_port: config.get_listening_port().to_string(), + client_core: nym_client_core::init::InitResults::new(&config.base, address), + client_listening_port: config.socket.listening_port, client_address: address.to_string(), } } @@ -119,18 +122,24 @@ impl Display for InitResults { } } +fn init_paths(id: &str) -> io::Result<()> { + fs::create_dir_all(default_data_directory(id))?; + fs::create_dir_all(default_config_directory(id)) +} + pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> { eprintln!("Initialising client..."); let id = &args.id; - let already_init = Config::default_config_file_path(id).exists(); - if already_init { - // in case we're using old config, try to upgrade it - // (if we're using the current version, it's a no-op) - try_upgrade_v1_1_13_config(id)?; + let old_config = if default_config_filepath(id).exists() { eprintln!("Client \"{id}\" was already initialised before"); - } + // if the file exist, try to load it (with checking for errors) + Some(try_load_current_config(&args.id)?) + } else { + init_paths(&args.id)?; + None + }; // Usually you only register with the gateway on the first init, however you can force // re-registering if wanted. @@ -142,7 +151,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> { // If the client was already initialized, don't generate new keys and don't re-register with // the gateway (because this would create a new shared key). // Unless the user really wants to. - let register_gateway = !already_init || user_wants_force_register; + let register_gateway = old_config.is_none() || user_wants_force_register; // Attempt to use a user-provided gateway, if possible let user_chosen_gateway_id = args.gateway; @@ -152,42 +161,38 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> { // Setup gateway by either registering a new one, or creating a new config from the selected // one but with keys kept, or reusing the gateway configuration. - let key_store = OnDiskKeys::from_config(config.get_base()); - let gateway = nym_client_core::init::setup_gateway_from_config::( + let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone()); + let gateway = nym_client_core::init::setup_gateway_from_config::<_>( &key_store, register_gateway, user_chosen_gateway_id, - config.get_base(), + &config.base, + old_config.map(|cfg| cfg.base.client.gateway_endpoint), args.latency_based_selection, ) .await .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; - config.get_base_mut().set_gateway_endpoint(gateway); + config.base.set_gateway_endpoint(gateway); - config.save_to_file(None).tap_err(|_| { + let config_save_location = config.default_location(); + config.save_to_default_location().tap_err(|_| { log::error!("Failed to save the config file"); })?; + eprintln!( + "Saved configuration file to {}", + config_save_location.display() + ); - print_saved_config(&config); + let address = nym_client_core::init::get_client_address_from_stored_ondisk_keys( + &config.storage_paths.common_paths.keys, + &config.base.client.gateway_endpoint, + )?; + + eprintln!("Client configuration completed.\n"); - let address = - nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?; let init_results = InitResults::new(&config, &address); println!("{}", args.output.format(&init_results)); Ok(()) } - -fn print_saved_config(config: &Config) { - let config_save_location = config.get_config_file_save_location(); - eprintln!("Saved configuration file to {config_save_location:?}"); - eprintln!("Using gateway: {}", config.get_base().get_gateway_id()); - log::debug!("Gateway id: {}", config.get_base().get_gateway_id()); - log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner()); - log::debug!( - "Gateway listener: {}", - config.get_base().get_gateway_listener() - ); - eprintln!("Client configuration completed.\n"); -} diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index b2227a2795..ab91433462 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -2,14 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13; -use crate::client::config::{BaseConfig, Config}; +use crate::client::config::old_config_v1_1_20::ConfigV1_1_20; +use crate::client::config::{BaseClientConfig, Config}; +use crate::error::ClientError; use clap::CommandFactory; use clap::{Parser, Subcommand}; use lazy_static::lazy_static; -use log::info; +use log::{error, info}; use nym_bin_common::build_information::BinaryBuildInformation; use nym_bin_common::completions::{fig_generate, ArgShell}; -use nym_config::{NymConfig, OptionalSet}; +use nym_config::OptionalSet; use std::error::Error; use std::net::IpAddr; @@ -82,40 +84,92 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box Config { config .with_optional(Config::with_disabled_socket, args.disable_socket) - .with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode) - .with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover) + .with_base( + BaseClientConfig::with_high_default_traffic_volume, + args.fastmode, + ) + .with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover) .with_optional(Config::with_port, args.port) .with_optional(Config::with_host, args.host) .with_optional_custom_env_ext( - BaseConfig::with_custom_nym_apis, + BaseClientConfig::with_custom_nym_apis, args.nym_apis, nym_network_defaults::var_names::NYM_API, nym_config::parse_urls, ) .with_optional_custom_env_ext( - BaseConfig::with_custom_nyxd, + BaseClientConfig::with_custom_nyxd, args.nyxd_urls, nym_network_defaults::var_names::NYXD, nym_config::parse_urls, ) .with_optional_ext( - BaseConfig::with_disabled_credentials, + BaseClientConfig::with_disabled_credentials, args.enabled_credentials_mode.map(|b| !b), ) } -fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> { - // explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+) +fn try_upgrade_v1_1_13_config(id: &str) -> Result { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19) let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else { // if we failed to load it, there might have been nothing to upgrade // or maybe it was an even older file. in either way. just ignore it and carry on with our day - return Ok(()); + return Ok(false); }; info!("It seems the client is using <= v1.1.13 config template."); info!("It is going to get updated to the current specification."); + let updated_step1: ConfigV1_1_20 = old_config.into(); + let updated: Config = updated_step1.into(); + + updated.save_to_default_location()?; + Ok(true) +} + +fn try_upgrade_v1_1_20_config(id: &str) -> Result { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21) + let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else { + // if we failed to load it, there might have been nothing to upgrade + // or maybe it was an even older file. in either way. just ignore it and carry on with our day + return Ok(false); + }; + info!("It seems the client is using <= v1.1.20 config template."); + info!("It is going to get updated to the current specification."); + let updated: Config = old_config.into(); - updated.save_to_file(None) + updated.save_to_default_location()?; + Ok(true) +} + +fn try_upgrade_config(id: &str) -> Result<(), ClientError> { + let upgraded = try_upgrade_v1_1_13_config(id)?; + if !upgraded { + try_upgrade_v1_1_20_config(id)?; + } + + Ok(()) +} + +fn try_load_current_config(id: &str) -> Result { + try_upgrade_config(id)?; + + let config = match Config::read_from_default_path(id) { + Ok(cfg) => cfg, + Err(err) => { + error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})"); + return Err(ClientError::FailedToLoadConfig(id.to_string())); + } + }; + + if !config.validate() { + return Err(ClientError::ConfigValidationFailure); + } + + Ok(config) } #[cfg(test)] diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs index 93936a7d24..23cf54588b 100644 --- a/clients/native/src/commands/run.rs +++ b/clients/native/src/commands/run.rs @@ -1,10 +1,7 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::error::Error; -use std::net::IpAddr; - -use crate::commands::try_upgrade_v1_1_13_config; +use crate::commands::try_load_current_config; use crate::{ client::{config::Config, SocketClient}, commands::{override_config, OverrideConfig}, @@ -13,8 +10,9 @@ use crate::{ use clap::Args; use log::*; use nym_bin_common::version_checker::is_minor_version_compatible; -use nym_config::NymConfig; use nym_crypto::asymmetric::identity; +use std::error::Error; +use std::net::IpAddr; #[derive(Args, Clone)] pub(crate) struct Run { @@ -82,7 +80,7 @@ impl From for OverrideConfig { // network version. It might do so in the future. fn version_check(cfg: &Config) -> bool { let binary_version = env!("CARGO_PKG_VERSION"); - let config_version = cfg.get_base().get_version(); + let config_version = &cfg.base.client.version; if binary_version == config_version { true } else { @@ -98,30 +96,10 @@ fn version_check(cfg: &Config) -> bool { } pub(crate) async fn execute(args: &Run) -> Result<(), Box> { - let id = &args.id; + eprintln!("Starting client {}...", args.id); - // in case we're using old config, try to upgrade it - // (if we're using the current version, it's a no-op) - try_upgrade_v1_1_13_config(id)?; - - let mut config = match Config::load_from_file(id) { - Ok(cfg) => cfg, - Err(err) => { - error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id); - return Err(Box::new(ClientError::FailedToLoadConfig(id.to_string()))); - } - }; - - if !config.validate() { - return Err(Box::new(ClientError::ConfigValidationFailure)); - } - - let override_config_fields = OverrideConfig::from(args.clone()); - config = override_config(config, override_config_fields); - - if config.get_base_mut().set_empty_fields_to_defaults() { - warn!("some of the core config options were left unset. the default values are going to get used instead."); - } + let mut config = try_load_current_config(&args.id)?; + config = override_config(config, OverrideConfig::from(args.clone())); if !version_check(&config) { error!("failed the local version check"); diff --git a/clients/native/src/commands/upgrade.rs b/clients/native/src/commands/upgrade.rs index 97e7dddba6..b4c2d45ad3 100644 --- a/clients/native/src/commands/upgrade.rs +++ b/clients/native/src/commands/upgrade.rs @@ -1,42 +1,14 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::client::config::{Config, MISSING_VALUE}; - -use nym_bin_common::version_checker::Version; -use nym_config::NymConfig; - +use crate::client::config::Config; +use crate::commands::try_load_current_config; use clap::Args; -use std::fmt::Display; +use nym_bin_common::version_checker::Version; use std::process; -#[allow(dead_code)] -fn fail_upgrade(from_version: D1, to_version: D2) -> ! { - print_failed_upgrade(from_version, to_version); - process::exit(1) -} - -fn print_start_upgrade(from: D1, to: D2) { - println!("\n==================\nTrying to upgrade client from {from} to {to} ..."); -} - -fn print_failed_upgrade(from: D1, to: D2) { - eprintln!("Upgrade from {from} to {to} failed!\n==================\n"); -} - -fn print_successful_upgrade(from: D1, to: D2) { - println!("Upgrade from {from} to {to} was successful!\n==================\n"); -} - -fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! { - eprintln!( - "Cannot perform upgrade from {config_version} to {package_version}. Your version is too old to perform the upgrade.!" - ); - process::exit(1) -} - -fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! { - eprintln!("Cannot perform upgrade from {config_version} to {current_version}. Please let the developers know about this issue if you expected it to work!"); +fn unimplemented_upgrade(current_version: &Version, config_version: &Version) -> ! { + eprintln!("Cannot perform upgrade from {config_version} to {current_version} as it hasn't been implemented yet"); process::exit(1) } @@ -48,7 +20,7 @@ pub(crate) struct Upgrade { } fn parse_config_version(config: &Config) -> Version { - let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| { + let version = Version::parse(&config.base.client.version).unwrap_or_else(|err| { eprintln!("failed to parse client version! - {err}"); process::exit(1) }); @@ -77,53 +49,14 @@ fn parse_package_version() -> Version { version } -fn minor_0_12_upgrade( - mut config: Config, - _matches: &Upgrade, - config_version: &Version, - package_version: &Version, -) -> Config { - let to_version = if package_version.major == 0 && package_version.minor == 12 { - package_version.clone() - } else { - Version::new(0, 12, 0) - }; - - print_start_upgrade(config_version, &to_version); - - config - .get_base_mut() - .set_custom_version(to_version.to_string().as_ref()); - - 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); - }); - - print_successful_upgrade(config_version, to_version); - - config -} - -fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) { - loop { - let config_version = parse_config_version(&config); - - if &config_version == package_version { - println!("You're using the most recent version!"); - return; - } - - config = match config_version.major { - 0 => match config_version.minor { - 9 | 10 => outdated_upgrade(&config_version, package_version), - 11 => minor_0_12_upgrade(config, args, &config_version, package_version), - _ => unsupported_upgrade(&config_version, package_version), - }, - _ => unsupported_upgrade(&config_version, package_version), - } +fn do_upgrade(config: Config, _args: &Upgrade, package_version: &Version) { + let config_version = parse_config_version(&config); + if &config_version == package_version { + println!("You're using the most recent version!"); + return; } + + unimplemented_upgrade(package_version, &config_version) } pub(crate) fn execute(args: &Upgrade) { @@ -131,16 +64,15 @@ pub(crate) fn execute(args: &Upgrade) { let id = &args.id; - let existing_config = Config::load_from_file(id).unwrap_or_else(|err| { + let existing_config = try_load_current_config(id).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); - if existing_config.get_base().get_version() == MISSING_VALUE { + if existing_config.base.client.version.is_empty() { eprintln!("the existing configuration file does not seem to contain version number."); process::exit(1); } - // here be upgrade path to 0.9.X and beyond based on version number from config do_upgrade(existing_config, args, &package_version) } diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 3b4eb2cf11..e09a23ff3c 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -1,7 +1,10 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::commands::try_upgrade_v1_1_13_config; +use crate::commands::try_load_current_config; +use crate::config::{ + default_config_directory, default_config_filepath, default_data_directory, Config, +}; use crate::{ commands::{override_config, OverrideConfig}, error::Socks5ClientError, @@ -9,12 +12,11 @@ use crate::{ use clap::Args; use nym_bin_common::output_format::OutputFormat; use nym_client_core::client::key_manager::persistence::OnDiskKeys; -use nym_config::NymConfig; use nym_crypto::asymmetric::identity; -use nym_socks5_client_core::config::Config; use nym_sphinx::addressing::clients::Recipient; use serde::Serialize; use std::fmt::Display; +use std::{fs, io}; use tap::TapFallible; #[derive(Args, Clone)] @@ -100,15 +102,15 @@ impl From for OverrideConfig { pub struct InitResults { #[serde(flatten)] client_core: nym_client_core::init::InitResults, - socks5_listening_port: String, + socks5_listening_port: u16, client_address: String, } impl InitResults { fn new(config: &Config, address: &Recipient) -> Self { Self { - client_core: nym_client_core::init::InitResults::new(config.get_base(), address), - socks5_listening_port: config.get_socks5().get_listening_port().to_string(), + client_core: nym_client_core::init::InitResults::new(&config.core.base, address), + socks5_listening_port: config.core.socks5.listening_port, client_address: address.to_string(), } } @@ -122,19 +124,25 @@ impl Display for InitResults { } } +fn init_paths(id: &str) -> io::Result<()> { + fs::create_dir_all(default_data_directory(id))?; + fs::create_dir_all(default_config_directory(id)) +} + pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> { eprintln!("Initialising client..."); let id = &args.id; let provider_address = &args.provider; - let already_init = Config::default_config_file_path(id).exists(); - if already_init { - // in case we're using old config, try to upgrade it - // (if we're using the current version, it's a no-op) - try_upgrade_v1_1_13_config(id)?; + let old_config = if default_config_filepath(id).exists() { eprintln!("SOCKS5 client \"{id}\" was already initialised before"); - } + // if the file exist, try to load it (with checking for errors) + Some(try_load_current_config(&args.id)?) + } else { + init_paths(&args.id)?; + None + }; // Usually you only register with the gateway on the first init, however you can force // re-registering if wanted. @@ -146,7 +154,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> { // If the client was already initialized, don't generate new keys and don't re-register with // the gateway (because this would create a new shared key). // Unless the user really wants to. - let register_gateway = !already_init || user_wants_force_register; + let register_gateway = old_config.is_none() || user_wants_force_register; // Attempt to use a user-provided gateway, if possible let user_chosen_gateway_id = args.gateway; @@ -159,44 +167,37 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> { // Setup gateway by either registering a new one, or creating a new config from the selected // one but with keys kept, or reusing the gateway configuration. - let key_store = OnDiskKeys::from_config(config.get_base()); - let gateway = nym_client_core::init::setup_gateway_from_config::( + let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone()); + let gateway = nym_client_core::init::setup_gateway_from_config::<_>( &key_store, register_gateway, user_chosen_gateway_id, - config.get_base(), + &config.core.base, + old_config.map(|cfg| cfg.core.base.client.gateway_endpoint), args.latency_based_selection, ) .await .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; - config.get_base_mut().set_gateway_endpoint(gateway); + config.core.base.set_gateway_endpoint(gateway); // TODO: ask the service provider we specified for its interface version and set it in the config - config.save_to_file(None).tap_err(|_| { + let config_save_location = config.default_location(); + config.save_to_default_location().tap_err(|_| { log::error!("Failed to save the config file"); })?; + eprintln!( + "Saved configuration file to {}", + config_save_location.display() + ); - print_saved_config(&config); - - let address = - nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?; + let address = nym_client_core::init::get_client_address_from_stored_ondisk_keys( + &config.storage_paths.common_paths.keys, + &config.core.base.client.gateway_endpoint, + )?; let init_results = InitResults::new(&config, &address); println!("{}", args.output.format(&init_results)); Ok(()) } - -fn print_saved_config(config: &Config) { - let config_save_location = config.get_config_file_save_location(); - eprintln!("Saved configuration file to {:?}", config_save_location); - eprintln!("Using gateway: {}", config.get_base().get_gateway_id()); - log::debug!("Gateway id: {}", config.get_base().get_gateway_id()); - log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner()); - log::debug!( - "Gateway listener: {}", - config.get_base().get_gateway_listener() - ); - eprintln!("Client configuration completed.\n"); -} diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 2100e9915f..7bab4fbe9a 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -1,15 +1,17 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::config::old_config_v1_1_13::OldConfigV1_1_13; +use crate::config::old_config_v1_1_20::ConfigV1_1_20; +use crate::config::{BaseClientConfig, Config}; +use crate::error::Socks5ClientError; use clap::CommandFactory; use clap::{Parser, Subcommand}; use lazy_static::lazy_static; -use log::info; +use log::{error, info}; use nym_bin_common::build_information::BinaryBuildInformation; use nym_bin_common::completions::{fig_generate, ArgShell}; -use nym_config::{NymConfig, OptionalSet}; -use nym_socks5_client_core::config::old_config_v1_1_13::OldConfigV1_1_13; -use nym_socks5_client_core::config::{BaseConfig, Config}; +use nym_config::OptionalSet; use nym_sphinx::params::PacketType; use std::error::Error; @@ -88,41 +90,93 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { PacketType::Mix }; config - .with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode) - .with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover) - .with_base(BaseConfig::with_packet_type, packet_type) + .with_base( + BaseClientConfig::with_high_default_traffic_volume, + args.fastmode, + ) + .with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover) + .with_base(BaseClientConfig::with_packet_type, packet_type) .with_optional(Config::with_anonymous_replies, args.use_anonymous_replies) .with_optional(Config::with_port, args.port) - .with_optional_custom_env_ext( - BaseConfig::with_custom_nym_apis, + .with_optional_base_custom_env( + BaseClientConfig::with_custom_nym_apis, args.nym_apis, nym_network_defaults::var_names::NYM_API, nym_config::parse_urls, ) - .with_optional_custom_env_ext( - BaseConfig::with_custom_nyxd, + .with_optional_base_custom_env( + BaseClientConfig::with_custom_nyxd, args.nyxd_urls, nym_network_defaults::var_names::NYXD, nym_config::parse_urls, ) - .with_optional_ext( - BaseConfig::with_disabled_credentials, + .with_optional_base( + BaseClientConfig::with_disabled_credentials, args.enabled_credentials_mode.map(|b| !b), ) } -fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> { - // explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+) +fn try_upgrade_v1_1_13_config(id: &str) -> Result { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19) let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else { // if we failed to load it, there might have been nothing to upgrade // or maybe it was an even older file. in either way. just ignore it and carry on with our day - return Ok(()); + return Ok(false); }; info!("It seems the client is using <= v1.1.13 config template."); info!("It is going to get updated to the current specification."); + let updated_step1: ConfigV1_1_20 = old_config.into(); + let updated: Config = updated_step1.into(); + + updated.save_to_default_location()?; + Ok(true) +} + +fn try_upgrade_v1_1_20_config(id: &str) -> Result { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21) + let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else { + // if we failed to load it, there might have been nothing to upgrade + // or maybe it was an even older file. in either way. just ignore it and carry on with our day + return Ok(false); + }; + info!("It seems the client is using <= v1.1.20 config template."); + info!("It is going to get updated to the current specification."); + let updated: Config = old_config.into(); - updated.save_to_file(None) + updated.save_to_default_location()?; + Ok(true) +} + +fn try_upgrade_config(id: &str) -> Result<(), Socks5ClientError> { + let upgraded = try_upgrade_v1_1_13_config(id)?; + if !upgraded { + try_upgrade_v1_1_20_config(id)?; + } + + Ok(()) +} + +fn try_load_current_config(id: &str) -> Result { + try_upgrade_config(id)?; + + let config = match Config::read_from_default_path(id) { + Ok(cfg) => cfg, + Err(err) => { + error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})"); + return Err(Socks5ClientError::FailedToLoadConfig(id.to_string())); + } + }; + + if !config.validate() { + return Err(Socks5ClientError::ConfigValidationFailure); + } + + Ok(config) } #[cfg(test)] diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index 09264adc58..a6bc88344b 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -1,7 +1,8 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::commands::try_upgrade_v1_1_13_config; +use crate::commands::try_load_current_config; +use crate::config::Config; use crate::{ commands::{override_config, OverrideConfig}, error::Socks5ClientError, @@ -10,9 +11,8 @@ use clap::Args; use log::*; use nym_bin_common::version_checker::is_minor_version_compatible; use nym_client_core::client::base_client::storage::OnDiskPersistent; -use nym_config::NymConfig; use nym_crypto::asymmetric::identity; -use nym_socks5_client_core::{config::Config, NymClient}; +use nym_socks5_client_core::NymClient; use nym_sphinx::addressing::clients::Recipient; #[derive(Args, Clone)] @@ -21,10 +21,6 @@ pub(crate) struct Run { #[clap(long)] id: String, - /// Custom path to the nym-mixnet-client configuration file - #[clap(long)] - config: Option, - /// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. /// While this is going to hide its actual address information, it will make the actual communication /// slower and consume nearly double the bandwidth as it will require sending reply SURBs. @@ -92,13 +88,12 @@ impl From for OverrideConfig { // network version. It might do so in the future. fn version_check(cfg: &Config) -> bool { let binary_version = env!("CARGO_PKG_VERSION"); - let config_version = cfg.get_base().get_version(); + let config_version = &cfg.core.base.client.version; if binary_version == config_version { true } else { warn!( - "The mixnode binary has different version than what is specified in config file! {} and {}", - binary_version, config_version + "The socks5-client binary has different version than what is specified in config file! {binary_version} and {config_version}", ); if is_minor_version_compatible(binary_version, config_version) { info!("but they are still semver compatible. However, consider running the `upgrade` command"); @@ -111,38 +106,18 @@ fn version_check(cfg: &Config) -> bool { } pub(crate) async fn execute(args: &Run) -> Result<(), Box> { - let id = &args.id; + eprintln!("Starting client {}...", args.id); - // in case we're using old config, try to upgrade it - // (if we're using the current version, it's a no-op) - try_upgrade_v1_1_13_config(id)?; - - let mut config = match Config::load_from_file(id) { - Ok(cfg) => cfg, - Err(err) => { - error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id); - return Err(Box::new(Socks5ClientError::FailedToLoadConfig( - id.to_string(), - ))); - } - }; - - if !config.validate() { - return Err(Box::new(Socks5ClientError::ConfigValidationFailure)); - } - - let override_config_fields = OverrideConfig::from(args.clone()); - config = override_config(config, override_config_fields); - - if config.get_base_mut().set_empty_fields_to_defaults() { - warn!("some of the core config options were left unset. the default values are going to get used instead."); - } + let mut config = try_load_current_config(&args.id)?; + config = override_config(config, OverrideConfig::from(args.clone())); if !version_check(&config) { error!("failed the local version check"); return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck)); } - let storage = OnDiskPersistent::from_config(config.get_base()).await?; - NymClient::new(config, storage).run_forever().await + let storage = + OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug) + .await?; + NymClient::new(config.core, storage).run_forever().await } diff --git a/clients/socks5/src/commands/upgrade.rs b/clients/socks5/src/commands/upgrade.rs index 8162029370..00ffb9252b 100644 --- a/clients/socks5/src/commands/upgrade.rs +++ b/clients/socks5/src/commands/upgrade.rs @@ -1,50 +1,14 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_bin_common::version_checker::Version; -use nym_config::NymConfig; -use nym_socks5_client_core::config::{Config, MISSING_VALUE}; - +use crate::commands::try_load_current_config; +use crate::config::Config; use clap::Args; -use std::{fmt::Display, process}; +use nym_bin_common::version_checker::Version; +use std::process; -#[allow(dead_code)] -fn fail_upgrade(from_version: D1, to_version: D2) -> ! { - print_failed_upgrade(from_version, to_version); - process::exit(1) -} - -fn print_start_upgrade(from: D1, to: D2) { - println!( - "\n==================\nTrying to upgrade client from {} to {} ...", - from, to - ); -} - -fn print_failed_upgrade(from: D1, to: D2) { - eprintln!( - "Upgrade from {} to {} failed!\n==================\n", - from, to - ); -} - -fn print_successful_upgrade(from: D1, to: D2) { - println!( - "Upgrade from {} to {} was successful!\n==================\n", - from, to - ); -} - -fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! { - eprintln!( - "Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!", - config_version, package_version - ); - process::exit(1) -} - -fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! { - eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, current_version); +fn unimplemented_upgrade(current_version: &Version, config_version: &Version) -> ! { + eprintln!("Cannot perform upgrade from {config_version} to {current_version} as it hasn't been implemented yet"); process::exit(1) } @@ -56,15 +20,14 @@ pub(crate) struct Upgrade { } fn parse_config_version(config: &Config) -> Version { - let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| { + let version = Version::parse(&config.core.base.client.version).unwrap_or_else(|err| { eprintln!("failed to parse client version! - {err}"); process::exit(1) }); if version.is_prerelease() || !version.build.is_empty() { eprintln!( - "Trying to upgrade from a non-released version {}. This is not supported!", - version + "Trying to upgrade from a non-released version {version}. This is not supported!" ); process::exit(1) } @@ -79,63 +42,21 @@ fn parse_package_version() -> Version { // however, we are not using them ourselves at the moment and hence it should be fine. // if we change our mind, we could easily tweak this code if version.is_prerelease() || !version.build.is_empty() { - eprintln!( - "Trying to upgrade to a non-released version {}. This is not supported!", - version - ); + eprintln!("Trying to upgrade to a non-released version {version}. This is not supported!"); process::exit(1) } version } -fn minor_0_12_upgrade( - mut config: Config, - _args: &Upgrade, - config_version: &Version, - package_version: &Version, -) -> Config { - let to_version = if package_version.major == 0 && package_version.minor == 12 { - package_version.clone() - } else { - Version::new(0, 12, 0) - }; - - print_start_upgrade(config_version, &to_version); - - config - .get_base_mut() - .set_custom_version(to_version.to_string().as_ref()); - - 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); - }); - - print_successful_upgrade(config_version, to_version); - - config -} - -fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) { - loop { - let config_version = parse_config_version(&config); - - if &config_version == package_version { - println!("You're using the most recent version!"); - return; - } - - config = match config_version.major { - 0 => match config_version.minor { - 9 | 10 => outdated_upgrade(&config_version, package_version), - 11 => minor_0_12_upgrade(config, args, &config_version, package_version), - _ => unsupported_upgrade(&config_version, package_version), - }, - _ => unsupported_upgrade(&config_version, package_version), - } +fn do_upgrade(config: Config, _args: &Upgrade, package_version: &Version) { + let config_version = parse_config_version(&config); + if &config_version == package_version { + println!("You're using the most recent version!"); + return; } + + unimplemented_upgrade(package_version, &config_version) } pub(crate) fn execute(args: &Upgrade) { @@ -143,16 +64,15 @@ pub(crate) fn execute(args: &Upgrade) { let id = &args.id; - let existing_config = Config::load_from_file(id).unwrap_or_else(|err| { + let existing_config = try_load_current_config(id).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); - if existing_config.get_base().get_version() == MISSING_VALUE { + if existing_config.core.base.client.version.is_empty() { eprintln!("the existing configuration file does not seem to contain version number."); process::exit(1); } - // here be upgrade path to 0.9.X and beyond based on version number from config do_upgrade(existing_config, args, &package_version) } diff --git a/clients/socks5/src/config/mod.rs b/clients/socks5/src/config/mod.rs new file mode 100644 index 0000000000..1c308ccc8b --- /dev/null +++ b/clients/socks5/src/config/mod.rs @@ -0,0 +1,155 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::SocksClientPaths; +use crate::config::template::CONFIG_TEMPLATE; +use nym_bin_common::logging::LoggingSettings; +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::fmt::Debug; +use std::io; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +pub use nym_client_core::config::Config as BaseClientConfig; +pub use nym_socks5_client_core::config::Config as CoreConfig; + +pub mod old_config_v1_1_13; +pub mod old_config_v1_1_20; +mod persistence; +mod template; + +const DEFAULT_SOCKS5_CLIENTS_DIR: &str = "socks5-clients"; + +/// Derive default path to clients's config directory. +/// It should get resolved to `$HOME/.nym/socks5-clients//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_SOCKS5_CLIENTS_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) +} + +/// Derive default path to client's config file. +/// It should get resolved to `$HOME/.nym/socks5-clients//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) +} + +/// Derive default path to client's data directory where files, such as keys, are stored. +/// It should get resolved to `$HOME/.nym/socks5-clients//data` +pub fn default_data_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_SOCKS5_CLIENTS_DIR) + .join(id) + .join(DEFAULT_DATA_DIR) +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + pub core: CoreConfig, + + pub storage_paths: SocksClientPaths, + + pub logging: LoggingSettings, +} + +impl NymConfigTemplate for Config { + fn template() -> &'static str { + CONFIG_TEMPLATE + } +} + +impl Config { + pub fn new>(id: S, provider_mix_address: S) -> Self { + Config { + core: CoreConfig::new(id.as_ref(), provider_mix_address.as_ref()), + storage_paths: SocksClientPaths::new_default(default_data_directory(id.as_ref())), + logging: 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 default_location(&self) -> PathBuf { + default_config_filepath(&self.core.base.client.id) + } + + pub fn save_to_default_location(&self) -> io::Result<()> { + let config_save_location: PathBuf = self.default_location(); + save_formatted_config_to_file(self, config_save_location) + } + + pub fn validate(&self) -> bool { + // no other sections have explicit requirements (yet) + self.core.validate() + } + + pub fn with_port(mut self, port: u16) -> Self { + self.core.socks5.listening_port = port; + self + } + + pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self { + self.core.socks5.send_anonymously = anonymous_replies; + self + } + + // poor man's 'builder' method + + pub fn with_base(mut self, f: F, val: T) -> Self + where + F: Fn(BaseClientConfig, T) -> BaseClientConfig, + { + self.core = self.core.with_base(f, val); + self + } + + pub fn with_optional_base(mut self, f: F, val: Option) -> Self + where + F: Fn(BaseClientConfig, T) -> BaseClientConfig, + { + self.core = self.core.with_optional_base(f, val); + self + } + + #[allow(unused)] + pub fn with_optional_base_env(mut self, f: F, val: Option, env_var: &str) -> Self + where + F: Fn(BaseClientConfig, T) -> BaseClientConfig, + T: FromStr, + ::Err: Debug, + { + self.core = self.core.with_optional_base_env(f, val, env_var); + self + } + + pub fn with_optional_base_custom_env( + mut self, + f: F, + val: Option, + env_var: &str, + parser: G, + ) -> Self + where + F: Fn(BaseClientConfig, T) -> BaseClientConfig, + G: Fn(&str) -> T, + { + self.core = self + .core + .with_optional_base_custom_env(f, val, env_var, parser); + self + } +} diff --git a/clients/socks5/src/config/old_config_v1_1_13.rs b/clients/socks5/src/config/old_config_v1_1_13.rs new file mode 100644 index 0000000000..954632a0c1 --- /dev/null +++ b/clients/socks5/src/config/old_config_v1_1_13.rs @@ -0,0 +1,38 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::old_config_v1_1_20::{ConfigV1_1_20, Socks5V1_1_20}; +use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13; +use nym_config::legacy_helpers::nym_config::MigrationNymConfig; +use nym_config::must_get_home; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct OldConfigV1_1_13 { + #[serde(flatten)] + pub base: OldBaseConfigV1_1_13, + + pub socks5: Socks5V1_1_20, +} + +impl MigrationNymConfig for OldConfigV1_1_13 { + fn default_root_directory() -> PathBuf { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let base_dir = must_get_home(); + #[cfg(any(target_os = "android", target_os = "ios"))] + let base_dir = PathBuf::from("/tmp"); + + base_dir.join(".nym").join("socks5-clients") + } +} + +impl From for ConfigV1_1_20 { + fn from(value: OldConfigV1_1_13) -> Self { + ConfigV1_1_20 { + base: value.base.into(), + socks5: value.socks5, + } + } +} diff --git a/clients/socks5/src/config/old_config_v1_1_20.rs b/clients/socks5/src/config/old_config_v1_1_20.rs new file mode 100644 index 0000000000..4b8d47558b --- /dev/null +++ b/clients/socks5/src/config/old_config_v1_1_20.rs @@ -0,0 +1,135 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::SocksClientPaths; +use crate::config::{BaseClientConfig, Config, CoreConfig}; +use nym_bin_common::logging::LoggingSettings; +use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths; +use nym_client_core::config::disk_persistence::CommonClientPaths; +use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20; +use nym_client_core::config::Client; +use nym_config::legacy_helpers::nym_config::MigrationNymConfig; +use nym_config::must_get_home; +use nym_socks5_client_core::config::{ + ProviderInterfaceVersion, Socks5, Socks5Debug, Socks5ProtocolVersion, +}; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::path::PathBuf; + +const DEFAULT_CONNECTION_START_SURBS: u32 = 20; +const DEFAULT_PER_REQUEST_SURBS: u32 = 3; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_20 { + #[serde(flatten)] + pub base: BaseConfigV1_1_20, + + pub socks5: Socks5V1_1_20, +} + +impl From for Config { + fn from(value: ConfigV1_1_20) -> Self { + Config { + core: CoreConfig { + base: BaseClientConfig { + client: Client { + version: value.base.client.version, + id: value.base.client.id, + disabled_credentials_mode: value.base.client.disabled_credentials_mode, + nyxd_urls: value.base.client.nyxd_urls, + nym_api_urls: value.base.client.nym_api_urls, + gateway_endpoint: value.base.client.gateway_endpoint.into(), + }, + debug: value.base.debug.into(), + }, + socks5: value.socks5.into(), + }, + storage_paths: SocksClientPaths { + common_paths: CommonClientPaths { + keys: ClientKeysPaths { + private_identity_key_file: value.base.client.private_identity_key_file, + public_identity_key_file: value.base.client.public_identity_key_file, + private_encryption_key_file: value.base.client.private_encryption_key_file, + public_encryption_key_file: value.base.client.public_encryption_key_file, + gateway_shared_key_file: value.base.client.gateway_shared_key_file, + ack_key_file: value.base.client.ack_key_file, + }, + credentials_database: value.base.client.database_path, + reply_surb_database: value.base.client.reply_surb_database_path, + }, + }, + logging: LoggingSettings::default(), + } + } +} + +impl MigrationNymConfig for ConfigV1_1_20 { + fn default_root_directory() -> PathBuf { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let base_dir = must_get_home(); + #[cfg(any(target_os = "android", target_os = "ios"))] + let base_dir = PathBuf::from("/tmp"); + + base_dir.join(".nym").join("socks5-clients") + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Socks5V1_1_20 { + pub listening_port: u16, + + pub provider_mix_address: String, + + #[serde(default = "ProviderInterfaceVersion::new_legacy")] + pub provider_interface_version: ProviderInterfaceVersion, + + #[serde(default = "Socks5ProtocolVersion::new_legacy")] + pub socks5_protocol_version: Socks5ProtocolVersion, + + #[serde(default)] + pub send_anonymously: bool, + + #[serde(default)] + pub socks5_debug: Socks5DebugV1_1_20, +} + +impl From for Socks5 { + fn from(value: Socks5V1_1_20) -> Self { + Socks5 { + listening_port: value.listening_port, + provider_mix_address: value.provider_mix_address, + provider_interface_version: value.provider_interface_version, + socks5_protocol_version: value.socks5_protocol_version, + send_anonymously: value.send_anonymously, + socks5_debug: value.socks5_debug.into(), + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Socks5DebugV1_1_20 { + connection_start_surbs: u32, + per_request_surbs: u32, +} + +impl From for Socks5Debug { + fn from(value: Socks5DebugV1_1_20) -> Self { + Socks5Debug { + connection_start_surbs: value.connection_start_surbs, + per_request_surbs: value.per_request_surbs, + } + } +} + +impl Default for Socks5DebugV1_1_20 { + fn default() -> Self { + Socks5DebugV1_1_20 { + connection_start_surbs: DEFAULT_CONNECTION_START_SURBS, + per_request_surbs: DEFAULT_PER_REQUEST_SURBS, + } + } +} diff --git a/clients/socks5/src/config/persistence.rs b/clients/socks5/src/config/persistence.rs new file mode 100644 index 0000000000..6df47997c9 --- /dev/null +++ b/clients/socks5/src/config/persistence.rs @@ -0,0 +1,20 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_client_core::config::disk_persistence::CommonClientPaths; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)] +pub struct SocksClientPaths { + #[serde(flatten)] + pub common_paths: CommonClientPaths, +} + +impl SocksClientPaths { + pub fn new_default>(base_data_directory: P) -> Self { + SocksClientPaths { + common_paths: CommonClientPaths::new_default(base_data_directory), + } + } +} diff --git a/common/socks5-client-core/src/config/template.rs b/clients/socks5/src/config/template.rs similarity index 54% rename from common/socks5-client-core/src/config/template.rs rename to clients/socks5/src/config/template.rs index bdf8c61e51..fd4aec5f52 100644 --- a/common/socks5-client-core/src/config/template.rs +++ b/clients/socks5/src/config/template.rs @@ -1,102 +1,97 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub(crate) fn config_template() -> &'static str { - // While using normal toml marshalling would have been way simpler with less overhead, - // I think it's useful to have comments attached to the saved config file to explain behaviour of - // particular fields. - // Note: any changes to the template must be reflected in the appropriate structs. - r#" +// While using normal toml marshalling would have been way simpler with less overhead, +// I think it's useful to have comments attached to the saved config file to explain behaviour of +// particular fields. +// Note: any changes to the template must be reflected in the appropriate structs. +pub(crate) const CONFIG_TEMPLATE: &str = r#" # This is a TOML config file. # For more information, see https://github.com/toml-lang/toml ##### main base client config options ##### -[client] +[core.client] # Version of the client for which this configuration was created. -version = '{{ client.version }}' +version = '{{ core.client.version }}' # Human readable ID of this particular client. -id = '{{ client.id }}' +id = '{{ core.client.id }}' # Indicates whether this client is running in a disabled credentials mode, thus attempting # to claim bandwidth without presenting bandwidth credentials. -disabled_credentials_mode = {{ client.disabled_credentials_mode }} +disabled_credentials_mode = {{ core.client.disabled_credentials_mode }} # Addresses to nyxd validators via which the client can communicate with the chain. nyxd_urls = [ - {{#each client.nyxd_urls }} + {{#each core.client.nyxd_urls }} '{{this}}', {{/each}} ] # Addresses to APIs running on validator from which the client gets the view of the network. nym_api_urls = [ - {{#each client.nym_api_urls }} + {{#each core.client.nym_api_urls }} '{{this}}', {{/each}} ] +[storage_paths] + # Path to file containing private identity key. -private_identity_key_file = '{{ client.private_identity_key_file }}' +keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' # Path to file containing public identity key. -public_identity_key_file = '{{ client.public_identity_key_file }}' +keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}' # Path to file containing private encryption key. -private_encryption_key_file = '{{ client.private_encryption_key_file }}' +keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}' # Path to file containing public encryption key. -public_encryption_key_file = '{{ client.public_encryption_key_file }}' - -# Path to the database containing bandwidth credentials -database_path = '{{ client.database_path }}' - -# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. -reply_surb_database_path = '{{ client.reply_surb_database_path }}' - -##### additional client config options ##### +keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}' # A gateway specific, optional, base58 stringified shared key used for # communication with particular gateway. -gateway_shared_key_file = '{{ client.gateway_shared_key_file }}' +keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}' # Path to file containing key used for encrypting and decrypting the content of an # acknowledgement so that nobody besides the client knows which packet it refers to. -ack_key_file = '{{ client.ack_key_file }}' +keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}' -##### advanced configuration options ##### +# Path to the database containing bandwidth credentials +credentials_database = '{{ storage_paths.credentials_database }}' -# Absolute path to the home Nym Clients directory. -nym_root_directory = '{{ client.nym_root_directory }}' +# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. +reply_surb_database = '{{ storage_paths.reply_surb_database }}' -[client.gateway_endpoint] +# DEPRECATED +[core.client.gateway_endpoint] # ID of the gateway from which the client should be fetching messages. -gateway_id = '{{ client.gateway_endpoint.gateway_id }}' +gateway_id = '{{ core.client.gateway_endpoint.gateway_id }}' # Address of the gateway owner to which the client should send messages. -gateway_owner = '{{ client.gateway_endpoint.gateway_owner }}' +gateway_owner = '{{ core.client.gateway_endpoint.gateway_owner }}' # Address of the gateway listener to which all client requests should be sent. -gateway_listener = '{{ client.gateway_endpoint.gateway_listener }}' +gateway_listener = '{{ core.client.gateway_endpoint.gateway_listener }}' ##### socket config options ##### -[socks5] +[core.socks5] # The mix address of the provider to which all requests are going to be sent. -provider_mix_address = '{{ socks5.provider_mix_address }}' +provider_mix_address = '{{ core.socks5.provider_mix_address }}' # The port on which the client will be listening for incoming requests -listening_port = {{ socks5.listening_port }} +listening_port = {{ core.socks5.listening_port }} # Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. # While this is going to hide its actual address information, it will make the actual communication # slower and consume nearly double the bandwidth as it will require sending reply SURBs. # # Note that some service providers might not support this. -send_anonymously = {{ socks5.send_anonymously }} +send_anonymously = {{ core.socks5.send_anonymously }} ##### logging configuration options ##### @@ -109,20 +104,19 @@ send_anonymously = {{ socks5.send_anonymously }} # The following options should not be modified unless you know EXACTLY what you are doing # as if set incorrectly, they may impact your anonymity. -# [socks5.socks5_debug] +# [core.socks5.socks5_debug] -[debug] +[core.debug] -[debug.traffic] -average_packet_delay = '{{ debug.traffic.average_packet_delay }}' -message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}' +[core.debug.traffic] +average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}' +message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}' -[debug.acknowledgements] -average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}' +[core.debug.acknowledgements] +average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}' -[debug.cover_traffic] -loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}' +[core.debug.cover_traffic] +loop_cover_traffic_average_delay = '{{ core.debug.cover_traffic.loop_cover_traffic_average_delay }}' -"# -} +"#; diff --git a/clients/socks5/src/main.rs b/clients/socks5/src/main.rs index 7125c0c042..b592c62817 100644 --- a/clients/socks5/src/main.rs +++ b/clients/socks5/src/main.rs @@ -8,6 +8,7 @@ use nym_bin_common::logging::{maybe_print_banner, setup_logging}; use nym_network_defaults::setup_env; mod commands; +mod config; pub mod error; #[tokio::main] diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index dbbdbb4e12..96c9c8b525 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -995,7 +995,16 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", ] [[package]] @@ -1009,6 +1018,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -2228,6 +2249,7 @@ dependencies = [ "log", "pretty_env_logger", "semver 0.11.0", + "serde", "vergen", ] @@ -2237,7 +2259,7 @@ version = "1.1.14" dependencies = [ "async-trait", "dashmap", - "dirs", + "dirs 4.0.0", "futures", "gloo-timers", "humantime-serde", @@ -2370,12 +2392,12 @@ dependencies = [ name = "nym-config" version = "0.1.0" dependencies = [ - "cfg-if 1.0.0", + "dirs 5.0.1", "handlebars", "log", "nym-network-defaults", "serde", - "toml", + "toml 0.7.4", "url", ] @@ -2962,6 +2984,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "pairing" version = "0.20.0" @@ -3834,6 +3862,15 @@ dependencies = [ "syn 2.0.16", ] +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4317,7 +4354,7 @@ dependencies = [ "serde", "serde_json", "tendermint", - "toml", + "toml 0.5.11", "url", ] @@ -4554,6 +4591,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -5142,6 +5213,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" diff --git a/clients/webassembly/internal-dev/worker.js b/clients/webassembly/internal-dev/worker.js index 373fd999f7..b52963b0e8 100644 --- a/clients/webassembly/internal-dev/worker.js +++ b/clients/webassembly/internal-dev/worker.js @@ -107,20 +107,20 @@ function printAndDisplayTestResult(result) { } async function testWithTester() { - const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9"; + const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9"; // A) construct with hardcoded topology const topology = dummyTopology() - const nodeTester = await new NymNodeTester(topology, dummyGateway); + const nodeTester = await new NymNodeTester(topology, preferredGateway); // B) first get topology directly from nym-api // const validator = 'https://qwerty-validator-api.qa.nymte.ch/api'; // const topology = await current_network_topology(validator) - // const nodeTester = await new NymNodeTester(topology, dummyGateway); + // const nodeTester = await new NymNodeTester(topology, preferredGateway); // // C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers) // const validator = 'https://qwerty-validator-api.qa.nymte.ch/api'; - // const nodeTester = await NymNodeTester.new_with_api(validator, dummyGateway) + // const nodeTester = await NymNodeTester.new_with_api(validator, preferredGateway) // D, E, F) you also don't have to specify the gateway. if you don't, a random one (from your topology) will be used // const topology = dummyTopology() @@ -142,7 +142,7 @@ async function testWithTester() { } async function testWithNymClient() { - const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9"; + const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9"; const topology = dummyTopology() let received = 0 @@ -164,7 +164,7 @@ async function testWithNymClient() { console.log('Instantiating WASM client...'); - let clientBuilder = NymClientBuilder.new_tester(topology, onMessageHandler, dummyGateway) + let clientBuilder = NymClientBuilder.new_tester(topology, onMessageHandler, preferredGateway) console.log('Web worker creating WASM client...'); let local_client = await clientBuilder.start_client(); console.log('WASM client running!'); @@ -222,10 +222,10 @@ async function normalNymClientUsage() { debug.topology_refresh_rate_ms = BigInt(60000) - const dummyGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9"; + const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9"; const validator = 'https://qwerty-validator-api.qa.nymte.ch/api'; - const config = new Config('my-awesome-wasm-client', validator, dummyGateway, debug); + const config = new Config('my-awesome-wasm-client', validator, debug); const onMessageHandler = (message) => { console.log(message); diff --git a/clients/webassembly/src/client/config.rs b/clients/webassembly/src/client/config.rs index ff6a6323a4..86d34bd52b 100644 --- a/clients/webassembly/src/client/config.rs +++ b/clients/webassembly/src/client/config.rs @@ -7,73 +7,49 @@ #![allow(clippy::drop_copy)] use nym_client_core::config::{ - Acknowledgements as ConfigAcknowledgements, CoverTraffic as ConfigCoverTraffic, - DebugConfig as ConfigDebug, GatewayConnection as ConfigGatewayConnection, - ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology, Traffic as ConfigTraffic, + Acknowledgements as ConfigAcknowledgements, Config as BaseClientConfig, + CoverTraffic as ConfigCoverTraffic, DebugConfig as ConfigDebug, + GatewayConnection as ConfigGatewayConnection, ReplySurbs as ConfigReplySurbs, + Topology as ConfigTopology, Traffic as ConfigTraffic, }; use nym_sphinx::params::{PacketSize, PacketType}; -use nym_validator_client::client::IdentityKey; use serde::{Deserialize, Serialize}; use std::time::Duration; -use url::Url; use wasm_bindgen::prelude::*; #[wasm_bindgen] #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { - /// ID specifies the human readable ID of this particular client. - pub(crate) id: String, - - pub(crate) nym_api_url: Option, - - pub(crate) disabled_credentials_mode: bool, - - /// Information regarding how the client should choose gateway. - /// If unspecified, the client will attempt to load the config from the storage. - pub(crate) gateway: Option, - - pub(crate) debug: ConfigDebug, - - pub(crate) packet_type: PacketType, + pub(crate) base: BaseClientConfig, } #[wasm_bindgen] impl Config { #[wasm_bindgen(constructor)] - pub fn new( - id: String, - validator_server: String, - packet_type: Option, - gateway: Option, - debug: Option, - ) -> Self { - let packet_type = if let Some(packet_type) = packet_type { - match packet_type.as_str() { - "outfox" => PacketType::Outfox, - _ => PacketType::Mix, - } - } else { - PacketType::Mix - }; + pub fn new(id: String, validator_server: String, debug: Option) -> Self { Config { - id, - nym_api_url: Some( - validator_server + base: BaseClientConfig::new(id) + .with_custom_nyxd(vec![validator_server .parse() - .expect("provided url was malformed"), - ), - disabled_credentials_mode: true, - gateway, - debug: debug.map(Into::into).unwrap_or_default(), - packet_type, + .expect("provided url was malformed")]) + .with_debug_config(debug.map(Into::into).unwrap_or_default()), + } + } + + pub(crate) fn new_tester_config>(id: S) -> Self { + Config { + base: BaseClientConfig::new(id) + .with_disabled_credentials(true) + .with_disabled_cover_traffic(true) + .with_disabled_topology_refresh(true), } } } #[wasm_bindgen] #[derive(Debug, Copy, Clone)] -pub struct Traffic { +pub struct TrafficWasm { /// The parameter of Poisson distribution determining how long, on average, /// sent packet is going to be delayed at any given mix node. /// So for a packet going through three mix nodes, on average, it will take three times this value @@ -92,14 +68,23 @@ pub struct Traffic { /// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size. pub use_extended_packet_size: bool, + + /// Controls whether the sent packets should use outfox as opposed to the default sphinx. + pub use_outfox: bool, } -impl From for ConfigTraffic { - fn from(traffic: Traffic) -> Self { +impl From for ConfigTraffic { + fn from(traffic: TrafficWasm) -> Self { let use_extended_packet_size = traffic .use_extended_packet_size .then(|| PacketSize::ExtendedPacket32); + let packet_type = if traffic.use_outfox { + PacketType::Outfox + } else { + PacketType::Mix + }; + ConfigTraffic { average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms), message_sending_average_delay: Duration::from_millis( @@ -109,27 +94,28 @@ impl From for ConfigTraffic { .disable_main_poisson_packet_distribution, primary_packet_size: PacketSize::RegularPacket, secondary_packet_size: use_extended_packet_size, - packet_type: None, + packet_type, } } } -impl From for Traffic { +impl From for TrafficWasm { fn from(traffic: ConfigTraffic) -> Self { - Traffic { + TrafficWasm { average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u64, message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis() as u64, disable_main_poisson_packet_distribution: traffic .disable_main_poisson_packet_distribution, use_extended_packet_size: traffic.secondary_packet_size.is_some(), + use_outfox: traffic.packet_type == PacketType::Outfox, } } } #[wasm_bindgen] #[derive(Debug, Copy, Clone)] -pub struct CoverTraffic { +pub struct CoverTrafficWasm { /// The parameter of Poisson distribution determining how long, on average, /// it is going to take for another loop cover traffic message to be sent. pub loop_cover_traffic_average_delay_ms: u64, @@ -143,8 +129,8 @@ pub struct CoverTraffic { pub disable_loop_cover_traffic_stream: bool, } -impl From for ConfigCoverTraffic { - fn from(cover_traffic: CoverTraffic) -> Self { +impl From for ConfigCoverTraffic { + fn from(cover_traffic: CoverTrafficWasm) -> Self { ConfigCoverTraffic { loop_cover_traffic_average_delay: Duration::from_millis( cover_traffic.loop_cover_traffic_average_delay_ms, @@ -155,9 +141,9 @@ impl From for ConfigCoverTraffic { } } -impl From for CoverTraffic { +impl From for CoverTrafficWasm { fn from(cover_traffic: ConfigCoverTraffic) -> Self { - CoverTraffic { + CoverTrafficWasm { loop_cover_traffic_average_delay_ms: cover_traffic .loop_cover_traffic_average_delay .as_millis() as u64, @@ -169,14 +155,14 @@ impl From for CoverTraffic { #[wasm_bindgen] #[derive(Debug, Copy, Clone)] -pub struct GatewayConnection { +pub struct GatewayConnectionWasm { /// How long we're willing to wait for a response to a message sent to the gateway, /// before giving up on it. pub gateway_response_timeout_ms: u64, } -impl From for ConfigGatewayConnection { - fn from(gateway_connection: GatewayConnection) -> Self { +impl From for ConfigGatewayConnection { + fn from(gateway_connection: GatewayConnectionWasm) -> Self { ConfigGatewayConnection { gateway_response_timeout: Duration::from_millis( gateway_connection.gateway_response_timeout_ms, @@ -185,9 +171,9 @@ impl From for ConfigGatewayConnection { } } -impl From for GatewayConnection { +impl From for GatewayConnectionWasm { fn from(gateway_connection: ConfigGatewayConnection) -> Self { - GatewayConnection { + GatewayConnectionWasm { gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis() as u64, } @@ -196,7 +182,7 @@ impl From for GatewayConnection { #[wasm_bindgen] #[derive(Debug, Copy, Clone)] -pub struct Acknowledgements { +pub struct AcknowledgementsWasm { /// The parameter of Poisson distribution determining how long, on average, /// sent acknowledgement is going to be delayed at any given mix node. /// So for an ack going through three mix nodes, on average, it will take three times this value @@ -214,8 +200,8 @@ pub struct Acknowledgements { pub ack_wait_addition_ms: u64, } -impl From for ConfigAcknowledgements { - fn from(acknowledgements: Acknowledgements) -> Self { +impl From for ConfigAcknowledgements { + fn from(acknowledgements: AcknowledgementsWasm) -> Self { ConfigAcknowledgements { average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms), ack_wait_multiplier: acknowledgements.ack_wait_multiplier, @@ -224,9 +210,9 @@ impl From for ConfigAcknowledgements { } } -impl From for Acknowledgements { +impl From for AcknowledgementsWasm { fn from(acknowledgements: ConfigAcknowledgements) -> Self { - Acknowledgements { + AcknowledgementsWasm { average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u64, ack_wait_multiplier: acknowledgements.ack_wait_multiplier, ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u64, @@ -236,7 +222,7 @@ impl From for Acknowledgements { #[wasm_bindgen] #[derive(Debug, Copy, Clone)] -pub struct Topology { +pub struct TopologyWasm { /// The uniform delay every which clients are querying the directory server /// to try to obtain a compatible network topology to send sphinx packets through. pub topology_refresh_rate_ms: u64, @@ -252,8 +238,8 @@ pub struct Topology { pub disable_refreshing: bool, } -impl From for ConfigTopology { - fn from(topology: Topology) -> Self { +impl From for ConfigTopology { + fn from(topology: TopologyWasm) -> Self { ConfigTopology { topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms), topology_resolution_timeout: Duration::from_millis( @@ -264,9 +250,9 @@ impl From for ConfigTopology { } } -impl From for Topology { +impl From for TopologyWasm { fn from(topology: ConfigTopology) -> Self { - Topology { + TopologyWasm { topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64, topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64, disable_refreshing: topology.disable_refreshing, @@ -276,7 +262,7 @@ impl From for Topology { #[wasm_bindgen] #[derive(Debug, Copy, Clone)] -pub struct ReplySurbs { +pub struct ReplySurbsWasm { /// Defines the minimum number of reply surbs the client wants to keep in its storage at all times. /// It can only allow to go below that value if its to request additional reply surbs. pub minimum_reply_surb_storage_threshold: usize, @@ -310,8 +296,8 @@ pub struct ReplySurbs { pub maximum_reply_key_age_ms: u64, } -impl From for ConfigReplySurbs { - fn from(reply_surbs: ReplySurbs) -> Self { +impl From for ConfigReplySurbs { + fn from(reply_surbs: ReplySurbsWasm) -> Self { ConfigReplySurbs { minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold, maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold, @@ -331,9 +317,9 @@ impl From for ConfigReplySurbs { } } -impl From for ReplySurbs { +impl From for ReplySurbsWasm { fn from(reply_surbs: ConfigReplySurbs) -> Self { - ReplySurbs { + ReplySurbsWasm { minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold, maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold, minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size, @@ -355,28 +341,28 @@ impl From for ReplySurbs { // just a helper structure to more easily pass through the JS boundary #[wasm_bindgen] #[derive(Debug, Copy, Clone)] -pub struct Debug { +pub struct DebugWasm { /// Defines all configuration options related to traffic streams. - pub traffic: Traffic, + pub traffic: TrafficWasm, /// Defines all configuration options related to cover traffic stream(s). - pub cover_traffic: CoverTraffic, + pub cover_traffic: CoverTrafficWasm, /// Defines all configuration options related to the gateway connection. - pub gateway_connection: GatewayConnection, + pub gateway_connection: GatewayConnectionWasm, /// Defines all configuration options related to acknowledgements, such as delays or wait timeouts. - pub acknowledgements: Acknowledgements, + pub acknowledgements: AcknowledgementsWasm, /// Defines all configuration options related topology, such as refresh rates or timeouts. - pub topology: Topology, + pub topology: TopologyWasm, /// Defines all configuration options related to reply SURBs. - pub reply_surbs: ReplySurbs, + pub reply_surbs: ReplySurbsWasm, } -impl From for ConfigDebug { - fn from(debug: Debug) -> Self { +impl From for ConfigDebug { + fn from(debug: DebugWasm) -> Self { ConfigDebug { traffic: debug.traffic.into(), cover_traffic: debug.cover_traffic.into(), @@ -388,9 +374,9 @@ impl From for ConfigDebug { } } -impl From for Debug { +impl From for DebugWasm { fn from(debug: ConfigDebug) -> Self { - Debug { + DebugWasm { traffic: debug.traffic.into(), cover_traffic: debug.cover_traffic.into(), gateway_connection: debug.gateway_connection.into(), @@ -402,6 +388,6 @@ impl From for Debug { } #[wasm_bindgen] -pub fn default_debug() -> Debug { +pub fn default_debug() -> DebugWasm { ConfigDebug::default().into() } diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index baf72a7c85..d30dd4b989 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -21,7 +21,6 @@ use nym_client_core::client::base_client::{ }; use nym_client_core::client::inbound_messages::InputMessage; use nym_client_core::client::replies::reply_storage::browser_backend; -use nym_client_core::config::{CoverTraffic, DebugConfig, Topology, Traffic}; use nym_credential_storage::ephemeral_storage::EphemeralStorage; use nym_sphinx::params::PacketType; use nym_task::connections::TransmissionLane; @@ -53,13 +52,15 @@ pub struct NymClient { // even though we don't use graceful shutdowns, other components rely on existence of this struct // and if it's dropped, everything will start going offline _task_manager: TaskManager, - packet_type: Option, + + packet_type: PacketType, } #[wasm_bindgen] pub struct NymClientBuilder { config: Config, custom_topology: Option, + preferred_gateway: Option, storage_passphrase: Option, reply_surb_storage_backend: browser_backend::Backend, @@ -70,7 +71,6 @@ pub struct NymClientBuilder { bandwidth_controller: Option, EphemeralStorage>>, disabled_credentials: bool, - packet_type: Option, } #[wasm_bindgen] @@ -79,17 +79,20 @@ impl NymClientBuilder { pub fn new( config: Config, on_message: js_sys::Function, + preferred_gateway: Option, storage_passphrase: Option, ) -> Self { NymClientBuilder { - reply_surb_storage_backend: setup_reply_surb_storage_backend(config.debug.reply_surbs), + reply_surb_storage_backend: setup_reply_surb_storage_backend( + config.base.debug.reply_surbs, + ), config, custom_topology: None, storage_passphrase, on_message, bandwidth_controller: None, disabled_credentials: true, - packet_type: None, + preferred_gateway, } } @@ -108,32 +111,11 @@ impl NymClientBuilder { } } - let full_config = Config { - id: NODE_TESTER_CLIENT_ID.to_string(), - nym_api_url: None, - disabled_credentials_mode: true, - gateway, - debug: DebugConfig { - traffic: Traffic { - disable_main_poisson_packet_distribution: true, - ..Default::default() - }, - cover_traffic: CoverTraffic { - disable_loop_cover_traffic_stream: true, - ..Default::default() - }, - topology: Topology { - disable_refreshing: true, - ..Default::default() - }, - ..Default::default() - }, - packet_type: PacketType::Mix, - }; + let full_config = Config::new_tester_config(NODE_TESTER_CLIENT_ID); NymClientBuilder { reply_surb_storage_backend: setup_reply_surb_storage_backend( - full_config.debug.reply_surbs, + full_config.base.debug.reply_surbs, ), config: full_config, custom_topology: Some(topology.into()), @@ -141,7 +123,7 @@ impl NymClientBuilder { bandwidth_controller: None, disabled_credentials: true, storage_passphrase: None, - packet_type: None, + preferred_gateway: gateway, } } @@ -166,20 +148,18 @@ impl NymClientBuilder { CredentialsToggle::Enabled }; - let nym_api_endpoints = match &self.config.nym_api_url { - Some(endpoint) => vec![endpoint.clone()], - None => Vec::new(), - }; + let nym_api_endpoints = self.config.base.client.nym_api_urls.clone(); // TODO: this will have to be re-used for surbs. but this is a problem for another PR. let client_store = - ClientStorage::new_async(&self.config.id, self.storage_passphrase.take()).await?; + ClientStorage::new_async(&self.config.base.client.id, self.storage_passphrase.take()) + .await?; // if we provided hardcoded topology, get gateway from it, otherwise get it the 'standard' way let gateway_endpoint = if let Some(topology) = &self.custom_topology { gateway_from_topology( &mut thread_rng(), - self.config.gateway.as_deref(), + self.preferred_gateway.as_deref(), topology, &client_store, ) @@ -187,7 +167,7 @@ impl NymClientBuilder { } else { choose_gateway( &client_store, - self.config.gateway.clone(), + self.preferred_gateway.clone(), &nym_api_endpoints, ) .await? @@ -197,7 +177,7 @@ impl NymClientBuilder { let mut base_builder: BaseClientBuilder<_, FullWasmClientStorage> = BaseClientBuilder::new( &gateway_endpoint, - &self.config.debug, + &self.config.base.debug, client_store, self.bandwidth_controller, self.reply_surb_storage_backend, @@ -208,8 +188,7 @@ impl NymClientBuilder { base_builder = base_builder.with_topology_provider(topology_provider); } - let packet_type = self.config.packet_type; - let mut started_client = base_builder.start_base(packet_type).await?; + let mut started_client = base_builder.start_base().await?; let self_address = started_client.address.to_string(); let client_input = started_client.client_input.register_producer(); @@ -223,7 +202,7 @@ impl NymClientBuilder { client_state: Arc::new(started_client.client_state), _full_topology: None, _task_manager: started_client.task_manager, - packet_type: self.packet_type, + packet_type: self.config.base.debug.traffic.packet_type, }) } @@ -237,9 +216,10 @@ impl NymClient { async fn _new( config: Config, on_message: js_sys::Function, + preferred_gateway: Option, storage_passphrase: Option, ) -> Result { - NymClientBuilder::new(config, on_message, storage_passphrase) + NymClientBuilder::new(config, on_message, preferred_gateway, storage_passphrase) .start_client_async() .await } @@ -249,10 +229,11 @@ impl NymClient { pub fn new( config: Config, on_message: js_sys::Function, + preferred_gateway: Option, storage_passphrase: Option, ) -> Promise { future_to_promise(async move { - Self::_new(config, on_message, storage_passphrase) + Self::_new(config, on_message, preferred_gateway, storage_passphrase) .await .into_promise_result() }) @@ -319,7 +300,7 @@ impl NymClient { let lane = TransmissionLane::General; - let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type); + let input_msg = InputMessage::new_regular(recipient, message, lane, Some(self.packet_type)); self.client_input.send_message(input_msg) } @@ -346,8 +327,13 @@ impl NymClient { let lane = TransmissionLane::General; - let input_msg = - InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type); + let input_msg = InputMessage::new_anonymous( + recipient, + message, + reply_surbs, + lane, + Some(self.packet_type), + ); self.client_input.send_message(input_msg) } @@ -365,7 +351,7 @@ impl NymClient { let lane = TransmissionLane::General; - let input_msg = InputMessage::new_reply(sender_tag, message, lane, self.packet_type); + let input_msg = InputMessage::new_reply(sender_tag, message, lane, Some(self.packet_type)); self.client_input.send_message(input_msg) } } diff --git a/clients/webassembly/src/helpers.rs b/clients/webassembly/src/helpers.rs index e66b67c520..c59c10ed22 100644 --- a/clients/webassembly/src/helpers.rs +++ b/clients/webassembly/src/helpers.rs @@ -22,7 +22,7 @@ use wasm_bindgen_futures::future_to_promise; use wasm_utils::{console_log, PromisableResult}; // don't get too excited about the name, under the hood it's just a big fat placeholder -// with no persistence +// with no disk_persistence pub(crate) fn setup_reply_surb_storage_backend( config: config::ReplySurbs, ) -> browser_backend::Backend { diff --git a/common/bin-common/Cargo.toml b/common/bin-common/Cargo.toml index a08ae8c77f..b46424d677 100644 --- a/common/bin-common/Cargo.toml +++ b/common/bin-common/Cargo.toml @@ -15,7 +15,7 @@ clap_complete_fig = "4.0" log = { workspace = true } pretty_env_logger = "0.4.0" semver = "0.11" -serde = { workspace = true, features = ["derive"], optional = true } +serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, optional = true } ## tracing @@ -42,7 +42,7 @@ vergen = { version = "=7.4.3", default-features = false, features = [ [features] default = [] -output_format = ["serde", "serde_json"] +output_format = ["serde_json"] tracing = [ "tracing-subscriber", "tracing-tree", diff --git a/common/bin-common/src/build_information/mod.rs b/common/bin-common/src/build_information/mod.rs index c5d7e7b4af..0e4efeff7f 100644 --- a/common/bin-common/src/build_information/mod.rs +++ b/common/bin-common/src/build_information/mod.rs @@ -4,6 +4,8 @@ // TODO: at a later date this crate should probably also expose `ContractBuildInformation` // and be used by our smart contracts +use serde::{Deserialize, Serialize}; + #[derive(Debug)] pub struct BinaryBuildInformation { // VERGEN_BUILD_TIMESTAMP @@ -99,8 +101,7 @@ impl BinaryBuildInformation { } } -#[derive(Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Serialize, Deserialize)] pub struct BinaryBuildInformationOwned { // VERGEN_BUILD_TIMESTAMP /// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`. diff --git a/common/bin-common/src/lib.rs b/common/bin-common/src/lib.rs index 8659264149..ca642f4883 100644 --- a/common/bin-common/src/lib.rs +++ b/common/bin-common/src/lib.rs @@ -4,5 +4,7 @@ pub mod build_information; pub mod completions; pub mod logging; -pub mod output_format; pub mod version_checker; + +#[cfg(feature = "output_format")] +pub mod output_format; diff --git a/common/bin-common/src/logging/mod.rs b/common/bin-common/src/logging/mod.rs index 256063602c..135736ac2b 100644 --- a/common/bin-common/src/logging/mod.rs +++ b/common/bin-common/src/logging/mod.rs @@ -1,6 +1,8 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use serde::{Deserialize, Serialize}; + #[cfg(feature = "tracing")] pub use opentelemetry; #[cfg(feature = "tracing")] @@ -12,6 +14,12 @@ pub use tracing_subscriber; #[cfg(feature = "tracing")] pub use tracing_tree; +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct LoggingSettings { + // well, we need to implement something here at some point... +} + // I'd argue we should start transitioning from `log` to `tracing` pub fn setup_logging() { let mut log_builder = pretty_env_logger::formatted_timed_builder(); diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index bf4803140a..3c70a50b87 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -176,15 +176,15 @@ where C: DkgQueryClient + Send + Sync + 'static, { // TODO: combine all storages - pub fn new_from_base_config( - base_config: &'a Config, + pub fn new_from_base_config( + base_config: &'a Config, key_store: S::KeyStore, bandwidth_controller: Option>, reply_storage_backend: S::ReplyStore, ) -> BaseClientBuilder<'a, C, S> { BaseClientBuilder { gateway_config: base_config.get_gateway_endpoint_config(), - debug_config: base_config.get_debug_config(), + debug_config: &base_config.debug, disabled_credentials: base_config.get_disabled_credentials_mode(), nym_api_endpoints: base_config.get_nym_api_endpoints(), bandwidth_controller, @@ -479,10 +479,7 @@ where self.managed_keys = ManagedKeys::load_or_generate(&mut rng, &self.key_store).await; } - pub async fn start_base( - mut self, - packet_type: PacketType, - ) -> Result + pub async fn start_base(mut self) -> Result where ::StorageError: Sync + Send, S::ReplyStore: Send + Sync, @@ -586,7 +583,7 @@ where shared_lane_queue_lengths.clone(), client_connection_rx, task_manager.subscribe(), - packet_type, + self.debug_config.traffic.packet_type, ); if !self diff --git a/common/client-core/src/client/base_client/non_wasm_helpers.rs b/common/client-core/src/client/base_client/non_wasm_helpers.rs index b3e9ae4cf1..8d34a50b11 100644 --- a/common/client-core/src/client/base_client/non_wasm_helpers.rs +++ b/common/client-core/src/client/base_client/non_wasm_helpers.rs @@ -101,8 +101,8 @@ pub async fn setup_fs_reply_surb_backend>( } } -pub fn create_bandwidth_controller( - config: &Config, +pub fn create_bandwidth_controller( + config: &Config, storage: St, ) -> BandwidthController, St> { let nyxd_url = config diff --git a/common/client-core/src/client/base_client/storage/mod.rs b/common/client-core/src/client/base_client/storage/mod.rs index b9029d0850..caf8942143 100644 --- a/common/client-core/src/client/base_client/storage/mod.rs +++ b/common/client-core/src/client/base_client/storage/mod.rs @@ -17,15 +17,14 @@ use crate::client::base_client::non_wasm_helpers; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] use crate::client::key_manager::persistence::OnDiskKeys; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] -use crate::config::{persistence::key_pathfinder::ClientKeyPathfinder, Config}; +use crate::client::replies::reply_storage::fs_backend; +#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] +use crate::config::{self, disk_persistence::CommonClientPaths}; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] use crate::error::ClientCoreError; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage; -#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] -use crate::client::replies::reply_storage::fs_backend; - pub trait MixnetClientStorage { type KeyStore: KeyStore; type ReplyStore: ReplyStorageBackend; @@ -95,18 +94,20 @@ impl OnDiskPersistent { } } - pub async fn from_config(config: &Config) -> Result { - let pathfinder = ClientKeyPathfinder::new_from_config(config); - let key_store = OnDiskKeys::new(pathfinder); + pub async fn from_paths( + paths: CommonClientPaths, + debug_config: &config::DebugConfig, + ) -> Result { + let key_store = OnDiskKeys::new(paths.keys); let reply_store = non_wasm_helpers::setup_fs_reply_surb_backend( - config.get_reply_surb_database_path(), - &config.get_debug_config().reply_surbs, + paths.reply_surb_database, + &debug_config.reply_surbs, ) .await?; let credential_store = - nym_credential_storage::initialise_persistent_storage(config.get_database_path()).await; + nym_credential_storage::initialise_persistent_storage(paths.credentials_database).await; Ok(OnDiskPersistent { key_store, diff --git a/common/client-core/src/client/key_manager/persistence.rs b/common/client-core/src/client/key_manager/persistence.rs index 61e8085caf..9f5d482787 100644 --- a/common/client-core/src/client/key_manager/persistence.rs +++ b/common/client-core/src/client/key_manager/persistence.rs @@ -6,9 +6,7 @@ use async_trait::async_trait; use std::error::Error; #[cfg(not(target_arch = "wasm32"))] -use crate::config::persistence::key_pathfinder::ClientKeyPathfinder; -#[cfg(not(target_arch = "wasm32"))] -use crate::config::Config; +use crate::config::disk_persistence::keys_paths::ClientKeysPaths; #[cfg(not(target_arch = "wasm32"))] use nym_crypto::asymmetric::{encryption, identity}; #[cfg(not(target_arch = "wasm32"))] @@ -65,24 +63,30 @@ pub enum OnDiskKeysError { #[cfg(not(target_arch = "wasm32"))] pub struct OnDiskKeys { - pathfinder: ClientKeyPathfinder, + paths: ClientKeysPaths, } #[cfg(not(target_arch = "wasm32"))] -impl From for OnDiskKeys { - fn from(pathfinder: ClientKeyPathfinder) -> Self { - OnDiskKeys { pathfinder } +impl From for OnDiskKeys { + fn from(paths: ClientKeysPaths) -> Self { + OnDiskKeys { paths } } } #[cfg(not(target_arch = "wasm32"))] impl OnDiskKeys { - pub fn new(pathfinder: ClientKeyPathfinder) -> Self { - OnDiskKeys { pathfinder } + pub fn new(paths: ClientKeysPaths) -> Self { + OnDiskKeys { paths } } - pub fn from_config(config: &Config) -> Self { - OnDiskKeys::new(ClientKeyPathfinder::new_from_config(config)) + pub fn load_encryption_keypair(&self) -> Result { + let encryption_paths = self.paths.encryption_key_pair_path(); + self.load_keypair(encryption_paths, "encryption keys") + } + + pub fn load_identity_keypair(&self) -> Result { + let identity_paths = self.paths.identity_key_pair_path(); + self.load_keypair(identity_paths, "identity keys") } fn load_key( @@ -138,17 +142,12 @@ impl OnDiskKeys { } fn load_keys(&self) -> Result { - let identity_paths = self.pathfinder.identity_key_pair_path(); - let encryption_paths = self.pathfinder.encryption_key_pair_path(); + let identity_keypair = self.load_identity_keypair()?; + let encryption_keypair = self.load_encryption_keypair()?; - let identity_keypair: identity::KeyPair = - self.load_keypair(identity_paths, "identity keys")?; - let encryption_keypair: encryption::KeyPair = - self.load_keypair(encryption_paths, "encryption keys")?; - - let ack_key: AckKey = self.load_key(self.pathfinder.ack_key(), "ack key")?; + let ack_key: AckKey = self.load_key(self.paths.ack_key(), "ack key")?; let gateway_shared_key: SharedKeys = - self.load_key(self.pathfinder.gateway_shared_key(), "gateway shared keys")?; + self.load_key(self.paths.gateway_shared_key(), "gateway shared keys")?; Ok(KeyManager::from_keys( identity_keypair, @@ -159,8 +158,8 @@ impl OnDiskKeys { } fn store_keys(&self, keys: &KeyManager) -> Result<(), OnDiskKeysError> { - let identity_paths = self.pathfinder.identity_key_pair_path(); - let encryption_paths = self.pathfinder.encryption_key_pair_path(); + let identity_paths = self.paths.identity_key_pair_path(); + let encryption_paths = self.paths.encryption_key_pair_path(); self.store_keypair( keys.identity_keypair.as_ref(), @@ -173,10 +172,10 @@ impl OnDiskKeys { "encryption keys", )?; - self.store_key(keys.ack_key.as_ref(), self.pathfinder.ack_key(), "ack key")?; + self.store_key(keys.ack_key.as_ref(), self.paths.ack_key(), "ack key")?; self.store_key( keys.gateway_shared_key.as_ref(), - self.pathfinder.gateway_shared_key(), + self.paths.gateway_shared_key(), "gateway shared keys", )?; diff --git a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs index c0f9cce6aa..39c0e25f41 100644 --- a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use self::sending_delay_controller::SendingDelayController; @@ -247,7 +247,7 @@ where self.config.average_ack_delay, self.config.traffic.average_packet_delay, cover_traffic_packet_size, - self.config.traffic.packet_type.unwrap_or_default(), + self.config.traffic.packet_type, ) .expect( "Somehow failed to generate a loop cover message with a valid topology", diff --git a/common/client-core/src/config/disk_persistence/keys_paths.rs b/common/client-core/src/config/disk_persistence/keys_paths.rs new file mode 100644 index 0000000000..3ae1400a19 --- /dev/null +++ b/common/client-core/src/config/disk_persistence/keys_paths.rs @@ -0,0 +1,117 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem"; +pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem"; +pub const DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem"; +pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem"; +pub const DEFAULT_GATEWAY_SHARED_KEY_FILENAME: &str = "gateway_shared.pem"; +pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem"; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +pub struct ClientKeysPaths { + /// 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 encryption key. + pub private_encryption_key_file: PathBuf, + + /// Path to file containing public encryption key. + pub public_encryption_key_file: PathBuf, + + /// Path to file containing shared key derived with the specified gateway that is used + /// for all communication with it. + pub gateway_shared_key_file: PathBuf, + + /// Path to file containing key used for encrypting and decrypting the content of an + /// acknowledgement so that nobody besides the client knows which packet it refers to. + pub ack_key_file: PathBuf, +} + +impl ClientKeysPaths { + pub fn new_default>(base_data_directory: P) -> Self { + let base_dir = base_data_directory.as_ref(); + + ClientKeysPaths { + private_identity_key_file: base_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME), + public_identity_key_file: base_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME), + private_encryption_key_file: base_dir.join(DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME), + public_encryption_key_file: base_dir.join(DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME), + gateway_shared_key_file: base_dir.join(DEFAULT_GATEWAY_SHARED_KEY_FILENAME), + ack_key_file: base_dir.join(DEFAULT_ACK_KEY_FILENAME), + } + } + + pub fn identity_key_pair_path(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + self.private_identity_key().to_path_buf(), + self.public_identity_key().to_path_buf(), + ) + } + + pub fn encryption_key_pair_path(&self) -> nym_pemstore::KeyPairPath { + nym_pemstore::KeyPairPath::new( + self.private_encryption_key().to_path_buf(), + self.public_encryption_key().to_path_buf(), + ) + } + + pub fn any_file_exists(&self) -> bool { + matches!(self.public_identity_key_file.try_exists(), Ok(true)) + || matches!(self.private_identity_key_file.try_exists(), Ok(true)) + || matches!(self.public_encryption_key_file.try_exists(), Ok(true)) + || matches!(self.private_encryption_key_file.try_exists(), Ok(true)) + || matches!(self.gateway_shared_key_file.try_exists(), Ok(true)) + || matches!(self.ack_key_file.try_exists(), Ok(true)) + } + + pub fn any_file_exists_and_return(&self) -> Option { + file_exists(&self.public_identity_key_file) + .or_else(|| file_exists(&self.private_identity_key_file)) + .or_else(|| file_exists(&self.public_encryption_key_file)) + .or_else(|| file_exists(&self.private_encryption_key_file)) + .or_else(|| file_exists(&self.gateway_shared_key_file)) + .or_else(|| file_exists(&self.ack_key_file)) + } + + pub fn gateway_key_file_exists(&self) -> bool { + matches!(self.gateway_shared_key_file.try_exists(), Ok(true)) + } + + 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_encryption_key_file + } + + pub fn public_encryption_key(&self) -> &Path { + &self.public_encryption_key_file + } + + pub fn gateway_shared_key(&self) -> &Path { + &self.gateway_shared_key_file + } + + pub fn ack_key(&self) -> &Path { + &self.ack_key_file + } +} + +fn file_exists(path: &Path) -> Option { + if matches!(path.try_exists(), Ok(true)) { + return Some(path.to_path_buf()); + } + None +} diff --git a/common/client-core/src/config/disk_persistence/mod.rs b/common/client-core/src/config/disk_persistence/mod.rs new file mode 100644 index 0000000000..9b4532e153 --- /dev/null +++ b/common/client-core/src/config/disk_persistence/mod.rs @@ -0,0 +1,36 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::disk_persistence::keys_paths::ClientKeysPaths; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +pub mod keys_paths; + +pub const DEFAULT_REPLY_SURB_DB_FILENAME: &str = "persistent_reply_store.sqlite"; +pub const DEFAULT_CREDENTIALS_DB_FILENAME: &str = "credentials_database.db"; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +pub struct CommonClientPaths { + pub keys: ClientKeysPaths, + + // TODO: + // pub gateway_config_pathfinder: (), + /// Path to the database containing bandwidth credentials of this client. + pub credentials_database: PathBuf, + + /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. + pub reply_surb_database: PathBuf, +} + +impl CommonClientPaths { + pub fn new_default>(base_data_directory: P) -> Self { + let base_dir = base_data_directory.as_ref(); + + CommonClientPaths { + credentials_database: base_dir.join(DEFAULT_CREDENTIALS_DB_FILENAME), + reply_surb_database: base_dir.join(DEFAULT_REPLY_SURB_DB_FILENAME), + keys: ClientKeysPaths::new_default(base_data_directory), + } + } +} diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index bd919d3a37..0f6ee540de 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -1,13 +1,10 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use nym_config::defaults::NymNetworkDetails; -use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME}; use nym_crypto::asymmetric::identity; use nym_sphinx::params::{PacketSize, PacketType}; use serde::{Deserialize, Serialize}; -use std::marker::PhantomData; -use std::path::{Path, PathBuf}; use std::time::Duration; use url::Url; @@ -15,19 +12,9 @@ use crate::error::ClientCoreError; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; +pub mod disk_persistence; pub mod old_config_v1_1_13; -pub mod persistence; - -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_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem"; -pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem"; -pub const DEFAULT_GATEWAY_KEYS_FILENAME: &str = "gateway_shared.pem"; -pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem"; -pub const DEFAULT_REPLY_STORE_FILENAME: &str = "persistent_reply_store.sqlite"; -pub const DEFAULT_CREDENTIAL_STORE_FILENAME: &str = CRED_DB_FILE_NAME; - -pub const MISSING_VALUE: &str = "MISSING VALUE"; +pub mod old_config_v1_1_20; // 'DEBUG' const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5; @@ -68,154 +55,32 @@ const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 6 // 24 hours const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60); -pub fn missing_string_value() -> String { - MISSING_VALUE.to_string() -} - -pub trait ClientCoreConfigTrait { - fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig; -} - #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct Config { - client: Client, +pub struct Config { + pub client: Client, #[serde(default)] - logging: Logging, - #[serde(default)] - debug: DebugConfig, + pub debug: DebugConfig, } -impl ClientCoreConfigTrait for Config { - fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig { - &self.client.gateway_endpoint - } -} - -impl OptionalSet for Config where T: NymConfig {} - -impl Config { - pub fn new>(id: S) -> Self - where - T: NymConfig, - { - Config::default().with_id(id) +impl Config { + pub fn new>(id: S) -> Self { + Config { + client: Client::new_default(id), + debug: Default::default(), + } } pub fn validate(&self) -> bool { - // no other sections have explicit requirements (yet) - self.debug.validate() + self.client.validate() && self.debug.validate() } - #[must_use] - pub fn with_id>(mut self, id: S) -> Self - where - T: NymConfig, - { - self.client.id = id.into(); - self.set_empty_fields_to_defaults(); + pub fn with_debug_config(mut self, debug: DebugConfig) -> Self { + self.debug = debug; self } - #[must_use] - #[doc(hidden)] - // TODO: this totally contradicts our trait... we REALLY have to refactor it... - pub fn reset_data_directory>(mut self, dir: P) -> Self { - self.client.private_identity_key_file = - dir.as_ref().join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME); - self.client.public_identity_key_file = - dir.as_ref().join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME); - self.client.private_encryption_key_file = - dir.as_ref().join(DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME); - self.client.public_encryption_key_file = - dir.as_ref().join(DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME); - self.client.gateway_shared_key_file = dir.as_ref().join(DEFAULT_GATEWAY_KEYS_FILENAME); - self.client.ack_key_file = dir.as_ref().join(DEFAULT_ACK_KEY_FILENAME); - self.client.reply_surb_database_path = dir.as_ref().join(DEFAULT_REPLY_STORE_FILENAME); - self.client.database_path = dir.as_ref().join(DEFAULT_CREDENTIAL_STORE_FILENAME); - - self - } - - #[must_use] - #[doc(hidden)] - // TODO: this totally contradicts our trait... we REALLY have to refactor it... - pub fn reset_nym_root_directory>(mut self, dir: P) -> Self - where - T: NymConfig, - { - self.client.nym_root_directory = dir.as_ref().to_owned(); - self - } - - pub fn set_empty_fields_to_defaults(&mut self) -> bool - where - T: NymConfig, - { - let id = &self.client.id; - let mut changes_made = false; - - // identity key setting - if self.client.private_identity_key_file.as_os_str().is_empty() { - changes_made = true; - self.client.private_identity_key_file = - self::Client::::default_private_identity_key_file(id); - } - if self.client.public_identity_key_file.as_os_str().is_empty() { - changes_made = true; - self.client.public_identity_key_file = - self::Client::::default_public_identity_key_file(id); - } - - // encryption key setting - if self - .client - .private_encryption_key_file - .as_os_str() - .is_empty() - { - changes_made = true; - self.client.private_encryption_key_file = - self::Client::::default_private_encryption_key_file(id); - } - if self - .client - .public_encryption_key_file - .as_os_str() - .is_empty() - { - changes_made = true; - self.client.public_encryption_key_file = - self::Client::::default_public_encryption_key_file(id); - } - - // shared gateway key setting - if self.client.gateway_shared_key_file.as_os_str().is_empty() { - changes_made = true; - self.client.gateway_shared_key_file = - self::Client::::default_gateway_shared_key_file(id); - } - - // ack key setting - if self.client.ack_key_file.as_os_str().is_empty() { - changes_made = true; - self.client.ack_key_file = self::Client::::default_ack_key_file(id); - } - - if self.client.reply_surb_database_path.as_os_str().is_empty() { - changes_made = true; - self.client.reply_surb_database_path = - self::Client::::default_reply_surb_database_path(id); - } - - if self.client.database_path.as_os_str().is_empty() { - changes_made = true; - self.client.database_path = self::Client::::default_database_path(id); - } - changes_made - } - pub fn with_disabled_credentials(mut self, disabled_credentials_mode: bool) -> Self { self.client.disabled_credentials_mode = disabled_credentials_mode; self @@ -260,7 +125,7 @@ impl Config { } pub fn with_packet_type(mut self, packet_type: PacketType) -> Self { - self.client.packet_type = Some(packet_type); + self.debug.traffic.packet_type = packet_type; self } @@ -280,6 +145,11 @@ impl Config { self } + pub fn with_disabled_topology_refresh(mut self, disable_topology_refresh: bool) -> Self { + self.debug.topology.disable_refreshing = disable_topology_refresh; + self + } + pub fn set_no_cover_traffic(&mut self) { self.debug.cover_traffic.disable_loop_cover_traffic_stream = true; self.debug.traffic.disable_main_poisson_packet_distribution = true; @@ -297,34 +167,6 @@ impl Config { self.client.disabled_credentials_mode } - pub fn get_nym_root_directory(&self) -> PathBuf { - self.client.nym_root_directory.clone() - } - - pub fn get_private_identity_key_file(&self) -> PathBuf { - self.client.private_identity_key_file.clone() - } - - pub fn get_public_identity_key_file(&self) -> PathBuf { - self.client.public_identity_key_file.clone() - } - - pub fn get_private_encryption_key_file(&self) -> PathBuf { - self.client.private_encryption_key_file.clone() - } - - pub fn get_public_encryption_key_file(&self) -> PathBuf { - self.client.public_encryption_key_file.clone() - } - - pub fn get_gateway_shared_key_file(&self) -> PathBuf { - self.client.gateway_shared_key_file.clone() - } - - pub fn get_ack_key_file(&self) -> PathBuf { - self.client.ack_key_file.clone() - } - pub fn get_validator_endpoints(&self) -> Vec { self.client.nyxd_urls.clone() } @@ -348,123 +190,6 @@ impl Config { pub fn get_gateway_endpoint_config(&self) -> &GatewayEndpointConfig { &self.client.gateway_endpoint } - - pub fn get_database_path(&self) -> PathBuf { - self.client.database_path.clone() - } - - pub fn get_reply_surb_database_path(&self) -> PathBuf { - self.client.reply_surb_database_path.clone() - } - - pub fn get_version(&self) -> &str { - &self.client.version - } - - // Debug getters - pub fn get_debug_config(&self) -> &DebugConfig { - &self.debug - } - - pub fn get_average_packet_delay(&self) -> Duration { - self.debug.traffic.average_packet_delay - } - - pub fn get_average_ack_delay(&self) -> Duration { - self.debug.acknowledgements.average_ack_delay - } - - pub fn get_ack_wait_multiplier(&self) -> f64 { - self.debug.acknowledgements.ack_wait_multiplier - } - - pub fn get_ack_wait_addition(&self) -> Duration { - self.debug.acknowledgements.ack_wait_addition - } - - pub fn get_loop_cover_traffic_average_delay(&self) -> Duration { - self.debug.cover_traffic.loop_cover_traffic_average_delay - } - - pub fn get_message_sending_average_delay(&self) -> Duration { - self.debug.traffic.message_sending_average_delay - } - - pub fn get_gateway_response_timeout(&self) -> Duration { - self.debug.gateway_connection.gateway_response_timeout - } - - pub fn get_topology_refresh_rate(&self) -> Duration { - self.debug.topology.topology_refresh_rate - } - - pub fn get_topology_resolution_timeout(&self) -> Duration { - self.debug.topology.topology_resolution_timeout - } - - pub fn get_disabled_loop_cover_traffic_stream(&self) -> bool { - self.debug.cover_traffic.disable_loop_cover_traffic_stream - } - - pub fn get_disabled_main_poisson_packet_distribution(&self) -> bool { - self.debug.traffic.disable_main_poisson_packet_distribution - } - - pub fn get_minimum_reply_surb_storage_threshold(&self) -> usize { - self.debug.reply_surbs.minimum_reply_surb_storage_threshold - } - - pub fn get_maximum_reply_surb_storage_threshold(&self) -> usize { - self.debug.reply_surbs.maximum_reply_surb_storage_threshold - } - - pub fn get_minimum_reply_surb_request_size(&self) -> u32 { - self.debug.reply_surbs.minimum_reply_surb_request_size - } - - pub fn get_maximum_reply_surb_request_size(&self) -> u32 { - self.debug.reply_surbs.maximum_reply_surb_request_size - } - - pub fn get_maximum_allowed_reply_surb_request_size(&self) -> u32 { - self.debug - .reply_surbs - .maximum_allowed_reply_surb_request_size - } - - pub fn get_maximum_reply_surb_rerequest_waiting_period(&self) -> Duration { - self.debug - .reply_surbs - .maximum_reply_surb_rerequest_waiting_period - } - - pub fn get_maximum_reply_surb_drop_waiting_period(&self) -> Duration { - self.debug - .reply_surbs - .maximum_reply_surb_drop_waiting_period - } - - pub fn get_maximum_reply_surb_age(&self) -> Duration { - self.debug.reply_surbs.maximum_reply_surb_age - } - - pub fn get_maximum_reply_key_age(&self) -> Duration { - self.debug.reply_surbs.maximum_reply_key_age - } - - pub fn get_packet_type(&self) -> PacketType { - self.client.packet_type.unwrap_or(PacketType::Mix) - } -} - -impl Default for Config { - fn default() -> Self { - Config { - client: Client::::default(), - logging: Default::default(), - debug: Default::default(), - } - } } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] @@ -517,9 +242,8 @@ impl From for GatewayEndpointConfig { } #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] -pub struct Client { +pub struct Client { /// Version of the client for which this configuration was created. - #[serde(default = "missing_string_value")] pub version: String, /// ID specifies the human readable ID of this particular client. @@ -538,51 +262,14 @@ pub struct Client { #[serde(alias = "validator_api_urls")] pub nym_api_urls: Vec, - /// 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 encryption key. - pub private_encryption_key_file: PathBuf, - - /// Path to file containing public encryption key. - pub public_encryption_key_file: PathBuf, - - /// Path to file containing shared key derived with the specified gateway that is used - /// for all communication with it. - pub gateway_shared_key_file: PathBuf, - - /// Path to file containing key used for encrypting and decrypting the content of an - /// acknowledgement so that nobody besides the client knows which packet it refers to. - pub ack_key_file: PathBuf, - /// Information regarding how the client should send data to gateway. + // #[deprecated(note = "this shall be moved to separate file because it doesn't belong here...")] + // TODO: this should be removed from config files and be moved to separate file instead pub gateway_endpoint: GatewayEndpointConfig, - - /// Path to the database containing bandwidth credentials of this client. - pub database_path: PathBuf, - - /// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. - // this was set to use #[serde(default)] for the purposes of compatibility for multi-surbs introduced in 1.1.4. - // if you're reading this message and we have already introduced some breaking changes, feel free - // to remove that attribute since at this point the client configs should have gotten regenerated - #[serde(default)] - pub reply_surb_database_path: PathBuf, - - /// nym_home_directory specifies absolute path to the home nym Clients directory. - /// It is expected to use default value and hence .toml file should not redefine this field. - pub nym_root_directory: PathBuf, - - #[serde(skip)] - pub super_struct: PhantomData, - - pub packet_type: Option, } -impl Default for Client { - fn default() -> Self { +impl Client { + pub fn new_default>(id: S) -> Self { let network = NymNetworkDetails::new_mainnet(); let nyxd_urls = network .endpoints @@ -595,71 +282,23 @@ impl Default for Client { .filter_map(|validator| validator.api_url()) .collect::>(); - if nym_api_urls.is_empty() { - panic!("we do not have any default nym-api urls available!") - } - - // there must be explicit checks for whether id is not empty later Client { version: env!("CARGO_PKG_VERSION").to_string(), - id: "".to_string(), + id: id.into(), disabled_credentials_mode: true, nyxd_urls, nym_api_urls, - private_identity_key_file: Default::default(), - public_identity_key_file: Default::default(), - private_encryption_key_file: Default::default(), - public_encryption_key_file: Default::default(), - gateway_shared_key_file: Default::default(), - ack_key_file: Default::default(), gateway_endpoint: Default::default(), - database_path: Default::default(), - reply_surb_database_path: Default::default(), - nym_root_directory: T::default_root_directory(), - super_struct: Default::default(), - packet_type: Default::default(), } } -} -impl Client { - fn default_private_identity_key_file(id: &str) -> PathBuf { - T::default_data_directory(id).join("private_identity.pem") - } - - fn default_public_identity_key_file(id: &str) -> PathBuf { - T::default_data_directory(id).join("public_identity.pem") - } - - fn default_private_encryption_key_file(id: &str) -> PathBuf { - T::default_data_directory(id).join("private_encryption.pem") - } - - fn default_public_encryption_key_file(id: &str) -> PathBuf { - T::default_data_directory(id).join("public_encryption.pem") - } - - fn default_gateway_shared_key_file(id: &str) -> PathBuf { - T::default_data_directory(id).join("gateway_shared.pem") - } - - fn default_ack_key_file(id: &str) -> PathBuf { - T::default_data_directory(id).join("ack_key.pem") - } - - fn default_reply_surb_database_path(id: &str) -> PathBuf { - T::default_data_directory(id).join("persistent_reply_store.sqlite") - } - - fn default_database_path(id: &str) -> PathBuf { - T::default_data_directory(id).join(CRED_DB_FILE_NAME) + pub fn validate(&self) -> bool { + !self.gateway_endpoint.gateway_id.is_empty() + && !self.gateway_endpoint.gateway_owner.is_empty() + && !self.gateway_endpoint.gateway_owner.is_empty() } } -#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct Logging {} - #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default)] pub struct Traffic { @@ -690,7 +329,7 @@ pub struct Traffic { /// Do not set it it unless you understand the consequences of that change. pub secondary_packet_size: Option, - pub packet_type: Option, + pub packet_type: PacketType, } impl Traffic { @@ -714,7 +353,7 @@ impl Default for Traffic { disable_main_poisson_packet_distribution: false, primary_packet_size: PacketSize::RegularPacket, secondary_packet_size: None, - packet_type: None, + packet_type: PacketType::Mix, } } } diff --git a/common/client-core/src/config/old_config_v1_1_13.rs b/common/client-core/src/config/old_config_v1_1_13.rs index 9c06f885b0..df1246c88b 100644 --- a/common/client-core/src/config/old_config_v1_1_13.rs +++ b/common/client-core/src/config/old_config_v1_1_13.rs @@ -1,19 +1,18 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::{ - Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection, Logging, - ReplySurbs, Topology, Traffic, DEFAULT_ACK_WAIT_ADDITION, DEFAULT_ACK_WAIT_MULTIPLIER, - DEFAULT_AVERAGE_PACKET_DELAY, DEFAULT_GATEWAY_RESPONSE_TIMEOUT, - DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, - DEFAULT_MAXIMUM_REPLY_KEY_AGE, DEFAULT_MAXIMUM_REPLY_SURB_AGE, - DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, - DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD, +use crate::config::old_config_v1_1_20::{ + AcknowledgementsV1_1_20, ClientV1_1_20, ConfigV1_1_20, CoverTrafficV1_1_20, DebugConfigV1_1_20, + GatewayConnectionV1_1_20, LoggingV1_1_20, ReplySurbsV1_1_20, TopologyV1_1_20, TrafficV1_1_20, + DEFAULT_ACK_WAIT_ADDITION, DEFAULT_ACK_WAIT_MULTIPLIER, DEFAULT_AVERAGE_PACKET_DELAY, + DEFAULT_GATEWAY_RESPONSE_TIMEOUT, DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, + DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, DEFAULT_MAXIMUM_REPLY_KEY_AGE, + DEFAULT_MAXIMUM_REPLY_SURB_AGE, DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, + DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD, DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, DEFAULT_TOPOLOGY_REFRESH_RATE, DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, }; -use nym_config::NymConfig; use nym_sphinx::params::PacketSize; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; @@ -40,21 +39,21 @@ impl From for PacketSize { #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct OldConfigV1_1_13 { - pub client: Client, + pub client: ClientV1_1_20, #[serde(default)] - logging: Logging, + pub logging: OldLoggingV1_1_13, #[serde(default)] - debug: OldDebugConfigV1_1_13, + pub debug: OldDebugConfigV1_1_13, } -impl Default for OldConfigV1_1_13 { - fn default() -> Self { - OldConfigV1_1_13 { - client: Client::::default(), - logging: Default::default(), - debug: Default::default(), - } +#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct OldLoggingV1_1_13 {} + +impl From for LoggingV1_1_20 { + fn from(_value: OldLoggingV1_1_13) -> Self { + LoggingV1_1_20 {} } } @@ -115,37 +114,36 @@ pub struct OldDebugConfigV1_1_13 { pub maximum_reply_key_age: Duration, } -impl From for DebugConfig { +impl From for DebugConfigV1_1_20 { fn from(value: OldDebugConfigV1_1_13) -> Self { - DebugConfig { - traffic: Traffic { + DebugConfigV1_1_20 { + traffic: TrafficV1_1_20 { average_packet_delay: value.average_packet_delay, message_sending_average_delay: value.message_sending_average_delay, disable_main_poisson_packet_distribution: value .disable_main_poisson_packet_distribution, primary_packet_size: PacketSize::RegularPacket, secondary_packet_size: value.use_extended_packet_size.map(Into::into), - packet_type: None, }, - cover_traffic: CoverTraffic { + cover_traffic: CoverTrafficV1_1_20 { loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay, disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream, - ..CoverTraffic::default() + ..CoverTrafficV1_1_20::default() }, - gateway_connection: GatewayConnection { + gateway_connection: GatewayConnectionV1_1_20 { gateway_response_timeout: value.gateway_response_timeout, }, - acknowledgements: Acknowledgements { + acknowledgements: AcknowledgementsV1_1_20 { average_ack_delay: value.average_ack_delay, ack_wait_multiplier: value.ack_wait_multiplier, ack_wait_addition: value.ack_wait_addition, }, - topology: Topology { + topology: TopologyV1_1_20 { topology_refresh_rate: value.topology_refresh_rate, topology_resolution_timeout: value.topology_resolution_timeout, disable_refreshing: false, }, - reply_surbs: ReplySurbs { + reply_surbs: ReplySurbsV1_1_20 { minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold, maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold, minimum_reply_surb_request_size: value.minimum_reply_surb_request_size, @@ -192,10 +190,10 @@ impl Default for OldDebugConfigV1_1_13 { } } -impl From> for Config { +impl From> for ConfigV1_1_20 { fn from(value: OldConfigV1_1_13) -> Self { - Config { - client: Client { + ConfigV1_1_20 { + client: ClientV1_1_20 { version: value.client.version, id: value.client.id, disabled_credentials_mode: value.client.disabled_credentials_mode, @@ -211,10 +209,10 @@ impl From> for Config { database_path: value.client.database_path, reply_surb_database_path: value.client.reply_surb_database_path, nym_root_directory: value.client.nym_root_directory, + super_struct: PhantomData, - packet_type: Some(nym_sphinx::params::PacketType::Mix), }, - logging: value.logging, + logging: value.logging.into(), debug: value.debug.into(), } } diff --git a/common/client-core/src/config/old_config_v1_1_20.rs b/common/client-core/src/config/old_config_v1_1_20.rs new file mode 100644 index 0000000000..c2bc27b89b --- /dev/null +++ b/common/client-core/src/config/old_config_v1_1_20.rs @@ -0,0 +1,354 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::{ + Acknowledgements, CoverTraffic, DebugConfig, GatewayConnection, GatewayEndpointConfig, + ReplySurbs, Topology, Traffic, +}; +use nym_sphinx::params::{PacketSize, PacketType}; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; +use std::path::PathBuf; +use std::time::Duration; +use url::Url; + +// 'DEBUG' +pub(crate) const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5; + +pub(crate) const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500); +pub(crate) const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200); +pub(crate) const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20); +pub(crate) const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50); +pub(crate) const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min +pub(crate) const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000); +// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause +// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the +// bandwidth bridging protocol, we can come back to a smaller timeout value +pub(crate) const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60); + +pub(crate) const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70; + +// reply-surbs related: + +// define when to request +// clients/client-core/src/client/replies/reply_storage/surb_storage.rs +pub(crate) const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10; +pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200; + +// define how much to request at once +// clients/client-core/src/client/replies/reply_controller.rs +pub(crate) const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10; +pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100; + +pub(crate) const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500; + +pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = + Duration::from_secs(10); +pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = + Duration::from_secs(5 * 60); + +// 12 hours +pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60); + +// 24 hours +pub(crate) const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60); + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_20 { + pub client: ClientV1_1_20, + + #[serde(default)] + pub logging: LoggingV1_1_20, + #[serde(default)] + pub debug: DebugConfigV1_1_20, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +pub struct GatewayEndpointConfigV1_1_20 { + pub gateway_id: String, + pub gateway_owner: String, + pub gateway_listener: String, +} + +impl From for GatewayEndpointConfig { + fn from(value: GatewayEndpointConfigV1_1_20) -> Self { + GatewayEndpointConfig { + gateway_id: value.gateway_id, + gateway_owner: value.gateway_owner, + gateway_listener: value.gateway_listener, + } + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +pub struct ClientV1_1_20 { + pub version: String, + pub id: String, + #[serde(default)] + pub disabled_credentials_mode: bool, + #[serde(alias = "validator_urls")] + pub nyxd_urls: Vec, + #[serde(alias = "validator_api_urls")] + pub nym_api_urls: Vec, + pub private_identity_key_file: PathBuf, + pub public_identity_key_file: PathBuf, + pub private_encryption_key_file: PathBuf, + pub public_encryption_key_file: PathBuf, + pub gateway_shared_key_file: PathBuf, + pub ack_key_file: PathBuf, + pub gateway_endpoint: GatewayEndpointConfigV1_1_20, + pub database_path: PathBuf, + #[serde(default)] + pub reply_surb_database_path: PathBuf, + pub nym_root_directory: PathBuf, + + #[serde(skip)] + pub super_struct: PhantomData, +} + +#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct LoggingV1_1_20 {} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct TrafficV1_1_20 { + #[serde(with = "humantime_serde")] + pub average_packet_delay: Duration, + #[serde(with = "humantime_serde")] + pub message_sending_average_delay: Duration, + pub disable_main_poisson_packet_distribution: bool, + pub primary_packet_size: PacketSize, + pub secondary_packet_size: Option, +} + +impl From for Traffic { + fn from(value: TrafficV1_1_20) -> Self { + Traffic { + average_packet_delay: value.average_packet_delay, + message_sending_average_delay: value.message_sending_average_delay, + disable_main_poisson_packet_distribution: value + .disable_main_poisson_packet_distribution, + primary_packet_size: value.primary_packet_size, + secondary_packet_size: value.secondary_packet_size, + packet_type: PacketType::Mix, + } + } +} + +impl Default for TrafficV1_1_20 { + fn default() -> Self { + TrafficV1_1_20 { + average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY, + message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY, + disable_main_poisson_packet_distribution: false, + primary_packet_size: PacketSize::RegularPacket, + secondary_packet_size: None, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct CoverTrafficV1_1_20 { + #[serde(with = "humantime_serde")] + pub loop_cover_traffic_average_delay: Duration, + pub cover_traffic_primary_size_ratio: f64, + pub disable_loop_cover_traffic_stream: bool, +} + +impl From for CoverTraffic { + fn from(value: CoverTrafficV1_1_20) -> Self { + CoverTraffic { + loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay, + cover_traffic_primary_size_ratio: value.cover_traffic_primary_size_ratio, + disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream, + } + } +} + +impl Default for CoverTrafficV1_1_20 { + fn default() -> Self { + CoverTrafficV1_1_20 { + loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, + cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO, + disable_loop_cover_traffic_stream: false, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct GatewayConnectionV1_1_20 { + #[serde(with = "humantime_serde")] + pub gateway_response_timeout: Duration, +} + +impl From for GatewayConnection { + fn from(value: GatewayConnectionV1_1_20) -> Self { + GatewayConnection { + gateway_response_timeout: value.gateway_response_timeout, + } + } +} + +impl Default for GatewayConnectionV1_1_20 { + fn default() -> Self { + GatewayConnectionV1_1_20 { + gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct AcknowledgementsV1_1_20 { + #[serde(with = "humantime_serde")] + pub average_ack_delay: Duration, + pub ack_wait_multiplier: f64, + #[serde(with = "humantime_serde")] + pub ack_wait_addition: Duration, +} + +impl From for Acknowledgements { + fn from(value: AcknowledgementsV1_1_20) -> Self { + Acknowledgements { + average_ack_delay: value.average_ack_delay, + ack_wait_multiplier: value.ack_wait_multiplier, + ack_wait_addition: value.ack_wait_addition, + } + } +} + +impl Default for AcknowledgementsV1_1_20 { + fn default() -> Self { + AcknowledgementsV1_1_20 { + average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY, + ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER, + ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct TopologyV1_1_20 { + #[serde(with = "humantime_serde")] + pub topology_refresh_rate: Duration, + #[serde(with = "humantime_serde")] + pub topology_resolution_timeout: Duration, + pub disable_refreshing: bool, +} + +impl From for Topology { + fn from(value: TopologyV1_1_20) -> Self { + Topology { + topology_refresh_rate: value.topology_refresh_rate, + topology_resolution_timeout: value.topology_resolution_timeout, + disable_refreshing: value.disable_refreshing, + } + } +} + +impl Default for TopologyV1_1_20 { + fn default() -> Self { + TopologyV1_1_20 { + topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, + topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, + disable_refreshing: false, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct ReplySurbsV1_1_20 { + pub minimum_reply_surb_storage_threshold: usize, + pub maximum_reply_surb_storage_threshold: usize, + pub minimum_reply_surb_request_size: u32, + pub maximum_reply_surb_request_size: u32, + pub maximum_allowed_reply_surb_request_size: u32, + #[serde(with = "humantime_serde")] + pub maximum_reply_surb_rerequest_waiting_period: Duration, + #[serde(with = "humantime_serde")] + pub maximum_reply_surb_drop_waiting_period: Duration, + #[serde(with = "humantime_serde")] + pub maximum_reply_surb_age: Duration, + #[serde(with = "humantime_serde")] + pub maximum_reply_key_age: Duration, +} + +impl From for ReplySurbs { + fn from(value: ReplySurbsV1_1_20) -> Self { + ReplySurbs { + minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold, + maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold, + minimum_reply_surb_request_size: value.minimum_reply_surb_request_size, + maximum_reply_surb_request_size: value.maximum_reply_surb_request_size, + maximum_allowed_reply_surb_request_size: value.maximum_allowed_reply_surb_request_size, + maximum_reply_surb_rerequest_waiting_period: value + .maximum_reply_surb_rerequest_waiting_period, + maximum_reply_surb_drop_waiting_period: value.maximum_reply_surb_drop_waiting_period, + maximum_reply_surb_age: value.maximum_reply_surb_age, + maximum_reply_key_age: value.maximum_reply_key_age, + } + } +} + +impl Default for ReplySurbsV1_1_20 { + fn default() -> Self { + ReplySurbsV1_1_20 { + minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD, + maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, + minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, + maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, + maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, + maximum_reply_surb_rerequest_waiting_period: + DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD, + maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, + maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE, + maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct DebugConfigV1_1_20 { + pub traffic: TrafficV1_1_20, + pub cover_traffic: CoverTrafficV1_1_20, + pub gateway_connection: GatewayConnectionV1_1_20, + pub acknowledgements: AcknowledgementsV1_1_20, + pub topology: TopologyV1_1_20, + pub reply_surbs: ReplySurbsV1_1_20, +} + +impl From for DebugConfig { + fn from(value: DebugConfigV1_1_20) -> Self { + DebugConfig { + traffic: value.traffic.into(), + cover_traffic: value.cover_traffic.into(), + gateway_connection: value.gateway_connection.into(), + acknowledgements: value.acknowledgements.into(), + topology: value.topology.into(), + reply_surbs: value.reply_surbs.into(), + } + } +} + +// it could be derived, sure, but I'd rather have an explicit implementation in case we had to change +// something manually at some point +#[allow(clippy::derivable_impls)] +impl Default for DebugConfigV1_1_20 { + fn default() -> Self { + DebugConfigV1_1_20 { + traffic: Default::default(), + cover_traffic: Default::default(), + gateway_connection: Default::default(), + acknowledgements: Default::default(), + topology: Default::default(), + reply_surbs: Default::default(), + } + } +} diff --git a/common/client-core/src/config/persistence/key_pathfinder.rs b/common/client-core/src/config/persistence/key_pathfinder.rs deleted file mode 100644 index 135b8f61da..0000000000 --- a/common/client-core/src/config/persistence/key_pathfinder.rs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::Config; -use std::path::{Path, PathBuf}; - -#[derive(Debug)] -pub struct ClientKeyPathfinder { - pub identity_private_key: PathBuf, - pub identity_public_key: PathBuf, - pub encryption_private_key: PathBuf, - pub encryption_public_key: PathBuf, - pub gateway_shared_key: PathBuf, - pub ack_key: PathBuf, -} - -impl ClientKeyPathfinder { - pub fn new(id: String) -> Self { - let os_config_dir = dirs::config_dir().expect("no config directory known for this OS"); // grabs the OS default config dir - let config_dir = os_config_dir.join("nym").join("clients").join(id); - ClientKeyPathfinder { - identity_private_key: config_dir.join("private_identity.pem"), - identity_public_key: config_dir.join("public_identity.pem"), - encryption_private_key: config_dir.join("private_encryption.pem"), - encryption_public_key: config_dir.join("public_encryption.pem"), - gateway_shared_key: config_dir.join("gateway_shared.pem"), - ack_key: config_dir.join("ack_key.pem"), - } - } - - pub fn new_from_config(config: &Config) -> Self { - ClientKeyPathfinder { - identity_private_key: config.get_private_identity_key_file(), - identity_public_key: config.get_public_identity_key_file(), - encryption_private_key: config.get_private_encryption_key_file(), - encryption_public_key: config.get_public_encryption_key_file(), - gateway_shared_key: config.get_gateway_shared_key_file(), - ack_key: config.get_ack_key_file(), - } - } - - pub fn identity_key_pair_path(&self) -> nym_pemstore::KeyPairPath { - nym_pemstore::KeyPairPath::new( - self.private_identity_key().to_path_buf(), - self.public_identity_key().to_path_buf(), - ) - } - - pub fn encryption_key_pair_path(&self) -> nym_pemstore::KeyPairPath { - nym_pemstore::KeyPairPath::new( - self.private_encryption_key().to_path_buf(), - self.public_encryption_key().to_path_buf(), - ) - } - - pub fn any_file_exists(&self) -> bool { - matches!(self.identity_public_key.try_exists(), Ok(true)) - || matches!(self.identity_private_key.try_exists(), Ok(true)) - || matches!(self.encryption_public_key.try_exists(), Ok(true)) - || matches!(self.encryption_private_key.try_exists(), Ok(true)) - || matches!(self.gateway_shared_key.try_exists(), Ok(true)) - || matches!(self.ack_key.try_exists(), Ok(true)) - } - - pub fn any_file_exists_and_return(&self) -> Option { - file_exists(&self.identity_public_key) - .or_else(|| file_exists(&self.identity_private_key)) - .or_else(|| file_exists(&self.encryption_public_key)) - .or_else(|| file_exists(&self.encryption_private_key)) - .or_else(|| file_exists(&self.gateway_shared_key)) - .or_else(|| file_exists(&self.ack_key)) - } - - pub fn gateway_key_file_exists(&self) -> bool { - matches!(self.gateway_shared_key.try_exists(), Ok(true)) - } - - pub fn private_identity_key(&self) -> &Path { - &self.identity_private_key - } - - pub fn public_identity_key(&self) -> &Path { - &self.identity_public_key - } - - pub fn private_encryption_key(&self) -> &Path { - &self.encryption_private_key - } - - pub fn public_encryption_key(&self) -> &Path { - &self.encryption_public_key - } - - pub fn gateway_shared_key(&self) -> &Path { - &self.gateway_shared_key - } - - pub fn ack_key(&self) -> &Path { - &self.ack_key - } -} - -fn file_exists(path: &Path) -> Option { - if matches!(path.try_exists(), Ok(true)) { - return Some(path.to_path_buf()); - } - None -} diff --git a/common/client-core/src/config/persistence/mod.rs b/common/client-core/src/config/persistence/mod.rs deleted file mode 100644 index f0080c1f99..0000000000 --- a/common/client-core/src/config/persistence/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -pub mod key_pathfinder; diff --git a/common/client-core/src/init/mod.rs b/common/client-core/src/init/mod.rs index 4a2b96f3ec..5139fb7dbd 100644 --- a/common/client-core/src/init/mod.rs +++ b/common/client-core/src/init/mod.rs @@ -8,20 +8,15 @@ use crate::client::key_manager::persistence::KeyStore; use crate::client::key_manager::{KeyManager, ManagedKeys}; use crate::init::helpers::{choose_gateway_by_latency, current_gateways, uniformly_random_gateway}; use crate::{ - config::{ - persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config, - GatewayEndpointConfig, - }, + config::{disk_persistence::keys_paths::ClientKeysPaths, Config, GatewayEndpointConfig}, error::ClientCoreError, }; -use nym_config::NymConfig; use nym_crypto::asymmetric::{encryption, identity}; use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity}; use nym_validator_client::client::IdentityKey; use rand::rngs::OsRng; use serde::Serialize; use std::fmt::{Debug, Display}; -use tap::TapFallible; use url::Url; mod helpers; @@ -122,13 +117,10 @@ pub struct InitResults { } impl InitResults { - pub fn new(config: &Config, address: &Recipient) -> Self - where - T: NymConfig, - { + pub fn new(config: &Config, address: &Recipient) -> Self { Self { - version: config.get_version().to_string(), - id: config.get_id(), + version: config.client.version.clone(), + id: config.client.id.clone(), identity_key: address.identity().to_base58_string(), encryption_key: address.encryption_key().to_base58_string(), gateway_id: config.get_gateway_id(), @@ -206,26 +198,23 @@ where /// b. Create a new gateway configuration but keep existing keys. This assumes that the caller /// knows what they are doing and that the keys match the requested gateway. /// c. Create a new gateway configuration with a newly registered gateway and keys. -pub async fn setup_gateway_from_config( +pub async fn setup_gateway_from_config( key_store: &KSt, register_gateway: bool, user_chosen_gateway_id: Option, - config: &Config, + config: &Config, + old_gateway_config: Option, by_latency: bool, ) -> Result where - C: NymConfig + ClientCoreConfigTrait, - T: NymConfig, KSt: KeyStore, ::StorageError: Send + Sync + 'static, { - let id = config.get_id(); - // If we are not going to register gateway, and an explicitly chosen gateway is not passed in, // load the existing configuration file if !register_gateway && user_chosen_gateway_id.is_none() { eprintln!("Not registering gateway, will reuse existing config and keys"); - return load_existing_gateway_config::(&id); + return old_gateway_config.ok_or(ClientCoreError::FailedToSetupGateway); } let gateway_setup = GatewaySetup::new( @@ -266,25 +255,6 @@ where Ok(gateway) } -/// Read and reuse the existing gateway configuration from a file that was generate earlier. -pub fn load_existing_gateway_config(id: &str) -> Result -where - T: NymConfig + ClientCoreConfigTrait, -{ - T::load_from_file(id) - .map(|existing_config| existing_config.get_gateway_endpoint().clone()) - .map_err(|err| { - log::error!( - "Unable to configure gateway: {err}. \n - Seems like the client was already initialized but it was not possible to read \ - the existing configuration file. \n - CAUTION: Consider backing up your gateway keys and try force gateway registration, or \ - removing the existing configuration and starting over." - ); - ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err) - }) -} - /// Get the full client address from the client keys and the gateway identity pub fn get_client_address( key_manager: &KeyManager, @@ -299,51 +269,23 @@ pub fn get_client_address( ) } -pub fn load_identity_keys( - pathfinder: &ClientKeyPathfinder, -) -> Result { - let identity_keypair: identity::KeyPair = - nym_pemstore::load_keypair(&pathfinder.identity_key_pair_path()) - .tap_err(|_| log::error!("Failed to read stored identity key files"))?; - Ok(identity_keypair) -} - /// Get the client address by loading the keys from stored files. // TODO: rethink that sucker -pub fn get_client_address_from_stored_ondisk_keys( - config: &Config, -) -> Result -where - T: nym_config::NymConfig, -{ - fn load_identity_keys( - pathfinder: &ClientKeyPathfinder, - ) -> Result { - let identity_keypair: identity::KeyPair = - nym_pemstore::load_keypair(&pathfinder.identity_key_pair_path()) - .tap_err(|_| log::error!("Failed to read stored identity key files"))?; - Ok(identity_keypair) - } - - fn load_sphinx_keys( - pathfinder: &ClientKeyPathfinder, - ) -> Result { - let sphinx_keypair: encryption::KeyPair = - nym_pemstore::load_keypair(&pathfinder.encryption_key_pair_path()) - .tap_err(|_| log::error!("Failed to read stored sphinx key files"))?; - Ok(sphinx_keypair) - } - - let pathfinder = ClientKeyPathfinder::new_from_config(config); - let identity_keypair = load_identity_keys(&pathfinder)?; - let sphinx_keypair = load_sphinx_keys(&pathfinder)?; +pub fn get_client_address_from_stored_ondisk_keys( + keys_paths: &ClientKeysPaths, + gateway_config: &GatewayEndpointConfig, +) -> Result { + let public_identity: identity::PublicKey = + nym_pemstore::load_key(&keys_paths.public_identity_key_file)?; + let public_encryption: encryption::PublicKey = + nym_pemstore::load_key(&keys_paths.public_encryption_key_file)?; let client_recipient = Recipient::new( - *identity_keypair.public_key(), - *sphinx_keypair.public_key(), + public_identity, + public_encryption, // TODO: below only works under assumption that gateway address == gateway id // (which currently is true) - NodeIdentity::from_base58_string(config.get_gateway_id())?, + NodeIdentity::from_base58_string(&gateway_config.gateway_id)?, ); Ok(client_recipient) 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..cfed0dc1c7 --- /dev/null +++ b/common/config/src/helpers.rs @@ -0,0 +1,208 @@ +// 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 + } +} + +// helper for when we want to use `OptionalSet` on an inner field +// (used by clients wanting to set the `BaseConfig` values) +#[macro_export] +macro_rules! define_optional_set_inner { + ( $x: ident, $inner_field_name: ident, $inner_field_typ: ty ) => { + impl $x { + pub fn with_optional_inner(mut self, f: F, val: Option) -> Self + where + F: Fn($inner_field_typ, T) -> $inner_field_typ, + { + self.$inner_field_name = self.$inner_field_name.with_optional(f, val); + self + } + + pub fn with_validated_optional_inner( + mut self, + f: F, + value: Option, + validate: V, + ) -> Result + where + F: Fn($inner_field_typ, T) -> $inner_field_typ, + V: Fn(&T) -> Result<(), E>, + { + self.$inner_field_name = self + .$inner_field_name + .with_validated_optional(f, value, validate)?; + Ok(self) + } + + pub fn with_optional_env_inner( + mut self, + f: F, + val: Option, + env_var: &str, + ) -> Self + where + F: Fn($inner_field_typ, T) -> $inner_field_typ, + T: FromStr, + ::Err: Debug, + { + self.$inner_field_name = self.$inner_field_name.with_optional_env(f, val, env_var); + self + } + + pub fn with_optional_custom_env_inner( + mut self, + f: F, + val: Option, + env_var: &str, + parser: G, + ) -> Self + where + F: Fn($inner_field_typ, T) -> $inner_field_typ, + G: Fn(&str) -> T, + { + self.$inner_field_name = self + .$inner_field_name + .with_optional_custom_env(f, val, env_var, parser); + 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/legacy_helpers.rs b/common/config/src/legacy_helpers.rs new file mode 100644 index 0000000000..315b6c2b49 --- /dev/null +++ b/common/config/src/legacy_helpers.rs @@ -0,0 +1,55 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +// removed in 1.1.19/1.1.20 +pub mod nym_config { + use serde::de::DeserializeOwned; + use serde::Serialize; + use std::path::{Path, PathBuf}; + use std::{fs, io}; + + pub const CONFIG_DIR: &str = "config"; + pub const DATA_DIR: &str = "data"; + + // no need for anything to do with saving. + pub trait MigrationNymConfig: Serialize + DeserializeOwned { + fn config_file_name() -> String { + "config.toml".to_string() + } + + fn default_root_directory() -> PathBuf; + + 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_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_config_file_path(id: &str) -> PathBuf { + Self::default_config_directory(id).join(Self::config_file_name()) + } + + 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)) + } + } +} diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs index 030a991bfc..fe91fc3d6d 100644 --- a/common/config/src/lib.rs +++ b/common/config/src/lib.rs @@ -1,219 +1,199 @@ // 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 use helpers::{parse_urls, OptionalSet}; +pub use toml::de::Error as TomlDeError; + pub mod defaults; +pub mod helpers; +pub mod legacy_helpers; -pub const CONFIG_DIR: &str = "config"; -pub const DATA_DIR: &str = "data"; -pub const CRED_DB_FILE_NAME: &str = "credentials_database.db"; +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"; -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(()) + fn format_to_writer(&self, writer: W) -> io::Result<()> { + if let Err(err) = + Handlebars::new().render_template_to_write(Self::template(), &self, writer) + { + match err { + TemplateRenderError::IOError(err, _) => return Err(err), + other_err => { + // it is responsibility of whoever is implementing the trait to ensure the template is valid + panic!("invalid template: {other_err}") } } - 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); - 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/credential-storage/src/lib.rs b/common/credential-storage/src/lib.rs index 34376e67e7..c27e904192 100644 --- a/common/credential-storage/src/lib.rs +++ b/common/credential-storage/src/lib.rs @@ -4,8 +4,11 @@ */ use crate::ephemeral_storage::EphemeralStorage; + #[cfg(not(target_arch = "wasm32"))] use crate::persistent_storage::PersistentStorage; +#[cfg(not(target_arch = "wasm32"))] +use std::path::Path; mod backends; pub mod ephemeral_storage; @@ -16,7 +19,7 @@ pub mod persistent_storage; pub mod storage; #[cfg(not(target_arch = "wasm32"))] -pub async fn initialise_persistent_storage(path: std::path::PathBuf) -> PersistentStorage { +pub async fn initialise_persistent_storage>(path: P) -> PersistentStorage { match persistent_storage::PersistentStorage::init(path).await { Err(err) => panic!("failed to initialise credential storage - {err}"), Ok(storage) => storage, diff --git a/common/credential-storage/src/persistent_storage.rs b/common/credential-storage/src/persistent_storage.rs index e5e454bbde..f214e40345 100644 --- a/common/credential-storage/src/persistent_storage.rs +++ b/common/credential-storage/src/persistent_storage.rs @@ -23,7 +23,7 @@ impl PersistentStorage { /// # Arguments /// /// * `database_path`: path to the database. - pub async fn init + Send>(database_path: P) -> Result { + pub async fn init>(database_path: P) -> Result { debug!( "Attempting to connect to database {:?}", database_path.as_ref().as_os_str() 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/common/nymsphinx/acknowledgements/src/surb_ack.rs b/common/nymsphinx/acknowledgements/src/surb_ack.rs index b303aa2e8b..6fb5b26c18 100644 --- a/common/nymsphinx/acknowledgements/src/surb_ack.rs +++ b/common/nymsphinx/acknowledgements/src/surb_ack.rs @@ -58,6 +58,7 @@ impl SurbAck { let packet_size = match packet_type { PacketType::Outfox => surb_ack_payload.len().max(MIN_PACKET_SIZE), PacketType::Mix => PacketSize::AckPacket.payload_size(), + #[allow(deprecated)] PacketType::Vpn => PacketSize::AckPacket.payload_size(), }; @@ -75,6 +76,7 @@ impl SurbAck { &destination, &delays, )?, + #[allow(deprecated)] PacketType::Vpn => NymPacket::sphinx_build( packet_size, surb_ack_payload, @@ -105,6 +107,7 @@ impl SurbAck { PacketSize::OutfoxAckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN } PacketType::Mix => PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN, + #[allow(deprecated)] PacketType::Vpn => PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN, } } @@ -137,6 +140,7 @@ impl SurbAck { let packet = match packet_type { PacketType::Outfox => NymPacket::outfox_from_bytes(&b[address_offset..])?, PacketType::Mix => NymPacket::sphinx_from_bytes(&b[address_offset..])?, + #[allow(deprecated)] PacketType::Vpn => NymPacket::sphinx_from_bytes(&b[address_offset..])?, }; diff --git a/common/nymsphinx/framing/src/codec.rs b/common/nymsphinx/framing/src/codec.rs index 76b241cf11..b0d7ccdb67 100644 --- a/common/nymsphinx/framing/src/codec.rs +++ b/common/nymsphinx/framing/src/codec.rs @@ -85,6 +85,7 @@ impl Decoder for NymCodec { match header.packet_type { PacketType::Outfox => NymPacket::outfox_from_bytes(slice)?, PacketType::Mix => NymPacket::sphinx_from_bytes(slice)?, + #[allow(deprecated)] PacketType::Vpn => NymPacket::sphinx_from_bytes(slice)?, } } else { diff --git a/common/nymsphinx/params/src/packet_sizes.rs b/common/nymsphinx/params/src/packet_sizes.rs index 874cbd9805..a6613f3024 100644 --- a/common/nymsphinx/params/src/packet_sizes.rs +++ b/common/nymsphinx/params/src/packet_sizes.rs @@ -244,6 +244,7 @@ impl PacketSize { ) -> Result { let overhead = match packet_type { PacketType::Mix => SPHINX_PACKET_OVERHEAD, + #[allow(deprecated)] PacketType::Vpn => SPHINX_PACKET_OVERHEAD, PacketType::Outfox => OUTFOX_PACKET_OVERHEAD, }; diff --git a/common/nymsphinx/params/src/packet_types.rs b/common/nymsphinx/params/src/packet_types.rs index 39a5701367..d6654078cc 100644 --- a/common/nymsphinx/params/src/packet_types.rs +++ b/common/nymsphinx/params/src/packet_types.rs @@ -1,13 +1,15 @@ // Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(deprecated)] +// allow the u8 repr of `Vpn` PacketType whilst deprecating all of its other uses + +use crate::PacketSize; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt; use thiserror::Error; -use crate::PacketSize; - #[derive(Error, Debug)] #[error("{received} is not a valid packet mode tag")] pub struct InvalidPacketType { @@ -15,17 +17,23 @@ pub struct InvalidPacketType { } #[repr(u8)] +#[allow(deprecated)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub enum PacketType { /// Represents 'normal' packet sent through the network that should be delayed by an appropriate /// value at each hop. #[default] + #[serde(rename = "mix")] + #[serde(alias = "sphinx")] Mix = 0, /// Represents a packet that should be sent through the network as fast as possible. + #[deprecated] + #[serde(rename = "unsupported-mix-vpn")] Vpn = 1, /// Abusing this to add Outfox support + #[serde(rename = "outfox")] Outfox = 2, } @@ -33,6 +41,7 @@ impl fmt::Display for PacketType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { PacketType::Mix => write!(f, "Mix"), + #[allow(deprecated)] PacketType::Vpn => write!(f, "Vpn"), PacketType::Outfox => write!(f, "Outfox"), } diff --git a/common/nymsphinx/src/preparer/mod.rs b/common/nymsphinx/src/preparer/mod.rs index 2a9bca6159..e97a9d011f 100644 --- a/common/nymsphinx/src/preparer/mod.rs +++ b/common/nymsphinx/src/preparer/mod.rs @@ -251,6 +251,7 @@ pub trait FragmentPreparer { &destination, &delays, )?, + #[allow(deprecated)] PacketType::Vpn => NymPacket::sphinx_build( packet_size.payload_size(), packet_payload, diff --git a/common/socks5-client-core/src/config/mod.rs b/common/socks5-client-core/src/config/mod.rs index 33e6bd2774..2411f2fbbc 100644 --- a/common/socks5-client-core/src/config/mod.rs +++ b/common/socks5-client-core/src/config/mod.rs @@ -1,88 +1,39 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::template::config_template; -pub use nym_client_core::config::Config as BaseConfig; -pub use nym_client_core::config::MISSING_VALUE; -use nym_client_core::config::{ClientCoreConfigTrait, DebugConfig}; +pub use nym_client_core::config::Config as BaseClientConfig; use nym_config::defaults::DEFAULT_SOCKS5_LISTENING_PORT; -use nym_config::{NymConfig, OptionalSet}; -use nym_service_providers_common::interface::ProviderInterfaceVersion; -use nym_socks5_requests::Socks5ProtocolVersion; +use nym_config::OptionalSet; use nym_sphinx::addressing::clients::Recipient; use serde::{Deserialize, Serialize}; use std::fmt::Debug; -use std::path::{Path, PathBuf}; use std::str::FromStr; -pub mod old_config_v1_1_13; -mod template; +pub use nym_service_providers_common::interface::ProviderInterfaceVersion; +pub use nym_socks5_requests::Socks5ProtocolVersion; const DEFAULT_CONNECTION_START_SURBS: u32 = 20; const DEFAULT_PER_REQUEST_SURBS: u32 = 3; -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { #[serde(flatten)] - base: BaseConfig, + pub base: BaseClientConfig, - socks5: Socks5, -} - -impl NymConfig for Config { - fn template() -> &'static str { - config_template() - } - - fn default_root_directory() -> PathBuf { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let base_dir = dirs::home_dir().expect("Failed to evaluate $HOME value"); - #[cfg(any(target_os = "android", target_os = "ios"))] - let base_dir = PathBuf::from("/tmp"); - - base_dir.join(".nym").join("socks5-clients") - } - - fn try_default_root_directory() -> Option { - dirs::home_dir().map(|path| path.join(".nym").join("socks5-clients")) - } - - fn root_directory(&self) -> PathBuf { - self.base.get_nym_root_directory() - } - - fn config_directory(&self) -> PathBuf { - self.root_directory() - .join(self.base.get_id()) - .join("config") - } - - fn data_directory(&self) -> PathBuf { - self.root_directory().join(self.base.get_id()).join("data") - } -} - -impl ClientCoreConfigTrait for Config { - fn get_gateway_endpoint(&self) -> &nym_client_core::config::GatewayEndpointConfig { - self.base.get_gateway_endpoint() - } + pub socks5: Socks5, } impl Config { pub fn new>(id: S, provider_mix_address: S) -> Self { Config { - base: BaseConfig::new(id), + base: BaseClientConfig::new(id), socks5: Socks5::new(provider_mix_address), } } - #[must_use] - pub fn with_root_directory>(mut self, root_dir: P) -> Self { - self.base = self.base.reset_nym_root_directory(root_dir); - let data_dir = self.data_directory(); - self.base = self.base.reset_data_directory(data_dir); - self + pub fn from_base(base: BaseClientConfig, socks5: Socks5) -> Self { + Config { base, socks5 } } pub fn validate(&self) -> bool { @@ -90,63 +41,38 @@ impl Config { self.base.validate() } - // getters - pub fn get_base(&self) -> &BaseConfig { - &self.base + pub fn with_port(mut self, port: u16) -> Self { + self.socks5.listening_port = port; + self } - pub fn get_base_mut(&mut self) -> &mut BaseConfig { - &mut self.base - } - - pub fn get_socks5(&self) -> &Socks5 { - &self.socks5 - } - - pub fn get_socks5_mut(&mut self) -> &mut Socks5 { - &mut self.socks5 - } - - pub fn get_debug_settings(&self) -> &DebugConfig { - self.get_base().get_debug_config() - } - - pub fn get_config_file_save_location(&self) -> PathBuf { - self.config_directory().join(Self::config_file_name()) + pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self { + self.socks5.send_anonymously = anonymous_replies; + self } // poor man's 'builder' method pub fn with_base(mut self, f: F, val: T) -> Self where - F: Fn(BaseConfig, T) -> BaseConfig, + F: Fn(BaseClientConfig, T) -> BaseClientConfig, { self.base = f(self.base, val); self } - pub fn with_port(mut self, port: u16) -> Self { - self.socks5.with_port(port); - self - } - - pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self { - self.socks5.with_anonymous_replies(anonymous_replies); - self - } - // helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config // (plz, lets refactor it) - pub fn with_optional_ext(mut self, f: F, val: Option) -> Self + pub fn with_optional_base(mut self, f: F, val: Option) -> Self where - F: Fn(BaseConfig, T) -> BaseConfig, + F: Fn(BaseClientConfig, T) -> BaseClientConfig, { self.base = self.base.with_optional(f, val); self } - pub fn with_optional_env_ext(mut self, f: F, val: Option, env_var: &str) -> Self + pub fn with_optional_base_env(mut self, f: F, val: Option, env_var: &str) -> Self where - F: Fn(BaseConfig, T) -> BaseConfig, + F: Fn(BaseClientConfig, T) -> BaseClientConfig, T: FromStr, ::Err: Debug, { @@ -154,7 +80,7 @@ impl Config { self } - pub fn with_optional_custom_env_ext( + pub fn with_optional_base_custom_env( mut self, f: F, val: Option, @@ -162,7 +88,7 @@ impl Config { parser: G, ) -> Self where - F: Fn(BaseConfig, T) -> BaseConfig, + F: Fn(BaseClientConfig, T) -> BaseClientConfig, G: Fn(&str) -> T, { self.base = self.base.with_optional_custom_env(f, val, env_var, parser); @@ -174,19 +100,19 @@ impl Config { #[serde(deny_unknown_fields)] pub struct Socks5 { /// The port on which the client will be listening for incoming requests - listening_port: u16, + pub listening_port: u16, /// The mix address of the provider to which all requests are going to be sent. - provider_mix_address: String, + pub provider_mix_address: String, /// The version of the 'service provider' this client is going to use in its communication with the /// specified socks5 provider. // if in doubt, use the legacy version as initially nobody will be using the updated binaries #[serde(default = "ProviderInterfaceVersion::new_legacy")] - provider_interface_version: ProviderInterfaceVersion, + pub provider_interface_version: ProviderInterfaceVersion, #[serde(default = "Socks5ProtocolVersion::new_legacy")] - socks5_protocol_version: Socks5ProtocolVersion, + pub socks5_protocol_version: Socks5ProtocolVersion, /// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. /// While this is going to hide its actual address information, it will make the actual communication @@ -194,10 +120,10 @@ pub struct Socks5 { /// /// Note that some service providers might not support this. #[serde(default)] - send_anonymously: bool, + pub send_anonymously: bool, #[serde(default)] - socks5_debug: Socks5Debug, + pub socks5_debug: Socks5Debug, } impl Socks5 { @@ -212,81 +138,20 @@ impl Socks5 { } } - pub fn with_port(&mut self, port: u16) { - self.listening_port = port; - } - - pub fn with_provider_mix_address(&mut self, address: String) { - self.provider_mix_address = address; - } - - pub fn with_provider_interface_version(&mut self, version: ProviderInterfaceVersion) { - self.provider_interface_version = version; - } - - pub fn with_socks5_protocol_version(&mut self, version: Socks5ProtocolVersion) { - self.socks5_protocol_version = version; - } - - pub fn with_anonymous_replies(&mut self, anonymous_replies: bool) { - self.send_anonymously = anonymous_replies; - } - - pub fn get_raw_provider_mix_address(&self) -> String { - self.provider_mix_address.clone() - } - pub fn get_provider_mix_address(&self) -> Recipient { Recipient::try_from_base58_string(&self.provider_mix_address) .expect("malformed provider address") } - - pub fn get_provider_interface_version(&self) -> ProviderInterfaceVersion { - self.provider_interface_version - } - - pub fn get_socks5_protocol_version(&self) -> Socks5ProtocolVersion { - self.socks5_protocol_version - } - - pub fn get_send_anonymously(&self) -> bool { - self.send_anonymously - } - - pub fn get_listening_port(&self) -> u16 { - self.listening_port - } - - pub fn get_connection_start_surbs(&self) -> u32 { - self.socks5_debug.connection_start_surbs - } - - pub fn get_per_request_surbs(&self) -> u32 { - self.socks5_debug.per_request_surbs - } } -impl Default for Socks5 { - fn default() -> Self { - Socks5 { - listening_port: DEFAULT_SOCKS5_LISTENING_PORT, - provider_mix_address: "".into(), - provider_interface_version: ProviderInterfaceVersion::Legacy, - socks5_protocol_version: Socks5ProtocolVersion::Legacy, - send_anonymously: false, - socks5_debug: Default::default(), - } - } -} - -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] pub struct Socks5Debug { /// Number of reply SURBs attached to each `Request::Connect` message. - connection_start_surbs: u32, + pub connection_start_surbs: u32, /// Number of reply SURBs attached to each `Request::Send` message. - per_request_surbs: u32, + pub per_request_surbs: u32, } impl Default for Socks5Debug { diff --git a/common/socks5-client-core/src/config/old_config_v1_1_13.rs b/common/socks5-client-core/src/config/old_config_v1_1_13.rs index 754b363d81..095b4b21d0 100644 --- a/common/socks5-client-core/src/config/old_config_v1_1_13.rs +++ b/common/socks5-client-core/src/config/old_config_v1_1_13.rs @@ -3,60 +3,60 @@ use crate::config::{Config, Socks5}; use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13; -use nym_config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct OldConfigV1_1_13 { - #[serde(flatten)] - base: OldBaseConfigV1_1_13, - - socks5: Socks5, -} - -impl NymConfig for OldConfigV1_1_13 { - fn template() -> &'static str { - // not intended to be used - unimplemented!() - } - - fn default_root_directory() -> PathBuf { - #[cfg(not(any(target_os = "android", target_os = "ios")))] - let base_dir = dirs::home_dir().expect("Failed to evaluate $HOME value"); - #[cfg(any(target_os = "android", target_os = "ios"))] - let base_dir = PathBuf::from("/tmp"); - - base_dir.join(".nym").join("socks5-clients") - } - - fn try_default_root_directory() -> Option { - dirs::home_dir().map(|path| path.join(".nym").join("socks5-clients")) - } - - fn root_directory(&self) -> PathBuf { - self.base.client.nym_root_directory.clone() - } - - fn config_directory(&self) -> PathBuf { - self.root_directory() - .join(&self.base.client.id) - .join("config") - } - - fn data_directory(&self) -> PathBuf { - self.root_directory() - .join(&self.base.client.id) - .join("data") - } -} - -impl From for Config { - fn from(value: OldConfigV1_1_13) -> Self { - Config { - base: value.base.into(), - socks5: value.socks5, - } - } -} +// #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +// #[serde(deny_unknown_fields)] +// pub struct OldConfigV1_1_13 { +// #[serde(flatten)] +// base: OldBaseConfigV1_1_13, +// +// socks5: Socks5, +// } +// +// // +// // impl NymConfig for OldConfigV1_1_13 { +// // fn template() -> &'static str { +// // // not intended to be used +// // unimplemented!() +// // } +// // +// // fn default_root_directory() -> PathBuf { +// // #[cfg(not(target_os = "android"))] +// // let base_dir = dirs::home_dir().expect("Failed to evaluate $HOME value"); +// // #[cfg(target_os = "android")] +// // let base_dir = PathBuf::from("/tmp"); +// // +// // base_dir.join(".nym").join("socks5-clients") +// // } +// // +// // fn try_default_root_directory() -> Option { +// // dirs::home_dir().map(|path| path.join(".nym").join("socks5-clients")) +// // } +// // +// // fn root_directory(&self) -> PathBuf { +// // self.base.client.nym_root_directory.clone() +// // } +// // +// // fn config_directory(&self) -> PathBuf { +// // self.root_directory() +// // .join(&self.base.client.id) +// // .join("config") +// // } +// // +// // fn data_directory(&self) -> PathBuf { +// // self.root_directory() +// // .join(&self.base.client.id) +// // .join("data") +// // } +// // } +// +// impl From for Config { +// fn from(value: OldConfigV1_1_13) -> Self { +// Config { +// base: value.base.into(), +// socks5: value.socks5, +// } +// } +// } diff --git a/common/socks5-client-core/src/lib.rs b/common/socks5-client-core/src/lib.rs index 8564325f78..dcc77f5a29 100644 --- a/common/socks5-client-core/src/lib.rs +++ b/common/socks5-client-core/src/lib.rs @@ -1,7 +1,7 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::{Config, Socks5}; +use crate::config::Config; use crate::error::Socks5ClientCoreError; use crate::socks::{ authentication::{AuthenticationMethods, Authenticator, User}, @@ -67,8 +67,8 @@ where #[allow(clippy::too_many_arguments)] pub fn start_socks5_listener( - socks5_config: &Socks5, - debug_config: DebugConfig, + socks5_config: &config::Socks5, + base_debug: DebugConfig, client_input: ClientInput, client_output: ClientOutput, client_status: ClientState, @@ -94,25 +94,24 @@ where .. } = client_status; - let packet_size = debug_config + let packet_size = base_debug .traffic .secondary_packet_size - .unwrap_or(debug_config.traffic.primary_packet_size); + .unwrap_or(base_debug.traffic.primary_packet_size); let authenticator = Authenticator::new(auth_methods, allowed_users); let mut sphinx_socks = NymSocksServer::new( - socks5_config.get_listening_port(), + socks5_config.listening_port, authenticator, socks5_config.get_provider_mix_address(), self_address, shared_lane_queue_lengths, socks::client::Config::new( packet_size, - socks5_config.get_provider_interface_version(), - socks5_config.get_socks5_protocol_version(), - socks5_config.get_send_anonymously(), - socks5_config.get_connection_start_surbs(), - socks5_config.get_per_request_surbs(), + socks5_config.provider_interface_version, + socks5_config.socks5_protocol_version, + socks5_config.send_anonymously, + socks5_config.socks5_debug, ), shutdown.clone(), packet_type, @@ -191,43 +190,40 @@ where let (key_store, reply_storage_backend, credential_store) = self.storage.into_split(); // don't create bandwidth controller if credentials are disabled - let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() { + let bandwidth_controller = if self.config.base.client.disabled_credentials_mode { None } else { Some(non_wasm_helpers::create_bandwidth_controller( - self.config.get_base(), + &self.config.base, credential_store, )) }; let base_builder = BaseClientBuilder::<_, S>::new_from_base_config( - self.config.get_base(), + &self.config.base, key_store, bandwidth_controller, reply_storage_backend, ); - let packet_type = self.config.get_base().get_packet_type(); - let mut started_client = base_builder.start_base(packet_type).await?; + let packet_type = self.config.base.debug.traffic.packet_type; + let mut started_client = base_builder.start_base().await?; let self_address = started_client.address; let client_input = started_client.client_input.register_producer(); let client_output = started_client.client_output.register_consumer(); let client_state = started_client.client_state; - info!( - "Running with {:?} packets", - self.config.get_base().get_packet_type() - ); + info!("Running with {packet_type} packets",); Self::start_socks5_listener( - self.config.get_socks5(), - *self.config.get_debug_settings(), + &self.config.socks5, + self.config.base.debug, client_input, client_output, client_state, self_address, started_client.task_manager.subscribe(), - self.config.get_base().get_packet_type(), + packet_type, ); info!("Client startup finished!"); diff --git a/common/socks5-client-core/src/socks/client.rs b/common/socks5-client-core/src/socks/client.rs index ebd980a0e6..c0b3d2d827 100644 --- a/common/socks5-client-core/src/socks/client.rs +++ b/common/socks5-client-core/src/socks/client.rs @@ -4,6 +4,7 @@ use super::authentication::{AuthenticationMethods, Authenticator, User}; use super::request::{SocksCommand, SocksRequest}; use super::types::{ResponseCodeV4, ResponseCodeV5, SocksProxyError}; use super::{SocksVersion, RESERVED, SOCKS4_VERSION, SOCKS5_VERSION}; +use crate::config; use futures::channel::mpsc; use futures::task::{Context, Poll}; use log::*; @@ -147,16 +148,15 @@ impl Config { provider_interface_version: ProviderInterfaceVersion, socks5_protocol_version: Socks5ProtocolVersion, use_surbs_for_responses: bool, - connection_start_surbs: u32, - per_request_surbs: u32, + debug_config: config::Socks5Debug, ) -> Self { Self { biggest_packet_size, provider_interface_version, socks5_protocol_version, use_surbs_for_responses, - connection_start_surbs, - per_request_surbs, + connection_start_surbs: debug_config.connection_start_surbs, + per_request_surbs: debug_config.per_request_surbs, } } diff --git a/common/types/src/gateway.rs b/common/types/src/gateway.rs index f2a90d3e60..125cf4ba03 100644 --- a/common/types/src/gateway.rs +++ b/common/types/src/gateway.rs @@ -81,7 +81,6 @@ impl GatewayBond { pub struct GatewayNodeDetailsResponse { pub identity_key: String, pub sphinx_key: String, - pub announce_address: String, pub bind_address: String, pub version: String, pub mix_port: u16, @@ -93,11 +92,6 @@ impl fmt::Display for GatewayNodeDetailsResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "Identity Key: {}", self.identity_key)?; writeln!(f, "Sphinx Key: {}", self.sphinx_key)?; - writeln!( - f, - "Host: {} (bind address: {})", - self.announce_address, self.bind_address - )?; writeln!(f, "Version: {}", self.version)?; writeln!( f, diff --git a/common/types/src/mixnode.rs b/common/types/src/mixnode.rs index 44ae234eac..145f9c409f 100644 --- a/common/types/src/mixnode.rs +++ b/common/types/src/mixnode.rs @@ -13,6 +13,7 @@ use nym_mixnet_contract_common::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt; +use std::net::IpAddr; #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( @@ -167,31 +168,23 @@ impl MixNodeCostParams { pub struct MixnodeNodeDetailsResponse { pub identity_key: String, pub sphinx_key: String, - pub announce_address: String, - pub bind_address: String, + pub bind_address: IpAddr, pub version: String, pub mix_port: u16, pub http_api_port: u16, pub verloc_port: u16, - pub wallet_address: Option, } impl fmt::Display for MixnodeNodeDetailsResponse { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let wallet_address = self.wallet_address.clone().unwrap_or_default(); writeln!(f, "Identity Key: {}", self.identity_key)?; writeln!(f, "Sphinx Key: {}", self.sphinx_key)?; - writeln!( - f, - "Host: {} (bind address: {})", - self.announce_address, self.bind_address - )?; + writeln!(f, "Host: {}", self.bind_address)?; writeln!(f, "Version: {}", self.version)?; writeln!( f, "Mix Port: {}, Verloc port: {}, Http Port: {}\n", self.mix_port, self.verloc_port, self.http_api_port - )?; - writeln!(f, "You are bonding to wallet address: {wallet_address}\n\n") + ) } } diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index 675cc474ef..2901e116ef 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -1,18 +1,18 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::config::{default_config_directory, default_config_filepath, default_data_directory}; use crate::{ commands::{override_config, OverrideConfig}, - config::{persistence::pathfinder::GatewayPathfinder, Config}, + config::Config, OutputFormat, }; use clap::Args; -use nym_config::NymConfig; use nym_crypto::asymmetric::{encryption, identity}; -use nym_validator_client::nyxd; use std::error::Error; use std::net::IpAddr; use std::path::PathBuf; +use std::{fs, io}; #[derive(Args, Clone)] pub struct Init { @@ -24,10 +24,6 @@ pub struct Init { #[clap(long)] host: IpAddr, - /// The wallet address you will use to bond this gateway, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9 - #[clap(long)] - wallet_address: nyxd::AccountId, - /// The port on which the gateway will be listening for sphinx packets #[clap(long)] mix_port: Option, @@ -36,11 +32,6 @@ pub struct Init { #[clap(long)] clients_port: Option, - /// The host that will be reported to the directory server - #[clap(long)] - // TODO: could this be changed to `Option`? - announce_host: Option, - /// Path to sqlite database containing all gateway persistent data #[clap(long)] datastore: Option, @@ -86,11 +77,9 @@ impl From for OverrideConfig { fn from(init_config: Init) -> Self { OverrideConfig { host: Some(init_config.host), - wallet_address: Some(init_config.wallet_address), mix_port: init_config.mix_port, clients_port: init_config.clients_port, datastore: init_config.datastore, - announce_host: init_config.announce_host, nym_apis: init_config.nym_apis, mnemonic: init_config.mnemonic, @@ -103,10 +92,15 @@ impl From for OverrideConfig { } } +fn init_paths(id: &str) -> io::Result<()> { + fs::create_dir_all(default_data_directory(id))?; + fs::create_dir_all(default_config_directory(id)) +} + pub async fn execute(args: Init) -> Result<(), Box> { eprintln!("Initialising gateway {}...", args.id); - let already_init = if Config::default_config_file_path(&args.id).exists() { + let already_init = if default_config_filepath(&args.id).exists() { eprintln!( "Gateway \"{}\" was already initialised before! Config information will be \ overwritten (but keys will be kept)!", @@ -114,6 +108,7 @@ pub async fn execute(args: Init) -> Result<(), Box> { ); true } else { + init_paths(&args.id)?; false }; @@ -128,33 +123,36 @@ pub async fn execute(args: Init) -> Result<(), Box> { let identity_keys = identity::KeyPair::new(&mut rng); let sphinx_keys = encryption::KeyPair::new(&mut rng); - let pathfinder = GatewayPathfinder::new_from_config(&config); - nym_pemstore::store_keypair( - &sphinx_keys, - &nym_pemstore::KeyPairPath::new( - pathfinder.private_encryption_key().to_owned(), - pathfinder.public_encryption_key().to_owned(), - ), - ) - .expect("Failed to save sphinx keys"); nym_pemstore::store_keypair( &identity_keys, &nym_pemstore::KeyPairPath::new( - pathfinder.private_identity_key().to_owned(), - pathfinder.public_identity_key().to_owned(), + config.storage_paths.private_identity_key(), + config.storage_paths.public_identity_key(), ), ) .expect("Failed to save identity keys"); + nym_pemstore::store_keypair( + &sphinx_keys, + &nym_pemstore::KeyPairPath::new( + config.storage_paths.private_encryption_key(), + config.storage_paths.public_encryption_key(), + ), + ) + .expect("Failed to save sphinx keys"); + eprintln!("Saved identity and mixnet sphinx keypairs"); } - let config_save_location = config.get_config_file_save_location(); + let config_save_location = config.default_location(); 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!( + "Saved configuration file to {}", + config_save_location.display() + ); eprintln!("Gateway configuration completed.\n\n\n"); crate::node::create_gateway(config) @@ -176,10 +174,8 @@ mod tests { let args = Init { id: "foo-id".to_string(), host: "1.1.1.1".parse().unwrap(), - wallet_address: "n1z9egw0knv47nmur0p8vk4rcx59h9gg4zjx9ede".parse().unwrap(), mix_port: Some(42), clients_port: Some(43), - announce_host: Some("foo-announce-host".to_string()), datastore: Some("/foo-datastore".parse().unwrap()), nym_apis: None, mnemonic: None, diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 6a49ca4c68..8430e0b7e5 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -1,16 +1,19 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::config::default_config_filepath; +use crate::config::old_config_v1_1_20::ConfigV1_1_20; use crate::error::GatewayError; use crate::{config::Config, Cli}; use clap::CommandFactory; use clap::Subcommand; +use log::{error, info}; use nym_bin_common::completions::{fig_generate, ArgShell}; use nym_bin_common::version_checker; use nym_config::OptionalSet; use nym_network_defaults::var_names::NYXD; use nym_network_defaults::var_names::{BECH32_PREFIX, NYM_API, STATISTICS_SERVICE_DOMAIN_ADDRESS}; -use nym_validator_client::nyxd::{self, AccountId}; +use nym_validator_client::nyxd::AccountId; use std::error::Error; use std::net::IpAddr; use std::path::PathBuf; @@ -49,11 +52,9 @@ pub(crate) enum Commands { #[derive(Default)] pub(crate) struct OverrideConfig { host: Option, - wallet_address: Option, mix_port: Option, clients_port: Option, datastore: Option, - announce_host: Option, enabled_statistics: Option, statistics_service_url: Option, nym_apis: Option>, @@ -81,21 +82,8 @@ pub(crate) fn override_config( mut config: Config, args: OverrideConfig, ) -> Result { - // 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 'mix-announce-host' always defaults to 'mix-host' - config = config.announce_host_from_listening_host(); - } - config = config + .with_optional(Config::with_listening_address, args.host) .with_optional(Config::with_mix_port, args.mix_port) .with_optional(Config::with_clients_port, args.clients_port) .with_optional_custom_env( @@ -110,11 +98,6 @@ pub(crate) fn override_config( args.statistics_service_url, STATISTICS_SERVICE_DOMAIN_ADDRESS, ) - .with_validated_optional( - |cfg, wallet_address| cfg.with_wallet_address(wallet_address), - args.wallet_address, - ensure_correct_bech32_prefix, - )? .with_optional(Config::with_custom_persistent_store, args.datastore) .with_optional(Config::with_cosmos_mnemonic, args.mnemonic) .with_optional_custom_env( @@ -150,7 +133,7 @@ pub(crate) fn ensure_correct_bech32_prefix(address: &AccountId) -> Result<(), Ga // network version. It might do so in the future. pub(crate) fn ensure_config_version_compatibility(cfg: &Config) -> Result<(), GatewayError> { let binary_version = env!("CARGO_PKG_VERSION"); - let config_version = cfg.get_version(); + let config_version = &cfg.gateway.version; if binary_version == config_version { Ok(()) @@ -169,3 +152,51 @@ pub(crate) fn ensure_config_version_compatibility(cfg: &Config) -> Result<(), Ga }) } } + +fn try_upgrade_v1_1_20_config(id: &str) -> Result<(), GatewayError> { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.20 (which is incompatible with the current, i.e. 1.1.21+) + let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else { + // if we failed to load it, there might have been nothing to upgrade + // or maybe it was an even older file. in either way. just ignore it and carry on with our day + return Ok(()); + }; + info!("It seems the gateway is using <= v1.1.20 config template."); + info!("It is going to get updated to the current specification."); + + let updated: Config = old_config.into(); + updated + .save_to_default_location() + .map_err(|err| GatewayError::ConfigSaveFailure { + path: default_config_filepath(id), + id: id.to_string(), + source: err, + }) +} + +pub(crate) fn try_load_current_config(id: &str) -> Result { + try_upgrade_v1_1_20_config(id)?; + + Config::read_from_default_path(id).map_err(|err| { + error!( + "Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})", + ); + GatewayError::ConfigLoadFailure { + path: default_config_filepath(id), + id: id.to_string(), + source: err, + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Cli::command().debug_assert(); + } +} diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 51e397bf31..d60e66c7b5 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -5,7 +5,7 @@ use crate::commands::{ensure_config_version_compatibility, OverrideConfig}; use crate::support::config::build_config; use clap::Args; use nym_bin_common::output_format::OutputFormat; -use nym_validator_client::nyxd; +use nym_config::helpers::SPECIAL_ADDRESSES; use std::error::Error; use std::net::IpAddr; use std::path::PathBuf; @@ -20,10 +20,6 @@ pub struct Run { #[clap(long)] host: Option, - /// The wallet address you will use to bond this gateway, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9 - #[clap(long)] - wallet_address: Option, - /// The port on which the gateway will be listening for sphinx packets #[clap(long)] mix_port: Option, @@ -32,11 +28,6 @@ pub struct Run { #[clap(long)] clients_port: Option, - /// The host that will be reported to the directory server - #[clap(long)] - // TODO: could this be changed to `Option`? - announce_host: Option, - /// Path to sqlite database containing all gateway persistent data #[clap(long)] datastore: Option, @@ -82,11 +73,9 @@ impl From for OverrideConfig { fn from(run_config: Run) -> Self { OverrideConfig { host: run_config.host, - wallet_address: run_config.wallet_address, mix_port: run_config.mix_port, clients_port: run_config.clients_port, datastore: run_config.datastore, - announce_host: run_config.announce_host, nym_apis: run_config.nym_apis, mnemonic: run_config.mnemonic, @@ -98,21 +87,16 @@ impl From for OverrideConfig { } } -fn show_binding_warning(address: String) { +fn show_binding_warning(address: &str) { eprintln!("\n##### NOTE #####"); eprintln!( - "\nYou are trying to bind to {} - you might not be accessible to other nodes\n\ + "\nYou are trying to bind to {address} - you might not be accessible to other nodes\n\ You can ignore this warning if you're running setup on a local network \n\ - or have set a custom 'announce-host'", - address + or have used different host when bonding your node" ); eprintln!("\n\n"); } -fn special_addresses() -> Vec<&'static str> { - vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"] -} - pub async fn execute(args: Run) -> Result<(), Box> { let id = args.id.clone(); eprintln!("Starting gateway {id}..."); @@ -121,8 +105,8 @@ pub async fn execute(args: Run) -> Result<(), Box> { let config = build_config(id, args)?; ensure_config_version_compatibility(&config)?; - if special_addresses().contains(&&*config.get_listening_address().to_string()) { - show_binding_warning(config.get_listening_address().to_string()); + if SPECIAL_ADDRESSES.contains(&config.gateway.listening_address) { + show_binding_warning(&config.gateway.listening_address.to_string()); } let mut gateway = crate::node::create_gateway(config).await; diff --git a/gateway/src/commands/sign.rs b/gateway/src/commands/sign.rs index c322fd4390..c8a7a1a728 100644 --- a/gateway/src/commands/sign.rs +++ b/gateway/src/commands/sign.rs @@ -5,8 +5,7 @@ use crate::commands::{ensure_correct_bech32_prefix, OverrideConfig}; use crate::error::GatewayError; use crate::support::config::build_config; use crate::{ - commands::ensure_config_version_compatibility, - config::persistence::pathfinder::GatewayPathfinder, + commands::ensure_config_version_compatibility, config::persistence::paths::GatewayPaths, }; use anyhow::{bail, Result}; use clap::{ArgGroup, Args}; @@ -65,11 +64,11 @@ impl TryFrom for SignedTarget { } } -pub fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair { +pub fn load_identity_keys(paths: &GatewayPaths) -> 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(), + paths.private_identity_key().to_owned(), + paths.public_identity_key().to_owned(), )) .expect("Failed to read stored identity key files"); identity_keypair @@ -139,8 +138,8 @@ pub fn execute(args: Sign) -> Result<(), Box> { let output = args.output; let signed_target = SignedTarget::try_from(args)?; - let pathfinder = GatewayPathfinder::new_from_config(&config); - let identity_keypair = load_identity_keys(&pathfinder); + + let identity_keypair = load_identity_keys(&config.storage_paths); match signed_target { SignedTarget::Text(text) => { diff --git a/gateway/src/commands/upgrade.rs b/gateway/src/commands/upgrade.rs index e3f7e10bdf..c726971b58 100644 --- a/gateway/src/commands/upgrade.rs +++ b/gateway/src/commands/upgrade.rs @@ -1,10 +1,9 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::{Config, MISSING_VALUE}; +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; @@ -56,7 +55,7 @@ fn unsupported_upgrade(current_version: &Version, config_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.gateway.version).unwrap_or_else(|err| { eprintln!("failed to parse client version! - {err}"); process::exit(1) }); @@ -105,11 +104,13 @@ fn minor_0_12_upgrade( 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); @@ -139,12 +140,12 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) { pub async 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_VALUE { + if existing_config.gateway.version.is_empty() { eprintln!("the existing configuration file does not seem to contain version number."); process::exit(1); } diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 2d3f57abf0..16cf17aa2b 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -1,22 +1,29 @@ -// Copyright 2020 - Nym Technologies SA +// Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::template::config_template; +use crate::config::persistence::paths::GatewayPaths; +use crate::config::template::CONFIG_TEMPLATE; +use nym_bin_common::logging::LoggingSettings; use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT}; -use nym_config::NymConfig; -use nym_network_defaults::mainnet::{NYM_API, NYXD_URL, STATISTICS_SERVICE_DOMAIN_ADDRESS}; -use nym_validator_client::nyxd; +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 nym_network_defaults::mainnet; use serde::{Deserialize, Serialize}; +use std::io; use std::net::IpAddr; -use std::path::PathBuf; -use std::str::FromStr; +use std::path::{Path, PathBuf}; use std::time::Duration; use url::Url; +use zeroize::{Zeroize, ZeroizeOnDrop}; +pub(crate) mod old_config_v1_1_20; pub mod persistence; mod template; -pub(crate) const MISSING_VALUE: &str = "MISSING VALUE"; +const DEFAULT_GATEWAYS_DIR: &str = "gateways"; // 'DEBUG' // where applicable, the below are defined in milliseconds @@ -29,103 +36,77 @@ const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; -pub fn missing_string_value() -> String { - MISSING_VALUE.to_string() +/// Derive default path to gateway's config directory. +/// It should get resolved to `$HOME/.nym/gateways//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_GATEWAYS_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) } -fn bind_all_address() -> IpAddr { - "0.0.0.0".parse().unwrap() +/// Derive default path to gateways's config file. +/// It should get resolved to `$HOME/.nym/gateways//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) } -fn default_mix_port() -> u16 { - DEFAULT_MIX_LISTENING_PORT +/// Derive default path to gateways's data directory where files, such as keys, are stored. +/// It should get resolved to `$HOME/.nym/gateways//data` +pub fn default_data_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_GATEWAYS_DIR) + .join(id) + .join(DEFAULT_DATA_DIR) } -fn default_clients_port() -> u16 { - DEFAULT_CLIENT_LISTENING_PORT -} - -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] pub struct Config { - gateway: Gateway, + pub gateway: Gateway, + + pub storage_paths: GatewayPaths, #[serde(default)] - logging: Logging, + pub logging: LoggingSettings, + #[serde(default)] - debug: Debug, + pub debug: Debug, } -impl NymConfig for Config { +impl NymConfigTemplate for Config { fn template() -> &'static str { - config_template() - } - - fn default_root_directory() -> PathBuf { - dirs::home_dir() - .expect("Failed to evaluate $HOME value") - .join(".nym") - .join("gateways") - } - - fn try_default_root_directory() -> Option { - dirs::home_dir().map(|path| path.join(".nym").join("gateways")) - } - - fn root_directory(&self) -> PathBuf { - self.gateway.nym_root_directory.clone() - } - - fn config_directory(&self) -> PathBuf { - self.gateway - .nym_root_directory - .join(&self.gateway.id) - .join("config") - } - - fn data_directory(&self) -> PathBuf { - self.gateway - .nym_root_directory - .join(&self.gateway.id) - .join("data") + CONFIG_TEMPLATE } } impl Config { - pub fn new>(id: S) -> Self { - Config::default().with_id(id) + pub fn new>(id: S) -> Self { + Config { + gateway: Gateway::new_default(id.as_ref()), + storage_paths: GatewayPaths::new_default(id.as_ref()), + logging: Default::default(), + debug: Default::default(), + } } - // builder methods - pub fn with_id>(mut self, id: S) -> Self { - let id = id.into(); - if self.gateway.private_sphinx_key_file.as_os_str().is_empty() { - self.gateway.private_sphinx_key_file = - self::Gateway::default_private_sphinx_key_file(&id); - } - if self.gateway.public_sphinx_key_file.as_os_str().is_empty() { - self.gateway.public_sphinx_key_file = - self::Gateway::default_public_sphinx_key_file(&id); - } - if self - .gateway - .private_identity_key_file - .as_os_str() - .is_empty() - { - self.gateway.private_identity_key_file = - self::Gateway::default_private_identity_key_file(&id); - } - if self.gateway.public_identity_key_file.as_os_str().is_empty() { - self.gateway.public_identity_key_file = - self::Gateway::default_public_identity_key_file(&id); - } + pub fn read_from_toml_file>(path: P) -> io::Result { + read_config_from_toml_file(path) + } - if self.gateway.persistent_storage.as_os_str().is_empty() { - self.gateway.persistent_storage = self::Gateway::default_database_path(&id); - } + pub fn read_from_default_path>(id: P) -> io::Result { + Self::read_from_toml_file(default_config_filepath(id)) + } - self.gateway.id = id; - self + pub fn default_location(&self) -> PathBuf { + default_config_filepath(&self.gateway.id) + } + + pub fn save_to_default_location(&self) -> io::Result<()> { + let config_save_location: PathBuf = self.default_location(); + save_formatted_config_to_file(self, config_save_location) } pub fn with_only_coconut_credentials(mut self, only_coconut_credentials: bool) -> Self { @@ -163,11 +144,6 @@ impl Config { self } - pub fn with_announce_address>(mut self, announce_address: S) -> Self { - self.gateway.announce_address = announce_address.into(); - self - } - pub fn with_mix_port(mut self, port: u16) -> Self { self.gateway.mix_port = port; self @@ -178,13 +154,8 @@ impl Config { self } - pub fn announce_host_from_listening_host(mut self) -> Self { - self.gateway.announce_address = self.gateway.listening_address.to_string(); - self - } - pub fn with_custom_persistent_store(mut self, store_dir: PathBuf) -> Self { - self.gateway.persistent_storage = store_dir; + self.storage_paths.clients_storage = store_dir; self } @@ -193,40 +164,6 @@ impl Config { self } - pub fn with_wallet_address(mut self, wallet_address: nyxd::AccountId) -> Self { - self.gateway.wallet_address = Some(wallet_address); - self - } - - // getters - pub fn get_config_file_save_location(&self) -> PathBuf { - self.config_directory().join(Self::config_file_name()) - } - - pub fn get_only_coconut_credentials(&self) -> bool { - self.gateway.only_coconut_credentials - } - - pub fn get_private_identity_key_file(&self) -> PathBuf { - self.gateway.private_identity_key_file.clone() - } - - pub fn get_public_identity_key_file(&self) -> PathBuf { - self.gateway.public_identity_key_file.clone() - } - - pub fn get_private_sphinx_key_file(&self) -> PathBuf { - self.gateway.private_sphinx_key_file.clone() - } - - pub fn get_public_sphinx_key_file(&self) -> PathBuf { - self.gateway.public_sphinx_key_file.clone() - } - - pub fn get_enabled_statistics(&self) -> bool { - self.gateway.enabled_statistics - } - pub fn get_statistics_service_url(&self) -> Url { self.gateway.statistics_service_url.clone() } @@ -242,225 +179,112 @@ impl Config { pub fn get_cosmos_mnemonic(&self) -> bip39::Mnemonic { self.gateway.cosmos_mnemonic.clone() } - - pub fn get_listening_address(&self) -> IpAddr { - self.gateway.listening_address - } - - pub fn get_announce_address(&self) -> String { - self.gateway.announce_address.clone() - } - - pub fn get_mix_port(&self) -> u16 { - self.gateway.mix_port - } - - pub fn get_clients_port(&self) -> u16 { - self.gateway.clients_port - } - - pub fn get_persistent_store_path(&self) -> PathBuf { - self.gateway.persistent_storage.clone() - } - - 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_message_retrieval_limit(&self) -> i64 { - self.debug.message_retrieval_limit - } - - pub fn get_version(&self) -> &str { - &self.gateway.version - } - - #[allow(unused)] - pub fn get_wallet_address(&self) -> Option { - self.gateway.wallet_address.clone() - } } -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +// we only really care about the mnemonic being zeroized +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Zeroize, ZeroizeOnDrop)] pub struct Gateway { /// Version of the gateway 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 gateway. - id: String, + pub id: String, /// Indicates whether this gateway is accepting only coconut credentials for accessing the /// the mixnet, or if it also accepts non-paying clients #[serde(default)] - only_coconut_credentials: bool, + pub only_coconut_credentials: bool, /// Address to which this mixnode will bind to and will be listening for packets. - #[serde(default = "bind_all_address")] - 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`. - #[serde(default = "missing_string_value")] - announce_address: String, + #[zeroize(skip)] + 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 all client-related traffic. /// (default: 9000) - #[serde(default = "default_clients_port")] - clients_port: u16, - - /// Path to file containing private identity key. - private_identity_key_file: PathBuf, - - /// Path to file containing public identity key. - 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 clients_port: u16, /// Whether gateway collects and sends anonymized statistics - enabled_statistics: bool, + pub enabled_statistics: bool, /// Domain address of the statistics service - statistics_service_url: Url, + #[zeroize(skip)] + pub statistics_service_url: Url, /// Addresses to APIs from which the node gets the view of the network. #[serde(alias = "validator_api_urls")] - nym_api_urls: Vec, + #[zeroize(skip)] + pub nym_api_urls: Vec, /// Addresses to validators which the node uses to check for double spending of ERC20 tokens. #[serde(alias = "validator_nymd_urls")] - nyxd_urls: Vec, + #[zeroize(skip)] + pub nyxd_urls: Vec, /// Mnemonic of a cosmos wallet used in checking for double spending. - cosmos_mnemonic: bip39::Mnemonic, - - /// nym_home_directory specifies absolute path to the home nym gateways directory. - /// It is expected to use default value and hence .toml file should not redefine this field. - nym_root_directory: PathBuf, - - /// Path to sqlite database containing all persistent data: messages for offline clients, - /// derived shared keys and available client bandwidths. - persistent_storage: PathBuf, - - /// The Cosmos wallet address that will control this gateway - // the only reason this is an Option is because of the lack of existence of a sane default value - wallet_address: Option, + // #[deprecated(note = "move to storage")] + // TODO: I don't think this should be stored directly in the config... + pub cosmos_mnemonic: bip39::Mnemonic, } impl Gateway { - 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") - } - - 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_database_path(id: &str) -> PathBuf { - Config::default_data_directory(id).join("db.sqlite") - } -} - -impl Default for Gateway { - fn default() -> Self { + pub fn new_default>(id: S) -> Self { Gateway { version: env!("CARGO_PKG_VERSION").to_string(), - id: "".to_string(), + id: id.into(), only_coconut_credentials: false, - listening_address: bind_all_address(), - announce_address: "127.0.0.1".to_string(), + listening_address: inaddr_any(), mix_port: DEFAULT_MIX_LISTENING_PORT, clients_port: DEFAULT_CLIENT_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(), enabled_statistics: false, - statistics_service_url: Url::from_str(STATISTICS_SERVICE_DOMAIN_ADDRESS) + statistics_service_url: mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS + .parse() .expect("Invalid default statistics service URL"), - nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")], - nyxd_urls: vec![Url::from_str(NYXD_URL).expect("Invalid default nyxd URL")], + nym_api_urls: vec![mainnet::NYM_API.parse().expect("Invalid default API URL")], + nyxd_urls: vec![mainnet::NYXD_URL.parse().expect("Invalid default nyxd URL")], cosmos_mnemonic: bip39::Mnemonic::generate(24).unwrap(), - nym_root_directory: Config::default_root_directory(), - persistent_storage: Default::default(), - wallet_address: None, } } } -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -struct Logging {} - #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(default)] -struct Debug { +pub struct Debug { /// 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, /// Delay between each subsequent presence data being sent. #[serde(with = "humantime_serde")] - presence_sending_delay: Duration, + pub presence_sending_delay: Duration, /// Length of filenames for new client messages. - stored_messages_filename_length: u16, + pub stored_messages_filename_length: u16, /// Number of messages from offline client that can be pulled at once from the storage. - message_retrieval_limit: i64, + pub message_retrieval_limit: i64, /// 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/gateway/src/config/old_config_v1_1_20.rs b/gateway/src/config/old_config_v1_1_20.rs new file mode 100644 index 0000000000..594043b8a7 --- /dev/null +++ b/gateway/src/config/old_config_v1_1_20.rs @@ -0,0 +1,196 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::paths::{GatewayPaths, KeysPaths}; +use crate::config::{Config, Debug, Gateway}; +use nym_bin_common::logging::LoggingSettings; +use nym_config::legacy_helpers::nym_config::MigrationNymConfig; +use nym_validator_client::nyxd; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; +use std::path::PathBuf; +use std::str::FromStr; +use std::time::Duration; +use url::Url; + +const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/"; +const NYXD_URL: &str = "https://rpc.nymtech.net"; +const NYM_API: &str = "https://validator.nymtech.net/api/"; +const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; +const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000; + +const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000); +const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); +const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); +const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); +const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; + +const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; +const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; + +fn bind_all_address() -> IpAddr { + "0.0.0.0".parse().unwrap() +} + +#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct ConfigV1_1_20 { + gateway: GatewayV1_1_20, + + #[serde(default)] + logging: LoggingV1_1_20, + #[serde(default)] + debug: DebugV1_1_20, +} + +impl From for Config { + fn from(value: ConfigV1_1_20) -> Self { + Config { + gateway: Gateway { + version: value.gateway.version, + id: value.gateway.id, + only_coconut_credentials: value.gateway.only_coconut_credentials, + listening_address: value.gateway.listening_address, + mix_port: value.gateway.mix_port, + clients_port: value.gateway.clients_port, + enabled_statistics: value.gateway.enabled_statistics, + nym_api_urls: value.gateway.nym_api_urls, + nyxd_urls: value.gateway.nyxd_urls, + statistics_service_url: value.gateway.statistics_service_url, + cosmos_mnemonic: value.gateway.cosmos_mnemonic, + }, + storage_paths: GatewayPaths { + keys: KeysPaths { + private_identity_key_file: value.gateway.private_identity_key_file, + public_identity_key_file: value.gateway.public_identity_key_file, + private_sphinx_key_file: value.gateway.private_sphinx_key_file, + public_sphinx_key_file: value.gateway.public_sphinx_key_file, + }, + clients_storage: value.gateway.persistent_storage, + }, + logging: value.logging.into(), + debug: value.debug.into(), + } + } +} + +impl MigrationNymConfig for ConfigV1_1_20 { + fn default_root_directory() -> PathBuf { + dirs::home_dir() + .expect("Failed to evaluate $HOME value") + .join(".nym") + .join("gateways") + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct GatewayV1_1_20 { + version: String, + id: String, + + #[serde(default)] + only_coconut_credentials: bool, + #[serde(default = "bind_all_address")] + listening_address: IpAddr, + announce_address: String, + mix_port: u16, + clients_port: u16, + private_identity_key_file: PathBuf, + public_identity_key_file: PathBuf, + private_sphinx_key_file: PathBuf, + public_sphinx_key_file: PathBuf, + enabled_statistics: bool, + statistics_service_url: Url, + #[serde(alias = "validator_api_urls")] + nym_api_urls: Vec, + #[serde(alias = "validator_nymd_urls")] + nyxd_urls: Vec, + cosmos_mnemonic: bip39::Mnemonic, + nym_root_directory: PathBuf, + persistent_storage: PathBuf, + wallet_address: Option, +} + +impl Default for GatewayV1_1_20 { + fn default() -> Self { + GatewayV1_1_20 { + version: env!("CARGO_PKG_VERSION").to_string(), + id: "".to_string(), + only_coconut_credentials: false, + listening_address: bind_all_address(), + announce_address: "127.0.0.1".to_string(), + mix_port: DEFAULT_MIX_LISTENING_PORT, + clients_port: DEFAULT_CLIENT_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(), + enabled_statistics: false, + statistics_service_url: Url::from_str(STATISTICS_SERVICE_DOMAIN_ADDRESS) + .expect("Invalid default statistics service URL"), + nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")], + nyxd_urls: vec![Url::from_str(NYXD_URL).expect("Invalid default nyxd URL")], + cosmos_mnemonic: bip39::Mnemonic::generate(24).unwrap(), + nym_root_directory: ConfigV1_1_20::default_root_directory(), + persistent_storage: Default::default(), + wallet_address: None, + } + } +} + +#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct LoggingV1_1_20 {} + +impl From for LoggingSettings { + fn from(_value: LoggingV1_1_20) -> Self { + LoggingSettings {} + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +struct DebugV1_1_20 { + #[serde(with = "humantime_serde")] + packet_forwarding_initial_backoff: Duration, + #[serde(with = "humantime_serde")] + packet_forwarding_maximum_backoff: Duration, + #[serde(with = "humantime_serde")] + initial_connection_timeout: Duration, + maximum_connection_buffer_size: usize, + #[serde(with = "humantime_serde")] + presence_sending_delay: Duration, + stored_messages_filename_length: u16, + message_retrieval_limit: i64, + use_legacy_framed_packet_version: bool, +} + +impl From for Debug { + fn from(value: DebugV1_1_20) -> Self { + Debug { + packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: value.packet_forwarding_maximum_backoff, + initial_connection_timeout: value.initial_connection_timeout, + maximum_connection_buffer_size: value.maximum_connection_buffer_size, + presence_sending_delay: value.presence_sending_delay, + stored_messages_filename_length: value.stored_messages_filename_length, + message_retrieval_limit: value.message_retrieval_limit, + use_legacy_framed_packet_version: value.use_legacy_framed_packet_version, + } + } +} + +impl Default for DebugV1_1_20 { + fn default() -> Self { + DebugV1_1_20 { + packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, + packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, + initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, + presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, + maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, + stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH, + message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + // TODO: remember to change it in one of future releases!! + use_legacy_framed_packet_version: true, + } + } +} diff --git a/gateway/src/config/persistence/mod.rs b/gateway/src/config/persistence/mod.rs index b106e358ae..cbaa92be3d 100644 --- a/gateway/src/config/persistence/mod.rs +++ b/gateway/src/config/persistence/mod.rs @@ -1,4 +1,4 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub mod pathfinder; +pub mod paths; diff --git a/gateway/src/config/persistence/pathfinder.rs b/gateway/src/config/persistence/pathfinder.rs deleted file mode 100644 index 4ee942e6cd..0000000000 --- a/gateway/src/config/persistence/pathfinder.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::Config; -use std::path::{Path, PathBuf}; - -#[derive(Debug)] -pub struct GatewayPathfinder { - pub config_dir: PathBuf, - pub private_sphinx_key: PathBuf, - pub public_sphinx_key: PathBuf, - pub private_identity_key: PathBuf, - pub public_identity_key: PathBuf, -} - -impl GatewayPathfinder { - pub fn new_from_config(config: &Config) -> Self { - GatewayPathfinder { - config_dir: config.get_config_file_save_location(), - private_sphinx_key: config.get_private_sphinx_key_file(), - public_sphinx_key: config.get_public_sphinx_key_file(), - private_identity_key: config.get_private_identity_key_file(), - public_identity_key: config.get_public_identity_key_file(), - } - } - - pub fn private_identity_key(&self) -> &Path { - &self.private_identity_key - } - - pub fn public_identity_key(&self) -> &Path { - &self.public_identity_key - } - - pub fn private_encryption_key(&self) -> &Path { - &self.private_sphinx_key - } - - pub fn public_encryption_key(&self) -> &Path { - &self.public_sphinx_key - } -} diff --git a/gateway/src/config/persistence/paths.rs b/gateway/src/config/persistence/paths.rs new file mode 100644 index 0000000000..b5f44b6cd4 --- /dev/null +++ b/gateway/src/config/persistence/paths.rs @@ -0,0 +1,99 @@ +// Copyright 2020-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::default_data_directory; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem"; +pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem"; +pub const DEFAULT_PRIVATE_SPHINX_KEY_FILENAME: &str = "private_sphinx.pem"; +pub const DEFAULT_PUBLIC_SPHINX_KEY_FILENAME: &str = "public_sphinx.pem"; + +pub const DEFAULT_CLIENTS_STORAGE_FILENAME: &str = "db.sqlite"; + +// pub const DEFAULT_DESCRIPTION_FILENAME: &str = "description.toml"; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct GatewayPaths { + pub keys: KeysPaths, + + /// Path to sqlite database containing all persistent data: messages for offline clients, + /// derived shared keys and available client bandwidths. + #[serde(alias = "persistent_storage")] + pub clients_storage: PathBuf, + // pub node_description: PathBuf, + + // pub cosmos_bip39_mnemonic: PathBuf, +} + +impl GatewayPaths { + pub fn new_default>(id: P) -> Self { + GatewayPaths { + keys: KeysPaths::new_default(id.as_ref()), + clients_storage: default_data_directory(id).join(DEFAULT_CLIENTS_STORAGE_FILENAME), + // node_description: default_config_filepath(id).join(DEFAULT_DESCRIPTION_FILENAME), + } + } + + pub fn private_identity_key(&self) -> &Path { + self.keys.private_identity_key() + } + + pub fn public_identity_key(&self) -> &Path { + self.keys.public_identity_key() + } + + pub fn private_encryption_key(&self) -> &Path { + self.keys.private_encryption_key() + } + + pub fn public_encryption_key(&self) -> &Path { + self.keys.public_encryption_key() + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +pub struct KeysPaths { + /// Path to file containing private identity key. + pub private_identity_key_file: PathBuf, + + /// Path to file containing public identity key. + pub public_identity_key_file: PathBuf, + + /// Path to file containing private sphinx key. + pub private_sphinx_key_file: PathBuf, + + /// Path to file containing public sphinx key. + pub public_sphinx_key_file: PathBuf, +} + +impl KeysPaths { + pub fn new_default>(id: P) -> Self { + let data_dir = default_data_directory(id); + + KeysPaths { + private_identity_key_file: data_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME), + public_identity_key_file: data_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME), + private_sphinx_key_file: data_dir.join(DEFAULT_PRIVATE_SPHINX_KEY_FILENAME), + public_sphinx_key_file: data_dir.join(DEFAULT_PUBLIC_SPHINX_KEY_FILENAME), + } + } + + pub fn private_identity_key(&self) -> &Path { + &self.private_identity_key_file + } + + pub fn public_identity_key(&self) -> &Path { + &self.public_identity_key_file + } + + pub fn private_encryption_key(&self) -> &Path { + &self.private_sphinx_key_file + } + + pub fn public_encryption_key(&self) -> &Path { + &self.public_sphinx_key_file + } +} diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index 2690c38385..e8543b8f44 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -1,12 +1,11 @@ -// Copyright 2020 - Nym Technologies SA +// Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub(crate) fn config_template() -> &'static str { - // While using normal toml marshalling would have been way simpler with less overhead, - // I think it's useful to have comments attached to the saved config file to explain behaviour of - // particular fields. - // Note: any changes to the template must be reflected in the appropriate structs in verloc. - r#" +// While using normal toml marshalling would have been way simpler with less overhead, +// I think it's useful to have comments attached to the saved config file to explain behaviour of +// particular fields. +// Note: any changes to the template must be reflected in the appropriate structs. +pub(crate) const CONFIG_TEMPLATE: &str = r#" # This is a TOML config file. # For more information, see https://github.com/toml-lang/toml @@ -26,25 +25,6 @@ only_coconut_credentials = {{ gateway.only_coconut_credentials }} # Socket address to which this gateway will bind to and will be listening for packets. listening_address = '{{ gateway.listening_address }}' -# Path to file containing private identity key. -private_identity_key_file = '{{ gateway.private_identity_key_file }}' - -# Path to file containing public identity key. -public_identity_key_file = '{{ gateway.public_identity_key_file }}' - -# Path to file containing private sphinx key. -private_sphinx_key_file = '{{ gateway.private_sphinx_key_file }}' - -# Path to file containing public sphinx key. -public_sphinx_key_file = '{{ gateway.public_sphinx_key_file }}' - -##### additional gateway config options ##### - -# Optional address announced to the directory server for the clients to connect to. -# It is useful, say, in NAT scenarios or wanting to more easily update actual IP address -# later on by using name resolvable with a DNS query, such as `nymtech.net`. -announce_address = '{{ gateway.announce_address }}' - # Port used for listening for all mixnet traffic. # (default: 1789) mix_port = {{ gateway.mix_port }} @@ -75,18 +55,23 @@ nyxd_urls = [ cosmos_mnemonic = "{{ gateway.cosmos_mnemonic }}" -# Nym wallet address on the blockchain that should control this gateway -wallet_address = '{{ gateway.wallet_address }}' +[storage_paths] -##### advanced configuration options ##### +# Path to file containing private identity key. +keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' -# nym_home_directory specifies absolute path to the home nym gateway directory. -# It is expected to use default value and hence .toml file should not redefine this field. -nym_root_directory = '{{ gateway.nym_root_directory }}' +# Path to file containing public identity key. +keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}' + +# Path to file containing private identity key. +keys.private_sphinx_key_file = '{{ storage_paths.keys.private_sphinx_key_file }}' + +# Path to file containing public sphinx key. +keys.public_sphinx_key_file = '{{ storage_paths.keys.public_sphinx_key_file }}' # Path to sqlite database containing all persistent data: messages for offline clients, # derived shared keys and available client bandwidths. -persistent_storage = '{{ gateway.persistent_storage }}' +clients_storage = '{{ storage_paths.clients_storage }}' ##### logging configuration options ##### @@ -94,5 +79,4 @@ persistent_storage = '{{ gateway.persistent_storage }}' # TODO -"# -} +"#; diff --git a/gateway/src/error.rs b/gateway/src/error.rs index b72b63bd88..5786bd26fb 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -19,19 +19,22 @@ pub(crate) enum GatewayError { source: io::Error, }, + #[error( + "failed to save config file for id {id} using path {path}. detailed message: {source}" + )] + ConfigSaveFailure { + id: String, + path: PathBuf, + #[source] + source: io::Error, + }, + #[error("the configured version of the gateway ({config_version}) is incompatible with the binary version ({binary_version})")] LocalVersionCheckFailure { binary_version: String, config_version: String, }, - #[error("another node on the network seems to have identical announce-host ({host})! Their identity is {remote_identity}, while ours is {local_identity}")] - DuplicateNodeHost { - host: String, - local_identity: String, - remote_identity: String, - }, - #[error("could not obtain the information about current gateways on the network: {source}")] NetworkGatewaysQueryFailure { #[source] diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 1802fa413b..8487e6c99f 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use self::storage::PersistentStorage; -use crate::config::persistence::pathfinder::GatewayPathfinder; use crate::config::Config; use crate::error::GatewayError; use crate::node::client_handling::active_clients::ActiveClientsStore; @@ -37,15 +36,15 @@ pub(crate) async fn create_gateway(config: Config) -> Gateway } async fn initialise_storage(config: &Config) -> PersistentStorage { - let path = config.get_persistent_store_path(); - let retrieval_limit = config.get_message_retrieval_limit(); + let path = &config.storage_paths.clients_storage; + let retrieval_limit = config.debug.message_retrieval_limit; match PersistentStorage::init(path, retrieval_limit).await { - Err(err) => panic!("failed to initialise gateway storage - {err}"), + Err(err) => panic!("failed to initialise gateway storage: {err}"), Ok(storage) => storage, } } -pub(crate) struct Gateway { +pub(crate) struct Gateway { config: Config, /// ed25519 keypair used to assert one's identity. identity_keypair: Arc, @@ -54,20 +53,14 @@ pub(crate) struct Gateway { storage: St, } -impl Gateway -where - St: Storage + Clone + 'static, -{ +impl Gateway { /// Construct from the given `Config` instance. pub async fn new(config: Config, storage: St) -> Self { - let pathfinder = GatewayPathfinder::new_from_config(&config); - // let storage = Self::initialise_storage(&config).await; - Gateway { - config, - identity_keypair: Arc::new(Self::load_identity_keys(&pathfinder)), - sphinx_keypair: Arc::new(Self::load_sphinx_keys(&pathfinder)), storage, + identity_keypair: Arc::new(Self::load_identity_keys(&config)), + sphinx_keypair: Arc::new(Self::load_sphinx_keys(&config)), + config, } } @@ -86,21 +79,23 @@ where } } - fn load_identity_keys(pathfinder: &GatewayPathfinder) -> identity::KeyPair { + /// Loads identity keys stored on disk + pub(crate) fn load_identity_keys(config: &Config) -> identity::KeyPair { let identity_keypair: identity::KeyPair = nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( - pathfinder.private_identity_key().to_owned(), - pathfinder.public_identity_key().to_owned(), + config.storage_paths.keys.private_identity_key(), + config.storage_paths.keys.public_identity_key(), )) .expect("Failed to read stored identity key files"); identity_keypair } - fn load_sphinx_keys(pathfinder: &GatewayPathfinder) -> encryption::KeyPair { + /// Loads Sphinx keys stored on disk + fn load_sphinx_keys(config: &Config) -> encryption::KeyPair { let sphinx_keypair: encryption::KeyPair = nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( - pathfinder.private_encryption_key().to_owned(), - pathfinder.public_encryption_key().to_owned(), + config.storage_paths.keys.private_encryption_key(), + config.storage_paths.keys.public_encryption_key(), )) .expect("Failed to read stored sphinx key files"); sphinx_keypair @@ -110,16 +105,15 @@ where let node_details = nym_types::gateway::GatewayNodeDetailsResponse { 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(), - clients_port: self.config.get_clients_port(), + bind_address: self.config.gateway.listening_address.to_string(), + version: self.config.gateway.version.clone(), + mix_port: self.config.gateway.mix_port, + clients_port: self.config.gateway.clients_port, data_store: self .config - .get_persistent_store_path() - .to_str() - .unwrap_or(".") + .storage_paths + .clients_storage + .display() .to_string(), }; @@ -131,7 +125,9 @@ where ack_sender: MixForwardingSender, active_clients_store: ActiveClientsStore, shutdown: TaskClient, - ) { + ) where + St: Storage + Clone + 'static, + { info!("Starting mix socket listener..."); let packet_processor = @@ -145,8 +141,8 @@ where ); let listening_address = SocketAddr::new( - self.config.get_listening_address(), - self.config.get_mix_port(), + self.config.gateway.listening_address, + self.config.gateway.mix_port, ); mixnet_handling::Listener::new(listening_address, shutdown).start(connection_handler); @@ -158,18 +154,20 @@ where active_clients_store: ActiveClientsStore, shutdown: TaskClient, coconut_verifier: Arc, - ) { + ) where + St: Storage + Clone + 'static, + { info!("Starting client [web]socket listener..."); let listening_address = SocketAddr::new( - self.config.get_listening_address(), - self.config.get_clients_port(), + self.config.gateway.listening_address, + self.config.gateway.clients_port, ); websocket::Listener::new( listening_address, Arc::clone(&self.identity_keypair), - self.config.get_only_coconut_credentials(), + self.config.gateway.only_coconut_credentials, coconut_verifier, ) .start( @@ -184,11 +182,11 @@ where info!("Starting mix packet forwarder..."); let (mut packet_forwarder, packet_sender) = PacketForwarder::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, shutdown, ); @@ -236,48 +234,32 @@ where client } - async fn check_if_same_ip_gateway_exists(&self) -> Result, GatewayError> { + async fn check_if_bonded(&self) -> Result { + // 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(); - - let existing_gateways = match validator_client.get_cached_gateways().await { - Ok(gateways) => gateways, + let existing_nodes = match validator_client.get_cached_gateways().await { + Ok(nodes) => nodes, Err(err) => { error!("failed to grab initial network gateways - {err}\n Please try to startup again in few minutes"); return Err(GatewayError::NetworkGatewaysQueryFailure { source: err }); } }; - let our_host = self.config.get_announce_address(); - - Ok(existing_gateways - .iter() - .find(|node| node.gateway.host == our_host) - .map(|node| node.gateway().identity_key.clone())) + Ok(existing_nodes.iter().any(|node| { + node.gateway.identity_key == self.identity_keypair.public_key().to_base58_string() + })) } - async fn ensure_no_duplicate_host_exists(&self) -> Result<(), GatewayError> { - let local_identity = self.identity_keypair.public_key().to_base58_string(); - if let Some(remote_identity) = self.check_if_same_ip_gateway_exists().await? { - if remote_identity == local_identity { - warn!("We seem to have not unregistered after going offline - there's a node with identical identity and announce-host as us registered.") - } else { - error!( - "Our announce-host is identical to an existing node's announce-host! (its key is {remote_identity})", - ); - return Err(GatewayError::DuplicateNodeHost { - host: self.config.get_announce_address(), - local_identity, - remote_identity, - }); - } - } - Ok(()) - } - - pub async fn run(&mut self) -> Result<(), Box> { + pub async fn run(&mut self) -> Result<(), Box> + where + St: Storage + Clone + 'static, + { info!("Starting nym gateway!"); - self.ensure_no_duplicate_host_exists().await?; + if self.check_if_bonded().await? { + warn!("You seem to have bonded your gateway before starting it - that's highly unrecommended as in the future it might result in slashing"); + } let shutdown = TaskManager::new(10); @@ -295,7 +277,7 @@ where shutdown.subscribe(), ); - if self.config.get_enabled_statistics() { + if self.config.gateway.enabled_statistics { let statistics_service_url = self.config.get_statistics_service_url(); let stats_collector = GatewayStatisticsCollector::new( self.identity_keypair.public_key().to_base58_string(), diff --git a/gateway/src/support/config.rs b/gateway/src/support/config.rs index 4a6bda91a4..cbbd4dd798 100644 --- a/gateway/src/support/config.rs +++ b/gateway/src/support/config.rs @@ -1,29 +1,15 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::commands::{override_config, OverrideConfig}; +use crate::commands::{override_config, try_load_current_config, OverrideConfig}; use crate::config::Config; use crate::error::GatewayError; -use log::error; -use nym_config::NymConfig; pub(crate) fn build_config>( id: String, override_args: O, ) -> Result { - let config = match Config::load_from_file(&id) { - Ok(cfg) => cfg, - Err(err) => { - error!( - "Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})", - ); - return Err(GatewayError::ConfigLoadFailure { - path: Config::default_config_file_path(&id), - id, - source: err, - }); - } - }; + let config = try_load_current_config(&id)?; override_config(config, override_args.into()) } diff --git a/mixnode/src/commands/describe.rs b/mixnode/src/commands/describe.rs index 160e5de633..164ef9d740 100644 --- a/mixnode/src/commands/describe.rs +++ b/mixnode/src/commands/describe.rs @@ -1,11 +1,10 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::Config; +use crate::commands::try_load_current_config; use crate::node::node_description::NodeDescription; use clap::Args; use colored::Colorize; -use nym_config::NymConfig; use std::io; use std::io::Write; @@ -39,15 +38,9 @@ fn read_user_input() -> String { buf.trim().to_string() } -pub(crate) fn execute(args: Describe) { +pub(crate) fn execute(args: Describe) -> anyhow::Result<()> { // ensure that the mixnode has in fact been initialized - match Config::load_from_file(&args.id) { - Ok(cfg) => cfg, - Err(err) => { - error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", &args.id); - return; - } - }; + let config = try_load_current_config(&args.id)?; let example_url = "https://mixnode.yourdomain.com".bright_cyan(); let example_location = "City: London, Country: UK"; @@ -81,9 +74,6 @@ pub(crate) fn execute(args: Describe) { }; // save the struct - NodeDescription::save_to_file( - &node_description, - Config::default_config_directory(&args.id), - ) - .unwrap() + NodeDescription::save_to_file(&node_description, config.storage_paths.node_description)?; + Ok(()) } diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index 2b7c7bd364..c0834686ee 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -2,15 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use super::OverrideConfig; -use crate::config::Config; +use crate::commands::override_config; +use crate::config::{ + default_config_directory, default_config_filepath, default_data_directory, Config, +}; use crate::node::MixNode; -use crate::{commands::override_config, config::persistence::pathfinder::MixNodePathfinder}; use clap::Args; use nym_bin_common::output_format::OutputFormat; -use nym_config::NymConfig; use nym_crypto::asymmetric::{encryption, identity}; -use nym_validator_client::nyxd; use std::net::IpAddr; +use std::{fs, io}; #[derive(Args, Clone)] pub(crate) struct Init { @@ -22,10 +23,6 @@ pub(crate) struct Init { #[clap(long)] host: IpAddr, - /// The wallet address you will use to bond this mixnode, e.g. nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9 - #[clap(long)] - wallet_address: nyxd::AccountId, - /// The port on which the mixnode will be listening for mix packets #[clap(long)] mix_port: Option, @@ -38,10 +35,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,29 +49,33 @@ 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, } } } +fn init_paths(id: &str) -> io::Result<()> { + fs::create_dir_all(default_data_directory(id))?; + fs::create_dir_all(default_config_directory(id)) +} + pub(crate) fn execute(args: &Init) { let override_config_fields = OverrideConfig::from(args.clone()); - let id = &override_config_fields.id; + let id = override_config_fields.id.clone(); eprintln!("Initialising mixnode {id}..."); - let already_init = if Config::default_config_file_path(id).exists() { + let already_init = if default_config_filepath(&id).exists() { eprintln!("Mixnode \"{id}\" was already initialised before! Config information will be overwritten (but keys will be kept)!"); true } else { + init_paths(&id).expect("failed to initialise storage paths"); false }; - let mut config = Config::new(id); + let mut config = Config::new(&id); config = override_config(config, override_config_fields); // if node was already initialised, don't generate new keys @@ -87,12 +84,12 @@ pub(crate) fn execute(args: &Init) { let identity_keys = identity::KeyPair::new(&mut rng); let sphinx_keys = encryption::KeyPair::new(&mut rng); - let pathfinder = MixNodePathfinder::new_from_config(&config); + nym_pemstore::store_keypair( &identity_keys, &nym_pemstore::KeyPairPath::new( - pathfinder.private_identity_key().to_owned(), - pathfinder.public_identity_key().to_owned(), + config.storage_paths.private_identity_key(), + config.storage_paths.public_identity_key(), ), ) .expect("Failed to save identity keys"); @@ -100,19 +97,25 @@ pub(crate) fn execute(args: &Init) { nym_pemstore::store_keypair( &sphinx_keys, &nym_pemstore::KeyPairPath::new( - pathfinder.private_encryption_key().to_owned(), - pathfinder.public_encryption_key().to_owned(), + config.storage_paths.private_encryption_key(), + config.storage_paths.public_encryption_key(), ), ) .expect("Failed to save sphinx keys"); eprintln!("Saved mixnet identity and sphinx keypairs"); } - let config_save_location = config.get_config_file_save_location(); - config - .save_to_file(None) - .expect("Failed to save the config file"); - eprintln!("Saved configuration file to {config_save_location:?}"); + let config_save_location = config.default_location(); + config.save_to_default_location().unwrap_or_else(|_| { + panic!( + "Failed to save the config file to {}", + config_save_location.display() + ) + }); + eprintln!( + "Saved configuration file to {}", + config_save_location.display() + ); eprintln!("Mixnode configuration completed.\n\n\n"); MixNode::new(config).print_node_details(args.output) diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index 09fc34ebdf..6582fe2c62 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -1,7 +1,9 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::config::old_config_v1_1_21::ConfigV1_1_21; use crate::{config::Config, Cli}; +use anyhow::anyhow; use clap::CommandFactory; use clap::Subcommand; use colored::Colorize; @@ -10,7 +12,6 @@ use nym_bin_common::version_checker; use nym_config::defaults::var_names::{BECH32_PREFIX, NYM_API}; use nym_config::OptionalSet; use nym_crypto::bech32_address_validation; -use nym_validator_client::nyxd; use std::net::IpAddr; use std::process; @@ -52,45 +53,31 @@ pub(crate) enum Commands { struct OverrideConfig { id: String, host: Option, - wallet_address: Option, mix_port: Option, verloc_port: Option, http_api_port: Option, - announce_host: Option, nym_apis: Option>, } -pub(crate) async fn execute(args: Cli) { +pub(crate) async fn execute(args: Cli) -> anyhow::Result<()> { let bin_name = "nym-mixnode"; match args.command { - Commands::Describe(m) => describe::execute(m), + Commands::Describe(m) => describe::execute(m)?, Commands::Init(m) => init::execute(&m), - Commands::Run(m) => run::execute(&m).await, - Commands::Sign(m) => sign::execute(&m), - Commands::Upgrade(m) => upgrade::execute(&m), - Commands::NodeDetails(m) => node_details::execute(&m), + Commands::Run(m) => run::execute(&m).await?, + Commands::Sign(m) => sign::execute(&m)?, + Commands::Upgrade(m) => upgrade::execute(&m)?, + Commands::NodeDetails(m) => node_details::execute(&m)?, Commands::Completions(s) => s.generate(&mut crate::Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name), } + Ok(()) } -fn override_config(mut config: Config, args: OverrideConfig) -> Config { - // special case that I'm not sure could be easily handled with the trait - let mut was_host_overridden = false; - if let Some(host) = args.host { - config = config.with_listening_address(host); - was_host_overridden = true; - } - - if let Some(announce_host) = args.announce_host { - config = config.with_announce_address(announce_host); - } else if was_host_overridden { - // make sure our 'announce-host' always defaults to 'host' - config = config.announce_address_from_listening_address() - } - +fn override_config(config: Config, args: OverrideConfig) -> Config { config + .with_optional(Config::with_listening_address, args.host) .with_optional(Config::with_mix_port, args.mix_port) .with_optional(Config::with_verloc_port, args.verloc_port) .with_optional(Config::with_http_api_port, args.http_api_port) @@ -100,13 +87,6 @@ fn override_config(mut config: Config, args: OverrideConfig) -> Config { NYM_API, nym_config::parse_urls, ) - .with_optional( - |cfg, wallet_address| { - validate_bech32_address_or_exit(wallet_address.as_ref()); - cfg.with_wallet_address(wallet_address) - }, - args.wallet_address, - ) } /// Ensures that a given bech32 address is valid, or exits @@ -135,7 +115,7 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) { // network version. It might do so in the future. pub(crate) fn version_check(cfg: &Config) -> bool { let binary_version = env!("CARGO_PKG_VERSION"); - let config_version = cfg.get_version(); + let config_version = &cfg.mixnode.version; if binary_version == config_version { true } else { @@ -149,3 +129,43 @@ pub(crate) fn version_check(cfg: &Config) -> bool { } } } + +fn try_upgrade_v1_1_21_config(id: &str) -> std::io::Result<()> { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.21 (which is incompatible with the current, i.e. 1.1.22+) + let Ok(old_config) = ConfigV1_1_21::load_from_file(id) else { + // if we failed to load it, there might have been nothing to upgrade + // or maybe it was an even older file. in either way. just ignore it and carry on with our day + return Ok(()); + }; + info!("It seems the mixnode is using <= v1.1.21 config template."); + info!("It is going to get updated to the current specification."); + + let updated: Config = old_config.into(); + updated.save_to_default_location() +} + +fn try_load_current_config(id: &str) -> anyhow::Result { + try_upgrade_v1_1_21_config(id)?; + + Config::read_from_default_path(id).map_err(|err| { + let error_msg = + format!( + "Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})", + ); + error!("{error_msg}"); + anyhow!(error_msg) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Cli::command().debug_assert(); + } +} diff --git a/mixnode/src/commands/node_details.rs b/mixnode/src/commands/node_details.rs index a4b185206c..c26bad8cab 100644 --- a/mixnode/src/commands/node_details.rs +++ b/mixnode/src/commands/node_details.rs @@ -1,11 +1,10 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::Config; +use crate::commands::try_load_current_config; use crate::node::MixNode; use clap::Args; use nym_bin_common::output_format::OutputFormat; -use nym_config::NymConfig; #[derive(Args)] pub(crate) struct NodeDetails { @@ -17,18 +16,9 @@ pub(crate) struct NodeDetails { output: OutputFormat, } -pub(crate) fn execute(args: &NodeDetails) { - let config = match Config::load_from_file(&args.id) { - Ok(cfg) => cfg, - Err(err) => { - error!( - "Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", - args.id, - err, - ); - return; - } - }; +pub(crate) fn execute(args: &NodeDetails) -> anyhow::Result<()> { + let config = try_load_current_config(&args.id)?; - MixNode::new(config).print_node_details(args.output) + MixNode::new(config).print_node_details(args.output); + Ok(()) } diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index 97afd5b86f..f617494aad 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -2,12 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use super::OverrideConfig; -use crate::commands::{override_config, version_check}; -use crate::config::Config; +use crate::commands::{override_config, try_load_current_config, version_check}; use crate::node::MixNode; +use anyhow::bail; use clap::Args; use nym_bin_common::output_format::OutputFormat; -use nym_config::NymConfig; +use nym_config::helpers::SPECIAL_ADDRESSES; use nym_validator_client::nyxd; use std::net::IpAddr; @@ -37,10 +37,6 @@ pub(crate) struct Run { #[clap(long)] http_api_port: Option, - /// 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, } } @@ -70,39 +64,24 @@ fn show_binding_warning(address: &str) { eprintln!( "\nYou are trying to bind to {address} - you might not be accessible to other nodes\n\ You can ignore this note if you're running setup on a local network \n\ - or have set a custom 'announce-host'" + or have used different host when bonding your node" ); eprintln!("\n\n"); } -fn special_addresses() -> Vec<&'static str> { - vec!["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"] -} - -pub(crate) async fn execute(args: &Run) { +pub(crate) async fn execute(args: &Run) -> anyhow::Result<()> { eprintln!("Starting mixnode {}...", args.id); - let mut config = match Config::load_from_file(&args.id) { - Ok(cfg) => cfg, - Err(err) => { - error!( - "Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", - args.id, - err - ); - return; - } - }; - + let mut config = try_load_current_config(&args.id)?; config = override_config(config, OverrideConfig::from(args.clone())); if !version_check(&config) { error!("failed the local version check"); - return; + bail!("failed the local version check") } - if special_addresses().contains(&&*config.get_listening_address().to_string()) { - show_binding_warning(&config.get_listening_address().to_string()); + if SPECIAL_ADDRESSES.contains(&config.mixnode.listening_address) { + show_binding_warning(&config.mixnode.listening_address.to_string()); } let mut mixnode = MixNode::new(config); @@ -112,5 +91,6 @@ pub(crate) async fn execute(args: &Run) { Select the correct version and install it to your machine. You will need to provide the following: \n "); mixnode.print_node_details(args.output); - mixnode.run().await + mixnode.run().await; + Ok(()) } diff --git a/mixnode/src/commands/sign.rs b/mixnode/src/commands/sign.rs index 29111b7354..8c10bdb995 100644 --- a/mixnode/src/commands/sign.rs +++ b/mixnode/src/commands/sign.rs @@ -1,18 +1,16 @@ // 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::commands::{try_load_current_config, validate_bech32_address_or_exit}; use crate::node::MixNode; use anyhow::{bail, Result}; use clap::{ArgGroup, Args}; use nym_bin_common::output_format::OutputFormat; -use nym_config::NymConfig; use nym_crypto::asymmetric::identity; use nym_types::helpers::ConsoleSigningOutput; use nym_validator_client::nyxd; +use std::convert::TryFrom; + #[cfg(feature = "cpucycles")] use tracing::error; @@ -116,32 +114,22 @@ fn print_signed_contract_msg( println!("{}", output.format(&sign_output)); } -pub(crate) fn execute(args: &Sign) { - let config = match Config::load_from_file(&args.id) { - Ok(cfg) => cfg, - Err(err) => { - error!( - "Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", - args.id, - ); - return; - } - }; +pub(crate) fn execute(args: &Sign) -> anyhow::Result<()> { + let config = try_load_current_config(&args.id)?; if !version_check(&config) { - error!("Failed the local version check"); - return; + error!("failed the local version check"); + bail!("failed the local version check") } let signed_target = match SignedTarget::try_from(args.clone()) { Ok(s) => s, Err(err) => { error!("{err}"); - return; + bail!(err); } }; - let pathfinder = MixNodePathfinder::new_from_config(&config); - let identity_keypair = MixNode::load_identity_keys(&pathfinder); + let identity_keypair = MixNode::load_identity_keys(&config); match signed_target { SignedTarget::Text(text) => { @@ -154,4 +142,5 @@ pub(crate) fn execute(args: &Sign) { print_signed_contract_msg(identity_keypair.private_key(), &raw_msg, args.output) } } + Ok(()) } diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs index ea636cdf14..5c8a95d313 100644 --- a/mixnode/src/commands/upgrade.rs +++ b/mixnode/src/commands/upgrade.rs @@ -1,10 +1,10 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::{missing_string_value, Config}; +use crate::commands::try_upgrade_v1_1_21_config; +use crate::config::Config; use clap::Args; use nym_bin_common::version_checker::Version; -use nym_config::NymConfig; use std::fmt::Display; use std::process; @@ -46,7 +46,7 @@ fn unsupported_upgrade(config_version: &Version, package_version: &Version) -> ! } fn parse_config_version(config: &Config) -> Version { - let version = Version::parse(config.get_version()).unwrap_or_else(|err| { + let version = Version::parse(&config.mixnode.version).unwrap_or_else(|err| { eprintln!("failed to parse client version! - {err}"); process::exit(1) }); @@ -89,13 +89,15 @@ fn minor_0_12_upgrade( print_start_upgrade(config_version, &to_version); - let upgraded_config = config.with_custom_version(to_version.to_string().as_ref()); + let upgraded_config = config.with_custom_version(to_version.to_string()); - upgraded_config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {err}"); - print_failed_upgrade(config_version, &to_version); - process::exit(1); - }); + upgraded_config + .save_to_default_location() + .unwrap_or_else(|err| { + eprintln!("failed to overwrite config file! - {err}"); + print_failed_upgrade(config_version, &to_version); + process::exit(1); + }); print_successful_upgrade(config_version, to_version); @@ -122,18 +124,21 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) { } } -pub(crate) fn execute(args: &Upgrade) { +pub(crate) fn execute(args: &Upgrade) -> anyhow::Result<()> { + try_upgrade_v1_1_21_config(&args.id)?; + let package_version = parse_package_version(); - let existing_config = Config::load_from_file(&args.id).unwrap_or_else(|err| { + let existing_config = Config::read_from_default_path(&args.id).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); - if existing_config.get_version() == missing_string_value::() { + if existing_config.mixnode.version.is_empty() { eprintln!("the existing configuration file does not seem to contain version number."); process::exit(1); } - do_upgrade(existing_config, args, package_version) + do_upgrade(existing_config, args, package_version); + Ok(()) } diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 5320a382f3..02ce8d875a 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -1,24 +1,31 @@ -// Copyright 2020 - Nym Technologies SA +// Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::template::config_template; -use nym_config::defaults::mainnet::NYM_API; +use crate::config::persistence::paths::MixNodePaths; +use crate::config::template::CONFIG_TEMPLATE; +use nym_bin_common::logging::LoggingSettings; use nym_config::defaults::{ - DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, + mainnet, DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, + DEFAULT_VERLOC_LISTENING_PORT, }; -use nym_config::NymConfig; -use nym_validator_client::nyxd; -use serde::{Deserialize, Deserializer, Serialize}; -use std::net::{IpAddr, SocketAddr}; -use std::path::PathBuf; +use nym_config::helpers::inaddr_any; +use nym_config::{ + must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, + DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, +}; +use serde::{Deserialize, Serialize}; +use std::io; +use std::net::IpAddr; +use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Duration; use url::Url; +pub(crate) mod old_config_v1_1_21; pub mod persistence; mod template; -pub(crate) const MISSING_VALUE: &str = "MISSING VALUE"; +const DEFAULT_MIXNODES_DIR: &str = "mixnodes"; // 'RTT MEASUREMENT' const DEFAULT_PACKETS_PER_NODE: usize = 100; @@ -37,125 +44,84 @@ const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_milli const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; -pub fn missing_string_value>() -> T { - MISSING_VALUE.to_string().into() +/// Derive default path to mixnodes's config directory. +/// It should get resolved to `$HOME/.nym/mixnodes//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_MIXNODES_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) } -fn bind_all_address() -> IpAddr { - "0.0.0.0".parse().unwrap() +/// Derive default path to mixnodes's config file. +/// It should get resolved to `$HOME/.nym/mixnodes//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) } -fn default_mix_port() -> u16 { - DEFAULT_MIX_LISTENING_PORT +/// Derive default path to mixnodes's data directory where files, such as keys, are stored. +/// It should get resolved to `$HOME/.nym/mixnodes//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_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 storage_paths: MixNodePaths, #[serde(default)] - verloc: Verloc, + pub verloc: Verloc, + #[serde(default)] - logging: Logging, + pub logging: LoggingSettings, + #[serde(default)] - debug: Debug, + pub debug: Debug, } -impl NymConfig for Config { +impl NymConfigTemplate for Config { fn template() -> &'static str { - config_template() - } - - fn default_root_directory() -> PathBuf { - dirs::home_dir() - .expect("Failed to evaluate $HOME value") - .join(".nym") - .join("mixnodes") - } - - fn try_default_root_directory() -> Option { - dirs::home_dir().map(|path| path.join(".nym").join("mixnodes")) - } - - fn root_directory(&self) -> PathBuf { - self.mixnode.nym_root_directory.clone() - } - - fn config_directory(&self) -> PathBuf { - self.mixnode - .nym_root_directory - .join(&self.mixnode.id) - .join("config") - } - - fn data_directory(&self) -> PathBuf { - self.mixnode - .nym_root_directory - .join(&self.mixnode.id) - .join("data") + CONFIG_TEMPLATE } } impl Config { - pub fn new>(id: S) -> Self { - Config::default().with_id(id) + pub fn new>(id: S) -> Self { + Config { + mixnode: MixNode::new_default(id.as_ref()), + storage_paths: MixNodePaths::new_default(id.as_ref()), + verloc: Default::default(), + logging: Default::default(), + debug: Default::default(), + } + } + + pub fn read_from_toml_file>(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 default_location(&self) -> PathBuf { + default_config_filepath(&self.mixnode.id) + } + + pub fn save_to_default_location(&self) -> io::Result<()> { + let config_save_location: PathBuf = self.default_location(); + save_formatted_config_to_file(self, config_save_location) } // builder methods - pub fn with_id>(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 +132,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 +147,86 @@ impl Config { self } - pub fn announce_address_from_listening_address(mut self) -> Self { - self.mixnode.announce_address = self.mixnode.listening_address.to_string(); + pub fn with_custom_version>(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 {} - #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -struct Verloc { +pub struct Verloc { /// Specifies number of echo packets sent to each node during a measurement run. - packets_per_node: usize, + pub packets_per_node: usize, /// Specifies maximum amount of time to wait for the connection to get established. - connection_timeout: Duration, + #[serde(with = "humantime_serde")] + pub connection_timeout: Duration, /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. - packet_timeout: Duration, + #[serde(with = "humantime_serde")] + pub packet_timeout: Duration, /// Specifies delay between subsequent test packets being sent (after receiving a reply). - delay_between_packets: Duration, + #[serde(with = "humantime_serde")] + pub delay_between_packets: Duration, /// Specifies number of nodes being tested at once. - tested_nodes_batch_size: usize, + pub tested_nodes_batch_size: usize, /// Specifies delay between subsequent test runs. - testing_interval: Duration, + #[serde(with = "humantime_serde")] + pub testing_interval: Duration, /// Specifies delay between attempting to run the measurement again if the previous run failed /// due to being unable to get the list of nodes. - retry_timeout: Duration, + #[serde(with = "humantime_serde")] + pub retry_timeout: Duration, } impl Default for Verloc { @@ -459,37 +245,37 @@ impl Default for Verloc { #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(default)] -struct Debug { +pub struct Debug { /// Delay between each subsequent node statistics being logged to the console #[serde(with = "humantime_serde")] - node_stats_logging_delay: Duration, + pub node_stats_logging_delay: Duration, /// Delay between each subsequent node statistics being updated #[serde(with = "humantime_serde")] - node_stats_updating_delay: Duration, + pub node_stats_updating_delay: Duration, /// Initial value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. #[serde(with = "humantime_serde")] - packet_forwarding_initial_backoff: Duration, + pub packet_forwarding_initial_backoff: Duration, /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. #[serde(with = "humantime_serde")] - packet_forwarding_maximum_backoff: Duration, + pub packet_forwarding_maximum_backoff: Duration, /// Timeout for establishing initial connection when trying to forward a sphinx packet. #[serde(with = "humantime_serde")] - initial_connection_timeout: Duration, + pub initial_connection_timeout: Duration, /// Maximum number of packets that can be stored waiting to get sent to a particular connection. - maximum_connection_buffer_size: usize, + pub maximum_connection_buffer_size: usize, /// Specifies whether the mixnode should be using the legacy framing for the sphinx packets. // it's set to true by default. The reason for that decision is to preserve compatibility with the // existing nodes whilst everyone else is upgrading and getting the code for handling the new field. // It shall be disabled in the subsequent releases. - use_legacy_framed_packet_version: bool, + pub use_legacy_framed_packet_version: bool, } impl Default for Debug { diff --git a/mixnode/src/config/old_config_v1_1_21.rs b/mixnode/src/config/old_config_v1_1_21.rs new file mode 100644 index 0000000000..5baa6307df --- /dev/null +++ b/mixnode/src/config/old_config_v1_1_21.rs @@ -0,0 +1,249 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::paths::{KeysPaths, MixNodePaths}; +use crate::config::{Config, Debug, MixNode, Verloc}; +use nym_bin_common::logging::LoggingSettings; +use nym_config::legacy_helpers::nym_config::MigrationNymConfig; +use nym_validator_client::nyxd; +use serde::{Deserialize, Deserializer, Serialize}; +use std::net::{IpAddr, SocketAddr}; +use std::path::PathBuf; +use std::str::FromStr; +use std::time::Duration; +use url::Url; + +const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; +const DEFAULT_VERLOC_LISTENING_PORT: u16 = 1790; +const DEFAULT_HTTP_API_LISTENING_PORT: u16 = 8000; +const NYM_API: &str = "https://validator.nymtech.net/api/"; +const DESCRIPTION_FILE: &str = "description.toml"; + +// 'RTT MEASUREMENT' +const DEFAULT_PACKETS_PER_NODE: usize = 100; +const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); +const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); +const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); +const DEFAULT_BATCH_SIZE: usize = 50; +const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); +const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); + +// 'DEBUG' +const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000); +const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000); +const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); +const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); +const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); +const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; + +pub(super) fn de_ipaddr_from_maybe_str_socks_addr<'de, D>( + deserializer: D, +) -> Result +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + if let Ok(socket_addr) = SocketAddr::from_str(&s) { + Ok(socket_addr.ip()) + } else { + IpAddr::from_str(&s).map_err(serde::de::Error::custom) + } +} + +fn bind_all_address() -> IpAddr { + "0.0.0.0".parse().unwrap() +} + +#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_21 { + mixnode: MixNodeV1_1_21, + + #[serde(default)] + verloc: VerlocV1_1_21, + #[serde(default)] + logging: LoggingV1_1_21, + #[serde(default)] + debug: DebugV1_1_21, +} + +impl From for Config { + fn from(value: ConfigV1_1_21) -> Self { + let node_description = + ConfigV1_1_21::default_config_directory(&value.mixnode.id).join(DESCRIPTION_FILE); + + Config { + mixnode: MixNode { + version: value.mixnode.version, + id: value.mixnode.id, + listening_address: value.mixnode.listening_address, + mix_port: value.mixnode.mix_port, + verloc_port: value.mixnode.verloc_port, + http_api_port: value.mixnode.http_api_port, + nym_api_urls: value.mixnode.nym_api_urls, + }, + storage_paths: MixNodePaths { + keys: KeysPaths { + private_identity_key_file: value.mixnode.private_identity_key_file, + public_identity_key_file: value.mixnode.public_identity_key_file, + private_sphinx_key_file: value.mixnode.private_sphinx_key_file, + public_sphinx_key_file: value.mixnode.public_sphinx_key_file, + }, + node_description, + }, + verloc: value.verloc.into(), + logging: value.logging.into(), + debug: value.debug.into(), + } + } +} + +impl MigrationNymConfig for ConfigV1_1_21 { + fn default_root_directory() -> PathBuf { + dirs::home_dir() + .expect("Failed to evaluate $HOME value") + .join(".nym") + .join("mixnodes") + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +struct MixNodeV1_1_21 { + version: String, + id: String, + #[serde(deserialize_with = "de_ipaddr_from_maybe_str_socks_addr")] + listening_address: IpAddr, + announce_address: String, + mix_port: u16, + verloc_port: u16, + http_api_port: u16, + private_identity_key_file: PathBuf, + public_identity_key_file: PathBuf, + private_sphinx_key_file: PathBuf, + public_sphinx_key_file: PathBuf, + nym_api_urls: Vec, + nym_root_directory: PathBuf, + wallet_address: Option, +} + +impl Default for MixNodeV1_1_21 { + fn default() -> Self { + MixNodeV1_1_21 { + version: env!("CARGO_PKG_VERSION").to_string(), + id: "".to_string(), + listening_address: bind_all_address(), + announce_address: "127.0.0.1".to_string(), + mix_port: DEFAULT_MIX_LISTENING_PORT, + verloc_port: DEFAULT_VERLOC_LISTENING_PORT, + http_api_port: DEFAULT_HTTP_API_LISTENING_PORT, + private_identity_key_file: Default::default(), + public_identity_key_file: Default::default(), + private_sphinx_key_file: Default::default(), + public_sphinx_key_file: Default::default(), + nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")], + nym_root_directory: ConfigV1_1_21::default_root_directory(), + wallet_address: None, + } + } +} + +#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct LoggingV1_1_21 {} + +impl From for LoggingSettings { + fn from(_value: LoggingV1_1_21) -> Self { + LoggingSettings {} + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct VerlocV1_1_21 { + packets_per_node: usize, + connection_timeout: Duration, + packet_timeout: Duration, + delay_between_packets: Duration, + tested_nodes_batch_size: usize, + testing_interval: Duration, + retry_timeout: Duration, +} + +impl From for Verloc { + fn from(value: VerlocV1_1_21) -> Self { + Verloc { + packets_per_node: value.packets_per_node, + connection_timeout: value.connection_timeout, + packet_timeout: value.packet_timeout, + delay_between_packets: value.delay_between_packets, + tested_nodes_batch_size: value.tested_nodes_batch_size, + testing_interval: value.testing_interval, + retry_timeout: value.retry_timeout, + } + } +} + +impl Default for VerlocV1_1_21 { + fn default() -> Self { + VerlocV1_1_21 { + packets_per_node: DEFAULT_PACKETS_PER_NODE, + connection_timeout: DEFAULT_CONNECTION_TIMEOUT, + packet_timeout: DEFAULT_PACKET_TIMEOUT, + delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS, + tested_nodes_batch_size: DEFAULT_BATCH_SIZE, + testing_interval: DEFAULT_TESTING_INTERVAL, + retry_timeout: DEFAULT_RETRY_TIMEOUT, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +struct DebugV1_1_21 { + #[serde(with = "humantime_serde")] + node_stats_logging_delay: Duration, + + #[serde(with = "humantime_serde")] + node_stats_updating_delay: Duration, + + #[serde(with = "humantime_serde")] + packet_forwarding_initial_backoff: Duration, + + #[serde(with = "humantime_serde")] + packet_forwarding_maximum_backoff: Duration, + + #[serde(with = "humantime_serde")] + initial_connection_timeout: Duration, + + maximum_connection_buffer_size: usize, + + use_legacy_framed_packet_version: bool, +} + +impl From for Debug { + fn from(value: DebugV1_1_21) -> Self { + Debug { + node_stats_logging_delay: value.node_stats_logging_delay, + node_stats_updating_delay: value.node_stats_updating_delay, + packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: value.packet_forwarding_maximum_backoff, + initial_connection_timeout: value.initial_connection_timeout, + maximum_connection_buffer_size: value.maximum_connection_buffer_size, + use_legacy_framed_packet_version: value.use_legacy_framed_packet_version, + } + } +} + +impl Default for DebugV1_1_21 { + fn default() -> Self { + DebugV1_1_21 { + node_stats_logging_delay: DEFAULT_NODE_STATS_LOGGING_DELAY, + node_stats_updating_delay: DEFAULT_NODE_STATS_UPDATING_DELAY, + packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, + packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, + initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, + maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, + use_legacy_framed_packet_version: true, + } + } +} diff --git a/mixnode/src/config/persistence/mod.rs b/mixnode/src/config/persistence/mod.rs index b106e358ae..cbaa92be3d 100644 --- a/mixnode/src/config/persistence/mod.rs +++ b/mixnode/src/config/persistence/mod.rs @@ -1,4 +1,4 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub mod pathfinder; +pub mod paths; diff --git a/mixnode/src/config/persistence/pathfinder.rs b/mixnode/src/config/persistence/pathfinder.rs deleted file mode 100644 index 1740263859..0000000000 --- a/mixnode/src/config/persistence/pathfinder.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::config::Config; -use std::path::{Path, PathBuf}; - -#[derive(Debug)] -pub struct MixNodePathfinder { - identity_private_key: PathBuf, - identity_public_key: PathBuf, - private_sphinx_key: PathBuf, - public_sphinx_key: PathBuf, -} - -impl MixNodePathfinder { - pub fn new_from_config(config: &Config) -> Self { - MixNodePathfinder { - identity_private_key: config.get_private_identity_key_file(), - identity_public_key: config.get_public_identity_key_file(), - private_sphinx_key: config.get_private_sphinx_key_file(), - public_sphinx_key: config.get_public_sphinx_key_file(), - } - } - - pub fn private_identity_key(&self) -> &Path { - &self.identity_private_key - } - - pub fn public_identity_key(&self) -> &Path { - &self.identity_public_key - } - - pub fn private_encryption_key(&self) -> &Path { - &self.private_sphinx_key - } - - pub fn public_encryption_key(&self) -> &Path { - &self.public_sphinx_key - } -} diff --git a/mixnode/src/config/persistence/paths.rs b/mixnode/src/config/persistence/paths.rs new file mode 100644 index 0000000000..a7b82cc9e2 --- /dev/null +++ b/mixnode/src/config/persistence/paths.rs @@ -0,0 +1,90 @@ +// Copyright 2020-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::{default_config_directory, default_data_directory}; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem"; +pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem"; +pub const DEFAULT_PRIVATE_SPHINX_KEY_FILENAME: &str = "private_sphinx.pem"; +pub const DEFAULT_PUBLIC_SPHINX_KEY_FILENAME: &str = "public_sphinx.pem"; + +pub const DEFAULT_DESCRIPTION_FILENAME: &str = "description.toml"; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct MixNodePaths { + pub keys: KeysPaths, + + pub node_description: PathBuf, +} + +impl MixNodePaths { + pub fn new_default>(id: P) -> Self { + MixNodePaths { + keys: KeysPaths::new_default(id.as_ref()), + node_description: default_config_directory(id).join(DEFAULT_DESCRIPTION_FILENAME), + } + } + + pub fn private_identity_key(&self) -> &Path { + self.keys.private_identity_key() + } + + pub fn public_identity_key(&self) -> &Path { + self.keys.public_identity_key() + } + + pub fn private_encryption_key(&self) -> &Path { + self.keys.private_encryption_key() + } + + pub fn public_encryption_key(&self) -> &Path { + self.keys.public_encryption_key() + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +pub struct KeysPaths { + /// Path to file containing private identity key. + pub private_identity_key_file: PathBuf, + + /// Path to file containing public identity key. + pub public_identity_key_file: PathBuf, + + /// Path to file containing private sphinx key. + pub private_sphinx_key_file: PathBuf, + + /// Path to file containing public sphinx key. + pub public_sphinx_key_file: PathBuf, +} + +impl KeysPaths { + pub fn new_default>(id: P) -> Self { + let data_dir = default_data_directory(id); + + KeysPaths { + private_identity_key_file: data_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME), + public_identity_key_file: data_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME), + private_sphinx_key_file: data_dir.join(DEFAULT_PRIVATE_SPHINX_KEY_FILENAME), + public_sphinx_key_file: data_dir.join(DEFAULT_PUBLIC_SPHINX_KEY_FILENAME), + } + } + + pub fn private_identity_key(&self) -> &Path { + &self.private_identity_key_file + } + + pub fn public_identity_key(&self) -> &Path { + &self.public_identity_key_file + } + + pub fn private_encryption_key(&self) -> &Path { + &self.private_sphinx_key_file + } + + pub fn public_encryption_key(&self) -> &Path { + &self.public_sphinx_key_file + } +} diff --git a/mixnode/src/config/template.rs b/mixnode/src/config/template.rs index f222275083..a885a1ecbb 100644 --- a/mixnode/src/config/template.rs +++ b/mixnode/src/config/template.rs @@ -1,12 +1,11 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub(crate) fn config_template() -> &'static str { - // While using normal toml marshalling would have been way simpler with less overhead, - // I think it's useful to have comments attached to the saved config file to explain behaviour of - // particular fields. - // Note: any changes to the template must be reflected in the appropriate structs in verloc. - r#" +// While using normal toml marshalling would have been way simpler with less overhead, +// I think it's useful to have comments attached to the saved config file to explain behaviour of +// particular fields. +// Note: any changes to the template must be reflected in the appropriate structs. +pub(crate) const CONFIG_TEMPLATE: &str = r#" # This is a TOML config file. # For more information, see https://github.com/toml-lang/toml @@ -22,25 +21,6 @@ id = '{{ mixnode.id }}' # Socket address to which this mixnode will bind to and will be listening for packets. listening_address = '{{ mixnode.listening_address }}' -# Path to file containing private identity key. -private_identity_key_file = '{{ mixnode.private_identity_key_file }}' - -# Path to file containing public identity key. -public_identity_key_file = '{{ mixnode.public_identity_key_file }}' - -# Path to file containing private identity key. -private_sphinx_key_file = '{{ mixnode.private_sphinx_key_file }}' - -# Path to file containing public sphinx key. -public_sphinx_key_file = '{{ mixnode.public_sphinx_key_file }}' - -##### additional mixnode config options ##### - -# Optional address announced to the directory server for the clients to connect to. -# It is useful, say, in NAT scenarios or wanting to more easily update actual IP address -# later on by using name resolvable with a DNS query, such as `nymtech.net`. -announce_address = '{{ mixnode.announce_address }}' - # Port used for listening for all mixnet traffic. # (default: 1789) mix_port = {{ mixnode.mix_port }} @@ -60,14 +40,22 @@ nym_api_urls = [ {{/each}} ] -# Nym wallet address on the blockchain that should control this mixnode -wallet_address = '{{ mixnode.wallet_address }}' +[storage_paths] -##### advanced configuration options ##### +# Path to file containing private identity key. +keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' -# Absolute path to the home Nym Clients directory. -nym_root_directory = '{{ mixnode.nym_root_directory }}' +# Path to file containing public identity key. +keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}' +# Path to file containing private identity key. +keys.private_sphinx_key_file = '{{ storage_paths.keys.private_sphinx_key_file }}' + +# Path to file containing public sphinx key. +keys.public_sphinx_key_file = '{{ storage_paths.keys.public_sphinx_key_file }}' + +# Path to file containing description of this node. +node_description = '{{ storage_paths.node_description }}' ##### logging configuration options ##### @@ -75,5 +63,4 @@ nym_root_directory = '{{ mixnode.nym_root_directory }}' # TODO -"# -} +"#; diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index 7d4228a063..5681e02cc7 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -48,7 +48,7 @@ fn test_function() { } #[tokio::main] -async fn main() { +async fn main() -> anyhow::Result<()> { cfg_if::cfg_if! { if #[cfg(feature = "cpucycles")] { setup_tracing!("mixnode"); @@ -63,12 +63,14 @@ async fn main() { let args = Cli::parse(); setup_env(args.config_env_file.as_ref()); - commands::execute(args).await; + commands::execute(args).await?; cfg_if::cfg_if! { if #[cfg(feature = "cpucycles")] { opentelemetry::global::shutdown_tracer_provider(); }} + + Ok(()) } #[cfg(test)] diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index a1c11d202d..a6571adbd5 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.storage_paths.node_description).unwrap_or_default() } /// Loads identity keys stored on disk - pub(crate) fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair { + pub(crate) fn load_identity_keys(config: &Config) -> identity::KeyPair { let identity_keypair: identity::KeyPair = nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( - pathfinder.private_identity_key().to_owned(), - pathfinder.public_identity_key().to_owned(), + config.storage_paths.keys.private_identity_key(), + config.storage_paths.keys.public_identity_key(), )) .expect("Failed to read stored identity key files"); identity_keypair } /// Loads Sphinx keys stored on disk - fn load_sphinx_keys(pathfinder: &MixNodePathfinder) -> encryption::KeyPair { + fn load_sphinx_keys(config: &Config) -> encryption::KeyPair { let sphinx_keypair: encryption::KeyPair = nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( - pathfinder.private_encryption_key().to_owned(), - pathfinder.public_encryption_key().to_owned(), + config.storage_paths.keys.private_encryption_key(), + config.storage_paths.keys.public_encryption_key(), )) .expect("Failed to read stored sphinx key files"); sphinx_keypair @@ -88,13 +84,11 @@ impl MixNode { let node_details = nym_types::mixnode::MixnodeNodeDetailsResponse { identity_key: self.identity_keypair.public_key().to_base58_string(), sphinx_key: self.sphinx_keypair.public_key().to_base58_string(), - announce_address: self.config.get_announce_address(), - bind_address: self.config.get_listening_address().to_string(), - version: self.config.get_version().to_string(), - mix_port: self.config.get_mix_port(), - http_api_port: self.config.get_http_api_port(), - verloc_port: self.config.get_verloc_port(), - wallet_address: self.config.get_wallet_address().map(|x| x.to_string()), + bind_address: self.config.mixnode.listening_address, + version: self.config.mixnode.version.clone(), + mix_port: self.config.mixnode.mix_port, + http_api_port: self.config.mixnode.http_api_port, + verloc_port: self.config.mixnode.verloc_port, }; println!("{}", output.format(&node_details)); @@ -105,13 +99,16 @@ impl MixNode { atomic_verloc_result: AtomicVerlocResult, node_stats_pointer: SharedNodeStats, ) { - info!("Starting HTTP API on http://localhost:8000"); + info!( + "Starting HTTP API on http://{}:{}", + self.config.mixnode.listening_address, self.config.mixnode.http_api_port + ); let mut config = rocket::config::Config::release_default(); // bind to the same address as we are using for mixnodes - config.address = self.config.get_listening_address(); - config.port = self.config.get_http_api_port(); + config.address = self.config.mixnode.listening_address; + config.port = self.config.mixnode.http_api_port; let verloc_state = VerlocState::new(atomic_verloc_result); let descriptor = self.descriptor.clone(); @@ -119,7 +116,7 @@ impl MixNode { tokio::spawn(async move { rocket::build() .configure(config) - .mount("/", routes![verlocRoute, description, stats, hardware]) + .mount("/", routes![verloc_route, description, stats, hardware]) .register("/", catchers![not_found]) .manage(verloc_state) .manage(descriptor) @@ -135,8 +132,8 @@ impl MixNode { ) -> (SharedNodeStats, node_statistics::UpdateSender) { info!("Starting node stats controller..."); let controller = node_statistics::Controller::new( - self.config.get_node_stats_logging_delay(), - self.config.get_node_stats_updating_delay(), + self.config.debug.node_stats_logging_delay, + self.config.debug.node_stats_updating_delay, shutdown, ); let node_stats_pointer = controller.get_node_stats_data_pointer(); @@ -159,8 +156,8 @@ impl MixNode { let connection_handler = ConnectionHandler::new(packet_processor, delay_forwarding_channel); let listening_address = SocketAddr::new( - self.config.get_listening_address(), - self.config.get_mix_port(), + self.config.mixnode.listening_address, + self.config.mixnode.mix_port, ); Listener::new(listening_address, shutdown).start(connection_handler); @@ -174,11 +171,11 @@ impl MixNode { info!("Starting packet delay-forwarder..."); let client_config = nym_mixnet_client::Config::new( - self.config.get_packet_forwarding_initial_backoff(), - self.config.get_packet_forwarding_maximum_backoff(), - self.config.get_initial_connection_timeout(), - self.config.get_maximum_connection_buffer_size(), - self.config.get_use_legacy_sphinx_framing(), + self.config.debug.packet_forwarding_initial_backoff, + self.config.debug.packet_forwarding_maximum_backoff, + self.config.debug.initial_connection_timeout, + self.config.debug.maximum_connection_buffer_size, + self.config.debug.use_legacy_framed_packet_version, ); let mut packet_forwarder = DelayForwarder::new( @@ -199,8 +196,8 @@ impl MixNode { // this is a sanity check to make sure we didn't mess up with the minimum version at some point // and whether the user has run update if they're using old config // if this code exists in the node, it MUST BE compatible - let config_version = - parse_version(self.config.get_version()).expect("malformed version in the config file"); + let config_version = parse_version(&self.config.mixnode.version) + .expect("malformed version in the config file"); let minimum_version = parse_version(verloc::MINIMUM_NODE_VERSION).unwrap(); if config_version < minimum_version { error!("You seem to have not updated your mixnode configuration file - please run `upgrade` before attempting again"); @@ -210,19 +207,19 @@ impl MixNode { // use the same binding address with the HARDCODED port for time being (I don't like that approach personally) let listening_address = SocketAddr::new( - self.config.get_listening_address(), - self.config.get_verloc_port(), + self.config.mixnode.listening_address, + self.config.mixnode.verloc_port, ); let config = verloc::ConfigBuilder::new() .listening_address(listening_address) - .packets_per_node(self.config.get_measurement_packets_per_node()) - .connection_timeout(self.config.get_measurement_connection_timeout()) - .packet_timeout(self.config.get_measurement_packet_timeout()) - .delay_between_packets(self.config.get_measurement_delay_between_packets()) - .tested_nodes_batch_size(self.config.get_measurement_tested_nodes_batch_size()) - .testing_interval(self.config.get_measurement_testing_interval()) - .retry_timeout(self.config.get_measurement_retry_timeout()) + .packets_per_node(self.config.verloc.packets_per_node) + .connection_timeout(self.config.verloc.connection_timeout) + .packet_timeout(self.config.verloc.packet_timeout) + .delay_between_packets(self.config.verloc.delay_between_packets) + .tested_nodes_batch_size(self.config.verloc.tested_nodes_batch_size) + .testing_interval(self.config.verloc.testing_interval) + .retry_timeout(self.config.verloc.retry_timeout) .nym_api_urls(self.config.get_nym_api_endpoints()) .build(); @@ -242,8 +239,7 @@ impl MixNode { nym_validator_client::NymApiClient::new(nym_api.clone()) } - // TODO: ask DH whether this function still makes sense in ^0.10 - async fn check_if_same_ip_node_exists(&mut self) -> Option { + 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(()) } } diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index a1794b1ed0..79a85bed06 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -62,6 +62,7 @@ sqlx = { version = "0.6.2", features = [ okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] } rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] } schemars = { version = "0.8", features = ["preserve_order"] } +zeroize = { workspace = true } ## internal nym-bandwidth-controller = { path = "../common/bandwidth-controller" } diff --git a/nym-api/src/circulating_supply_api/mod.rs b/nym-api/src/circulating_supply_api/mod.rs index 39dfe28edc..ea8d4caea7 100644 --- a/nym-api/src/circulating_supply_api/mod.rs +++ b/nym-api/src/circulating_supply_api/mod.rs @@ -6,7 +6,7 @@ use okapi::openapi3::OpenApi; use rocket::Route; use rocket_okapi::{openapi_get_routes_spec, settings::OpenApiSettings}; -use crate::support::{config::Config, nyxd}; +use crate::support::{config, nyxd}; use self::cache::refresher::CirculatingSupplyCacheRefresher; @@ -24,16 +24,16 @@ pub(crate) fn circulating_supply_routes(settings: &OpenApiSettings) -> (Vec Result<()> { +pub(crate) fn init_keypair(config: &config::CoconutSigner) -> Result<()> { let mut rng = OsRng; let dkg_params = nym_dkg::bte::setup(); let kp = DkgKeyPair::new(&dkg_params, &mut rng); nym_pemstore::store_keypair( &kp, &nym_pemstore::KeyPairPath::new( - config.decryption_key_path(), - config.public_key_with_proof_path(), + &config.storage_paths.decryption_key_path, + &config.storage_paths.public_key_with_proof_path, ), )?; Ok(()) @@ -48,39 +48,40 @@ pub(crate) struct DkgController { impl DkgController { pub(crate) async fn new( - config: &Config, + config: &config::CoconutSigner, nyxd_client: nyxd::Client, coconut_keypair: CoconutKeyPair, rng: R, ) -> Result { let dkg_keypair = nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( - config.decryption_key_path(), - config.public_key_with_proof_path(), + &config.storage_paths.decryption_key_path, + &config.storage_paths.public_key_with_proof_path, ))?; if let Ok(coconut_keypair_value) = nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( - config.secret_key_path(), - config.verification_key_path(), + &config.storage_paths.secret_key_path, + &config.storage_paths.verification_key_path, )) { coconut_keypair.set(Some(coconut_keypair_value)).await; } let persistent_state = - PersistentState::load_from_file(config.persistent_state_path()).unwrap_or_default(); + PersistentState::load_from_file(&config.storage_paths.dkg_persistent_state_path) + .unwrap_or_default(); Ok(DkgController { dkg_client: DkgClient::new(nyxd_client), - secret_key_path: config.secret_key_path(), - verification_key_path: config.verification_key_path(), + secret_key_path: config.storage_paths.secret_key_path.clone(), + verification_key_path: config.storage_paths.verification_key_path.clone(), state: State::new( - config.persistent_state_path(), + config.storage_paths.dkg_persistent_state_path.clone(), persistent_state, - config.get_announce_address(), + config.announce_address.clone(), dkg_keypair, coconut_keypair, ), rng, - polling_rate: config.get_dkg_contract_polling_rate(), + polling_rate: config.debug.dkg_contract_polling_rate, }) } @@ -199,7 +200,7 @@ impl DkgController { // TODO: can we make it non-async? it seems we'd have to modify `coconut_keypair.set(coconut_keypair_value)` in new // could we do it? pub(crate) async fn start( - config: &Config, + config: &config::CoconutSigner, nyxd_client: nyxd::Client, coconut_keypair: CoconutKeyPair, rng: R, diff --git a/nym-api/src/coconut/dkg/state.rs b/nym-api/src/coconut/dkg/state.rs index cfbd7b8238..b50d6fa3d9 100644 --- a/nym-api/src/coconut/dkg/state.rs +++ b/nym-api/src/coconut/dkg/state.rs @@ -14,7 +14,7 @@ use nym_dkg::{NodeIndex, RecoveredVerificationKeys, Threshold}; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::BTreeMap; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use url::Url; fn bte_pk_serialize( @@ -192,12 +192,12 @@ impl From<&State> for PersistentState { } impl PersistentState { - pub fn save_to_file(&self, path: PathBuf) -> Result<(), CoconutError> { + pub fn save_to_file>(&self, path: P) -> Result<(), CoconutError> { std::fs::write(path, serde_json::to_string(self)?)?; Ok(()) } - pub fn load_from_file(path: PathBuf) -> Result { + pub fn load_from_file>(path: P) -> Result { Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?) } } diff --git a/nym-api/src/main.rs b/nym-api/src/main.rs index 55e1943404..39719ba0fe 100644 --- a/nym-api/src/main.rs +++ b/nym-api/src/main.rs @@ -19,7 +19,6 @@ use coconut::dkg::controller::DkgController; use log::info; use node_status_api::NodeStatusCache; use nym_bin_common::logging::setup_logging; -use nym_config::NymConfig; use nym_contract_cache::cache::NymContractCache; use nym_sphinx::receiver::SphinxMessageReceiver; use nym_task::TaskManager; @@ -88,13 +87,13 @@ async fn start_nym_api_tasks( // start all the caches first let nym_contract_cache_listener = nym_contract_cache::start_refresher( - &config, + &config.node_status_api, nym_contract_cache_state, nyxd_client.clone(), &shutdown, ); node_status_api::start_cache_refresh( - &config, + &config.node_status_api, nym_contract_cache_state, node_status_cache_state, maybe_storage, @@ -102,16 +101,16 @@ async fn start_nym_api_tasks( &shutdown, ); circulating_supply_api::start_cache_refresh( - &config, + &config.circulating_supply_cacher, nyxd_client.clone(), circulating_supply_cache_state, &shutdown, ); // start dkg task - if config.get_coconut_signer_enabled() { + if config.coconut_signer.enabled { DkgController::start( - &config, + &config.coconut_signer, nyxd_client.clone(), coconut_keypair, OsRng, @@ -122,12 +121,12 @@ async fn start_nym_api_tasks( // and then only start the uptime updater (and the monitor itself, duh) // if the monitoring if it's enabled - if config.get_network_monitor_enabled() { + if config.network_monitor.enabled { // if network monitor is enabled, the storage MUST BE available let storage = maybe_storage.unwrap(); network_monitor::start::( - &config, + &config.network_monitor, nym_contract_cache_state, storage, nyxd_client.clone(), @@ -138,7 +137,7 @@ async fn start_nym_api_tasks( HistoricalUptimeUpdater::start(storage, &shutdown); // start 'rewarding' if its enabled - if config.get_rewarding_enabled() { + if config.rewarding.enabled { epoch_operations::ensure_rewarding_permission(&nyxd_client).await?; RewardedSetUpdater::start(nyxd_client, nym_contract_cache_state, storage, &shutdown); } @@ -160,7 +159,7 @@ async fn run_nym_api(cli_args: CliArgs) -> Result<(), Box +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::network_monitor; use crate::network_monitor::monitor::preparer::PacketPreparer; use crate::network_monitor::monitor::processor::{ ReceivedProcessor, ReceivedProcessorReceiver, ReceivedProcessorSender, @@ -14,8 +13,7 @@ use crate::network_monitor::monitor::summary_producer::SummaryProducer; use crate::network_monitor::monitor::Monitor; use crate::nym_contract_cache::cache::NymContractCache; use crate::storage::NymApiStorage; -use crate::support::config::Config; -use crate::support::nyxd; +use crate::support::{config, nyxd}; use futures::channel::mpsc; use nym_bandwidth_controller::BandwidthController; use nym_credential_storage::persistent_storage::PersistentStorage; @@ -34,7 +32,7 @@ pub(crate) mod test_route; pub(crate) const ROUTE_TESTING_TEST_NONCE: u64 = 0; pub(crate) fn setup<'a>( - config: &'a Config, + config: &'a config::NetworkMonitor, nym_contract_cache_state: &NymContractCache, storage: &NymApiStorage, nyxd_client: nyxd::Client, @@ -48,7 +46,7 @@ pub(crate) fn setup<'a>( } pub(crate) struct NetworkMonitorBuilder<'a> { - config: &'a Config, + config: &'a config::NetworkMonitor, nyxd_client: nyxd::Client, node_status_storage: NymApiStorage, validator_cache: NymContractCache, @@ -56,7 +54,7 @@ pub(crate) struct NetworkMonitorBuilder<'a> { impl<'a> NetworkMonitorBuilder<'a> { pub(crate) fn new( - config: &'a Config, + config: &'a config::NetworkMonitor, nyxd_client: nyxd::Client, node_status_storage: NymApiStorage, validator_cache: NymContractCache, @@ -87,7 +85,7 @@ impl<'a> NetworkMonitorBuilder<'a> { let packet_preparer = new_packet_preparer( self.validator_cache, - self.config.get_per_node_test_packets(), + self.config.debug.per_node_test_packets, Arc::clone(&ack_key), *identity_keypair.public_key(), *encryption_keypair.public_key(), @@ -96,7 +94,7 @@ impl<'a> NetworkMonitorBuilder<'a> { let bandwidth_controller = { BandwidthController::new( nym_credential_storage::initialise_persistent_storage( - self.config.get_credentials_database_path(), + &self.config.storage_paths.credentials_database_path, ) .await, self.nyxd_client.clone(), @@ -107,9 +105,9 @@ impl<'a> NetworkMonitorBuilder<'a> { self.config, gateway_status_update_sender, Arc::clone(&identity_keypair), - self.config.get_gateway_sending_rate(), + self.config.debug.gateway_sending_rate, bandwidth_controller, - self.config.get_disabled_credentials_mode(), + self.config.debug.disabled_credentials_mode, ); let received_processor = new_received_processor( @@ -117,13 +115,13 @@ impl<'a> NetworkMonitorBuilder<'a> { Arc::clone(&encryption_keypair), ack_key, ); - let summary_producer = new_summary_producer(self.config.get_per_node_test_packets()); + let summary_producer = new_summary_producer(self.config.debug.per_node_test_packets); let packet_receiver = new_packet_receiver( gateway_status_update_receiver, received_processor_sender_channel, ); - let monitor = monitor::Monitor::new( + let monitor = Monitor::new( self.config, packet_preparer, packet_sender, @@ -176,7 +174,7 @@ fn new_packet_preparer( } fn new_packet_sender( - config: &Config, + config: &config::NetworkMonitor, gateways_status_updater: GatewayClientUpdateSender, local_identity: Arc, max_sending_rate: usize, @@ -186,9 +184,9 @@ fn new_packet_sender( PacketSender::new( gateways_status_updater, local_identity, - config.get_gateway_response_timeout(), - config.get_gateway_connection_timeout(), - config.get_max_concurrent_gateway_clients(), + config.debug.gateway_response_timeout, + config.debug.gateway_connection_timeout, + config.debug.max_concurrent_gateway_clients, max_sending_rate, bandwidth_controller, disabled_credentials_mode, @@ -219,14 +217,13 @@ fn new_packet_receiver( // TODO: 1) does it still have to have separate builder or could we get rid of it now? // TODO: 2) how do we make it non-async as other 'start' methods? pub(crate) async fn start( - config: &Config, + config: &config::NetworkMonitor, nym_contract_cache_state: &NymContractCache, storage: &NymApiStorage, nyxd_client: nyxd::Client, shutdown: &TaskManager, ) { - let monitor_builder = - network_monitor::setup(config, nym_contract_cache_state, storage, nyxd_client); + let monitor_builder = setup(config, nym_contract_cache_state, storage, nyxd_client); info!("Starting network monitor..."); let runnables: NetworkMonitorRunnables = monitor_builder.build().await; runnables.spawn_tasks(shutdown); diff --git a/nym-api/src/network_monitor/monitor/mod.rs b/nym-api/src/network_monitor/monitor/mod.rs index 77e48634d5..126a2f1a5f 100644 --- a/nym-api/src/network_monitor/monitor/mod.rs +++ b/nym-api/src/network_monitor/monitor/mod.rs @@ -8,7 +8,7 @@ use crate::network_monitor::monitor::summary_producer::{SummaryProducer, TestSum use crate::network_monitor::test_packet::NodeTestMessage; use crate::network_monitor::test_route::TestRoute; use crate::storage::NymApiStorage; -use crate::support::config::Config; +use crate::support::config; use log::{debug, error, info}; use nym_sphinx::params::PacketType; use nym_sphinx::receiver::MessageReceiver; @@ -51,7 +51,7 @@ pub(super) struct Monitor { impl Monitor { pub(super) fn new( - config: &Config, + config: &config::NetworkMonitor, packet_preparer: PacketPreparer, packet_sender: PacketSender, received_processor: ReceivedProcessor, @@ -66,12 +66,12 @@ impl Monitor { received_processor, summary_producer, node_status_storage, - run_interval: config.get_network_monitor_run_interval(), - gateway_ping_interval: config.get_gateway_ping_interval(), - packet_delivery_timeout: config.get_packet_delivery_timeout(), - route_test_packets: config.get_route_test_packets(), - test_routes: config.get_test_routes(), - minimum_test_routes: config.get_minimum_test_routes(), + run_interval: config.debug.run_interval, + gateway_ping_interval: config.debug.gateway_ping_interval, + packet_delivery_timeout: config.debug.packet_delivery_timeout, + route_test_packets: config.debug.route_test_packets, + test_routes: config.debug.test_routes, + minimum_test_routes: config.debug.minimum_test_routes, packet_type, } } diff --git a/nym-api/src/node_status_api/mod.rs b/nym-api/src/node_status_api/mod.rs index 6906e8aab3..d3488eb99a 100644 --- a/nym-api/src/node_status_api/mod.rs +++ b/nym-api/src/node_status_api/mod.rs @@ -8,9 +8,10 @@ use rocket::Route; use rocket_okapi::{openapi_get_routes_spec, settings::OpenApiSettings}; use std::time::Duration; +use crate::support::config; use crate::{ nym_contract_cache::cache::NymContractCache, - support::{self, config::Config, storage}, + support::{self, storage}, }; use self::cache::refresher::NodeStatusCacheRefresher; @@ -74,7 +75,7 @@ pub(crate) fn node_status_routes( /// It is primarily refreshed in-sync with the nym contract cache, however provide a fallback /// caching interval that is twice the nym contract cache pub(crate) fn start_cache_refresh( - config: &Config, + config: &config::NodeStatusAPI, nym_contract_cache_state: &NymContractCache, node_status_cache_state: &NodeStatusCache, storage: Option<&storage::NymApiStorage>, @@ -83,7 +84,7 @@ pub(crate) fn start_cache_refresh( ) { let mut nym_api_cache_refresher = NodeStatusCacheRefresher::new( node_status_cache_state.to_owned(), - config.get_node_status_caching_interval(), + config.debug.caching_interval, nym_contract_cache_state.to_owned(), nym_contract_cache_listener, storage.cloned(), diff --git a/nym-api/src/nym_contract_cache/mod.rs b/nym-api/src/nym_contract_cache/mod.rs index 2926a3304f..91b686bc1f 100644 --- a/nym-api/src/nym_contract_cache/mod.rs +++ b/nym-api/src/nym_contract_cache/mod.rs @@ -2,8 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::nym_contract_cache::cache::NymContractCache; -use crate::support::config::Config; -use crate::support::{self, nyxd}; +use crate::support::{self, config, nyxd}; use nym_task::TaskManager; use okapi::openapi3::OpenApi; use rocket::Route; @@ -34,14 +33,14 @@ pub(crate) fn nym_contract_cache_routes(settings: &OpenApiSettings) -> (Vec tokio::sync::watch::Receiver { let nym_contract_cache_refresher = NymContractCacheRefresher::new( nyxd_client, - config.get_topology_caching_interval(), + config.debug.caching_interval, nym_contract_cache_state.to_owned(), ); let nym_contract_cache_listener = nym_contract_cache_refresher.subscribe(); diff --git a/nym-api/src/support/cli/mod.rs b/nym-api/src/support/cli/mod.rs index 39189c49f1..facbc6c7ff 100644 --- a/nym-api/src/support/cli/mod.rs +++ b/nym-api/src/support/cli/mod.rs @@ -2,14 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use super::config::Config; +use crate::support::config::default_config_filepath; +use crate::support::config::helpers::{initialise_new, try_load_current_config}; use ::nym_config::defaults::var_names::{MIXNET_CONTRACT_ADDRESS, VESTING_CONTRACT_ADDRESS}; use anyhow::Result; use clap::Parser; use lazy_static::lazy_static; use nym_bin_common::build_information::BinaryBuildInformation; -use nym_config::{NymConfig, OptionalSet}; +use nym_config::OptionalSet; use nym_validator_client::nyxd; -use std::fs; lazy_static! { pub static ref PRETTY_BUILD_INFORMATION: String = @@ -106,41 +107,8 @@ pub(crate) struct CliArgs { pub(crate) enable_coconut: Option, } -pub(crate) fn build_config(args: CliArgs) -> Result { - let id = args.id.clone(); - - // try to load config from the file, if it doesn't exist, use default values - let (config_from_file, already_initialized) = match Config::load_from_file(&id) { - Ok(cfg) => (cfg, true), - Err(_) => { - let config_path = Config::default_config_file_path(&id) - .into_os_string() - .into_string() - .unwrap(); - warn!( - "Could not load the configuration file from {}. Either the file did not exist or was malformed. Using the default values instead", - config_path - ); - (Config::new(), false) - } - }; - - let config = override_config(config_from_file, args); - - if !already_initialized { - fs::create_dir_all(Config::default_config_directory(&id)) - .expect("Could not create config directory"); - fs::create_dir_all(Config::default_data_directory(&id)) - .expect("Could not create data directory"); - crate::coconut::dkg::controller::init_keypair(&config)?; - } - - Ok(config) -} - pub(crate) fn override_config(config: Config, args: CliArgs) -> Config { config - .with_id(&args.id) .with_optional(Config::with_custom_nyxd_validator, args.nyxd_validator) .with_optional_env( Config::with_custom_mixnet_contract, @@ -174,3 +142,25 @@ pub(crate) fn override_config(config: Config, args: CliArgs) -> Config { .with_optional(Config::with_announce_address, args.announce_address) .with_optional(Config::with_coconut_signer_enabled, args.enable_coconut) } + +pub(crate) fn build_config(args: CliArgs) -> Result { + let id = args.id.clone(); + + // try to load config from the file, if it doesn't exist, use default values + let config = match try_load_current_config(&id) { + Ok(cfg) => cfg, + Err(err) => { + let config_path = default_config_filepath(&id); + warn!( + "Could not load the configuration file from {}: {err}. Either the file did not exist or was malformed. Using the default values instead", + config_path.display() + ); + + initialise_new(&id)? + } + }; + + let config = override_config(config, args); + + Ok(config) +} diff --git a/nym-api/src/support/config/helpers.rs b/nym-api/src/support/config/helpers.rs new file mode 100644 index 0000000000..4c20088ad0 --- /dev/null +++ b/nym-api/src/support/config/helpers.rs @@ -0,0 +1,41 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::support::config::old_config_v1_1_21::ConfigV1_1_21; +use crate::support::config::{default_config_directory, default_data_directory, Config}; +use anyhow::Result; +use std::{fs, io}; + +fn try_upgrade_v1_1_21_config(id: &str) -> Result<()> { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.21 (which is incompatible with the current, i.e. 1.1.22+) + let Ok(old_config) = ConfigV1_1_21::load_from_file(id) else { + // if we failed to load it, there might have been nothing to upgrade + // or maybe it was an even older file. in either way. just ignore it and carry on with our day + return Ok(()); + }; + info!("It seems the nym-api is using <= v1.1.21 config template."); + info!("It is going to get updated to the current specification."); + + let updated: Config = old_config.into(); + Ok(updated.save_to_default_location()?) +} + +fn init_paths(id: &str) -> io::Result<()> { + fs::create_dir_all(default_data_directory(id))?; + fs::create_dir_all(default_config_directory(id)) +} + +pub(crate) fn initialise_new(id: &str) -> Result { + let config = Config::new(id); + init_paths(id)?; + crate::coconut::dkg::controller::init_keypair(&config.coconut_signer)?; + Ok(config) +} + +pub(crate) fn try_load_current_config(id: &str) -> Result { + try_upgrade_v1_1_21_config(id)?; + + Ok(Config::read_from_default_path(id)?) +} diff --git a/nym-api/src/support/config/mod.rs b/nym-api/src/support/config/mod.rs index b8bb3ad0e9..1e74708bda 100644 --- a/nym-api/src/support/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -1,19 +1,32 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use self::template::config_template; -use nym_config::defaults::mainnet::{MIXNET_CONTRACT_ADDRESS, VESTING_CONTRACT_ADDRESS}; -use nym_config::defaults::DEFAULT_NYM_API_PORT; -use nym_config::NymConfig; +use crate::support::config::persistence::{ + CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, +}; +use crate::support::config::template::CONFIG_TEMPLATE; +use nym_config::defaults::mainnet; +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 nym_validator_client::nyxd; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::io; +use std::path::{Path, PathBuf}; use std::time::Duration; use url::Url; +use zeroize::{Zeroize, ZeroizeOnDrop}; +pub(crate) mod helpers; +pub(crate) mod old_config_v1_1_21; +mod persistence; mod template; +const DEFAULT_NYM_APIS_DIR: &str = "nym-api"; + pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; +pub const DEFAULT_NYM_API_PORT: u16 = 8080; pub const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10); @@ -41,363 +54,85 @@ const DEFAULT_MONITOR_THRESHOLD: u8 = 60; const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50; const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20; -#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +/// Derive default path to nym-api's config directory. +/// It should get resolved to `$HOME/.nym/nym-api//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_NYM_APIS_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) +} + +/// Derive default path to nym-api's config file. +/// It should get resolved to `$HOME/.nym/nym-api//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) +} + +/// Derive default path to nym-api's data directory where files, such as keys, are stored. +/// It should get resolved to `$HOME/.nym/nym-api//data` +pub fn default_data_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_NYM_APIS_DIR) + .join(id) + .join(DEFAULT_DATA_DIR) +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct Config { - #[serde(default)] - base: Base, + pub base: Base, - #[serde(default)] - network_monitor: NetworkMonitor, + // TODO: perhaps introduce separate 'path finder' field for all the paths and directories like we have with other configs + pub network_monitor: NetworkMonitor, - #[serde(default)] - node_status_api: NodeStatusAPI, + pub node_status_api: NodeStatusAPI, - #[serde(default)] - topology_cacher: TopologyCacher, + pub topology_cacher: TopologyCacher, - #[serde(default)] - circulating_supply_cacher: CirculatingSupplyCacher, + pub circulating_supply_cacher: CirculatingSupplyCacher, - #[serde(default)] - rewarding: Rewarding, + pub rewarding: Rewarding, - #[serde(default)] - coconut_signer: CoconutSigner, + pub coconut_signer: CoconutSigner, } -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("nym-api") - } - - fn try_default_root_directory() -> Option { - dirs::home_dir().map(|path| path.join(".nym").join("nym-api")) - } - - fn root_directory(&self) -> PathBuf { - Self::default_root_directory() - } - - fn config_directory(&self) -> PathBuf { - self.root_directory().join(self.get_id()).join("config") - } - - fn data_directory(&self) -> PathBuf { - self.root_directory().join(self.get_id()).join("data") - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -pub struct Base { - /// ID specifies the human readable ID of this particular nym-api. - id: String, - - local_validator: Url, - - /// Address announced to the directory server for the clients to connect to. - // It is useful, say, in NAT scenarios or wanting to more easily update actual IP address - // later on by using name resolvable with a DNS query, such as `nymtech.net`. - announce_address: Url, - - /// Address of the validator contract managing the network - mixnet_contract_address: nyxd::AccountId, - - /// Address of the vesting contract holding locked tokens - vesting_contract_address: nyxd::AccountId, - - /// Mnemonic used for rewarding and/or multisig operations - mnemonic: bip39::Mnemonic, -} - -impl Default for Base { - fn default() -> Self { - let default_validator: Url = DEFAULT_LOCAL_VALIDATOR - .parse() - .expect("default local validator is malformed!"); - let mut default_announce_address = default_validator.clone(); - default_announce_address - .set_port(Some(DEFAULT_NYM_API_PORT)) - .expect("default local validator is malformed!"); - - Base { - id: String::default(), - local_validator: default_validator, - announce_address: default_announce_address, - mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.parse().unwrap(), - vesting_contract_address: VESTING_CONTRACT_ADDRESS.parse().unwrap(), - mnemonic: bip39::Mnemonic::generate(24).unwrap(), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -pub struct NetworkMonitor { - // Mixnodes and gateways with relialability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set. - min_mixnode_reliability: u8, // defaults to 50 - min_gateway_reliability: u8, // defaults to 20 - /// Specifies whether network monitoring service is enabled in this process. - enabled: bool, - - /// Indicates whether this validator api is running in a disabled credentials mode, thus attempting - /// to claim bandwidth without presenting bandwidth credentials. - #[serde(default)] - disabled_credentials_mode: bool, - - /// Specifies the interval at which the network monitor sends the test packets. - #[serde(with = "humantime_serde")] - run_interval: Duration, - - /// Specifies interval at which we should be sending ping packets to all active gateways - /// in order to keep the websocket connections alive. - #[serde(with = "humantime_serde")] - gateway_ping_interval: Duration, - - /// Specifies maximum rate (in packets per second) of test packets being sent to gateway - gateway_sending_rate: usize, - - /// Maximum number of gateway clients the network monitor will try to talk to concurrently. - /// 0 = no limit - max_concurrent_gateway_clients: usize, - - /// Maximum allowed time for receiving gateway response. - #[serde(with = "humantime_serde")] - gateway_response_timeout: Duration, - - /// Maximum allowed time for the gateway connection to get established. - #[serde(with = "humantime_serde")] - gateway_connection_timeout: Duration, - - /// Specifies the duration the monitor is going to wait after sending all measurement - /// packets before declaring nodes unreachable. - #[serde(with = "humantime_serde")] - packet_delivery_timeout: Duration, - - /// Path to the database containing bandwidth credentials of this client. - credentials_database_path: PathBuf, - - /// Desired number of test routes to be constructed (and working) during a monitor test run. - test_routes: usize, - - /// The minimum number of test routes that need to be constructed (and working) in order for - /// a monitor test run to be valid. - minimum_test_routes: usize, - - /// Number of test packets sent via each pseudorandom route to verify whether they work correctly, - /// before using them for testing the rest of the network. - route_test_packets: usize, - - /// Number of test packets sent to each node during regular monitor test run. - per_node_test_packets: usize, -} - -impl NetworkMonitor { - pub const DB_FILE: &'static str = "credentials_database.db"; - - fn default_credentials_database_path(id: &str) -> PathBuf { - Config::default_data_directory(id).join(Self::DB_FILE) - } -} - -impl Default for NetworkMonitor { - fn default() -> Self { - NetworkMonitor { - min_mixnode_reliability: DEFAULT_MIN_MIXNODE_RELIABILITY, - min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY, - enabled: false, - disabled_credentials_mode: true, - run_interval: DEFAULT_MONITOR_RUN_INTERVAL, - gateway_ping_interval: DEFAULT_GATEWAY_PING_INTERVAL, - gateway_sending_rate: DEFAULT_GATEWAY_SENDING_RATE, - max_concurrent_gateway_clients: DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS, - gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, - gateway_connection_timeout: DEFAULT_GATEWAY_CONNECTION_TIMEOUT, - packet_delivery_timeout: DEFAULT_PACKET_DELIVERY_TIMEOUT, - credentials_database_path: Default::default(), - test_routes: DEFAULT_TEST_ROUTES, - minimum_test_routes: DEFAULT_MINIMUM_TEST_ROUTES, - route_test_packets: DEFAULT_ROUTE_TEST_PACKETS, - per_node_test_packets: DEFAULT_PER_NODE_TEST_PACKETS, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -pub struct NodeStatusAPI { - /// Path to the database file containing uptime statuses for all mixnodes and gateways. - database_path: PathBuf, - - #[serde(with = "humantime_serde")] - caching_interval: Duration, -} - -impl NodeStatusAPI { - pub const DB_FILE: &'static str = "db.sqlite"; - - fn default_database_path(id: &str) -> PathBuf { - Config::default_data_directory(id).join(Self::DB_FILE) - } -} - -impl Default for NodeStatusAPI { - fn default() -> Self { - NodeStatusAPI { - database_path: Default::default(), - caching_interval: DEFAULT_NODE_STATUS_CACHE_INTERVAL, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -pub struct TopologyCacher { - #[serde(with = "humantime_serde")] - caching_interval: Duration, -} - -impl Default for TopologyCacher { - fn default() -> Self { - TopologyCacher { - caching_interval: DEFAULT_TOPOLOGY_CACHE_INTERVAL, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -pub struct CirculatingSupplyCacher { - enabled: bool, - - #[serde(with = "humantime_serde")] - caching_interval: Duration, -} - -impl Default for CirculatingSupplyCacher { - fn default() -> Self { - CirculatingSupplyCacher { - enabled: true, - caching_interval: DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -pub struct Rewarding { - /// Specifies whether rewarding service is enabled in this process. - enabled: bool, - - /// Specifies the minimum percentage of monitor test run data present in order to - /// distribute rewards for given interval. - /// Note, only values in range 0-100 are valid - minimum_interval_monitor_threshold: u8, -} - -impl Default for Rewarding { - fn default() -> Self { - Rewarding { - enabled: false, - minimum_interval_monitor_threshold: DEFAULT_MONITOR_THRESHOLD, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -pub struct CoconutSigner { - /// Specifies whether rewarding service is enabled in this process. - enabled: bool, - - /// Path to a JSON file where state is persisted between different stages of DKG. - dkg_persistent_state_path: PathBuf, - - /// Path to the coconut verification key. - verification_key_path: PathBuf, - - /// Path to the coconut secret key. - secret_key_path: PathBuf, - - /// Path to the dkg dealer decryption key. - decryption_key_path: PathBuf, - - /// Path to the dkg dealer public key with proof. - public_key_with_proof_path: PathBuf, - - /// Duration of the interval for polling the dkg contract. - dkg_contract_polling_rate: Duration, -} - -impl CoconutSigner { - pub const DKG_PERSISTENT_STATE_FILE: &'static str = "dkg_persistent_state.json"; - pub const DKG_DECRYPTION_KEY_FILE: &'static str = "dkg_decryption_key.pem"; - pub const DKG_PUBLIC_KEY_WITH_PROOF_FILE: &'static str = "dkg_public_key_with_proof.pem"; - pub const COCONUT_VERIFICATION_KEY_FILE: &'static str = "coconut_verification_key.pem"; - pub const COCONUT_SECRET_KEY_FILE: &'static str = "coconut_secret_key.pem"; - - fn default_coconut_verification_key_path(id: &str) -> PathBuf { - Config::default_data_directory(id).join(Self::COCONUT_VERIFICATION_KEY_FILE) - } - - fn default_coconut_secret_key_path(id: &str) -> PathBuf { - Config::default_data_directory(id).join(Self::COCONUT_SECRET_KEY_FILE) - } - - fn default_dkg_persistent_state_path(id: &str) -> PathBuf { - Config::default_data_directory(id).join(Self::DKG_PERSISTENT_STATE_FILE) - } - - fn default_dkg_decryption_key_path(id: &str) -> PathBuf { - Config::default_data_directory(id).join(Self::DKG_DECRYPTION_KEY_FILE) - } - - fn default_dkg_public_key_with_proof_path(id: &str) -> PathBuf { - Config::default_data_directory(id).join(Self::DKG_PUBLIC_KEY_WITH_PROOF_FILE) - } -} - -impl Default for CoconutSigner { - fn default() -> Self { - Self { - enabled: Default::default(), - dkg_persistent_state_path: Default::default(), - verification_key_path: Default::default(), - secret_key_path: Default::default(), - decryption_key_path: Default::default(), - public_key_with_proof_path: Default::default(), - dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE, - } + CONFIG_TEMPLATE } } impl Config { - pub fn new() -> Self { - Config::default() + pub fn new>(id: S) -> Self { + let base_data_dir = default_data_directory(id.as_ref()); + Config { + base: Base::new_default(id.as_ref()), + network_monitor: NetworkMonitor::new_default(&base_data_dir), + node_status_api: NodeStatusAPI::new_default(&base_data_dir), + topology_cacher: Default::default(), + circulating_supply_cacher: Default::default(), + rewarding: Default::default(), + coconut_signer: CoconutSigner::new_default(base_data_dir), + } } - pub fn with_id(mut self, id: &str) -> Self { - self.base.id = id.to_string(); - self.node_status_api.database_path = NodeStatusAPI::default_database_path(id); - self.network_monitor.credentials_database_path = - NetworkMonitor::default_credentials_database_path(id); - self.coconut_signer.dkg_persistent_state_path = - CoconutSigner::default_dkg_persistent_state_path(id); - self.coconut_signer.verification_key_path = - CoconutSigner::default_coconut_verification_key_path(id); - self.coconut_signer.secret_key_path = CoconutSigner::default_coconut_secret_key_path(id); - self.coconut_signer.decryption_key_path = - CoconutSigner::default_dkg_decryption_key_path(id); - self.coconut_signer.public_key_with_proof_path = - CoconutSigner::default_dkg_public_key_with_proof_path(id); - self + 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 default_location(&self) -> PathBuf { + default_config_filepath(&self.base.id) + } + + pub fn save_to_default_location(&self) -> io::Result<()> { + let config_save_location: PathBuf = self.default_location(); + save_formatted_config_to_file(self, config_save_location) } pub fn with_network_monitor_enabled(mut self, enabled: bool) -> Self { @@ -406,7 +141,7 @@ impl Config { } pub fn with_disabled_credentials_mode(mut self, disabled_credentials_mode: bool) -> Self { - self.network_monitor.disabled_credentials_mode = disabled_credentials_mode; + self.network_monitor.debug.disabled_credentials_mode = disabled_credentials_mode; self } @@ -426,7 +161,7 @@ impl Config { } pub fn with_announce_address(mut self, announce_address: Url) -> Self { - self.base.announce_address = announce_address; + self.coconut_signer.announce_address = announce_address; self } @@ -446,52 +181,24 @@ impl Config { } pub fn with_minimum_interval_monitor_threshold(mut self, threshold: u8) -> Self { - self.rewarding.minimum_interval_monitor_threshold = threshold; + self.rewarding.debug.minimum_interval_monitor_threshold = threshold; self } pub fn with_min_mixnode_reliability(mut self, min_mixnode_reliability: u8) -> Self { - self.network_monitor.min_mixnode_reliability = min_mixnode_reliability; + self.network_monitor.debug.min_mixnode_reliability = min_mixnode_reliability; self } pub fn with_min_gateway_reliability(mut self, min_gateway_reliability: u8) -> Self { - self.network_monitor.min_gateway_reliability = min_gateway_reliability; + self.network_monitor.debug.min_gateway_reliability = min_gateway_reliability; self } - pub fn get_id(&self) -> String { - self.base.id.clone() - } - - pub fn get_network_monitor_enabled(&self) -> bool { - self.network_monitor.enabled - } - - pub fn get_coconut_signer_enabled(&self) -> bool { - self.coconut_signer.enabled - } - - pub fn get_disabled_credentials_mode(&self) -> bool { - self.network_monitor.disabled_credentials_mode - } - - pub fn get_credentials_database_path(&self) -> PathBuf { - self.network_monitor.credentials_database_path.clone() - } - - pub fn get_rewarding_enabled(&self) -> bool { - self.rewarding.enabled - } - pub fn get_nyxd_url(&self) -> Url { self.base.local_validator.clone() } - pub fn get_announce_address(&self) -> Url { - self.base.announce_address.clone() - } - pub fn get_mixnet_contract_address(&self) -> nyxd::AccountId { self.base.mixnet_contract_address.clone() } @@ -503,98 +210,322 @@ impl Config { pub fn get_mnemonic(&self) -> bip39::Mnemonic { self.base.mnemonic.clone() } +} - pub fn get_network_monitor_run_interval(&self) -> Duration { - self.network_monitor.run_interval - } +// we only really care about the mnemonic being zeroized +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Zeroize, ZeroizeOnDrop)] +pub struct Base { + /// ID specifies the human readable ID of this particular nym-api. + id: String, - pub fn get_gateway_ping_interval(&self) -> Duration { - self.network_monitor.gateway_ping_interval - } + #[zeroize(skip)] + local_validator: Url, - pub fn get_packet_delivery_timeout(&self) -> Duration { - self.network_monitor.packet_delivery_timeout - } + /// Address of the validator contract managing the network + #[zeroize(skip)] + mixnet_contract_address: nyxd::AccountId, - pub fn get_gateway_sending_rate(&self) -> usize { - self.network_monitor.gateway_sending_rate - } + /// Address of the vesting contract holding locked tokens + #[zeroize(skip)] + vesting_contract_address: nyxd::AccountId, - pub fn get_max_concurrent_gateway_clients(&self) -> usize { - self.network_monitor.max_concurrent_gateway_clients - } + /// Mnemonic used for rewarding and/or multisig operations + // TODO: similarly to the note in gateway, this should get moved to a separate file + mnemonic: bip39::Mnemonic, +} - pub fn get_gateway_response_timeout(&self) -> Duration { - self.network_monitor.gateway_response_timeout - } +impl Base { + pub fn new_default>(id: S) -> Self { + let default_validator: Url = DEFAULT_LOCAL_VALIDATOR + .parse() + .expect("default local validator is malformed!"); - pub fn get_gateway_connection_timeout(&self) -> Duration { - self.network_monitor.gateway_connection_timeout - } - - pub fn get_test_routes(&self) -> usize { - self.network_monitor.test_routes - } - - pub fn get_minimum_test_routes(&self) -> usize { - self.network_monitor.minimum_test_routes - } - - pub fn get_route_test_packets(&self) -> usize { - self.network_monitor.route_test_packets - } - - pub fn get_per_node_test_packets(&self) -> usize { - self.network_monitor.per_node_test_packets - } - - pub fn get_topology_caching_interval(&self) -> Duration { - self.topology_cacher.caching_interval - } - - pub fn get_node_status_caching_interval(&self) -> Duration { - self.node_status_api.caching_interval - } - - pub fn get_circulating_supply_caching_interval(&self) -> Duration { - self.circulating_supply_cacher.caching_interval - } - - pub fn get_circulating_supply_enabled(&self) -> bool { - self.circulating_supply_cacher.enabled - } - - pub fn get_node_status_api_database_path(&self) -> PathBuf { - self.node_status_api.database_path.clone() - } - - pub fn persistent_state_path(&self) -> PathBuf { - self.coconut_signer.dkg_persistent_state_path.clone() - } - - pub fn verification_key_path(&self) -> PathBuf { - self.coconut_signer.verification_key_path.clone() - } - - pub fn secret_key_path(&self) -> PathBuf { - self.coconut_signer.secret_key_path.clone() - } - - pub fn decryption_key_path(&self) -> PathBuf { - self.coconut_signer.decryption_key_path.clone() - } - - pub fn public_key_with_proof_path(&self) -> PathBuf { - self.coconut_signer.public_key_with_proof_path.clone() - } - - pub fn get_dkg_contract_polling_rate(&self) -> Duration { - self.coconut_signer.dkg_contract_polling_rate - } - - // TODO: Remove if still unused - #[allow(dead_code)] - pub fn get_minimum_interval_monitor_threshold(&self) -> u8 { - self.rewarding.minimum_interval_monitor_threshold + Base { + id: id.into(), + local_validator: default_validator, + mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS.parse().unwrap(), + vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS.parse().unwrap(), + // this this doesn't make any sense since you really have a mnemonic beforehand... + mnemonic: bip39::Mnemonic::generate(24).unwrap(), + } + } +} + +// this got separated into 2 structs so that we could have a sane `default` implementation for the latter +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct NetworkMonitor { + /// Specifies whether network monitoring service is enabled in this process. + pub enabled: bool, + + pub storage_paths: NetworkMonitorPaths, + + #[serde(default)] + pub debug: NetworkMonitorDebug, +} + +impl NetworkMonitor { + pub fn new_default>(id: P) -> Self { + NetworkMonitor { + enabled: false, + storage_paths: NetworkMonitorPaths::new_default(id), + debug: Default::default(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct NetworkMonitorDebug { + // Mixnodes and gateways with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set. + pub min_mixnode_reliability: u8, // defaults to 50 + pub min_gateway_reliability: u8, // defaults to 20 + + /// Indicates whether this validator api is running in a disabled credentials mode, thus attempting + /// to claim bandwidth without presenting bandwidth credentials. + pub disabled_credentials_mode: bool, + + /// Specifies the interval at which the network monitor sends the test packets. + #[serde(with = "humantime_serde")] + pub run_interval: Duration, + + /// Specifies interval at which we should be sending ping packets to all active gateways + /// in order to keep the websocket connections alive. + #[serde(with = "humantime_serde")] + pub gateway_ping_interval: Duration, + + /// Specifies maximum rate (in packets per second) of test packets being sent to gateway + pub gateway_sending_rate: usize, + + /// Maximum number of gateway clients the network monitor will try to talk to concurrently. + /// 0 = no limit + pub max_concurrent_gateway_clients: usize, + + /// Maximum allowed time for receiving gateway response. + #[serde(with = "humantime_serde")] + pub gateway_response_timeout: Duration, + + /// Maximum allowed time for the gateway connection to get established. + #[serde(with = "humantime_serde")] + pub gateway_connection_timeout: Duration, + + /// Specifies the duration the monitor is going to wait after sending all measurement + /// packets before declaring nodes unreachable. + #[serde(with = "humantime_serde")] + pub packet_delivery_timeout: Duration, + + /// Desired number of test routes to be constructed (and working) during a monitor test run. + pub test_routes: usize, + + /// The minimum number of test routes that need to be constructed (and working) in order for + /// a monitor test run to be valid. + pub minimum_test_routes: usize, + + /// Number of test packets sent via each pseudorandom route to verify whether they work correctly, + /// before using them for testing the rest of the network. + pub route_test_packets: usize, + + /// Number of test packets sent to each node during regular monitor test run. + pub per_node_test_packets: usize, +} + +impl Default for NetworkMonitorDebug { + fn default() -> Self { + NetworkMonitorDebug { + min_mixnode_reliability: DEFAULT_MIN_MIXNODE_RELIABILITY, + min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY, + disabled_credentials_mode: true, + run_interval: DEFAULT_MONITOR_RUN_INTERVAL, + gateway_ping_interval: DEFAULT_GATEWAY_PING_INTERVAL, + gateway_sending_rate: DEFAULT_GATEWAY_SENDING_RATE, + max_concurrent_gateway_clients: DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS, + gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, + gateway_connection_timeout: DEFAULT_GATEWAY_CONNECTION_TIMEOUT, + packet_delivery_timeout: DEFAULT_PACKET_DELIVERY_TIMEOUT, + test_routes: DEFAULT_TEST_ROUTES, + minimum_test_routes: DEFAULT_MINIMUM_TEST_ROUTES, + route_test_packets: DEFAULT_ROUTE_TEST_PACKETS, + per_node_test_packets: DEFAULT_PER_NODE_TEST_PACKETS, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct NodeStatusAPI { + // pub enabled: bool, + pub storage_paths: NodeStatusAPIPaths, + + #[serde(default)] + pub debug: NodeStatusAPIDebug, +} + +impl NodeStatusAPI { + pub fn new_default>(id: P) -> Self { + NodeStatusAPI { + storage_paths: NodeStatusAPIPaths::new_default(id), + debug: Default::default(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct NodeStatusAPIDebug { + // TODO: allow for this... + // port: u16, + #[serde(with = "humantime_serde")] + pub caching_interval: Duration, +} + +impl Default for NodeStatusAPIDebug { + fn default() -> Self { + NodeStatusAPIDebug { + caching_interval: DEFAULT_NODE_STATUS_CACHE_INTERVAL, + } + } +} + +#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct TopologyCacher { + // pub enabled: bool, + + // pub paths: TopologyCacherPathfinder, + #[serde(default)] + pub debug: TopologyCacherDebug, +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct TopologyCacherDebug { + #[serde(with = "humantime_serde")] + pub caching_interval: Duration, +} + +impl Default for TopologyCacherDebug { + fn default() -> Self { + TopologyCacherDebug { + caching_interval: DEFAULT_TOPOLOGY_CACHE_INTERVAL, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct CirculatingSupplyCacher { + pub enabled: bool, + + // pub paths: CirculatingSupplyCacherPathfinder, + #[serde(default)] + pub debug: CirculatingSupplyCacherDebug, +} + +impl Default for CirculatingSupplyCacher { + fn default() -> Self { + CirculatingSupplyCacher { + enabled: true, + debug: CirculatingSupplyCacherDebug::default(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct CirculatingSupplyCacherDebug { + #[serde(with = "humantime_serde")] + pub caching_interval: Duration, +} + +impl Default for CirculatingSupplyCacherDebug { + fn default() -> Self { + CirculatingSupplyCacherDebug { + caching_interval: DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct Rewarding { + /// Specifies whether rewarding service is enabled in this process. + pub enabled: bool, + + // this should really be a thing too... + // pub paths: RewardingPathfinder, + #[serde(default)] + pub debug: RewardingDebug, +} + +#[allow(clippy::derivable_impls)] +impl Default for Rewarding { + fn default() -> Self { + Rewarding { + enabled: false, + debug: Default::default(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct RewardingDebug { + /// Specifies the minimum percentage of monitor test run data present in order to + /// distribute rewards for given interval. + /// Note, only values in range 0-100 are valid + pub minimum_interval_monitor_threshold: u8, +} + +impl Default for RewardingDebug { + fn default() -> Self { + RewardingDebug { + minimum_interval_monitor_threshold: DEFAULT_MONITOR_THRESHOLD, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct CoconutSigner { + /// Specifies whether rewarding service is enabled in this process. + pub enabled: bool, + + pub announce_address: Url, + + pub storage_paths: CoconutSignerPaths, + + #[serde(default)] + pub debug: CoconutSignerDebug, +} + +impl CoconutSigner { + pub fn new_default>(id: P) -> Self { + let default_validator: Url = DEFAULT_LOCAL_VALIDATOR + .parse() + .expect("default local validator is malformed!"); + let mut default_announce_address = default_validator; + default_announce_address + .set_port(Some(DEFAULT_NYM_API_PORT)) + .expect("default local validator is malformed!"); + + CoconutSigner { + enabled: false, + announce_address: default_announce_address, + storage_paths: CoconutSignerPaths::new_default(id), + debug: Default::default(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct CoconutSignerDebug { + /// Duration of the interval for polling the dkg contract. + #[serde(with = "humantime_serde")] + pub dkg_contract_polling_rate: Duration, +} + +impl Default for CoconutSignerDebug { + fn default() -> Self { + CoconutSignerDebug { + dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE, + } } } diff --git a/nym-api/src/support/config/old_config_v1_1_21.rs b/nym-api/src/support/config/old_config_v1_1_21.rs new file mode 100644 index 0000000000..3e5ded9964 --- /dev/null +++ b/nym-api/src/support/config/old_config_v1_1_21.rs @@ -0,0 +1,371 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::support::config::persistence::{ + CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, +}; +use crate::support::config::{ + Base, CirculatingSupplyCacher, CirculatingSupplyCacherDebug, CoconutSigner, CoconutSignerDebug, + Config, NetworkMonitor, NetworkMonitorDebug, NodeStatusAPI, NodeStatusAPIDebug, Rewarding, + RewardingDebug, TopologyCacher, TopologyCacherDebug, +}; +use nym_config::legacy_helpers::nym_config::MigrationNymConfig; +use nym_validator_client::nyxd; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::time::Duration; +use url::Url; + +const DEFAULT_NYM_API_PORT: u16 = 8080; +const MIXNET_CONTRACT_ADDRESS: &str = + "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr"; +const VESTING_CONTRACT_ADDRESS: &str = + "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; + +const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; + +const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10); + +const DEFAULT_GATEWAY_SENDING_RATE: usize = 200; +const DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS: usize = 50; +const DEFAULT_PACKET_DELIVERY_TIMEOUT: Duration = Duration::from_secs(20); +const DEFAULT_MONITOR_RUN_INTERVAL: Duration = Duration::from_secs(15 * 60); +const DEFAULT_GATEWAY_PING_INTERVAL: Duration = Duration::from_secs(60); +// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause +// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the +// bandwidth bridging protocol, we can come back to a smaller timeout value +const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60); +// This timeout value should be big enough to accommodate an initial bandwidth acquirement +const DEFAULT_GATEWAY_CONNECTION_TIMEOUT: Duration = Duration::from_secs(2 * 60); + +const DEFAULT_TEST_ROUTES: usize = 3; +const DEFAULT_MINIMUM_TEST_ROUTES: usize = 1; +const DEFAULT_ROUTE_TEST_PACKETS: usize = 1000; +const DEFAULT_PER_NODE_TEST_PACKETS: usize = 3; + +const DEFAULT_TOPOLOGY_CACHE_INTERVAL: Duration = Duration::from_secs(30); +const DEFAULT_NODE_STATUS_CACHE_INTERVAL: Duration = Duration::from_secs(120); +const DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL: Duration = Duration::from_secs(3600); +const DEFAULT_MONITOR_THRESHOLD: u8 = 60; +const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50; +const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20; + +#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_21 { + #[serde(default)] + base: BaseV1_1_21, + + #[serde(default)] + network_monitor: NetworkMonitorV1_1_21, + + #[serde(default)] + node_status_api: NodeStatusAPIV1_1_21, + + #[serde(default)] + topology_cacher: TopologyCacherV1_1_21, + + #[serde(default)] + circulating_supply_cacher: CirculatingSupplyCacherV1_1_21, + + #[serde(default)] + rewarding: RewardingV1_1_21, + + #[serde(default)] + coconut_signer: CoconutSignerV1_1_21, +} + +impl From for Config { + fn from(value: ConfigV1_1_21) -> Self { + // this value was never properly saved (probably a bug) + // so explicitly set it to the default + + let dkg_persistent_state_path = + CoconutSignerV1_1_21::default_dkg_persistent_state_path(&value.base.id); + + Config { + base: Base { + id: value.base.id, + local_validator: value.base.local_validator, + mixnet_contract_address: value.base.mixnet_contract_address, + vesting_contract_address: value.base.vesting_contract_address, + mnemonic: value.base.mnemonic, + }, + network_monitor: NetworkMonitor { + enabled: value.network_monitor.enabled, + storage_paths: NetworkMonitorPaths { + credentials_database_path: value.network_monitor.credentials_database_path, + }, + debug: NetworkMonitorDebug { + min_mixnode_reliability: value.network_monitor.min_mixnode_reliability, + min_gateway_reliability: value.network_monitor.min_gateway_reliability, + disabled_credentials_mode: value.network_monitor.disabled_credentials_mode, + run_interval: value.network_monitor.run_interval, + gateway_ping_interval: value.network_monitor.gateway_ping_interval, + gateway_sending_rate: value.network_monitor.gateway_sending_rate, + max_concurrent_gateway_clients: value + .network_monitor + .max_concurrent_gateway_clients, + gateway_response_timeout: value.network_monitor.gateway_response_timeout, + gateway_connection_timeout: value.network_monitor.gateway_connection_timeout, + packet_delivery_timeout: value.network_monitor.packet_delivery_timeout, + test_routes: value.network_monitor.test_routes, + minimum_test_routes: value.network_monitor.minimum_test_routes, + route_test_packets: value.network_monitor.route_test_packets, + per_node_test_packets: value.network_monitor.per_node_test_packets, + }, + }, + node_status_api: NodeStatusAPI { + storage_paths: NodeStatusAPIPaths { + database_path: value.node_status_api.database_path, + }, + debug: NodeStatusAPIDebug { + caching_interval: value.node_status_api.caching_interval, + }, + }, + topology_cacher: TopologyCacher { + debug: TopologyCacherDebug { + caching_interval: value.topology_cacher.caching_interval, + }, + }, + circulating_supply_cacher: CirculatingSupplyCacher { + enabled: value.circulating_supply_cacher.enabled, + debug: CirculatingSupplyCacherDebug { + caching_interval: value.circulating_supply_cacher.caching_interval, + }, + }, + rewarding: Rewarding { + enabled: value.rewarding.enabled, + debug: RewardingDebug { + minimum_interval_monitor_threshold: value + .rewarding + .minimum_interval_monitor_threshold, + }, + }, + coconut_signer: CoconutSigner { + enabled: value.coconut_signer.enabled, + announce_address: value.base.announce_address, + storage_paths: CoconutSignerPaths { + dkg_persistent_state_path, + verification_key_path: value.coconut_signer.verification_key_path, + secret_key_path: value.coconut_signer.secret_key_path, + decryption_key_path: value.coconut_signer.decryption_key_path, + public_key_with_proof_path: value.coconut_signer.public_key_with_proof_path, + }, + debug: CoconutSignerDebug { + dkg_contract_polling_rate: value.coconut_signer.dkg_contract_polling_rate, + }, + }, + } + } +} + +impl MigrationNymConfig for ConfigV1_1_21 { + fn default_root_directory() -> PathBuf { + dirs::home_dir() + .expect("Failed to evaluate $HOME value") + .join(".nym") + .join("nym-api") + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct BaseV1_1_21 { + /// ID specifies the human readable ID of this particular nym-api. + id: String, + + local_validator: Url, + + /// Address announced to the directory server for the clients to connect to. + // It is useful, say, in NAT scenarios or wanting to more easily update actual IP address + // later on by using name resolvable with a DNS query, such as `nymtech.net`. + announce_address: Url, + + /// Address of the validator contract managing the network + mixnet_contract_address: nyxd::AccountId, + + /// Address of the vesting contract holding locked tokens + vesting_contract_address: nyxd::AccountId, + + /// Mnemonic used for rewarding and/or multisig operations + mnemonic: bip39::Mnemonic, +} + +impl Default for BaseV1_1_21 { + fn default() -> Self { + let default_validator: Url = DEFAULT_LOCAL_VALIDATOR + .parse() + .expect("default local validator is malformed!"); + let mut default_announce_address = default_validator.clone(); + default_announce_address + .set_port(Some(DEFAULT_NYM_API_PORT)) + .expect("default local validator is malformed!"); + + BaseV1_1_21 { + id: String::default(), + local_validator: default_validator, + announce_address: default_announce_address, + mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.parse().unwrap(), + vesting_contract_address: VESTING_CONTRACT_ADDRESS.parse().unwrap(), + mnemonic: bip39::Mnemonic::generate(24).unwrap(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct NetworkMonitorV1_1_21 { + min_mixnode_reliability: u8, // defaults to 50 + min_gateway_reliability: u8, // defaults to 20 + enabled: bool, + #[serde(default)] + disabled_credentials_mode: bool, + #[serde(with = "humantime_serde")] + run_interval: Duration, + #[serde(with = "humantime_serde")] + gateway_ping_interval: Duration, + gateway_sending_rate: usize, + max_concurrent_gateway_clients: usize, + #[serde(with = "humantime_serde")] + gateway_response_timeout: Duration, + #[serde(with = "humantime_serde")] + gateway_connection_timeout: Duration, + #[serde(with = "humantime_serde")] + packet_delivery_timeout: Duration, + credentials_database_path: PathBuf, + test_routes: usize, + minimum_test_routes: usize, + route_test_packets: usize, + per_node_test_packets: usize, +} + +impl Default for NetworkMonitorV1_1_21 { + fn default() -> Self { + NetworkMonitorV1_1_21 { + min_mixnode_reliability: DEFAULT_MIN_MIXNODE_RELIABILITY, + min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY, + enabled: false, + disabled_credentials_mode: true, + run_interval: DEFAULT_MONITOR_RUN_INTERVAL, + gateway_ping_interval: DEFAULT_GATEWAY_PING_INTERVAL, + gateway_sending_rate: DEFAULT_GATEWAY_SENDING_RATE, + max_concurrent_gateway_clients: DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS, + gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, + gateway_connection_timeout: DEFAULT_GATEWAY_CONNECTION_TIMEOUT, + packet_delivery_timeout: DEFAULT_PACKET_DELIVERY_TIMEOUT, + credentials_database_path: Default::default(), + test_routes: DEFAULT_TEST_ROUTES, + minimum_test_routes: DEFAULT_MINIMUM_TEST_ROUTES, + route_test_packets: DEFAULT_ROUTE_TEST_PACKETS, + per_node_test_packets: DEFAULT_PER_NODE_TEST_PACKETS, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct NodeStatusAPIV1_1_21 { + database_path: PathBuf, + #[serde(with = "humantime_serde")] + caching_interval: Duration, +} + +impl Default for NodeStatusAPIV1_1_21 { + fn default() -> Self { + NodeStatusAPIV1_1_21 { + database_path: Default::default(), + caching_interval: DEFAULT_NODE_STATUS_CACHE_INTERVAL, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct TopologyCacherV1_1_21 { + #[serde(with = "humantime_serde")] + caching_interval: Duration, +} + +impl Default for TopologyCacherV1_1_21 { + fn default() -> Self { + TopologyCacherV1_1_21 { + caching_interval: DEFAULT_TOPOLOGY_CACHE_INTERVAL, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct CirculatingSupplyCacherV1_1_21 { + enabled: bool, + + #[serde(with = "humantime_serde")] + caching_interval: Duration, +} + +impl Default for CirculatingSupplyCacherV1_1_21 { + fn default() -> Self { + CirculatingSupplyCacherV1_1_21 { + enabled: true, + caching_interval: DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct RewardingV1_1_21 { + enabled: bool, + minimum_interval_monitor_threshold: u8, +} + +impl Default for RewardingV1_1_21 { + fn default() -> Self { + RewardingV1_1_21 { + enabled: false, + minimum_interval_monitor_threshold: DEFAULT_MONITOR_THRESHOLD, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct CoconutSignerV1_1_21 { + enabled: bool, + dkg_persistent_state_path: PathBuf, + verification_key_path: PathBuf, + secret_key_path: PathBuf, + decryption_key_path: PathBuf, + public_key_with_proof_path: PathBuf, + dkg_contract_polling_rate: Duration, +} + +impl CoconutSignerV1_1_21 { + pub const DKG_PERSISTENT_STATE_FILE: &'static str = "dkg_persistent_state.json"; + + fn default_dkg_persistent_state_path(id: &str) -> PathBuf { + ConfigV1_1_21::default_data_directory(id).join(Self::DKG_PERSISTENT_STATE_FILE) + } +} + +impl Default for CoconutSignerV1_1_21 { + fn default() -> Self { + Self { + enabled: Default::default(), + dkg_persistent_state_path: Default::default(), + verification_key_path: Default::default(), + secret_key_path: Default::default(), + decryption_key_path: Default::default(), + public_key_with_proof_path: Default::default(), + dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE, + } + } +} diff --git a/nym-api/src/support/config/persistence.rs b/nym-api/src/support/config/persistence.rs new file mode 100644 index 0000000000..9914705ae6 --- /dev/null +++ b/nym-api/src/support/config/persistence.rs @@ -0,0 +1,101 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::support::config::default_data_directory; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +pub const DEFAULT_NETWORK_MONITOR_CREDENTIALS_DATABASE_FILENAME: &str = "credentials_database.db"; + +pub const DEFAULT_NODE_STATUS_API_DATABASE_FILENAME: &str = "db.sqlite"; + +pub const DEFAULT_DKG_PERSISTENT_STATE_FILENAME: &str = "dkg_persistent_state.json"; +pub const DEFAULT_DKG_DECRYPTION_KEY_FILENAME: &str = "dkg_decryption_key.pem"; +pub const DEFAULT_DKG_PUBLIC_KEY_WITH_PROOF_FILENAME: &str = "dkg_public_key_with_proof.pem"; +pub const DEFAULT_COCONUT_VERIFICATION_KEY_FILENAME: &str = "coconut_verification_key.pem"; +pub const DEFAULT_COCONUT_SECRET_KEY_FILENAME: &str = "coconut_secret_key.pem"; + +// #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +// pub struct NymApiPathfinder { +// pub network_monitor: NetworkMonitorPathfinder, +// +// pub node_status_api: NodeStatusAPIPathfinder, +// +// pub coconut: CoconutSignerPathfinder, +// } +// +// impl NymApiPathfinder { +// pub fn new_default>(id: P) -> Self { +// NymApiPathfinder { +// network_monitor: NetworkMonitorPathfinder::new_default(id.as_ref()), +// node_status_api: NodeStatusAPIPathfinder::new_default(id.as_ref()), +// coconut: CoconutSignerPathfinder::new_default(id.as_ref()), +// } +// } +// } + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct NetworkMonitorPaths { + // TODO: this should contain the path to the database holding the results, but changing it would break backwards compatibility + /// Path to the database containing bandwidth credentials of this client. + pub credentials_database_path: PathBuf, +} + +impl NetworkMonitorPaths { + pub fn new_default>(id: P) -> Self { + let data_dir = default_data_directory(id); + + NetworkMonitorPaths { + credentials_database_path: data_dir + .join(DEFAULT_NETWORK_MONITOR_CREDENTIALS_DATABASE_FILENAME), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct NodeStatusAPIPaths { + /// Path to the database file containing uptime statuses for all mixnodes and gateways. + pub database_path: PathBuf, +} + +impl NodeStatusAPIPaths { + pub fn new_default>(id: P) -> Self { + let data_dir = default_data_directory(id); + + NodeStatusAPIPaths { + database_path: data_dir.join(DEFAULT_NODE_STATUS_API_DATABASE_FILENAME), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct CoconutSignerPaths { + /// Path to a JSON file where state is persisted between different stages of DKG. + pub dkg_persistent_state_path: PathBuf, + + /// Path to the coconut verification key. + pub verification_key_path: PathBuf, + + /// Path to the coconut secret key. + pub secret_key_path: PathBuf, + + /// Path to the dkg dealer decryption key. + pub decryption_key_path: PathBuf, + + /// Path to the dkg dealer public key with proof. + pub public_key_with_proof_path: PathBuf, +} + +impl CoconutSignerPaths { + pub fn new_default>(id: P) -> Self { + let data_dir = default_data_directory(id); + + CoconutSignerPaths { + dkg_persistent_state_path: data_dir.join(DEFAULT_DKG_PERSISTENT_STATE_FILENAME), + verification_key_path: data_dir.join(DEFAULT_COCONUT_VERIFICATION_KEY_FILENAME), + secret_key_path: data_dir.join(DEFAULT_COCONUT_SECRET_KEY_FILENAME), + decryption_key_path: data_dir.join(DEFAULT_DKG_DECRYPTION_KEY_FILENAME), + public_key_with_proof_path: data_dir.join(DEFAULT_DKG_PUBLIC_KEY_WITH_PROOF_FILENAME), + } + } +} diff --git a/nym-api/src/support/config/template.rs b/nym-api/src/support/config/template.rs index 9676eed563..7ef105d91d 100644 --- a/nym-api/src/support/config/template.rs +++ b/nym-api/src/support/config/template.rs @@ -1,8 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub(crate) fn config_template() -> &'static str { - r#" +pub(crate) const CONFIG_TEMPLATE: &str = r#" # This is a TOML config file. # For more information, see https://github.com/toml-lang/toml @@ -16,11 +15,6 @@ id = '{{ base.id }}' # Validator server to which the API will be getting information about the network. local_validator = '{{ base.local_validator }}' -# Address announced to the directory server for the clients to connect to. -# It is useful, say, in NAT scenarios or wanting to more easily update actual IP address -# later on by using name resolvable with a DNS query, such as `nymtech.net`. -announce_address = '{{ base.announce_address }}' - # Address of the validator contract managing the network. mixnet_contract_address = '{{ base.mixnet_contract_address }}' @@ -30,59 +24,72 @@ vesting_contract_address = '{{ base.vesting_contract_address }}' # Mnemonic used for rewarding and validator interaction mnemonic = '{{ base.mnemonic }}' + ##### network monitor config options ##### [network_monitor] # Specifies whether network monitoring service is enabled in this process. enabled = {{ network_monitor.enabled }} +[network_monitor.storage_paths] + +# Path to the database containing bandwidth credentials of this client. +credentials_database_path = '{{ network_monitor.storage_paths.credentials_database_path }}' + +[network_monitor.debug] + # Indicates whether this validator api is running in a disabled credentials mode, thus attempting # to claim bandwidth without presenting bandwidth credentials. -disabled_credentials_mode = {{ network_monitor.disabled_credentials_mode }} +disabled_credentials_mode = {{ network_monitor.debug.disabled_credentials_mode }} # Specifies the interval at which the network monitor sends the test packets. -run_interval = '{{ network_monitor.run_interval }}' - -# Specifies interval at which we should be sending ping packets to all active gateways -# in order to keep the websocket connections alive. -gateway_ping_interval = '{{ network_monitor.gateway_ping_interval }}' - -# Specifies maximum rate (in packets per second) of test packets being sent to gateway -gateway_sending_rate = {{ network_monitor.gateway_sending_rate }} - -# Maximum number of gateway clients the network monitor will try to talk to concurrently. -max_concurrent_gateway_clients = {{ network_monitor.max_concurrent_gateway_clients }} - -# Maximum allowed time for receiving gateway response. -gateway_response_timeout = '{{ network_monitor.gateway_response_timeout }}' - -# Maximum allowed time for the gateway connection to get established. -gateway_connection_timeout = '{{ network_monitor.gateway_connection_timeout }}' - -# Specifies the duration the monitor is going to wait after sending all measurement -# packets before declaring nodes unreachable. -packet_delivery_timeout = '{{ network_monitor.packet_delivery_timeout }}' - -credentials_database_path = '{{ network_monitor.credentials_database_path }}' +run_interval = '{{ network_monitor.debug.run_interval }}' # Desired number of test routes to be constructed (and working) during a monitor test run. -test_routes = {{ network_monitor.test_routes }} +test_routes = {{ network_monitor.debug.test_routes }} # The minimum number of test routes that need to be constructed (and working) in order for # a monitor test run to be valid. -minimum_test_routes = {{ network_monitor.minimum_test_routes }} +minimum_test_routes = {{ network_monitor.debug.minimum_test_routes }} # Number of test packets sent via each pseudorandom route to verify whether they work correctly, # before using them for testing the rest of the network. -route_test_packets = {{ network_monitor.route_test_packets }} +route_test_packets = {{ network_monitor.debug.route_test_packets }} # Number of test packets sent to each node during regular monitor test run. -per_node_test_packets = {{ network_monitor.per_node_test_packets }} +per_node_test_packets = {{ network_monitor.debug.per_node_test_packets }} -[node_status_api] + +##### node status api config options ##### + +[node_status_api.storage_paths] # Path to the database file containing uptime statuses for all mixnodes and gateways. -database_path = '{{ node_status_api.database_path }}' +database_path = '{{ node_status_api.storage_paths.database_path }}' + +[node_status_api.debug] + +caching_interval = '{{ node_status_api.debug.caching_interval }}' + + +##### topology cacher config options ##### + +[topology_cacher.debug] + +caching_interval = '{{ topology_cacher.debug.caching_interval }}' + + +##### circulating supply cacher config options ##### + +[circulating_supply_cacher] + +# Specifies whether circulating supply caching service is enabled in this process. +enabled = {{ circulating_supply_cacher.enabled }} + +[circulating_supply_cacher.debug] + +caching_interval = '{{ circulating_supply_cacher.debug.caching_interval }}' + ##### rewarding config options ##### @@ -91,27 +98,36 @@ database_path = '{{ node_status_api.database_path }}' # Specifies whether rewarding service is enabled in this process. enabled = {{ rewarding.enabled }} +[rewarding.debug] + # Specifies the minimum percentage of monitor test run data present in order to # distribute rewards for given interval. # Note, only values in range 0-100 are valid -minimum_interval_monitor_threshold = {{ rewarding.minimum_interval_monitor_threshold }} +minimum_interval_monitor_threshold = {{ rewarding.debug.minimum_interval_monitor_threshold }} [coconut_signer] # Specifies whether coconut signing protocol is enabled in this process. enabled = {{ coconut_signer.enabled }} -# Path to the coconut verification key -verification_key_path = '{{ coconut_signer.verification_key_path }}' +# address of this nym-api as announced to other instances for the purposes of performing the DKG. +announce_address = '{{ coconut_signer.announce_address }}' + +[coconut_signer.storage_paths] + +# Path to a JSON file where state is persisted between different stages of DKG. +dkg_persistent_state_path = '{{ coconut_signer.storage_paths.dkg_persistent_state_path }}' # Path to the coconut verification key -secret_key_path = '{{ coconut_signer.secret_key_path }}' +verification_key_path = '{{ coconut_signer.storage_paths.verification_key_path }}' + +# Path to the coconut verification key +secret_key_path = '{{ coconut_signer.storage_paths.secret_key_path }}' # Path to the dkg dealer decryption key -decryption_key_path = '{{ coconut_signer.decryption_key_path }}' +decryption_key_path = '{{ coconut_signer.storage_paths.decryption_key_path }}' # Path to the dkg dealer public key with proof -public_key_with_proof_path = '{{ coconut_signer.public_key_with_proof_path }}' +public_key_with_proof_path = '{{ coconut_signer.storage_paths.public_key_with_proof_path }}' -"# -} +"#; diff --git a/nym-api/src/support/http/mod.rs b/nym-api/src/support/http/mod.rs index 139b00d8e7..572d1c281b 100644 --- a/nym-api/src/support/http/mod.rs +++ b/nym-api/src/support/http/mod.rs @@ -33,7 +33,7 @@ pub(crate) async fn setup_rocket( "/" => (vec![], openapi::custom_openapi_spec()), "" => circulating_supply_api::circulating_supply_routes(&openapi_settings), "" => nym_contract_cache::nym_contract_cache_routes(&openapi_settings), - "/status" => node_status_api::node_status_routes(&openapi_settings, config.get_network_monitor_enabled()), + "/status" => node_status_api::node_status_routes(&openapi_settings, config.network_monitor.enabled), } let rocket = rocket @@ -45,13 +45,16 @@ pub(crate) async fn setup_rocket( // This is not a very nice approach. A lazy value would be more suitable, but that's still // a nightly feature: https://github.com/rust-lang/rust/issues/74465 - let storage = if config.get_coconut_signer_enabled() || config.get_network_monitor_enabled() { - Some(storage::NymApiStorage::init(config.get_node_status_api_database_path()).await?) + let storage = if config.coconut_signer.enabled || config.network_monitor.enabled { + Some( + storage::NymApiStorage::init(&config.node_status_api.storage_paths.database_path) + .await?, + ) } else { None }; - let rocket = if config.get_coconut_signer_enabled() { + let rocket = if config.coconut_signer.enabled { let comm_channel = QueryCommunicationChannel::new(_nyxd_client.clone()); rocket.attach(InternalSignRequest::stage( _nyxd_client.clone(), @@ -65,7 +68,7 @@ pub(crate) async fn setup_rocket( }; // see if we should start up network monitor - let rocket = if config.get_network_monitor_enabled() { + let rocket = if config.network_monitor.enabled { rocket.attach(storage::NymApiStorage::stage(storage.unwrap())) } else { rocket diff --git a/nym-api/src/support/storage/mod.rs b/nym-api/src/support/storage/mod.rs index 4a9d3691d2..c4f6d8e294 100644 --- a/nym-api/src/support/storage/mod.rs +++ b/nym-api/src/support/storage/mod.rs @@ -13,7 +13,7 @@ use crate::storage::models::{NodeStatus, TestingRoute}; use nym_mixnet_contract_common::MixId; use rocket::fairing::AdHoc; use sqlx::ConnectOptions; -use std::path::PathBuf; +use std::path::Path; use time::OffsetDateTime; use self::manager::{AvgGatewayReliability, AvgMixnodeReliability}; @@ -28,11 +28,11 @@ pub(crate) struct NymApiStorage { } impl NymApiStorage { - pub async fn init(database_path: PathBuf) -> Result { + pub async fn init>(database_path: P) -> Result { // TODO: we can inject here more stuff based on our nym-api global config // struct. Maybe different pool size or timeout intervals? let mut opts = sqlx::sqlite::SqliteConnectOptions::new() - .filename(&database_path) + .filename(database_path) .create_if_missing(true); // TODO: do we want auto_vacuum ? diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 4021204663..6993487ef8 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -465,7 +465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497049e9477329f8f6a559972ee42e117487d01d1e8c2cc9f836ea6fa23a9e1a" dependencies = [ "serde", - "toml", + "toml 0.5.11", ] [[package]] @@ -1242,7 +1242,16 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", ] [[package]] @@ -1266,6 +1275,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -3209,7 +3230,7 @@ version = "1.1.14" dependencies = [ "async-trait", "dashmap", - "dirs", + "dirs 4.0.0", "futures", "gloo-timers", "humantime-serde", @@ -3303,12 +3324,12 @@ dependencies = [ name = "nym-config" version = "0.1.0" dependencies = [ - "cfg-if", + "dirs 5.0.1", "handlebars", "log", "nym-network-defaults", "serde", - "toml", + "toml 0.7.4", "url", ] @@ -3318,7 +3339,7 @@ version = "1.1.13" dependencies = [ "anyhow", "bip39", - "dirs", + "dirs 4.0.0", "eyre", "fern", "fix-path-env", @@ -3622,7 +3643,7 @@ dependencies = [ name = "nym-socks5-client-core" version = "0.1.0" dependencies = [ - "dirs", + "dirs 4.0.0", "futures", "log", "nym-bandwidth-controller", @@ -4023,6 +4044,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_str_bytes" version = "6.5.0" @@ -5183,6 +5210,15 @@ dependencies = [ "syn 2.0.5", ] +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5795,7 +5831,7 @@ dependencies = [ "cfg-expr 0.9.1", "heck 0.3.3", "pkg-config", - "toml", + "toml 0.5.11", "version-compare 0.0.11", ] @@ -5808,7 +5844,7 @@ dependencies = [ "cfg-expr 0.11.0", "heck 0.4.1", "pkg-config", - "toml", + "toml 0.5.11", "version-compare 0.1.1", ] @@ -6120,7 +6156,7 @@ dependencies = [ "serde", "serde_json", "tendermint", - "toml", + "toml 0.5.11", "url", ] @@ -6400,18 +6436,35 @@ dependencies = [ ] [[package]] -name = "toml_datetime" -version = "0.6.1" +name = "toml" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" -version = "0.19.7" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -7050,7 +7103,7 @@ version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", ] [[package]] @@ -7085,12 +7138,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] @@ -7100,7 +7153,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -7109,15 +7171,30 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows-tokens" version = "0.39.0" @@ -7130,6 +7207,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.37.0" @@ -7148,6 +7231,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.37.0" @@ -7166,6 +7255,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.37.0" @@ -7184,6 +7279,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.37.0" @@ -7202,12 +7303,24 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.37.0" @@ -7227,10 +7340,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] -name = "winnow" -version = "0.3.6" +name = "windows_x86_64_msvc" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" dependencies = [ "memchr", ] @@ -7250,7 +7369,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] diff --git a/nym-connect/desktop/src-tauri/src/config/mod.rs b/nym-connect/desktop/src-tauri/src/config/mod.rs index b70524fce7..f9caf60ee9 100644 --- a/nym-connect/desktop/src-tauri/src/config/mod.rs +++ b/nym-connect/desktop/src-tauri/src/config/mod.rs @@ -1,77 +1,95 @@ -use crate::{ - error::{BackendError, Result}, - state::State, -}; +// Copyright 2022-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::NymConnectPaths; +use crate::config::template::CONFIG_TEMPLATE; +use crate::error::{BackendError, Result}; use nym_client_core::client::key_manager::persistence::OnDiskKeys; -use nym_client_core::config::Config as BaseConfig; -use nym_config_common::NymConfig; +use nym_config_common::{ + 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 nym_crypto::asymmetric::identity; -use nym_socks5_client_core::config::{Config as Socks5Config, Socks5}; -use std::path::PathBuf; -use std::sync::Arc; +use nym_socks5_client_core::config::Config as Socks5CoreConfig; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; +use std::{fs, io}; use tap::TapFallible; -use tokio::sync::RwLock; + +mod persistence; +mod template; static SOCKS5_CONFIG_ID: &str = "nym-connect"; -pub fn socks5_config_id_appended_with(gateway_id: &str) -> Result { - use std::fmt::Write as _; - let mut id = SOCKS5_CONFIG_ID.to_string(); - write!(id, "-{gateway_id}")?; - Ok(id) +// backwards compatibility : ) +const DEFAULT_NYM_CONNECT_CLIENTS_DIR: &str = "socks5-clients"; + +/// Derive default path to clients's config directory. +/// It should get resolved to `$HOME/.nym/socks5-clients//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_NYM_CONNECT_CLIENTS_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) } -#[tauri::command] -pub async fn get_config_id(state: tauri::State<'_, Arc>>) -> Result { - state.read().await.get_config_id() +/// Derive default path to client's config file. +/// It should get resolved to `$HOME/.nym/socks5-clients//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) } -#[tauri::command] -pub async fn get_config_file_location( - state: tauri::State<'_, Arc>>, -) -> Result { - let id = get_config_id(state).await?; - Config::config_file_location(&id).map(|d| d.to_string_lossy().to_string()) +/// Derive default path to nym connects's data directory where files, such as keys, are stored. +/// It should get resolved to `$HOME/.nym/socks5-clients//data` +pub fn default_data_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_NYM_CONNECT_CLIENTS_DIR) + .join(id) + .join(DEFAULT_DATA_DIR) } -#[derive(Debug)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct Config { - socks5: Socks5Config, + pub core: Socks5CoreConfig, + + pub storage_paths: NymConnectPaths, +} + +impl NymConfigTemplate for Config { + fn template() -> &'static str { + CONFIG_TEMPLATE + } +} + +pub fn socks5_config_id_appended_with(gateway_id: &str) -> String { + format!("{SOCKS5_CONFIG_ID}-{gateway_id}") } impl Config { - pub fn new>(id: S, provider_mix_address: S) -> Self { + pub fn new>(id: S, provider_mix_address: S) -> Self { Config { - socks5: Socks5Config::new(id, provider_mix_address), + core: Socks5CoreConfig::new(id.as_ref(), provider_mix_address.as_ref()), + storage_paths: NymConnectPaths::new_default(default_data_directory(id.as_ref())), } } - #[allow(unused)] - pub fn new_with_port>(id: S, provider_mix_address: S, port: u16) -> Self { - Config { - socks5: Socks5Config::new(id, provider_mix_address).with_port(port), - } + pub fn read_from_toml_file>(path: P) -> io::Result { + read_config_from_toml_file(path) } - pub fn get_config(&self) -> &Socks5Config { - &self.socks5 + pub fn read_from_default_path>(id: P) -> io::Result { + Self::read_from_toml_file(default_config_filepath(id)) } - pub fn get_socks5(&self) -> &Socks5 { - self.socks5.get_socks5() + pub fn default_location(&self) -> PathBuf { + default_config_filepath(&self.core.base.client.id) } - #[allow(unused)] - pub fn get_socks5_mut(&mut self) -> &mut Socks5 { - self.socks5.get_socks5_mut() - } - - pub fn get_base(&self) -> &BaseConfig { - self.socks5.get_base() - } - - pub fn get_base_mut(&mut self) -> &mut BaseConfig { - self.socks5.get_base_mut() + pub fn save_to_default_location(&self) -> io::Result<()> { + let config_save_location: PathBuf = self.default_location(); + save_formatted_config_to_file(self, config_save_location) } pub async fn init(service_provider: &str, chosen_gateway_id: &str) -> Result<()> { @@ -83,6 +101,7 @@ impl Config { // The client initialization was originally not written for this use case, so there are // lots of ways it can panic. Until we have proper error handling in the init code for the // clients we'll catch any panics here by spawning a new runtime in a separate thread. + // JS: why are we spawning a new thread here? std::thread::spawn(move || { tokio::runtime::Runtime::new() .expect("Failed to create tokio runtime") @@ -96,27 +115,26 @@ impl Config { log::info!("Configuration saved 🚀"); Ok(()) } +} - pub fn config_file_location(id: &str) -> Result { - Socks5Config::try_default_config_file_path(id) - .ok_or(BackendError::CouldNotGetConfigFilename) - } +fn init_paths(id: &str) -> io::Result<()> { + fs::create_dir_all(default_data_directory(id))?; + fs::create_dir_all(default_config_directory(id)) } pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: String) -> Result<()> { log::trace!("Initialising client..."); // Append the gateway id to the name id that we store the config under - let id = socks5_config_id_appended_with(&chosen_gateway_id)?; + let id = socks5_config_id_appended_with(&chosen_gateway_id); - log::debug!( - "Attempting to use config file location: {}", - Config::config_file_location(&id)?.to_string_lossy(), - ); - let already_init = Config::config_file_location(&id)?.exists(); - if already_init { - log::info!("SOCKS5 client \"{id}\" was already initialised before"); - } + let old_config = if default_config_filepath(&id).exists() { + eprintln!("SOCKS5 client \"{id}\" was already initialised before"); + Some(Config::read_from_default_path(&id)?) + } else { + init_paths(&id)?; + None + }; // Future proofing. This flag exists for the other clients let user_wants_force_register = false; @@ -124,64 +142,65 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str // If the client was already initialized, don't generate new keys and don't re-register with // the gateway (because this would create a new shared key). // Unless the user really wants to. - let register_gateway = !already_init || user_wants_force_register; + let register_gateway = old_config.is_none() || user_wants_force_register; - log::trace!("Creating config for id: {}", id); - let mut config = Config::new(id.as_str(), &provider_address); + log::trace!("Creating config for id: {id}"); + let mut config = Config::new(&id, &provider_address); if let Ok(raw_validators) = std::env::var(nym_config_common::defaults::var_names::NYM_API) { - config - .get_base_mut() - .set_custom_nym_apis(nym_config_common::parse_urls(&raw_validators)); + config.core.base.client.nym_api_urls = nym_config_common::parse_urls(&raw_validators); } let chosen_gateway_id = identity::PublicKey::from_base58_string(chosen_gateway_id) .map_err(|_| BackendError::UnableToParseGateway)?; // Setup gateway by either registering a new one, or reusing exiting keys - let key_store = OnDiskKeys::from_config(config.get_base()); - let gateway = nym_client_core::init::setup_gateway_from_config::( + let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone()); + let gateway = nym_client_core::init::setup_gateway_from_config::<_>( &key_store, register_gateway, Some(chosen_gateway_id), - config.get_base(), + &config.core.base, + old_config.map(|cfg| cfg.core.base.client.gateway_endpoint), // TODO: another instance where this setting should probably get used false, ) .await?; - config.get_base_mut().set_gateway_endpoint(gateway); + config.core.base.set_gateway_endpoint(gateway); - config.get_config().save_to_file(None).tap_err(|_| { + config.save_to_default_location().tap_err(|_| { log::error!("Failed to save the config file"); })?; print_saved_config(&config); - let address = - nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?; + let address = nym_client_core::init::get_client_address_from_stored_ondisk_keys( + &config.storage_paths.common_paths.keys, + &config.core.base.client.gateway_endpoint, + )?; log::info!("The address of this client is: {}", address); Ok(()) } fn print_saved_config(config: &Config) { log::info!( - "Saved configuration file to {:?}", - config.get_config().get_config_file_save_location() + "Saved configuration file to {}", + config.default_location().display() ); - log::info!("Gateway id: {}", config.get_base().get_gateway_id()); - log::info!("Gateway owner: {}", config.get_base().get_gateway_owner()); + log::info!("Gateway id: {}", config.core.base.get_gateway_id()); + log::info!("Gateway owner: {}", config.core.base.get_gateway_owner()); log::info!( "Gateway listener: {}", - config.get_base().get_gateway_listener() + config.core.base.get_gateway_listener() ); log::info!( "Service provider address: {}", - config.get_socks5().get_provider_mix_address() + config.core.socks5.provider_mix_address ); log::info!( "Service provider port: {}", - config.get_socks5().get_listening_port() + config.core.socks5.listening_port ); log::info!("Client configuration completed."); } diff --git a/nym-connect/desktop/src-tauri/src/config/persistence.rs b/nym-connect/desktop/src-tauri/src/config/persistence.rs new file mode 100644 index 0000000000..b5a67d1f54 --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/config/persistence.rs @@ -0,0 +1,20 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_client_core::config::disk_persistence::CommonClientPaths; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)] +pub struct NymConnectPaths { + #[serde(flatten)] + pub common_paths: CommonClientPaths, +} + +impl NymConnectPaths { + pub fn new_default>(base_data_directory: P) -> Self { + NymConnectPaths { + common_paths: CommonClientPaths::new_default(base_data_directory), + } + } +} diff --git a/nym-connect/desktop/src-tauri/src/config/template.rs b/nym-connect/desktop/src-tauri/src/config/template.rs new file mode 100644 index 0000000000..fd4aec5f52 --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/config/template.rs @@ -0,0 +1,122 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +// While using normal toml marshalling would have been way simpler with less overhead, +// I think it's useful to have comments attached to the saved config file to explain behaviour of +// particular fields. +// Note: any changes to the template must be reflected in the appropriate structs. +pub(crate) const CONFIG_TEMPLATE: &str = r#" +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base client config options ##### + +[core.client] +# Version of the client for which this configuration was created. +version = '{{ core.client.version }}' + +# Human readable ID of this particular client. +id = '{{ core.client.id }}' + +# Indicates whether this client is running in a disabled credentials mode, thus attempting +# to claim bandwidth without presenting bandwidth credentials. +disabled_credentials_mode = {{ core.client.disabled_credentials_mode }} + +# Addresses to nyxd validators via which the client can communicate with the chain. +nyxd_urls = [ + {{#each core.client.nyxd_urls }} + '{{this}}', + {{/each}} +] + +# Addresses to APIs running on validator from which the client gets the view of the network. +nym_api_urls = [ + {{#each core.client.nym_api_urls }} + '{{this}}', + {{/each}} +] + +[storage_paths] + +# Path to file containing private identity key. +keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' + +# Path to file containing public identity key. +keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}' + +# Path to file containing private encryption key. +keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}' + +# Path to file containing public encryption key. +keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}' + +# A gateway specific, optional, base58 stringified shared key used for +# communication with particular gateway. +keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}' + +# Path to file containing key used for encrypting and decrypting the content of an +# acknowledgement so that nobody besides the client knows which packet it refers to. +keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}' + +# Path to the database containing bandwidth credentials +credentials_database = '{{ storage_paths.credentials_database }}' + +# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. +reply_surb_database = '{{ storage_paths.reply_surb_database }}' + +# DEPRECATED +[core.client.gateway_endpoint] +# ID of the gateway from which the client should be fetching messages. +gateway_id = '{{ core.client.gateway_endpoint.gateway_id }}' + +# Address of the gateway owner to which the client should send messages. +gateway_owner = '{{ core.client.gateway_endpoint.gateway_owner }}' + +# Address of the gateway listener to which all client requests should be sent. +gateway_listener = '{{ core.client.gateway_endpoint.gateway_listener }}' + + +##### socket config options ##### + +[core.socks5] + +# The mix address of the provider to which all requests are going to be sent. +provider_mix_address = '{{ core.socks5.provider_mix_address }}' + +# The port on which the client will be listening for incoming requests +listening_port = {{ core.socks5.listening_port }} + +# Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. +# While this is going to hide its actual address information, it will make the actual communication +# slower and consume nearly double the bandwidth as it will require sending reply SURBs. +# +# Note that some service providers might not support this. +send_anonymously = {{ core.socks5.send_anonymously }} + +##### logging configuration options ##### + +[logging] + +# TODO + + +##### debug configuration options ##### +# The following options should not be modified unless you know EXACTLY what you are doing +# as if set incorrectly, they may impact your anonymity. + +# [core.socks5.socks5_debug] + + +[core.debug] + +[core.debug.traffic] +average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}' +message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}' + +[core.debug.acknowledgements] +average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}' + +[core.debug.cover_traffic] +loop_cover_traffic_average_delay = '{{ core.debug.cover_traffic.loop_cover_traffic_average_delay }}' + +"#; diff --git a/nym-connect/desktop/src-tauri/src/main.rs b/nym-connect/desktop/src-tauri/src/main.rs index 76a7666406..b1a599c41e 100644 --- a/nym-connect/desktop/src-tauri/src/main.rs +++ b/nym-connect/desktop/src-tauri/src/main.rs @@ -38,8 +38,8 @@ fn main() { tauri::Builder::default() .manage(Arc::new(RwLock::new(State::new()))) .invoke_handler(tauri::generate_handler![ - crate::config::get_config_file_location, - crate::config::get_config_id, + crate::operations::config::get_config_file_location, + crate::operations::config::get_config_id, crate::operations::connection::connect::get_gateway, crate::operations::connection::connect::get_service_provider, crate::operations::connection::connect::set_gateway, diff --git a/nym-connect/desktop/src-tauri/src/operations/config/mod.rs b/nym-connect/desktop/src-tauri/src/operations/config/mod.rs new file mode 100644 index 0000000000..5fdecb2cbd --- /dev/null +++ b/nym-connect/desktop/src-tauri/src/operations/config/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::default_config_filepath; +use crate::{error::Result, state::State}; +use std::sync::Arc; +use tokio::sync::RwLock; + +#[tauri::command] +pub async fn get_config_id(state: tauri::State<'_, Arc>>) -> Result { + state.read().await.get_config_id() +} + +#[tauri::command] +pub async fn get_config_file_location( + state: tauri::State<'_, Arc>>, +) -> Result { + let id = get_config_id(state).await?; + Ok(default_config_filepath(id).display().to_string()) +} diff --git a/nym-connect/desktop/src-tauri/src/operations/export.rs b/nym-connect/desktop/src-tauri/src/operations/export.rs index f3a3385263..3b11891e9c 100644 --- a/nym-connect/desktop/src-tauri/src/operations/export.rs +++ b/nym-connect/desktop/src-tauri/src/operations/export.rs @@ -1,4 +1,5 @@ -use std::{ffi::OsStr, fs, sync::Arc}; +use std::path::Path; +use std::{fs, sync::Arc}; use tokio::sync::RwLock; use crate::{ @@ -7,7 +8,6 @@ use crate::{ }; use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::client::key_manager::KeyManager; -use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use nym_crypto::asymmetric::identity; pub async fn get_identity_key( @@ -15,13 +15,13 @@ pub async fn get_identity_key( ) -> Result> { let config = { let state = state.read().await; - state.load_socks5_config()? + state.load_config()? }; - let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); + let paths = config.storage_paths.common_paths.keys; // wtf, why are we loading EVERYTHING to just get identity key?? - let key_store = OnDiskKeys::from(pathfinder); + let key_store = OnDiskKeys::from(paths); let key_manager = KeyManager::load_keys(&key_store) .await @@ -33,58 +33,51 @@ pub async fn get_identity_key( Ok(identity_keypair) } +fn key_filename>(path: P) -> Result { + path.as_ref() + .file_name() + .ok_or(BackendError::CouldNotGetFilename)? + .to_os_string() + .into_string() + .map_err(|_| BackendError::CouldNotGetFilename) +} + /// Export the gateway keys as a JSON string blob #[tauri::command] pub async fn export_keys(state: tauri::State<'_, Arc>>) -> Result { let config = { let state = state.read().await; - state.load_socks5_config()? + state.load_config()? }; + let key_paths = config.storage_paths.common_paths.keys; + // Get key paths - let ack_key_file = config.get_base().get_ack_key_file(); - let gateway_shared_key_file = config.get_base().get_gateway_shared_key_file(); + let ack_key_file = key_paths.ack_key(); + let gateway_shared_key_file = key_paths.gateway_shared_key(); - let pub_id_key_file = config.get_base().get_public_identity_key_file(); - let priv_id_key_file = config.get_base().get_private_identity_key_file(); + let pub_id_key_file = key_paths.public_identity_key(); + let priv_id_key_file = key_paths.private_identity_key(); - let pub_enc_key_file = config.get_base().get_public_encryption_key_file(); - let priv_enc_key_file = config.get_base().get_private_encryption_key_file(); + let pub_enc_key_file = key_paths.public_encryption_key(); + let priv_enc_key_file = key_paths.private_encryption_key(); // Read file contents - let ack_key = fs::read_to_string(ack_key_file.clone())?; - let gateway_shared_key = fs::read_to_string(gateway_shared_key_file.clone())?; + let ack_key = fs::read_to_string(ack_key_file)?; + let gateway_shared_key = fs::read_to_string(gateway_shared_key_file)?; - let pub_id_key = fs::read_to_string(pub_id_key_file.clone())?; - let priv_id_key = fs::read_to_string(priv_id_key_file.clone())?; + let pub_id_key = fs::read_to_string(pub_id_key_file)?; + let priv_id_key = fs::read_to_string(priv_id_key_file)?; - let pub_enc_key = fs::read_to_string(pub_enc_key_file.clone())?; - let priv_enc_key = fs::read_to_string(priv_enc_key_file.clone())?; + let pub_enc_key = fs::read_to_string(pub_enc_key_file)?; + let priv_enc_key = fs::read_to_string(priv_enc_key_file)?; - let ack_key_file = ack_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - let gateway_shared_key_file = gateway_shared_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - let pub_id_key_file = pub_id_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - let priv_id_key_file = priv_id_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - let pub_enc_key_file = pub_enc_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; - let priv_enc_key_file = priv_enc_key_file - .file_name() - .map(OsStr::to_string_lossy) - .ok_or(BackendError::CouldNotGetFilename)?; + let ack_key_file = key_filename(&key_paths.ack_key_file)?; + let gateway_shared_key_file = key_filename(&key_paths.gateway_shared_key_file)?; + let pub_id_key_file = key_filename(&key_paths.public_identity_key_file)?; + let priv_id_key_file = key_filename(&key_paths.private_identity_key_file)?; + let pub_enc_key_file = key_filename(&key_paths.public_encryption_key_file)?; + let priv_enc_key_file = key_filename(&key_paths.private_encryption_key_file)?; // Format and return as json let json = serde_json::json!({ diff --git a/nym-connect/desktop/src-tauri/src/operations/mod.rs b/nym-connect/desktop/src-tauri/src/operations/mod.rs index 9d7d45f236..87e01e79da 100644 --- a/nym-connect/desktop/src-tauri/src/operations/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/mod.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod connection; pub mod directory; pub mod export; diff --git a/nym-connect/desktop/src-tauri/src/state.rs b/nym-connect/desktop/src-tauri/src/state.rs index 58fc59d1d4..eb03a4b86a 100644 --- a/nym-connect/desktop/src-tauri/src/state.rs +++ b/nym-connect/desktop/src-tauri/src/state.rs @@ -1,16 +1,12 @@ -use std::time::Duration; - -use ::nym_config_common::NymConfig; use futures::SinkExt; use nym_client_core::error::ClientCoreStatusMessage; +use nym_socks5_client_core::{Socks5ControlMessage, Socks5ControlMessageSender}; +use std::time::Duration; use tap::TapFallible; use tauri::Manager; - -use nym_socks5_client_core::{ - config::Config as Socks5Config, Socks5ControlMessage, Socks5ControlMessageSender, -}; use tokio::time::Instant; +use crate::config::Config; use crate::{ config::{self, socks5_config_id_appended_with}, error::{BackendError, Result}, @@ -22,7 +18,7 @@ use crate::{ }; // The client will emit messages if the connection to the gateway is poor (or the gateway can't -// keep up with the messages we are sendind). If no messages about this has been received for a +// keep up with the messages we are sending). If no messages about this has been received for a // certain duration then we assume it's all good. const GATEWAY_CONNECTIVITY_TIMEOUT_SECS: u64 = 20; @@ -146,15 +142,16 @@ impl State { /// The effective config id is the static config id appended with the id of the gateway pub fn get_config_id(&self) -> Result { - self.get_gateway() + let gateway_id = self + .get_gateway() .as_ref() - .ok_or(BackendError::CouldNotGetIdWithoutGateway) - .and_then(|gateway_id| socks5_config_id_appended_with(gateway_id)) + .ok_or(BackendError::CouldNotGetIdWithoutGateway)?; + Ok(socks5_config_id_appended_with(gateway_id)) } - pub fn load_socks5_config(&self) -> Result { + pub fn load_config(&self) -> Result { let id = self.get_config_id()?; - let config = Socks5Config::load_from_file(&id) + let config = Config::read_from_default_path(id) .tap_err(|_| log::warn!("Failed to load configuration file"))?; Ok(config) } diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index c65289dde8..2e89864db9 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -1,18 +1,14 @@ use futures::{channel::mpsc, StreamExt}; use nym_client_core::client::base_client::storage::OnDiskPersistent; -use nym_client_core::{ - config::{ClientCoreConfigTrait, GatewayEndpointConfig}, - error::ClientCoreStatusMessage, -}; +use nym_client_core::{config::GatewayEndpointConfig, error::ClientCoreStatusMessage}; +use nym_socks5_client_core::NymClient as Socks5NymClient; +use nym_socks5_client_core::Socks5ControlMessageSender; use nym_task::manager::TaskStatus; use std::sync::Arc; use tap::TapFallible; use tokio::sync::RwLock; -use nym_config_common::NymConfig; -use nym_socks5_client_core::NymClient as Socks5NymClient; -use nym_socks5_client_core::{config::Config as Socks5Config, Socks5ControlMessageSender}; - +use crate::config::Config; use crate::{ error::Result, events::{self, emit_event, emit_status_event}, @@ -42,9 +38,9 @@ pub fn start_nym_socks5_client( GatewayEndpointConfig, )> { log::info!("Loading config from file: {id}"); - let config = Socks5Config::load_from_file(id) + let config = Config::read_from_default_path(id) .tap_err(|_| log::warn!("Failed to load configuration file"))?; - let used_gateway = config.get_base().get_gateway_endpoint().clone(); + let used_gateway = config.core.base.client.gateway_endpoint.clone(); log::info!("Starting socks5 client"); @@ -65,8 +61,12 @@ pub fn start_nym_socks5_client( let result = tokio::runtime::Runtime::new() .expect("Failed to create runtime for SOCKS5 client") .block_on(async move { - let storage = OnDiskPersistent::from_config(config.get_base()).await?; - let socks5_client = Socks5NymClient::new(config, storage); + let storage = OnDiskPersistent::from_paths( + config.storage_paths.common_paths, + &config.core.base.debug, + ) + .await?; + let socks5_client = Socks5NymClient::new(config.core, storage); socks5_client .run_and_listen(socks5_ctrl_rx, socks5_status_tx) diff --git a/nym-connect/mobile/src-tauri/src/config/mod.rs b/nym-connect/mobile/src-tauri/src/config/mod.rs index 57c76d6492..ba0d2ebf13 100644 --- a/nym-connect/mobile/src-tauri/src/config/mod.rs +++ b/nym-connect/mobile/src-tauri/src/config/mod.rs @@ -2,7 +2,7 @@ use crate::{ error::{BackendError, Result}, state::State, }; -use nym_client_core::{client::key_manager::KeyManager, config::Config as BaseConfig}; +use nym_client_core::{client::key_manager::KeyManager, config::Config as BaseClientConfig}; use nym_config_common::NymConfig; use nym_credential_storage::ephemeral_storage::EphemeralStorage; use nym_crypto::asymmetric::identity; @@ -63,11 +63,11 @@ impl Config { self.socks5.get_socks5_mut() } - pub fn get_base(&self) -> &BaseConfig { + pub fn get_base(&self) -> &BaseClientConfig { self.socks5.get_base() } - pub fn get_base_mut(&mut self) -> &mut BaseConfig { + pub fn get_base_mut(&mut self) -> &mut BaseClientConfig { self.socks5.get_base_mut() } diff --git a/nym-connect/mobile/src-tauri/src/operations/export.rs b/nym-connect/mobile/src-tauri/src/operations/export.rs index 61e96404cb..dea876fdd8 100644 --- a/nym-connect/mobile/src-tauri/src/operations/export.rs +++ b/nym-connect/mobile/src-tauri/src/operations/export.rs @@ -6,7 +6,7 @@ use crate::{ state::State, }; use nym_client_core::client::key_manager::KeyManager; -use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; +use nym_client_core::config::persistence::keys_paths::ClientKeysPaths; use nym_crypto::asymmetric::identity; pub async fn get_identity_key( @@ -17,8 +17,8 @@ pub async fn get_identity_key( state.load_socks5_config()? }; - let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); - let key_manager = KeyManager::load_keys(&pathfinder)?; + let paths = ClientKeysPaths::new_from_config(config.get_base()); + let key_manager = KeyManager::load_keys(&paths)?; let identity_keypair = key_manager.identity_keypair(); Ok(identity_keypair) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 636c4d695f..a6066e568a 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -455,7 +455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497049e9477329f8f6a559972ee42e117487d01d1e8c2cc9f836ea6fa23a9e1a" dependencies = [ "serde", - "toml", + "toml 0.5.11", ] [[package]] @@ -1174,7 +1174,16 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", ] [[package]] @@ -1198,6 +1207,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -2903,6 +2924,7 @@ dependencies = [ "log", "pretty_env_logger", "semver 0.11.0", + "serde", "vergen", ] @@ -2963,12 +2985,12 @@ dependencies = [ name = "nym-config" version = "0.1.0" dependencies = [ - "cfg-if", + "dirs 5.0.1", "handlebars", "log", "nym-network-defaults", "serde", - "toml", + "toml 0.7.4", "url", ] @@ -3284,7 +3306,7 @@ dependencies = [ "colored 2.0.0", "cosmrs", "cosmwasm-std", - "dirs", + "dirs 4.0.0", "dotenvy", "eyre", "fern", @@ -3320,7 +3342,7 @@ dependencies = [ "thiserror", "time", "tokio", - "toml", + "toml 0.5.11", "ts-rs", "url", "zeroize", @@ -3438,6 +3460,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -3852,7 +3880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.18.1", ] [[package]] @@ -4532,6 +4560,15 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4911,7 +4948,7 @@ dependencies = [ "cfg-expr 0.9.1", "heck 0.3.3", "pkg-config", - "toml", + "toml 0.5.11", "version-compare 0.0.11", ] @@ -4924,7 +4961,7 @@ dependencies = [ "cfg-expr 0.11.0", "heck 0.4.1", "pkg-config", - "toml", + "toml 0.5.11", "version-compare 0.1.1", ] @@ -5223,7 +5260,7 @@ dependencies = [ "serde", "serde_json", "tendermint", - "toml", + "toml 0.5.11", "url", ] @@ -5453,12 +5490,33 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.2", + "toml_edit 0.19.10", +] + [[package]] name = "toml_datetime" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", +] + [[package]] name = "toml_edit" version = "0.18.1" @@ -5467,7 +5525,20 @@ checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" dependencies = [ "indexmap", "nom8", - "toml_datetime", + "toml_datetime 0.5.1", +] + +[[package]] +name = "toml_edit" +version = "0.19.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.2", + "winnow", ] [[package]] @@ -6032,12 +6103,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.1", "windows_aarch64_msvc 0.42.1", "windows_i686_gnu 0.42.1", "windows_i686_msvc 0.42.1", "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.1", "windows_x86_64_msvc 0.42.1", ] @@ -6047,7 +6118,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -6056,15 +6136,30 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.1", "windows_aarch64_msvc 0.42.1", "windows_i686_gnu 0.42.1", "windows_i686_msvc 0.42.1", "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.1", "windows_x86_64_msvc 0.42.1", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows-tokens" version = "0.39.0" @@ -6077,6 +6172,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.37.0" @@ -6095,6 +6196,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.37.0" @@ -6113,6 +6220,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.37.0" @@ -6131,6 +6244,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.37.0" @@ -6149,12 +6268,24 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.37.0" @@ -6173,6 +6304,21 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" @@ -6188,7 +6334,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" dependencies = [ - "toml", + "toml 0.5.11", ] [[package]] diff --git a/sdk/lib/socks5-listener/Cargo.lock b/sdk/lib/socks5-listener/Cargo.lock index cf6ec3024f..3fc8b34151 100644 --- a/sdk/lib/socks5-listener/Cargo.lock +++ b/sdk/lib/socks5-listener/Cargo.lock @@ -963,7 +963,16 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "dirs-sys", + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", ] [[package]] @@ -977,6 +986,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -2274,7 +2295,7 @@ version = "1.1.14" dependencies = [ "async-trait", "dashmap", - "dirs", + "dirs 4.0.0", "futures", "gloo-timers", "humantime-serde", @@ -2368,12 +2389,12 @@ dependencies = [ name = "nym-config" version = "0.1.0" dependencies = [ - "cfg-if", + "dirs 5.0.1", "handlebars", "log", "nym-network-defaults", "serde", - "toml", + "toml 0.7.4", "url", ] @@ -2637,7 +2658,7 @@ dependencies = [ name = "nym-socks5-client-core" version = "0.1.0" dependencies = [ - "dirs", + "dirs 4.0.0", "futures", "log", "nym-bandwidth-controller", @@ -3017,6 +3038,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "pairing" version = "0.20.0" @@ -3889,6 +3916,15 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4395,7 +4431,7 @@ dependencies = [ "serde", "serde_json", "tendermint", - "toml", + "toml 0.5.11", "url", ] @@ -4632,6 +4668,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -5182,6 +5252,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" diff --git a/sdk/lib/socks5-listener/Cargo.toml b/sdk/lib/socks5-listener/Cargo.toml index 6936e9665c..f68f484d56 100644 --- a/sdk/lib/socks5-listener/Cargo.toml +++ b/sdk/lib/socks5-listener/Cargo.toml @@ -16,7 +16,7 @@ crate-type = ["cdylib", "staticlib", "rlib"] strip = true [dependencies] -anyhow = "1.0" +anyhow = { workspace = true } futures = "0.3" lazy_static = "1.4.0" nym-bin-common = { path = "../../../common/bin-common"} @@ -26,7 +26,8 @@ nym-credential-storage = { path = "../../../common/credential-storage" } nym-crypto = { path = "../../../common/crypto" } nym-socks5-client-core = { path = "../../../common/socks5-client-core", default-features = false } openssl = { version = "0.10", features = ["vendored"] } -tokio = { version = "1.24.1", features = ["sync", "time"] } +serde = { workspace = true } +tokio = { workspace = true, features = ["sync", "time"] } log = "0.4.17" rand = "0.7.3" diff --git a/sdk/lib/socks5-listener/src/config/mod.rs b/sdk/lib/socks5-listener/src/config/mod.rs new file mode 100644 index 0000000000..f2c80303d9 --- /dev/null +++ b/sdk/lib/socks5-listener/src/config/mod.rs @@ -0,0 +1,97 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::MobileSocksClientPaths; +use crate::config::template::CONFIG_TEMPLATE; +use nym_bin_common::logging::LoggingSettings; +use nym_config_common::{ + read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, + DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, +}; +use nym_socks5_client_core::config::Config as CoreConfig; +use serde::{Deserialize, Serialize}; +use std::io; +use std::path::{Path, PathBuf}; + +mod persistence; +mod template; + +const DEFAULT_SOCKS5_CLIENTS_DIR: &str = "socks5-clients"; + +/// Derive default path to clients's config directory. +/// It should get resolved to `$HOME/.nym/socks5-clients//config` +pub fn config_directory_from_root, R: AsRef>(root: P, id: R) -> PathBuf { + root.as_ref() + .join(NYM_DIR) + .join(DEFAULT_SOCKS5_CLIENTS_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) +} + +/// Derive default path to client's config file. +/// It should get resolved to `$HOME/.nym/socks5-clients//config/config.toml` +pub fn config_filepath_from_root, R: AsRef>(root: P, id: R) -> PathBuf { + config_directory_from_root(root, id).join(DEFAULT_CONFIG_FILENAME) +} + +/// Derive default path to client's data directory where files, such as keys, are stored. +/// It should get resolved to `$HOME/.nym/socks5-clients//data` +pub fn data_directory_from_root, R: AsRef>(root: P, id: R) -> PathBuf { + root.as_ref() + .join(NYM_DIR) + .join(DEFAULT_SOCKS5_CLIENTS_DIR) + .join(id) + .join(DEFAULT_DATA_DIR) +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + pub core: CoreConfig, + + pub storage_paths: Option, + + pub logging: LoggingSettings, +} + +impl NymConfigTemplate for Config { + fn template() -> &'static str { + CONFIG_TEMPLATE + } +} + +impl Config { + pub fn new(storage_root: Option

, id: S, provider_mix_address: S) -> Self + where + P: AsRef, + S: AsRef, + { + Config { + core: CoreConfig::new(id.as_ref(), provider_mix_address.as_ref()), + storage_paths: storage_root.map(|storage_root| { + MobileSocksClientPaths::new_default(data_directory_from_root( + storage_root, + id.as_ref(), + )) + }), + logging: Default::default(), + } + } + + pub fn read_from_toml_file>(path: P) -> io::Result { + read_config_from_toml_file(path) + } + + pub fn read_from_default_path, R: AsRef>( + storage_root: P, + id: R, + ) -> io::Result { + Self::read_from_toml_file(config_filepath_from_root(storage_root, id)) + } + + pub fn save_to_default_location>(&self, storage_root: P) -> io::Result<()> { + let config_save_location: PathBuf = + config_filepath_from_root(storage_root, &self.core.base.client.id); + save_formatted_config_to_file(self, config_save_location) + } +} diff --git a/sdk/lib/socks5-listener/src/config/persistence.rs b/sdk/lib/socks5-listener/src/config/persistence.rs new file mode 100644 index 0000000000..cdd46aad87 --- /dev/null +++ b/sdk/lib/socks5-listener/src/config/persistence.rs @@ -0,0 +1,26 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::data_directory_from_root; +use nym_client_core::config::disk_persistence::CommonClientPaths; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)] +pub struct MobileSocksClientPaths { + #[serde(flatten)] + pub common_paths: CommonClientPaths, +} + +impl MobileSocksClientPaths { + pub fn new_default>(base_data_directory: P) -> Self { + MobileSocksClientPaths { + common_paths: CommonClientPaths::new_default(base_data_directory), + } + } + + pub fn change_root, R: AsRef>(&mut self, new_root: P, id: R) { + let new_data_dir = data_directory_from_root(new_root, id); + self.common_paths = CommonClientPaths::new_default(new_data_dir) + } +} diff --git a/sdk/lib/socks5-listener/src/config/template.rs b/sdk/lib/socks5-listener/src/config/template.rs new file mode 100644 index 0000000000..0c9262e5c7 --- /dev/null +++ b/sdk/lib/socks5-listener/src/config/template.rs @@ -0,0 +1,122 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +// While using normal toml marshalling would have been way simpler with less overhead, +// I think it's useful to have comments attached to the saved config file to explain behaviour of +// particular fields. +// Note: any changes to the template must be reflected in the appropriate structs. +pub(crate) const CONFIG_TEMPLATE: &str = r#" +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base client config options ##### + +[core.client] +# Version of the client for which this configuration was created. +version = '{{ core.client.version }}' + +# Human readable ID of this particular client. +id = '{{ core.client.id }}' + +# Indicates whether this client is running in a disabled credentials mode, thus attempting +# to claim bandwidth without presenting bandwidth credentials. +disabled_credentials_mode = {{ core.client.disabled_credentials_mode }} + +# Addresses to nyxd validators via which the client can communicate with the chain. +nyxd_urls = [ + {{#each core.client.nyxd_urls }} + '{{this}}', + {{/each}} +] + +# Addresses to APIs running on validator from which the client gets the view of the network. +nym_api_urls = [ + {{#each core.client.nym_api_urls }} + '{{this}}', + {{/each}} +] + +[storage_paths] + +# Path to file containing private identity key. +keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' + +# Path to file containing public identity key. +keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}' + +# Path to file containing private encryption key. +keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}' + +# Path to file containing public encryption key. +keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}' + +# A gateway specific, optional, base58 stringified shared key used for +# communication with particular gateway. +keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}' + +# Path to file containing key used for encrypting and decrypting the content of an +# acknowledgement so that nobody besides the client knows which packet it refers to. +keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}' + +# Path to the database containing bandwidth credentials +credentials_database = '{{ storage_paths.credentials_database }}' + +# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. +reply_surb_database = '{{ storage_paths.reply_surb_database }}' + +# DEPRECATED +[core.client.gateway_endpoint] +# ID of the gateway from which the client should be fetching messages. +gateway_id = '{{ core.client.gateway_endpoint.gateway_id }}' + +# Address of the gateway owner to which the client should send messages. +gateway_owner = '{{ core.client.gateway_endpoint.gateway_owner }}' + +# Address of the gateway listener to which all client requests should be sent. +gateway_listener = '{{ core.client.gateway_endpoint.gateway_listener }}' + + +##### socket config options ##### + +[core.socks5] + +# The mix address of the provider to which all requests are going to be sent. +provider_mix_address = '{{ core.socks5.provider_mix_address }}' + +# The port on which the client will be listening for incoming requests +listening_port = {{ core.socks5.listening_port }} + +# Specifies whether this client is going to use an anonymous sender tag for communication with the service provider. +# While this is going to hide its actual address information, it will make the actual communication +# slower and consume nearly double the bandwidth as it will require sending reply SURBs. +# +# Note that some service providers might not support this. +send_anonymously = {{ core.socks5.send_anonymously }} + +##### logging configuration options ##### + +[logging] + +# TODO + + +##### debug configuration options ##### +# The following options should not be modified unless you know EXACTLY what you are doing +# as if set incorrectly, they may impact your anonymity. + +# [core.socks5.socks5_debug] + + +[core.debug] + +[core.debug.traffic] +average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}' +message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}' + +[core.debug.acknowledgements] +average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}' + +[core.debug.cover_traffic] +loop_cover_traffic_average_delay = '{{ core.debug.cover_traffic.loop_cover_traffic_average_delay }}' + +"#; diff --git a/sdk/lib/socks5-listener/src/lib.rs b/sdk/lib/socks5-listener/src/lib.rs index f08e09ffa3..ff4e41283a 100644 --- a/sdk/lib/socks5-listener/src/lib.rs +++ b/sdk/lib/socks5-listener/src/lib.rs @@ -1,15 +1,16 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::config::{config_filepath_from_root, Config}; use crate::persistence::MobileClientStorage; use ::safer_ffi::prelude::*; use anyhow::{anyhow, Result}; use lazy_static::lazy_static; use log::{info, warn}; use nym_bin_common::logging::setup_logging; +use nym_client_core::client::key_manager::persistence::InMemEphemeralKeys; +use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_config_common::defaults::setup_env; -use nym_config_common::NymConfig; -use nym_socks5_client_core::config::Config as Socks5Config; use nym_socks5_client_core::NymClient as Socks5NymClient; use safer_ffi::char_p::char_p_boxed; use std::marker::Send; @@ -19,14 +20,9 @@ use std::sync::Arc; use tokio::runtime::Runtime; use tokio::sync::{Mutex, Notify}; -#[cfg(not(target_os = "android"))] -use nym_client_core::client::key_manager::persistence::OnDiskKeys; - -#[cfg(target_os = "android")] -use nym_client_core::client::key_manager::persistence::InMemEphemeralKeys; - #[cfg(target_os = "android")] pub mod android; +mod config; mod persistence; static SOCKS5_CONFIG_ID: &str = "mobile-socks5-test"; @@ -183,19 +179,9 @@ pub fn reset_client_data(root_directory: char_p::Ref<'_>) { #[ffi_export] pub fn existing_service_provider(storage_directory: char_p::Ref<'_>) -> Option { - let expected_store_path = Socks5Config::default_config_file_path_with_root( - storage_directory.to_string(), - SOCKS5_CONFIG_ID, - ); - - if let Ok(config) = Socks5Config::load_from_filepath(expected_store_path) { - Some( - config - .get_socks5() - .get_raw_provider_mix_address() - .try_into() - .unwrap(), - ) + if let Ok(config) = Config::read_from_default_path(storage_directory.to_str(), SOCKS5_CONFIG_ID) + { + Some(config.core.socks5.provider_mix_address.try_into().unwrap()) } else { None } @@ -223,7 +209,7 @@ where let config = load_or_generate_base_config(storage_dir, client_id, service_provider).await?; let storage = MobileClientStorage::new(&config); - let socks5_client = Socks5NymClient::new(config, storage); + let socks5_client = Socks5NymClient::new(config.core, storage); eprintln!("starting the socks5 client"); let mut started_client = socks5_client.start().await?; @@ -250,28 +236,34 @@ async fn load_or_generate_base_config( storage_dir: Option, client_id: String, service_provider: Option, -) -> Result { +) -> Result { let Some(storage_dir) = storage_dir else { eprintln!("no storage path specified"); return setup_new_client_config(None, client_id, service_provider).await; }; - let expected_store_path = - Socks5Config::default_config_file_path_with_root(&storage_dir, &client_id.to_string()); - eprintln!("attempting to load socks5 config from {expected_store_path:?}"); + let expected_store_path = config_filepath_from_root(&storage_dir, &client_id); + eprintln!( + "attempting to load socks5 config from {}", + expected_store_path.display() + ); // simulator workaround - if let Ok(config) = Socks5Config::load_from_filepath(expected_store_path) { + if let Ok(mut config) = Config::read_from_toml_file(expected_store_path) { eprintln!("loaded config"); - let root = config.get_base().get_nym_root_directory(); - eprintln!("actual root: {storage_dir}"); - eprintln!("retrieved root: {root:?}"); - - if root.to_str() == Some(storage_dir.as_str()) { - return Ok(config); + if let Some(storage_paths) = &mut config.storage_paths { + if !storage_paths + .common_paths + .keys + .public_identity_key_file + .starts_with(&storage_dir) + { + eprintln!("... but it seems to have been made for different container - fixing it up... (ASSUMING DEFAULT PATHS)"); + storage_paths.change_root(storage_dir, &config.core.base.client.id); + } } - eprintln!("... but it seems to have been made for different container - fixing it up... (ASSUMING DEFAULT PATHS)"); - return Ok(config.with_root_directory(storage_dir)); + + return Ok(config); }; eprintln!("creating new config"); @@ -282,43 +274,46 @@ async fn setup_new_client_config( storage_dir: Option, client_id: String, service_provider: Option, -) -> Result { +) -> Result { let service_provider = service_provider.ok_or(anyhow!( "service provider was not specified for fresh config" ))?; - let mut new_config = Socks5Config::new(client_id, service_provider); - if let Some(storage_dir) = &storage_dir { - new_config = new_config.with_root_directory(storage_dir); - } - - // ugh that's disgusting... - #[cfg(not(target_os = "android"))] - let key_store = OnDiskKeys::from_config(new_config.get_base()); - - #[cfg(target_os = "android")] - let key_store = InMemEphemeralKeys; + let mut new_config = Config::new(storage_dir.as_ref(), client_id, service_provider); if let Ok(raw_validators) = std::env::var(nym_config_common::defaults::var_names::NYM_API) { new_config - .get_base_mut() + .core + .base .set_custom_nym_apis(nym_config_common::parse_urls(&raw_validators)); } - // note: this will also do key storage (annoyingly...) - let gateway = nym_client_core::init::setup_gateway_from_config::( - &key_store, - true, - None, - new_config.get_base(), - false, - ) - .await?; + let gateway = if let Some(storage_paths) = &new_config.storage_paths { + nym_client_core::init::setup_gateway_from_config::<_>( + &OnDiskKeys::new(storage_paths.common_paths.keys.clone()), + true, + None, + &new_config.core.base, + None, + false, + ) + .await? + } else { + nym_client_core::init::setup_gateway_from_config::<_>( + &InMemEphemeralKeys, + true, + None, + &new_config.core.base, + None, + false, + ) + .await? + }; - new_config.get_base_mut().set_gateway_endpoint(gateway); + new_config.core.base.set_gateway_endpoint(gateway); - if storage_dir.is_some() { - new_config.save_to_file(None)?; + if let Some(storage_dir) = storage_dir { + new_config.save_to_default_location(storage_dir)?; } Ok(new_config) diff --git a/sdk/lib/socks5-listener/src/persistence.rs b/sdk/lib/socks5-listener/src/persistence.rs index 05f87ed298..e6ef9fd634 100644 --- a/sdk/lib/socks5-listener/src/persistence.rs +++ b/sdk/lib/socks5-listener/src/persistence.rs @@ -4,15 +4,13 @@ use nym_client_core::client::base_client::storage::MixnetClientStorage; use nym_client_core::client::replies::reply_storage; use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage; -use nym_socks5_client_core::config::Config as Socks5Config; #[cfg(target_os = "android")] use nym_client_core::client::key_manager::persistence::InMemEphemeralKeys; +use crate::config::Config; #[cfg(not(target_os = "android"))] use nym_client_core::client::key_manager::persistence::OnDiskKeys; -#[cfg(not(target_os = "android"))] -use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; pub struct MobileClientStorage { #[cfg(not(target_os = "android"))] @@ -53,7 +51,7 @@ impl MixnetClientStorage for MobileClientStorage { } impl MobileClientStorage { - pub fn new(config: &Socks5Config) -> Self { + pub fn new(config: &Config) -> Self { #[cfg(target_os = "android")] let key_store = { let _ = config; @@ -61,10 +59,14 @@ impl MobileClientStorage { }; #[cfg(not(target_os = "android"))] - let key_store = { - let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); - OnDiskKeys::new(pathfinder) - }; + let key_store = OnDiskKeys::new( + config + .storage_paths + .clone() + .expect("storage paths unavailable") + .common_paths + .keys, + ); MobileClientStorage { key_store, diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index f1c10b373e..bd38afcd77 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -117,7 +117,7 @@ where } } - /// Change the underlying storage of this builder to use default implementation of on-disk persistence. + /// Change the underlying storage of this builder to use default implementation of on-disk disk_persistence. #[must_use] pub fn set_default_storage( self, @@ -473,8 +473,7 @@ where base_builder = base_builder.with_topology_provider(topology_provider); } - let packet_type = self.config.packet_type(); - let started_client = base_builder.start_base(packet_type).await?; + let started_client = base_builder.start_base().await?; let nym_address = started_client.address; Ok((started_client, nym_address)) diff --git a/sdk/rust/nym-sdk/src/mixnet/paths.rs b/sdk/rust/nym-sdk/src/mixnet/paths.rs index 55ca735c08..f005b6bd63 100644 --- a/sdk/rust/nym-sdk/src/mixnet/paths.rs +++ b/sdk/rust/nym-sdk/src/mixnet/paths.rs @@ -6,7 +6,8 @@ use nym_client_core::client::base_client::{non_wasm_helpers, storage}; use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::client::replies::reply_storage::fs_backend; use nym_client_core::config; -use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; +use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths; +use nym_client_core::config::disk_persistence::CommonClientPaths; use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage; use std::path::{Path, PathBuf}; @@ -117,38 +118,49 @@ impl StoragePaths { /// Instantiates default persistent key storage. pub fn on_disk_key_storage_spec(&self) -> OnDiskKeys { - OnDiskKeys::new(self.client_key_pathfinder()) + OnDiskKeys::new(self.client_keys_paths()) } - fn client_key_pathfinder(&self) -> ClientKeyPathfinder { - ClientKeyPathfinder { - identity_private_key: self.private_identity.clone(), - identity_public_key: self.public_identity.clone(), - encryption_private_key: self.private_encryption.clone(), - encryption_public_key: self.public_encryption.clone(), - gateway_shared_key: self.gateway_shared_key.clone(), - ack_key: self.ack_key.clone(), + fn client_keys_paths(&self) -> ClientKeysPaths { + ClientKeysPaths { + private_identity_key_file: self.private_identity.clone(), + public_identity_key_file: self.public_identity.clone(), + private_encryption_key_file: self.private_encryption.clone(), + public_encryption_key_file: self.public_encryption.clone(), + gateway_shared_key_file: self.gateway_shared_key.clone(), + ack_key_file: self.ack_key.clone(), } } } -impl From for ClientKeyPathfinder { - fn from(paths: StoragePaths) -> Self { - paths.client_key_pathfinder() - } -} - -impl From<&nym_client_core::config::Config> for StoragePaths { - fn from(value: &nym_client_core::config::Config) -> Self { - Self { - private_identity: value.get_private_identity_key_file(), - public_identity: value.get_public_identity_key_file(), - private_encryption: value.get_private_encryption_key_file(), - public_encryption: value.get_public_encryption_key_file(), - ack_key: value.get_ack_key_file(), - gateway_shared_key: value.get_gateway_shared_key_file(), - credential_database_path: value.get_database_path(), - reply_surb_database_path: value.get_reply_surb_database_path(), +impl From for CommonClientPaths { + fn from(value: StoragePaths) -> Self { + CommonClientPaths { + keys: ClientKeysPaths { + private_identity_key_file: value.private_identity, + public_identity_key_file: value.public_identity, + private_encryption_key_file: value.private_encryption, + public_encryption_key_file: value.public_encryption, + gateway_shared_key_file: value.gateway_shared_key, + ack_key_file: value.ack_key, + }, + credentials_database: value.credential_database_path, + reply_surb_database: value.reply_surb_database_path, + } + } +} + +impl From for StoragePaths { + fn from(value: CommonClientPaths) -> Self { + StoragePaths { + private_identity: value.keys.private_identity_key_file, + public_identity: value.keys.public_identity_key_file, + private_encryption: value.keys.private_encryption_key_file, + public_encryption: value.keys.public_encryption_key_file, + ack_key: value.keys.ack_key_file, + gateway_shared_key: value.keys.gateway_shared_key_file, + credential_database_path: value.credentials_database, + reply_surb_database_path: value.reply_surb_database, } } } diff --git a/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs b/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs index 7d1e277f12..add4294f4e 100644 --- a/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/socks5_client.rs @@ -57,10 +57,7 @@ impl Socks5MixnetClient { /// Get the SOCKS5 proxy URL that a HTTP(S) client can connect to. pub fn socks5_url(&self) -> String { - format!( - "socks5h://127.0.0.1:{}", - self.socks5_config.get_listening_port() - ) + format!("socks5h://127.0.0.1:{}", self.socks5_config.listening_port) } /// Get a shallow clone of [`LaneQueueLengths`]. This is useful to manually implement some form diff --git a/service-providers/common/Cargo.toml b/service-providers/common/Cargo.toml index 887526563a..a5993a74dc 100644 --- a/service-providers/common/Cargo.toml +++ b/service-providers/common/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -nym-bin-common = { path = "../../common/bin-common", features = ["serde"] } +nym-bin-common = { path = "../../common/bin-common" } nym-sphinx-anonymous-replies = { path = "../../common/nymsphinx/anonymous-replies" } async-trait = { workspace = true } diff --git a/service-providers/common/examples/control_requests.rs b/service-providers/common/examples/control_requests.rs index 37f472d9f0..eaff26b375 100644 --- a/service-providers/common/examples/control_requests.rs +++ b/service-providers/common/examples/control_requests.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -// use nym_client::client::config::{BaseConfig, Config, GatewayEndpointConfig}; +// use nym_client::client::config::{BaseClientConfig, Config, GatewayEndpointConfig}; // use nym_client::client::{DirectClient, KeyManager, Recipient, ReconstructedMessage, SocketClient}; use nym_sdk::mixnet::{IncludedSurbs, MixnetClient, Recipient, ReconstructedMessage}; use nym_service_providers_common::interface::{ diff --git a/service-providers/common/src/lib.rs b/service-providers/common/src/lib.rs index 5e1bba8943..b78161d134 100644 --- a/service-providers/common/src/lib.rs +++ b/service-providers/common/src/lib.rs @@ -10,6 +10,8 @@ use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag; pub mod interface; +pub const DEFAULT_SERVICE_PROVIDERS_DIR: &str = "service-providers"; + // FUTURE WORK: // for next version (v4) of the interface, refactor `reply::MixnetAddress` from the socks5 SP // and move it here so that you could optionally attach your sender address with any request for easier responses diff --git a/service-providers/network-requester/src/cli/init.rs b/service-providers/network-requester/src/cli/init.rs index 392d93f19f..bef82badce 100644 --- a/service-providers/network-requester/src/cli/init.rs +++ b/service-providers/network-requester/src/cli/init.rs @@ -1,7 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::cli::try_upgrade_v1_1_13_config; +use crate::cli::try_load_current_config; +use crate::config::{default_config_directory, default_config_filepath, default_data_directory}; use crate::{ cli::{override_config, OverrideConfig}, config::Config, @@ -10,11 +11,11 @@ use crate::{ use clap::Args; use nym_bin_common::output_format::OutputFormat; use nym_client_core::client::key_manager::persistence::OnDiskKeys; -use nym_config::NymConfig; use nym_crypto::asymmetric::identity; use nym_sphinx::addressing::clients::Recipient; use serde::Serialize; use std::fmt::Display; +use std::{fs, io}; use tap::TapFallible; #[derive(Args, Clone)] @@ -78,7 +79,7 @@ pub struct InitResults { impl InitResults { fn new(config: &Config, address: &Recipient) -> Self { Self { - client_core: nym_client_core::init::InitResults::new(config.get_base(), address), + client_core: nym_client_core::init::InitResults::new(&config.base, address), client_address: address.to_string(), } } @@ -95,18 +96,24 @@ impl Display for InitResults { } } +fn init_paths(id: &str) -> io::Result<()> { + fs::create_dir_all(default_data_directory(id))?; + fs::create_dir_all(default_config_directory(id)) +} + pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { eprintln!("Initialising client..."); let id = &args.id; - let already_init = Config::default_config_file_path(id).exists(); - if already_init { - // in case we're using old config, try to upgrade it - // (if we're using the current version, it's a no-op) - try_upgrade_v1_1_13_config(id)?; + let old_config = if default_config_filepath(id).exists() { eprintln!("Client \"{id}\" was already initialised before"); - } + // if the file exist, try to load it (with checking for errors) + Some(try_load_current_config(&args.id)?) + } else { + init_paths(&args.id)?; + None + }; // Usually you only register with the gateway on the first init, however you can force // re-registering if wanted. @@ -118,7 +125,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { // If the client was already initialized, don't generate new keys and don't re-register with // the gateway (because this would create a new shared key). // Unless the user really wants to. - let register_gateway = !already_init || user_wants_force_register; + let register_gateway = old_config.is_none() || user_wants_force_register; // Attempt to use a user-provided gateway, if possible let user_chosen_gateway_id = args.gateway; @@ -128,12 +135,13 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { // Setup gateway by either registering a new one, or creating a new config from the selected // one but with keys kept, or reusing the gateway configuration. - let key_store = OnDiskKeys::from_config(config.get_base()); - let gateway = nym_client_core::init::setup_gateway_from_config::( + let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone()); + let gateway = nym_client_core::init::setup_gateway_from_config::<_>( &key_store, register_gateway, user_chosen_gateway_id, - config.get_base(), + &config.base, + old_config.map(|cfg| cfg.base.client.gateway_endpoint), args.latency_based_selection, ) .await @@ -142,31 +150,26 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { NetworkRequesterError::FailedToSetupGateway { source } })?; - config.get_base_mut().set_gateway_endpoint(gateway); + config.base.set_gateway_endpoint(gateway); - config.save_to_file(None).tap_err(|_| { + let config_save_location = config.default_location(); + config.save_to_default_location().tap_err(|_| { log::error!("Failed to save the config file"); })?; + eprintln!( + "Saved configuration file to {}", + config_save_location.display() + ); - print_saved_config(&config); + let address = nym_client_core::init::get_client_address_from_stored_ondisk_keys( + &config.storage_paths.common_paths.keys, + &config.base.client.gateway_endpoint, + )?; + + eprintln!("Client configuration completed.\n"); - let address = - nym_client_core::init::get_client_address_from_stored_ondisk_keys(config.get_base())?; let init_results = InitResults::new(&config, &address); println!("{}", args.output.format(&init_results)); Ok(()) } - -fn print_saved_config(config: &Config) { - let config_save_location = config.get_config_file_save_location(); - eprintln!("Saved configuration file to {config_save_location:?}"); - eprintln!("Using gateway: {}", config.get_base().get_gateway_id()); - log::debug!("Gateway id: {}", config.get_base().get_gateway_id()); - log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner()); - log::debug!( - "Gateway listener: {}", - config.get_base().get_gateway_listener() - ); - eprintln!("Client configuration completed.\n"); -} diff --git a/service-providers/network-requester/src/cli/mod.rs b/service-providers/network-requester/src/cli/mod.rs index b2fdda09ee..237f3a38ed 100644 --- a/service-providers/network-requester/src/cli/mod.rs +++ b/service-providers/network-requester/src/cli/mod.rs @@ -1,18 +1,17 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::config::old_config_v1_1_13::OldConfigV1_1_13; +use crate::config::old_config_v1_1_20::ConfigV1_1_20; +use crate::{ + config::{BaseClientConfig, Config}, + error::NetworkRequesterError, +}; use clap::{CommandFactory, Parser, Subcommand}; -use log::info; +use log::{error, info}; use nym_bin_common::build_information::BinaryBuildInformation; use nym_bin_common::completions::{fig_generate, ArgShell}; use nym_bin_common::version_checker; -use nym_config::NymConfig; - -use crate::config::old_config_v1_1_13::OldConfigV1_1_13; -use crate::{ - config::{BaseConfig, Config}, - error::NetworkRequesterError, -}; mod init; mod run; @@ -70,22 +69,25 @@ pub(crate) struct OverrideConfig { pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { config - .with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode) - .with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover) - .with_optional_custom_env_ext( - BaseConfig::with_custom_nym_apis, + .with_base( + BaseClientConfig::with_high_default_traffic_volume, + args.fastmode, + ) + .with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover) + .with_optional_base_custom_env( + BaseClientConfig::with_custom_nym_apis, args.nym_apis, nym_network_defaults::var_names::NYM_API, nym_config::parse_urls, ) - .with_optional_custom_env_ext( - BaseConfig::with_custom_nyxd, + .with_optional_base_custom_env( + BaseClientConfig::with_custom_nyxd, args.nyxd_urls, nym_network_defaults::var_names::NYXD, nym_config::parse_urls, ) - .with_optional_ext( - BaseConfig::with_disabled_credentials, + .with_optional_base( + BaseClientConfig::with_disabled_credentials, args.enabled_credentials_mode.map(|b| !b), ) } @@ -103,33 +105,81 @@ pub(crate) async fn execute(args: Cli) -> Result<(), NetworkRequesterError> { Ok(()) } -fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> { - // explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+) +fn try_upgrade_v1_1_13_config(id: &str) -> Result { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19) let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else { // if we failed to load it, there might have been nothing to upgrade // or maybe it was an even older file. in either way. just ignore it and carry on with our day - return Ok(()); + return Ok(false); }; info!("It seems the client is using <= v1.1.13 config template."); info!("It is going to get updated to the current specification."); + let updated_step1: ConfigV1_1_20 = old_config.into(); + let updated: Config = updated_step1.into(); + + updated.save_to_default_location()?; + Ok(true) +} + +fn try_upgrade_v1_1_20_config(id: &str) -> Result { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21) + let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else { + // if we failed to load it, there might have been nothing to upgrade + // or maybe it was an even older file. in either way. just ignore it and carry on with our day + return Ok(false); + }; + + info!("It seems the client is using <= v1.1.20 config template."); + info!("It is going to get updated to the current specification."); + let updated: Config = old_config.into(); - updated.save_to_file(None) + updated.save_to_default_location()?; + Ok(true) +} + +fn try_upgrade_config(id: &str) -> Result<(), NetworkRequesterError> { + let upgraded = try_upgrade_v1_1_13_config(id)?; + if !upgraded { + try_upgrade_v1_1_20_config(id)?; + } + + Ok(()) +} + +fn try_load_current_config(id: &str) -> Result { + try_upgrade_config(id)?; + + let config = match Config::read_from_default_path(id) { + Ok(cfg) => cfg, + Err(err) => { + error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})"); + return Err(NetworkRequesterError::FailedToLoadConfig(id.to_string())); + } + }; + + if !config.validate() { + return Err(NetworkRequesterError::ConfigValidationFailure); + } + + Ok(config) } // this only checks compatibility between config the binary. It does not take into consideration // network version. It might do so in the future. fn version_check(cfg: &Config) -> bool { let binary_version = env!("CARGO_PKG_VERSION"); - let config_version = cfg.get_base().get_version(); + let config_version = &cfg.base.client.version; if binary_version == config_version { true } else { log::warn!( "The native-client binary has different version than what is specified \ - in config file! {} and {}", - binary_version, - config_version + in config file! {binary_version} and {config_version}", ); if version_checker::is_minor_version_compatible(binary_version, config_version) { log::info!( diff --git a/service-providers/network-requester/src/cli/run.rs b/service-providers/network-requester/src/cli/run.rs index cde999e83b..b7d32b2214 100644 --- a/service-providers/network-requester/src/cli/run.rs +++ b/service-providers/network-requester/src/cli/run.rs @@ -1,14 +1,13 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::cli::{try_upgrade_v1_1_13_config, version_check}; +use crate::cli::{try_load_current_config, version_check}; use crate::{ cli::{override_config, OverrideConfig}, - config::Config, error::NetworkRequesterError, }; use clap::Args; -use nym_config::NymConfig; +use log::error; use nym_sphinx::addressing::clients::Recipient; const ENABLE_STATISTICS: &str = "enable-statistics"; @@ -77,40 +76,11 @@ pub(crate) async fn execute(args: &Run) -> Result<(), NetworkRequesterError> { ); } - let id = &args.id; - - // in case we're using old config, try to upgrade it - // (if we're using the current version, it's a no-op) - try_upgrade_v1_1_13_config(id)?; - - let mut config = match Config::load_from_file(id) { - Ok(cfg) => cfg, - Err(err) => { - log::error!( - "Failed to load config for {}. \ - Are you sure you have run `init` before? (Error was: {err})", - id - ); - return Err(NetworkRequesterError::FailedToLoadConfig(id.to_string())); - } - }; - - if !config.validate() { - return Err(NetworkRequesterError::ConfigValidationFailure); - } - - let override_config_fields = OverrideConfig::from(args.clone()); - config = override_config(config, override_config_fields); - - if config.get_base_mut().set_empty_fields_to_defaults() { - log::warn!( - "Some of the core config options were left unset. \ - The default values are going to get used instead." - ); - } + let mut config = try_load_current_config(&args.id)?; + config = override_config(config, OverrideConfig::from(args.clone())); if !version_check(&config) { - log::error!("Failed the local version check"); + error!("failed the local version check"); return Err(NetworkRequesterError::FailedLocalVersionCheck); } diff --git a/service-providers/network-requester/src/cli/sign.rs b/service-providers/network-requester/src/cli/sign.rs index c5bfd94578..5535857a17 100644 --- a/service-providers/network-requester/src/cli/sign.rs +++ b/service-providers/network-requester/src/cli/sign.rs @@ -1,14 +1,15 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::cli::{try_load_current_config, version_check}; +use crate::error::NetworkRequesterError; use clap::Args; use nym_bin_common::output_format::OutputFormat; -use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; -use nym_config::NymConfig; +use nym_client_core::client::key_manager::persistence::OnDiskKeys; +use nym_client_core::error::ClientCoreError; use nym_crypto::asymmetric::identity; use nym_types::helpers::ConsoleSigningOutput; -use crate::{config::Config, error::NetworkRequesterError}; - -use super::version_check; - #[derive(Args, Clone)] pub(crate) struct Sign { /// The id of the mixnode you want to sign with @@ -54,37 +55,19 @@ fn print_signed_contract_msg( } pub(crate) async fn execute(args: &Sign) -> Result<(), NetworkRequesterError> { - let id = &args.id; - - let mut config = match Config::load_from_file(id) { - Ok(cfg) => cfg, - Err(err) => { - log::error!( - "Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", - id, - ); - return Err(NetworkRequesterError::FailedToLoadConfig(id.to_string())); - } - }; - - if !config.validate() { - return Err(NetworkRequesterError::ConfigValidationFailure); - } - - if config.get_base_mut().set_empty_fields_to_defaults() { - log::warn!( - "Some of the core config options were left unset. \ - The default values are going to get used instead." - ); - } + let config = try_load_current_config(&args.id)?; if !version_check(&config) { log::error!("Failed the local version check"); return Err(NetworkRequesterError::FailedLocalVersionCheck); } - let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base()); - let identity_keypair = nym_client_core::init::load_identity_keys(&pathfinder)?; + let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys); + let identity_keypair = key_store.load_identity_keypair().map_err(|source| { + NetworkRequesterError::ClientCoreError(ClientCoreError::KeyStoreError { + source: Box::new(source), + }) + })?; print_signed_contract_msg( identity_keypair.private_key(), diff --git a/service-providers/network-requester/src/config/mod.rs b/service-providers/network-requester/src/config/mod.rs index 3adc1739fd..428d4d4e3c 100644 --- a/service-providers/network-requester/src/config/mod.rs +++ b/service-providers/network-requester/src/config/mod.rs @@ -1,96 +1,167 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::template::config_template; -use nym_client_core::config::ClientCoreConfigTrait; -use nym_config::{NymConfig, OptionalSet}; +use crate::config::template::CONFIG_TEMPLATE; +use nym_bin_common::logging::LoggingSettings; +use nym_config::{ + must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, + OptionalSet, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, +}; +use nym_service_providers_common::DEFAULT_SERVICE_PROVIDERS_DIR; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; +use std::io; +use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Duration; -pub use nym_client_core::config::Config as BaseConfig; -pub use nym_client_core::config::MISSING_VALUE; +use crate::config::persistence::NetworkRequesterPaths; +pub use nym_client_core::config::Config as BaseClientConfig; pub use nym_client_core::config::{DebugConfig, GatewayEndpointConfig}; +pub mod old_config_v1_1_13; +pub mod old_config_v1_1_20; +mod persistence; +mod template; + +const DEFAULT_NETWORK_REQUESTERS_DIR: &str = "network-requester"; + pub const DEFAULT_STANDARD_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30 * 60); -pub mod old_config_v1_1_13; -mod template; +/// Derive default path to network requester's config directory. +/// It should get resolved to `$HOME/.nym/service-providers/network-requester//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_SERVICE_PROVIDERS_DIR) + .join(DEFAULT_NETWORK_REQUESTERS_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) +} -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +/// Derive default path to network requester's config file. +/// It should get resolved to `$HOME/.nym/service-providers/network-requester//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) +} + +/// Derive default path to network requester's data directory where files, such as keys, are stored. +/// It should get resolved to `$HOME/.nym/service-providers/network-requester//data` +pub fn default_data_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_SERVICE_PROVIDERS_DIR) + .join(DEFAULT_NETWORK_REQUESTERS_DIR) + .join(id) + .join(DEFAULT_DATA_DIR) +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { #[serde(flatten)] - base: BaseConfig, + pub base: BaseClientConfig, #[serde(default)] - pub network_requester: NetworkRequster, + pub network_requester: NetworkRequester, + + pub storage_paths: NetworkRequesterPaths, #[serde(default)] pub network_requester_debug: Debug, + + pub logging: LoggingSettings, } -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("service-providers") - .join("network-requester") - } - - fn try_default_root_directory() -> Option { - dirs::home_dir().map(|path| path.join(".nym").join("clients")) - } - - fn root_directory(&self) -> PathBuf { - self.base.get_nym_root_directory() - } - - fn config_directory(&self) -> PathBuf { - self.root_directory() - .join(self.base.get_id()) - .join("config") - } - - fn data_directory(&self) -> PathBuf { - self.root_directory().join(self.base.get_id()).join("data") + CONFIG_TEMPLATE } } -impl ClientCoreConfigTrait for Config { - fn get_gateway_endpoint(&self) -> &nym_client_core::config::GatewayEndpointConfig { - self.base.get_gateway_endpoint() - } -} - -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -#[serde(default, deny_unknown_fields)] -pub struct NetworkRequster { - /// Location of the file containing our allow.list - pub allowed_list_location: PathBuf, - - /// Location of the file containing our unknown.list - pub unknown_list_location: PathBuf, -} - -impl Default for NetworkRequster { - fn default() -> Self { - // same defaults as we had in <= v1.1.13 - NetworkRequster { - allowed_list_location: ::default_root_directory() - .join("allowed.list"), - unknown_list_location: ::default_root_directory() - .join("unknown.list"), +impl Config { + pub fn new>(id: S) -> Self { + Config { + base: BaseClientConfig::new(id.as_ref()), + network_requester: Default::default(), + storage_paths: NetworkRequesterPaths::new_default(default_data_directory(id.as_ref())), + network_requester_debug: Default::default(), + logging: 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 default_location(&self) -> PathBuf { + default_config_filepath(&self.base.client.id) + } + + pub fn save_to_default_location(&self) -> io::Result<()> { + let config_save_location: PathBuf = self.default_location(); + save_formatted_config_to_file(self, config_save_location) + } + + pub fn validate(&self) -> bool { + // no other sections have explicit requirements (yet) + self.base.validate() + } + + // poor man's 'builder' method + pub fn with_base(mut self, f: F, val: T) -> Self + where + F: Fn(BaseClientConfig, T) -> BaseClientConfig, + { + self.base = f(self.base, val); + self + } + + // helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config + // (plz, lets refactor it) + pub fn with_optional_base(mut self, f: F, val: Option) -> Self + where + F: Fn(BaseClientConfig, T) -> BaseClientConfig, + { + self.base = self.base.with_optional(f, val); + self + } + + #[allow(unused)] + pub fn with_optional_base_env(mut self, f: F, val: Option, env_var: &str) -> Self + where + F: Fn(BaseClientConfig, T) -> BaseClientConfig, + T: FromStr, + ::Err: std::fmt::Debug, + { + self.base = self.base.with_optional_env(f, val, env_var); + self + } + + #[allow(unused)] + pub fn with_optional_base_custom_env( + mut self, + f: F, + val: Option, + env_var: &str, + parser: G, + ) -> Self + where + F: Fn(BaseClientConfig, T) -> BaseClientConfig, + G: Fn(&str) -> T, + { + self.base = self.base.with_optional_custom_env(f, val, env_var, parser); + self + } } +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct NetworkRequester {} + #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] pub struct Debug { @@ -106,87 +177,3 @@ impl Default for Debug { } } } - -impl Config { - pub fn new>(id: S) -> Self { - let mut cfg = Config { - base: BaseConfig::new(id), - ..Default::default() - }; - - cfg.network_requester.allowed_list_location = cfg.data_directory().join("allowed.list"); - cfg.network_requester.unknown_list_location = cfg.data_directory().join("unknown.list"); - cfg - } - - pub fn validate(&self) -> bool { - // no other sections have explicit requirements (yet) - self.base.validate() - } - - // getters - pub fn get_config_file_save_location(&self) -> PathBuf { - self.config_directory().join(Self::config_file_name()) - } - - pub fn allow_list_file_location(&self) -> PathBuf { - self.network_requester.allowed_list_location.clone() - } - - pub fn unknown_list_file_location(&self) -> PathBuf { - self.network_requester.unknown_list_location.clone() - } - - pub fn get_base(&self) -> &BaseConfig { - &self.base - } - - pub fn get_base_mut(&mut self) -> &mut BaseConfig { - &mut self.base - } - - // poor man's 'builder' method - pub fn with_base(mut self, f: F, val: T) -> Self - where - F: Fn(BaseConfig, T) -> BaseConfig, - { - self.base = f(self.base, val); - self - } - - // helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config - // (plz, lets refactor it) - pub fn with_optional_ext(mut self, f: F, val: Option) -> Self - where - F: Fn(BaseConfig, T) -> BaseConfig, - { - self.base = self.base.with_optional(f, val); - self - } - - #[allow(dead_code)] - pub fn with_optional_env_ext(mut self, f: F, val: Option, env_var: &str) -> Self - where - F: Fn(BaseConfig, T) -> BaseConfig, - T: FromStr, - ::Err: std::fmt::Debug, - { - self.base = self.base.with_optional_env(f, val, env_var); - self - } - - pub fn with_optional_custom_env_ext( - mut self, - f: F, - val: Option, - env_var: &str, - parser: G, - ) -> Self - where - F: Fn(BaseConfig, T) -> BaseConfig, - G: Fn(&str) -> T, - { - self.base = self.base.with_optional_custom_env(f, val, env_var, parser); - self - } -} diff --git a/service-providers/network-requester/src/config/old_config_v1_1_13.rs b/service-providers/network-requester/src/config/old_config_v1_1_13.rs index 58e4eea3a0..83a9b60020 100644 --- a/service-providers/network-requester/src/config/old_config_v1_1_13.rs +++ b/service-providers/network-requester/src/config/old_config_v1_1_13.rs @@ -1,26 +1,20 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::Config; +use crate::config::old_config_v1_1_20::ConfigV1_1_20; use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13; -use nym_config::NymConfig; +use nym_config::legacy_helpers::nym_config::MigrationNymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct OldConfigV1_1_13 { #[serde(flatten)] - base: OldBaseConfigV1_1_13, + pub base: OldBaseConfigV1_1_13, } -impl NymConfig for OldConfigV1_1_13 { - fn template() -> &'static str { - // not intended to be used - unimplemented!() - } - - // TODO: merge base dir with `HostStore`. +impl MigrationNymConfig for OldConfigV1_1_13 { fn default_root_directory() -> PathBuf { dirs::home_dir() .expect("Failed to evaluate $HOME value") @@ -28,31 +22,11 @@ impl NymConfig for OldConfigV1_1_13 { .join("service-providers") .join("network-requester") } - - fn try_default_root_directory() -> Option { - dirs::home_dir().map(|path| path.join(".nym").join("clients")) - } - - fn root_directory(&self) -> PathBuf { - self.base.client.nym_root_directory.clone() - } - - fn config_directory(&self) -> PathBuf { - self.root_directory() - .join(&self.base.client.id) - .join("config") - } - - fn data_directory(&self) -> PathBuf { - self.root_directory() - .join(&self.base.client.id) - .join("data") - } } -impl From for Config { +impl From for ConfigV1_1_20 { fn from(value: OldConfigV1_1_13) -> Self { - Config { + ConfigV1_1_20 { base: value.base.into(), network_requester: Default::default(), network_requester_debug: Default::default(), diff --git a/service-providers/network-requester/src/config/old_config_v1_1_20.rs b/service-providers/network-requester/src/config/old_config_v1_1_20.rs new file mode 100644 index 0000000000..412fb71133 --- /dev/null +++ b/service-providers/network-requester/src/config/old_config_v1_1_20.rs @@ -0,0 +1,117 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::NetworkRequesterPaths; +use crate::config::{BaseClientConfig, Config, Debug}; +use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths; +use nym_client_core::config::disk_persistence::CommonClientPaths; +use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20; +use nym_client_core::config::Client; +use nym_config::legacy_helpers::nym_config::MigrationNymConfig; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::time::Duration; + +const DEFAULT_STANDARD_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30 * 60); + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_20 { + #[serde(flatten)] + pub base: BaseConfigV1_1_20, + + #[serde(default)] + pub network_requester: NetworkRequster, + + #[serde(default)] + pub network_requester_debug: DebugV1_1_20, +} + +impl From for Config { + fn from(value: ConfigV1_1_20) -> Self { + Config { + base: BaseClientConfig { + client: Client { + version: value.base.client.version, + id: value.base.client.id, + disabled_credentials_mode: value.base.client.disabled_credentials_mode, + nyxd_urls: value.base.client.nyxd_urls, + nym_api_urls: value.base.client.nym_api_urls, + gateway_endpoint: value.base.client.gateway_endpoint.into(), + }, + debug: Default::default(), + }, + network_requester: Default::default(), + storage_paths: NetworkRequesterPaths { + common_paths: CommonClientPaths { + keys: ClientKeysPaths { + private_identity_key_file: value.base.client.private_identity_key_file, + public_identity_key_file: value.base.client.public_identity_key_file, + private_encryption_key_file: value.base.client.private_encryption_key_file, + public_encryption_key_file: value.base.client.public_encryption_key_file, + gateway_shared_key_file: value.base.client.gateway_shared_key_file, + ack_key_file: value.base.client.ack_key_file, + }, + credentials_database: value.base.client.database_path, + reply_surb_database: value.base.client.reply_surb_database_path, + }, + allowed_list_location: value.network_requester.allowed_list_location, + unknown_list_location: value.network_requester.unknown_list_location, + }, + network_requester_debug: value.network_requester_debug.into(), + logging: Default::default(), + } + } +} + +impl MigrationNymConfig for ConfigV1_1_20 { + fn default_root_directory() -> PathBuf { + dirs::home_dir() + .expect("Failed to evaluate $HOME value") + .join(".nym") + .join("service-providers") + .join("network-requester") + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct NetworkRequster { + pub allowed_list_location: PathBuf, + pub unknown_list_location: PathBuf, +} + +impl Default for NetworkRequster { + fn default() -> Self { + // same defaults as we had in <= v1.1.13 + NetworkRequster { + allowed_list_location: ::default_root_directory() + .join("allowed.list"), + unknown_list_location: ::default_root_directory() + .join("unknown.list"), + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct DebugV1_1_20 { + #[serde(with = "humantime_serde")] + pub standard_list_update_interval: Duration, +} + +impl From for Debug { + fn from(value: DebugV1_1_20) -> Self { + Debug { + standard_list_update_interval: value.standard_list_update_interval, + } + } +} + +impl Default for DebugV1_1_20 { + fn default() -> Self { + DebugV1_1_20 { + standard_list_update_interval: DEFAULT_STANDARD_LIST_UPDATE_INTERVAL, + } + } +} diff --git a/service-providers/network-requester/src/config/persistence.rs b/service-providers/network-requester/src/config/persistence.rs new file mode 100644 index 0000000000..fe289e5c39 --- /dev/null +++ b/service-providers/network-requester/src/config/persistence.rs @@ -0,0 +1,33 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_client_core::config::disk_persistence::CommonClientPaths; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +pub const DEFAULT_ALLOWED_LIST_FILENAME: &str = "allowed.list"; +pub const DEFAULT_UNKNOWN_LIST_FILENAME: &str = "unknown.list"; + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)] +pub struct NetworkRequesterPaths { + #[serde(flatten)] + pub common_paths: CommonClientPaths, + + /// Location of the file containing our allow.list + pub allowed_list_location: PathBuf, + + /// Location of the file containing our unknown.list + pub unknown_list_location: PathBuf, +} + +impl NetworkRequesterPaths { + pub fn new_default>(base_data_directory: P) -> Self { + let base_dir = base_data_directory.as_ref(); + + NetworkRequesterPaths { + common_paths: CommonClientPaths::new_default(base_dir), + allowed_list_location: base_dir.join(DEFAULT_ALLOWED_LIST_FILENAME), + unknown_list_location: base_dir.join(DEFAULT_UNKNOWN_LIST_FILENAME), + } + } +} diff --git a/service-providers/network-requester/src/config/template.rs b/service-providers/network-requester/src/config/template.rs index e1371955c7..628a521f9d 100644 --- a/service-providers/network-requester/src/config/template.rs +++ b/service-providers/network-requester/src/config/template.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub(crate) fn config_template() -> &'static str { +pub(crate) const CONFIG_TEMPLATE: &str = // While using normal toml marshalling would have been way simpler with less overhead, // I think it's useful to have comments attached to the saved config file to explain behaviour of // particular fields. @@ -24,48 +24,55 @@ id = '{{ client.id }}' disabled_credentials_mode = {{ client.disabled_credentials_mode }} # Addresses to nyxd validators via which the client can communicate with the chain. -nyxd_urls = [{{#each client.nyxd_urls }} - '{{this}}', -{{/each}}] +nyxd_urls = [ + {{#each client.nyxd_urls }} + '{{this}}', + {{/each}} +] # Addresses to APIs running on validator from which the client gets the view of the network. -nym_api_urls = [{{#each client.nym_api_urls }} - '{{this}}', -{{/each}}] +nym_api_urls = [ + {{#each client.nym_api_urls }} + '{{this}}', + {{/each}} +] + +[storage_paths] # Path to file containing private identity key. -private_identity_key_file = '{{ client.private_identity_key_file }}' +keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' # Path to file containing public identity key. -public_identity_key_file = '{{ client.public_identity_key_file }}' +keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}' # Path to file containing private encryption key. -private_encryption_key_file = '{{ client.private_encryption_key_file }}' +keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}' # Path to file containing public encryption key. -public_encryption_key_file = '{{ client.public_encryption_key_file }}' - -# Path to the database containing bandwidth credentials -database_path = '{{ client.database_path }}' - -# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. -reply_surb_database_path = '{{ client.reply_surb_database_path }}' - -##### additional client config options ##### +keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}' # A gateway specific, optional, base58 stringified shared key used for # communication with particular gateway. -gateway_shared_key_file = '{{ client.gateway_shared_key_file }}' +keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}' # Path to file containing key used for encrypting and decrypting the content of an # acknowledgement so that nobody besides the client knows which packet it refers to. -ack_key_file = '{{ client.ack_key_file }}' +keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}' -##### advanced configuration options ##### +# Path to the database containing bandwidth credentials +credentials_database = '{{ storage_paths.credentials_database }}' -# Absolute path to the home Nym Clients directory. -nym_root_directory = '{{ client.nym_root_directory }}' +# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. +reply_surb_database = '{{ storage_paths.reply_surb_database }}' +# Location of the file containing our allow.list +allowed_list_location = '{{ storage_paths.allowed_list_location }}' + +# Location of the file containing our unknown.list +unknown_list_location = '{{ storage_paths.unknown_list_location }}' + + +# DEPRECATED [client.gateway_endpoint] # ID of the gateway from which the client should be fetching messages. gateway_id = '{{ client.gateway_endpoint.gateway_id }}' @@ -76,15 +83,6 @@ gateway_owner = '{{ client.gateway_endpoint.gateway_owner }}' # Address of the gateway listener to which all client requests should be sent. gateway_listener = '{{ client.gateway_endpoint.gateway_listener }}' -##### network requester specific config options ##### - -[network_requester] -# Location of the file containing our allow.list -allowed_list_location = '{{ network_requester.allowed_list_location }}' - -# Location of the file containing our unknown.list -unknown_list_location = '{{ network_requester.unknown_list_location }}' - ##### logging configuration options ##### [logging] @@ -108,5 +106,4 @@ average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}' [debug.cover_traffic] loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}' -"# -} +"#; diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs index c4d4ac8efd..efcf57fbd9 100644 --- a/service-providers/network-requester/src/core.rs +++ b/service-providers/network-requester/src/core.rs @@ -5,7 +5,7 @@ use crate::allowed_hosts; use crate::allowed_hosts::standard_list::StandardListUpdater; use crate::allowed_hosts::stored_allowed_hosts::{start_allowed_list_reloader, StoredAllowedHosts}; use crate::allowed_hosts::{OutboundRequestFilter, StandardList}; -use crate::config::Config; +use crate::config::{BaseClientConfig, Config}; use crate::error::NetworkRequesterError; use crate::reply::MixnetMessage; use crate::statistics::ServiceStatisticsCollector; @@ -14,6 +14,7 @@ use async_trait::async_trait; use futures::channel::mpsc; use log::warn; use nym_bin_common::build_information::BinaryBuildInformation; +use nym_client_core::config::disk_persistence::CommonClientPaths; use nym_network_defaults::NymNetworkDetails; use nym_service_providers_common::interface::{ BinaryInformation, ProviderInterfaceVersion, Request, RequestVersion, @@ -163,8 +164,9 @@ impl NRServiceProviderBuilder { ) -> NRServiceProviderBuilder { let standard_list = StandardList::new(); - let allowed_hosts = StoredAllowedHosts::new(config.allow_list_file_location()); - let unknown_hosts = allowed_hosts::HostsStore::new(config.unknown_list_file_location()); + let allowed_hosts = StoredAllowedHosts::new(&config.storage_paths.allowed_list_location); + let unknown_hosts = + allowed_hosts::HostsStore::new(&config.storage_paths.unknown_list_location); let outbound_request_filter = OutboundRequestFilter::new(allowed_hosts.clone(), standard_list.clone(), unknown_hosts); @@ -183,7 +185,9 @@ impl NRServiceProviderBuilder { /// Start all subsystems pub async fn run_service_provider(self) -> Result<(), NetworkRequesterError> { // Connect to the mixnet - let mixnet_client = create_mixnet_client(self.config.get_base()).await?; + let mixnet_client = + create_mixnet_client(&self.config.base, &self.config.storage_paths.common_paths) + .await?; // channels responsible for managing messages that are to be sent to the mix network. The receiver is // going to be used by `mixnet_response_listener` @@ -443,7 +447,7 @@ impl NRServiceProvider { return; } - let traffic_config = self.config.get_base().get_debug_config().traffic; + let traffic_config = self.config.base.debug.traffic; let packet_size = traffic_config .secondary_packet_size .unwrap_or(traffic_config.primary_packet_size); @@ -478,12 +482,13 @@ impl NRServiceProvider { // Helper function to create the mixnet client. // This is NOT in the SDK since we don't want to expose any of the client-core config types. // We could however consider moving it to a crate in common in the future. -async fn create_mixnet_client( - config: &nym_client_core::config::Config, +async fn create_mixnet_client( + config: &BaseClientConfig, + paths: &CommonClientPaths, ) -> Result { - let debug_config = *config.get_debug_config(); + let debug_config = config.debug; - let storage_paths = nym_sdk::mixnet::StoragePaths::from(config); + let storage_paths = nym_sdk::mixnet::StoragePaths::from(paths.clone()); let mut client_builder = nym_sdk::mixnet::MixnetClientBuilder::new_with_default_storage(storage_paths)