diff --git a/Cargo.lock b/Cargo.lock index e7fad3be70..29150b66be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,9 +373,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" @@ -3562,6 +3562,7 @@ name = "nym-client-core" version = "1.1.14" dependencies = [ "async-trait", + "base64 0.21.2", "dashmap 5.4.0", "dirs 4.0.0", "futures", @@ -3584,6 +3585,7 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", + "sha2 0.10.6", "sqlx 0.6.3", "tap", "tempfile", @@ -4855,6 +4857,12 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" version = "2.10.0" @@ -4864,12 +4872,6 @@ 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" @@ -5718,7 +5720,7 @@ version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -5988,7 +5990,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", ] [[package]] @@ -7300,7 +7302,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", "axum", - "base64 0.21.0", + "base64 0.21.2", "bytes", "futures-core", "futures-util", diff --git a/clients/native/src/client/config/mod.rs b/clients/native/src/client/config/mod.rs index 2f6e47391d..8072243c05 100644 --- a/clients/native/src/client/config/mod.rs +++ b/clients/native/src/client/config/mod.rs @@ -21,6 +21,7 @@ pub use nym_client_core::config::{DebugConfig, GatewayEndpointConfig}; pub mod old_config_v1_1_13; pub mod old_config_v1_1_20; +pub mod old_config_v1_1_20_2; mod persistence; mod template; @@ -52,19 +53,6 @@ pub fn default_data_directory>(id: P) -> PathBuf { .join(DEFAULT_DATA_DIR) } -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)] -#[serde(deny_unknown_fields)] -pub enum SocketType { - WebSocket, - None, -} - -impl SocketType { - pub fn is_websocket(&self) -> bool { - matches!(self, SocketType::WebSocket) - } -} - #[derive(Debug, Deserialize, PartialEq, Serialize)] pub struct Config { #[serde(flatten)] @@ -187,6 +175,19 @@ impl Config { // define_optional_set_inner!(Config, base, BaseClientConfig); +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)] +#[serde(deny_unknown_fields)] +pub enum SocketType { + WebSocket, + None, +} + +impl SocketType { + pub fn is_websocket(&self) -> bool { + matches!(self, SocketType::WebSocket) + } +} + #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] #[serde(default, deny_unknown_fields)] pub struct 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 index c68f3871c3..8c2452d3e1 100644 --- a/clients/native/src/client/config/old_config_v1_1_20.rs +++ b/clients/native/src/client/config/old_config_v1_1_20.rs @@ -1,13 +1,16 @@ // 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 crate::client::config::old_config_v1_1_20_2::{ + ClientPathsV1_1_20_2, ConfigV1_1_20_2, SocketTypeV1_1_20_2, SocketV1_1_20_2, +}; 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::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2; 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_client_core::config::old_config_v1_1_20_2::{ + ClientV1_1_20_2, ConfigV1_1_20_2 as BaseConfigV1_1_20_2, +}; use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; use nym_config::legacy_helpers::nym_config::MigrationNymConfig; use serde::{Deserialize, Serialize}; @@ -22,11 +25,11 @@ pub enum SocketTypeV1_1_20 { None, } -impl From for SocketType { +impl From for SocketTypeV1_1_20_2 { fn from(value: SocketTypeV1_1_20) -> Self { match value { - SocketTypeV1_1_20::WebSocket => SocketType::WebSocket, - SocketTypeV1_1_20::None => SocketType::None, + SocketTypeV1_1_20::WebSocket => SocketTypeV1_1_20_2::WebSocket, + SocketTypeV1_1_20::None => SocketTypeV1_1_20_2::None, } } } @@ -40,11 +43,11 @@ pub struct ConfigV1_1_20 { pub socket: SocketV1_1_20, } -impl From for Config { +impl From for ConfigV1_1_20_2 { fn from(value: ConfigV1_1_20) -> Self { - Config { - base: BaseConfig { - client: Client { + ConfigV1_1_20_2 { + base: BaseConfigV1_1_20_2 { + client: ClientV1_1_20_2 { version: value.base.client.version, id: value.base.client.id, disabled_credentials_mode: value.base.client.disabled_credentials_mode, @@ -55,8 +58,8 @@ impl From for Config { debug: value.base.debug.into(), }, socket: value.socket.into(), - storage_paths: ClientPaths { - common_paths: CommonClientPaths { + storage_paths: ClientPathsV1_1_20_2 { + common_paths: CommonClientPathsV1_1_20_2 { keys: ClientKeysPaths { private_identity_key_file: value.base.client.private_identity_key_file, public_identity_key_file: value.base.client.public_identity_key_file, @@ -91,9 +94,9 @@ pub struct SocketV1_1_20 { listening_port: u16, } -impl From for Socket { +impl From for SocketV1_1_20_2 { fn from(value: SocketV1_1_20) -> Self { - Socket { + SocketV1_1_20_2 { socket_type: value.socket_type.into(), host: value.host, listening_port: value.listening_port, diff --git a/clients/native/src/client/config/old_config_v1_1_20_2.rs b/clients/native/src/client/config/old_config_v1_1_20_2.rs new file mode 100644 index 0000000000..6f4686b063 --- /dev/null +++ b/clients/native/src/client/config/old_config_v1_1_20_2.rs @@ -0,0 +1,103 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::client::config::persistence::ClientPaths; +use crate::client::config::{default_config_filepath, Config, Socket, SocketType}; +use nym_bin_common::logging::LoggingSettings; +use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2; +use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseConfigV1_1_20_2; +use nym_client_core::config::GatewayEndpointConfig; +use nym_config::read_config_from_toml_file; +use nym_network_defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; +use serde::{Deserialize, Serialize}; +use std::io; +use std::net::{IpAddr, Ipv4Addr}; +use std::path::Path; + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)] +pub struct ClientPathsV1_1_20_2 { + #[serde(flatten)] + pub common_paths: CommonClientPathsV1_1_20_2, +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub struct ConfigV1_1_20_2 { + #[serde(flatten)] + pub base: BaseConfigV1_1_20_2, + + pub socket: SocketV1_1_20_2, + + pub storage_paths: ClientPathsV1_1_20_2, + + pub logging: LoggingSettings, +} + +impl ConfigV1_1_20_2 { + 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)) + } + + // in this upgrade, gateway endpoint configuration was moved out of the config file, + // so its returned to be stored elsewhere. + pub fn upgrade(self) -> (Config, GatewayEndpointConfig) { + let gateway_details = self.base.client.gateway_endpoint.clone().into(); + let config = Config { + base: self.base.into(), + socket: self.socket.into(), + storage_paths: ClientPaths { + common_paths: self.storage_paths.common_paths.upgrade_default(), + }, + logging: self.logging, + }; + + (config, gateway_details) + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)] +#[serde(deny_unknown_fields)] +pub enum SocketTypeV1_1_20_2 { + WebSocket, + None, +} + +impl From for SocketType { + fn from(value: SocketTypeV1_1_20_2) -> Self { + match value { + SocketTypeV1_1_20_2::WebSocket => SocketType::WebSocket, + SocketTypeV1_1_20_2::None => SocketType::None, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct SocketV1_1_20_2 { + pub socket_type: SocketTypeV1_1_20_2, + pub host: IpAddr, + pub listening_port: u16, +} + +impl From for Socket { + fn from(value: SocketV1_1_20_2) -> Self { + Socket { + socket_type: value.socket_type.into(), + host: value.host, + listening_port: value.listening_port, + } + } +} + +impl Default for SocketV1_1_20_2 { + fn default() -> Self { + SocketV1_1_20_2 { + socket_type: SocketTypeV1_1_20_2::WebSocket, + host: IpAddr::V4(Ipv4Addr::LOCALHOST), + listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT, + } + } +} diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index e3406a0101..413b61790d 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -64,18 +64,9 @@ 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 }}' - -# Address of the gateway owner to which the client should send messages. -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 }}' - - +# Path to the file containing information about gateway used by this client, +# i.e. details such as its public key, owner address or the network information. +gateway_details = '{{ storage_paths.gateway_details }}' ##### socket config options ##### diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index 6b79f1c161..8457268d79 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -9,15 +9,12 @@ use log::*; use nym_client_core::client::base_client::non_wasm_helpers::default_query_dkg_client_from_config; use nym_client_core::client::base_client::storage::OnDiskPersistent; use nym_client_core::client::base_client::{ - non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState, + BaseClientBuilder, ClientInput, ClientOutput, ClientState, }; use nym_client_core::client::inbound_messages::InputMessage; -use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::client::received_buffer::{ ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver, }; -use nym_client_core::client::replies::reply_storage::fs_backend; -use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::params::PacketType; use nym_task::connections::TransmissionLane; @@ -94,32 +91,12 @@ impl SocketClient { res } - fn initialise_key_store(&self) -> OnDiskKeys { - OnDiskKeys::new(self.config.storage_paths.common_paths.keys.clone()) - } - - async fn initialise_credential_store(&self) -> PersistentCredentialStorage { - nym_credential_storage::initialise_persistent_storage( - &self.config.storage_paths.common_paths.credentials_database, - ) - .await - } - - async fn initialise_reply_surb_store(&self) -> Result { - non_wasm_helpers::setup_fs_reply_surb_backend( - &self.config.storage_paths.common_paths.reply_surb_database, - &self.config.base.debug.reply_surbs, - ) - .await - .map_err(Into::into) - } - async fn initialise_storage(&self) -> Result { - Ok(OnDiskPersistent::new( - self.initialise_key_store(), - self.initialise_reply_surb_store().await?, - self.initialise_credential_store().await, - )) + Ok(OnDiskPersistent::from_paths( + self.config.storage_paths.common_paths.clone(), + &self.config.base.debug, + ) + .await?) } // TODO: see if this could also be shared with socks5 client / nym-sdk maybe diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index c037329c38..eca33f433c 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -4,7 +4,7 @@ use crate::client::config::{ default_config_directory, default_config_filepath, default_data_directory, }; -use crate::commands::try_load_current_config; +use crate::commands::try_upgrade_config; use crate::{ client::config::Config, commands::{override_config, OverrideConfig}, @@ -12,7 +12,10 @@ use crate::{ }; use clap::Args; use nym_bin_common::output_format::OutputFormat; +use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails; use nym_client_core::client::key_manager::persistence::OnDiskKeys; +use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::init::GatewaySetup; use nym_crypto::asymmetric::identity; use nym_sphinx::addressing::clients::Recipient; use serde::Serialize; @@ -105,9 +108,9 @@ pub struct InitResults { } impl InitResults { - fn new(config: &Config, address: &Recipient) -> Self { + fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self { Self { - client_core: nym_client_core::init::InitResults::new(&config.base, address), + client_core: nym_client_core::init::InitResults::new(&config.base, address, gateway), client_listening_port: config.socket.listening_port, client_address: address.to_string(), } @@ -132,13 +135,15 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> { let id = &args.id; - let old_config = if default_config_filepath(id).exists() { + let already_init = if default_config_filepath(id).exists() { + // 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_config(id)?; 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)?) + true } else { - init_paths(&args.id)?; - None + init_paths(id)?; + false }; // Usually you only register with the gateway on the first init, however you can force @@ -151,30 +156,33 @@ 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 = old_config.is_none() || user_wants_force_register; + let register_gateway = !already_init || user_wants_force_register; // Attempt to use a user-provided gateway, if possible let user_chosen_gateway_id = args.gateway; + let gateway_setup = GatewaySetup::new_fresh( + user_chosen_gateway_id.map(|id| id.to_base58_string()), + Some(args.latency_based_selection), + ); // Load and potentially override config - let mut config = override_config(Config::new(id), OverrideConfig::from(args.clone())); + let config = override_config(Config::new(id), OverrideConfig::from(args.clone())); // 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::new(config.storage_paths.common_paths.keys.clone()); - let gateway = nym_client_core::init::setup_gateway_from_config::<_>( + let details_store = + OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details); + let init_details = nym_client_core::init::setup_gateway( + &gateway_setup, &key_store, + &details_store, register_gateway, - user_chosen_gateway_id, - &config.base, - old_config.map(|cfg| cfg.base.client.gateway_endpoint), - args.latency_based_selection, + Some(&config.base.client.nym_api_urls), ) .await .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; - config.base.set_gateway_endpoint(gateway); - let config_save_location = config.default_location(); config.save_to_default_location().tap_err(|_| { log::error!("Failed to save the config file"); @@ -184,14 +192,11 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> { config_save_location.display() ); - let address = nym_client_core::init::get_client_address_from_stored_ondisk_keys( - &config.storage_paths.common_paths.keys, - &config.base.client.gateway_endpoint, - )?; + let address = init_details.client_address()?; eprintln!("Client configuration completed.\n"); - let init_results = InitResults::new(&config, &address); + let init_results = InitResults::new(&config, &address, &init_details.gateway_details); println!("{}", args.output.format(&init_results)); Ok(()) diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index ab91433462..735b314185 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -3,6 +3,7 @@ use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13; use crate::client::config::old_config_v1_1_20::ConfigV1_1_20; +use crate::client::config::old_config_v1_1_20_2::ConfigV1_1_20_2; use crate::client::config::{BaseClientConfig, Config}; use crate::error::ClientError; use clap::CommandFactory; @@ -11,6 +12,12 @@ use lazy_static::lazy_static; use log::{error, info}; use nym_bin_common::build_information::BinaryBuildInformation; use nym_bin_common::completions::{fig_generate, ArgShell}; +use nym_client_core::client::base_client::storage::gateway_details::{ + OnDiskGatewayDetails, PersistedGatewayDetails, +}; +use nym_client_core::client::key_manager::persistence::OnDiskKeys; +use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::error::ClientCoreError; use nym_config::OptionalSet; use std::error::Error; use std::net::IpAddr; @@ -109,6 +116,28 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { ) } +fn persist_gateway_details( + config: &Config, + details: GatewayEndpointConfig, +) -> Result<(), ClientError> { + let details_store = + OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details); + let keys_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone()); + let shared_keys = keys_store.ephemeral_load_gateway_keys().map_err(|source| { + ClientError::ClientCoreError(ClientCoreError::KeyStoreError { + source: Box::new(source), + }) + })?; + let persisted_details = PersistedGatewayDetails::new(details, &shared_keys); + details_store + .store_to_disk(&persisted_details) + .map_err(|source| { + ClientError::ClientCoreError(ClientCoreError::GatewayDetailsStoreError { + source: Box::new(source), + }) + }) +} + fn try_upgrade_v1_1_13_config(id: &str) -> Result { use nym_config::legacy_helpers::nym_config::MigrationNymConfig; @@ -122,7 +151,9 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result { 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(); + let updated_step2: ConfigV1_1_20_2 = updated_step1.into(); + let (updated, gateway_config) = updated_step2.upgrade(); + persist_gateway_details(&updated, gateway_config)?; updated.save_to_default_location()?; Ok(true) @@ -140,21 +171,56 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result { 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(); + let updated_step1: ConfigV1_1_20_2 = old_config.into(); + let (updated, gateway_config) = updated_step1.upgrade(); + persist_gateway_details(&updated, gateway_config)?; + + updated.save_to_default_location()?; + Ok(true) +} + +fn try_upgrade_v1_1_20_2_config(id: &str) -> Result { + // explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21) + let Ok(old_config) = ConfigV1_1_20_2::read_from_default_path(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_2 config template."); + info!("It is going to get updated to the current specification."); + + let (updated, gateway_config) = old_config.upgrade(); + persist_gateway_details(&updated, gateway_config)?; + 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)?; + if try_upgrade_v1_1_13_config(id)? { + return Ok(()); + } + if try_upgrade_v1_1_20_config(id)? { + return Ok(()); + } + if try_upgrade_v1_1_20_2_config(id)? { + return Ok(()); } Ok(()) } fn try_load_current_config(id: &str) -> Result { + // try to load the config as is + if let Ok(cfg) = Config::read_from_default_path(id) { + return if !cfg.validate() { + Err(ClientError::ConfigValidationFailure) + } else { + Ok(cfg) + }; + } + + // we couldn't load it - try upgrading it from older revisions try_upgrade_config(id)?; let config = match Config::read_from_default_path(id) { diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index e09a23ff3c..7c8fa68719 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -1,7 +1,7 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::commands::try_load_current_config; +use crate::commands::try_upgrade_config; use crate::config::{ default_config_directory, default_config_filepath, default_data_directory, Config, }; @@ -11,7 +11,10 @@ use crate::{ }; use clap::Args; use nym_bin_common::output_format::OutputFormat; +use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails; use nym_client_core::client::key_manager::persistence::OnDiskKeys; +use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::init::GatewaySetup; use nym_crypto::asymmetric::identity; use nym_sphinx::addressing::clients::Recipient; use serde::Serialize; @@ -107,9 +110,13 @@ pub struct InitResults { } impl InitResults { - fn new(config: &Config, address: &Recipient) -> Self { + fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self { Self { - client_core: nym_client_core::init::InitResults::new(&config.core.base, address), + client_core: nym_client_core::init::InitResults::new( + &config.core.base, + address, + gateway, + ), socks5_listening_port: config.core.socks5.listening_port, client_address: address.to_string(), } @@ -135,13 +142,15 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> { let id = &args.id; let provider_address = &args.provider; - let old_config = if default_config_filepath(id).exists() { + let already_init = if default_config_filepath(id).exists() { + // 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_config(id)?; 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)?) + true } else { - init_paths(&args.id)?; - None + init_paths(id)?; + false }; // Usually you only register with the gateway on the first init, however you can force @@ -154,13 +163,17 @@ 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 = old_config.is_none() || user_wants_force_register; + let register_gateway = !already_init || user_wants_force_register; // Attempt to use a user-provided gateway, if possible let user_chosen_gateway_id = args.gateway; + let gateway_setup = GatewaySetup::new_fresh( + user_chosen_gateway_id.map(|id| id.to_base58_string()), + Some(args.latency_based_selection), + ); // Load and potentially override config - let mut config = override_config( + let config = override_config( Config::new(id, &provider_address.to_string()), OverrideConfig::from(args.clone()), ); @@ -168,19 +181,18 @@ 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::new(config.storage_paths.common_paths.keys.clone()); - let gateway = nym_client_core::init::setup_gateway_from_config::<_>( + let details_store = + OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details); + let init_details = nym_client_core::init::setup_gateway( + &gateway_setup, &key_store, + &details_store, register_gateway, - user_chosen_gateway_id, - &config.core.base, - old_config.map(|cfg| cfg.core.base.client.gateway_endpoint), - args.latency_based_selection, + Some(&config.core.base.client.nym_api_urls), ) .await .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; - config.core.base.set_gateway_endpoint(gateway); - // TODO: ask the service provider we specified for its interface version and set it in the config let config_save_location = config.default_location(); @@ -192,11 +204,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> { config_save_location.display() ); - 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); + let address = init_details.client_address()?; + + let init_results = InitResults::new(&config, &address, &init_details.gateway_details); println!("{}", args.output.format(&init_results)); Ok(()) diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 7bab4fbe9a..60d6b6e0b7 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -3,6 +3,7 @@ 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::old_config_v1_1_20_2::ConfigV1_1_20_2; use crate::config::{BaseClientConfig, Config}; use crate::error::Socks5ClientError; use clap::CommandFactory; @@ -11,6 +12,12 @@ use lazy_static::lazy_static; use log::{error, info}; use nym_bin_common::build_information::BinaryBuildInformation; use nym_bin_common::completions::{fig_generate, ArgShell}; +use nym_client_core::client::base_client::storage::gateway_details::{ + OnDiskGatewayDetails, PersistedGatewayDetails, +}; +use nym_client_core::client::key_manager::persistence::OnDiskKeys; +use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::error::ClientCoreError; use nym_config::OptionalSet; use nym_sphinx::params::PacketType; use std::error::Error; @@ -116,6 +123,28 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { ) } +fn persist_gateway_details( + config: &Config, + details: GatewayEndpointConfig, +) -> Result<(), Socks5ClientError> { + let details_store = + OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details); + let keys_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone()); + let shared_keys = keys_store.ephemeral_load_gateway_keys().map_err(|source| { + Socks5ClientError::ClientCoreError(ClientCoreError::KeyStoreError { + source: Box::new(source), + }) + })?; + let persisted_details = PersistedGatewayDetails::new(details, &shared_keys); + details_store + .store_to_disk(&persisted_details) + .map_err(|source| { + Socks5ClientError::ClientCoreError(ClientCoreError::GatewayDetailsStoreError { + source: Box::new(source), + }) + }) +} + fn try_upgrade_v1_1_13_config(id: &str) -> Result { use nym_config::legacy_helpers::nym_config::MigrationNymConfig; @@ -129,7 +158,9 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result { 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(); + let updated_step2: ConfigV1_1_20_2 = updated_step1.into(); + let (updated, gateway_config) = updated_step2.upgrade(); + persist_gateway_details(&updated, gateway_config)?; updated.save_to_default_location()?; Ok(true) @@ -147,21 +178,56 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result { 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(); + let updated_step1: ConfigV1_1_20_2 = old_config.into(); + let (updated, gateway_config) = updated_step1.upgrade(); + persist_gateway_details(&updated, gateway_config)?; + + updated.save_to_default_location()?; + Ok(true) +} + +fn try_upgrade_v1_1_20_2_config(id: &str) -> Result { + // explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21) + let Ok(old_config) = ConfigV1_1_20_2::read_from_default_path(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_2 config template."); + info!("It is going to get updated to the current specification."); + + let (updated, gateway_config) = old_config.upgrade(); + persist_gateway_details(&updated, gateway_config)?; + 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)?; + if try_upgrade_v1_1_13_config(id)? { + return Ok(()); + } + if try_upgrade_v1_1_20_config(id)? { + return Ok(()); + } + if try_upgrade_v1_1_20_2_config(id)? { + return Ok(()); } Ok(()) } fn try_load_current_config(id: &str) -> Result { + // try to load the config as is + if let Ok(cfg) = Config::read_from_default_path(id) { + return if !cfg.validate() { + Err(Socks5ClientError::ConfigValidationFailure) + } else { + Ok(cfg) + }; + } + + // we couldn't load it - try upgrading it from older revisions try_upgrade_config(id)?; let config = match Config::read_from_default_path(id) { diff --git a/clients/socks5/src/config/mod.rs b/clients/socks5/src/config/mod.rs index 1c308ccc8b..1ca4c2b0ac 100644 --- a/clients/socks5/src/config/mod.rs +++ b/clients/socks5/src/config/mod.rs @@ -19,6 +19,7 @@ pub use nym_socks5_client_core::config::Config as CoreConfig; pub mod old_config_v1_1_13; pub mod old_config_v1_1_20; +pub mod old_config_v1_1_20_2; mod persistence; mod template; diff --git a/clients/socks5/src/config/old_config_v1_1_20.rs b/clients/socks5/src/config/old_config_v1_1_20.rs index 4b8d47558b..4acc4fccbd 100644 --- a/clients/socks5/src/config/old_config_v1_1_20.rs +++ b/clients/socks5/src/config/old_config_v1_1_20.rs @@ -1,18 +1,20 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::persistence::SocksClientPaths; -use crate::config::{BaseClientConfig, Config, CoreConfig}; +use crate::config::old_config_v1_1_20_2::{ + ConfigV1_1_20_2, CoreConfigV1_1_20_2, SocksClientPathsV1_1_20_2, +}; 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::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2; 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_client_core::config::old_config_v1_1_20_2::ClientV1_1_20_2; 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 nym_socks5_client_core::config::old_config_v1_1_20_2::{ + BaseClientConfigV1_1_20_2, Socks5DebugV1_1_20_2, Socks5V1_1_20_2, }; +use nym_socks5_client_core::config::{ProviderInterfaceVersion, Socks5ProtocolVersion}; use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::path::PathBuf; @@ -29,12 +31,12 @@ pub struct ConfigV1_1_20 { pub socks5: Socks5V1_1_20, } -impl From for Config { +impl From for ConfigV1_1_20_2 { fn from(value: ConfigV1_1_20) -> Self { - Config { - core: CoreConfig { - base: BaseClientConfig { - client: Client { + ConfigV1_1_20_2 { + core: CoreConfigV1_1_20_2 { + base: BaseClientConfigV1_1_20_2 { + client: ClientV1_1_20_2 { version: value.base.client.version, id: value.base.client.id, disabled_credentials_mode: value.base.client.disabled_credentials_mode, @@ -46,8 +48,8 @@ impl From for Config { }, socks5: value.socks5.into(), }, - storage_paths: SocksClientPaths { - common_paths: CommonClientPaths { + storage_paths: SocksClientPathsV1_1_20_2 { + common_paths: CommonClientPathsV1_1_20_2 { keys: ClientKeysPaths { private_identity_key_file: value.base.client.private_identity_key_file, public_identity_key_file: value.base.client.public_identity_key_file, @@ -96,9 +98,9 @@ pub struct Socks5V1_1_20 { pub socks5_debug: Socks5DebugV1_1_20, } -impl From for Socks5 { +impl From for Socks5V1_1_20_2 { fn from(value: Socks5V1_1_20) -> Self { - Socks5 { + Socks5V1_1_20_2 { listening_port: value.listening_port, provider_mix_address: value.provider_mix_address, provider_interface_version: value.provider_interface_version, @@ -116,9 +118,9 @@ pub struct Socks5DebugV1_1_20 { per_request_surbs: u32, } -impl From for Socks5Debug { +impl From for Socks5DebugV1_1_20_2 { fn from(value: Socks5DebugV1_1_20) -> Self { - Socks5Debug { + Socks5DebugV1_1_20_2 { connection_start_surbs: value.connection_start_surbs, per_request_surbs: value.per_request_surbs, } diff --git a/clients/socks5/src/config/old_config_v1_1_20_2.rs b/clients/socks5/src/config/old_config_v1_1_20_2.rs new file mode 100644 index 0000000000..47d6dabe3f --- /dev/null +++ b/clients/socks5/src/config/old_config_v1_1_20_2.rs @@ -0,0 +1,54 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::SocksClientPaths; +use crate::config::{default_config_filepath, Config}; +use nym_bin_common::logging::LoggingSettings; +use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2; +use nym_client_core::config::GatewayEndpointConfig; +use nym_config::read_config_from_toml_file; +pub use nym_socks5_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as CoreConfigV1_1_20_2; +use serde::{Deserialize, Serialize}; +use std::io; +use std::path::Path; + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)] +pub struct SocksClientPathsV1_1_20_2 { + #[serde(flatten)] + pub common_paths: CommonClientPathsV1_1_20_2, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_20_2 { + pub core: CoreConfigV1_1_20_2, + + pub storage_paths: SocksClientPathsV1_1_20_2, + + pub logging: LoggingSettings, +} + +impl ConfigV1_1_20_2 { + 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)) + } + + // in this upgrade, gateway endpoint configuration was moved out of the config file, + // so its returned to be stored elsewhere. + pub fn upgrade(self) -> (Config, GatewayEndpointConfig) { + let gateway_details = self.core.base.client.gateway_endpoint.clone().into(); + let config = Config { + core: self.core.into(), + storage_paths: SocksClientPaths { + common_paths: self.storage_paths.common_paths.upgrade_default(), + }, + logging: self.logging, + }; + + (config, gateway_details) + } +} diff --git a/clients/socks5/src/config/template.rs b/clients/socks5/src/config/template.rs index fd4aec5f52..22d047bb16 100644 --- a/clients/socks5/src/config/template.rs +++ b/clients/socks5/src/config/template.rs @@ -64,17 +64,9 @@ 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 }}' - +# Path to the file containing information about gateway used by this client, +# i.e. details such as its public key, owner address or the network information. +gateway_details = '{{ storage_paths.gateway_details }}' ##### socket config options ##### diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index 96c9c8b525..92cab52991 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -225,9 +225,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" @@ -2258,6 +2258,7 @@ name = "nym-client-core" version = "1.1.14" dependencies = [ "async-trait", + "base64 0.21.2", "dashmap", "dirs 4.0.0", "futures", @@ -2280,6 +2281,7 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", + "sha2 0.10.6", "sqlx 0.6.3", "tap", "thiserror", @@ -3496,7 +3498,7 @@ version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -3630,7 +3632,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", ] [[package]] diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index 850964a908..74af0c2c8e 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -7,7 +7,7 @@ use crate::client::response_pusher::ResponsePusher; use crate::constants::NODE_TESTER_CLIENT_ID; use crate::error::WasmClientError; use crate::helpers::{ - choose_gateway, gateway_from_topology, parse_recipient, parse_sender_tag, + parse_recipient, parse_sender_tag, setup_from_topology, setup_gateway_from_api, setup_reply_surb_storage_backend, }; use crate::storage::traits::FullWasmClientStorage; @@ -27,7 +27,7 @@ use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider}; use nym_topology::NymTopology; use nym_validator_client::client::IdentityKey; use rand::rngs::OsRng; -use rand::{thread_rng, RngCore}; +use rand::RngCore; use std::sync::Arc; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; @@ -122,7 +122,7 @@ impl NymClientBuilder { fn initialise_storage(config: &Config, base_storage: ClientStorage) -> FullWasmClientStorage { FullWasmClientStorage { - key_store: base_storage, + keys_and_gateway_store: base_storage, reply_storage: setup_reply_surb_storage_backend(config.base.debug.reply_surbs), credential_storage: EphemeralCredentialStorage::default(), } @@ -138,30 +138,17 @@ impl NymClientBuilder { ClientStorage::new_async(&self.config.base.client.id, self.storage_passphrase.take()) .await?; + let user_chosen = self.preferred_gateway.clone(); + // 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.preferred_gateway.as_deref(), - topology, - &client_store, - ) - .await? + if let Some(topology) = &self.custom_topology { + setup_from_topology(user_chosen, topology, &client_store).await? } else { - choose_gateway( - &client_store, - self.preferred_gateway.clone(), - &nym_api_endpoints, - ) - .await? + setup_gateway_from_api(&client_store, user_chosen, &nym_api_endpoints).await? }; let packet_type = self.config.base.debug.traffic.packet_type; - - // temporary workaround until it's properly moved into storage - self.config.base = self.config.base.with_gateway_endpoint(gateway_endpoint); let storage = Self::initialise_storage(&self.config, client_store); - let maybe_topology_provider = self.topology_provider(); let mut base_builder: BaseClientBuilder<_, FullWasmClientStorage> = diff --git a/clients/webassembly/src/helpers.rs b/clients/webassembly/src/helpers.rs index c59c10ed22..19557c8fb3 100644 --- a/clients/webassembly/src/helpers.rs +++ b/clients/webassembly/src/helpers.rs @@ -7,19 +7,18 @@ use crate::topology::WasmNymTopology; use js_sys::Promise; use nym_client_core::client::replies::reply_storage::browser_backend; use nym_client_core::config; -use nym_client_core::config::GatewayEndpointConfig; -use nym_client_core::init::GatewaySetup; -use nym_crypto::asymmetric::identity; +use nym_client_core::init::helpers::current_gateways; +use nym_client_core::init::{setup_gateway_from, GatewaySetup, InitialisationDetails}; use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; -use nym_topology::NymTopology; -use nym_validator_client::client::{IdentityKey, IdentityKeyRef}; +use nym_topology::{gateway, NymTopology}; +use nym_validator_client::client::IdentityKey; use nym_validator_client::NymApiClient; -use rand::{CryptoRng, Rng}; +use rand::thread_rng; use url::Url; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen_futures::future_to_promise; -use wasm_utils::{console_log, PromisableResult}; +use wasm_utils::PromisableResult; // don't get too excited about the name, under the hood it's just a big fat placeholder // with no disk_persistence @@ -79,87 +78,37 @@ pub fn current_network_topology(nym_api_url: String) -> Promise { }) } -pub(crate) async fn choose_gateway( +async fn setup_gateway( + client_store: &ClientStorage, + chosen_gateway: Option, + gateways: &[gateway::Node], +) -> Result { + let setup = if client_store.has_full_gateway_info().await? { + GatewaySetup::MustLoad + } else { + GatewaySetup::new_fresh(chosen_gateway.clone(), None) + }; + + setup_gateway_from(&setup, client_store, client_store, false, Some(gateways)) + .await + .map_err(Into::into) +} + +pub(crate) async fn setup_gateway_from_api( client_store: &ClientStorage, chosen_gateway: Option, nym_apis: &[Url], -) -> Result { - let existing_gateway_config = client_store.read_gateway_config().await?; - - console_log!("loaded: {:?}", existing_gateway_config); - - if let Some(existing) = existing_gateway_config { - if let Some(provided) = &chosen_gateway { - if provided != &existing.gateway_id { - return Err(WasmClientError::AlreadyRegistered { - gateway_config: existing, - }); - } - } - return Ok(existing); - }; - - // if NOTHING is specified nor available, choose gateway randomly. - let setup = GatewaySetup::new(None, chosen_gateway, None); - let config = setup.try_get_gateway_details(nym_apis).await?; - - // perform registration + persist the new gateway info - // TODO: this is actually quite bad. we shouldn't be persisting gateway info here since we did not have persisted - // the shared key yet. this will only happen when we start the base client itself. - // but unfortunately, we can't do much more until we do a bit more refactoring. - client_store.store_gateway_config(&config).await?; - - console_log!("stored: {:?}", config); - - Ok(config) +) -> Result { + let mut rng = thread_rng(); + let gateways = current_gateways(&mut rng, nym_apis).await?; + setup_gateway(client_store, chosen_gateway, &gateways).await } -pub(crate) async fn gateway_from_topology( - rng: &mut R, - explicit_gateway: Option>, +pub(crate) async fn setup_from_topology( + explicit_gateway: Option, topology: &NymTopology, client_store: &ClientStorage, -) -> Result { - let existing_gateway_config = client_store.read_gateway_config().await?; - console_log!("loaded: {:?}", existing_gateway_config); - - let new_gateway: GatewayEndpointConfig = if let Some(provided) = explicit_gateway { - if let Some(existing) = existing_gateway_config { - // we have stored gateway info and explicitly provided identity key - // - // check if they match, otherwise return an error - return if provided != existing.gateway_id { - Err(WasmClientError::AlreadyRegistered { - gateway_config: existing, - }) - } else { - Ok(existing) - }; - } else { - // we have explicitly provided identity key and didn't have any prior stored data - // - // try to grab details from the topology - let gateway_identity = identity::PublicKey::from_base58_string(provided) - .map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?; - if let Some(gateway) = topology.get_gateway(&gateway_identity) { - gateway.clone().into() - } else { - return Err(WasmClientError::NonExistentGateway { - gateway_identity: gateway_identity.to_base58_string(), - }); - } - } - } else if let Some(existing) = existing_gateway_config { - // we have stored data and didn't provide anything separately - use what's stored! - return Ok(existing); - } else { - // we don't have anything stored nor we have provided anything - // - // just grab random gateway from our topology - topology.random_gateway(rng)?.clone().into() - }; - - console_log!("storing: {:?}", new_gateway); - client_store.store_gateway_config(&new_gateway).await?; - Ok(new_gateway) +) -> Result { + let gateways = topology.gateways(); + setup_gateway(client_store, explicit_gateway, gateways).await } diff --git a/clients/webassembly/src/storage/errors.rs b/clients/webassembly/src/storage/errors.rs index 8e1dd97a43..fcb6db5ad1 100644 --- a/clients/webassembly/src/storage/errors.rs +++ b/clients/webassembly/src/storage/errors.rs @@ -16,6 +16,9 @@ pub enum ClientStorageError { #[error("{typ} cryptographic key is not available in storage")] CryptoKeyNotInStorage { typ: String }, + + #[error("the prior gateway details are not available in the storage")] + GatewayDetailsNotInStorage, } impl From for JsValue { diff --git a/clients/webassembly/src/storage/mod.rs b/clients/webassembly/src/storage/mod.rs index f372e723ee..d13e27e6e9 100644 --- a/clients/webassembly/src/storage/mod.rs +++ b/clients/webassembly/src/storage/mod.rs @@ -1,9 +1,10 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::client::config::Config; use crate::storage::errors::ClientStorageError; use js_sys::Promise; -use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::client::base_client::storage::gateway_details::PersistedGatewayDetails; use nym_crypto::asymmetric::{encryption, identity}; use nym_gateway_client::SharedKeys; use nym_sphinx::acknowledgements::AckKey; @@ -27,8 +28,8 @@ mod v1 { pub const CORE_STORE: &str = "core"; // keys - // TODO: to replace with FULL config - pub const GATEWAY_CONFIG: &str = "gateway_config"; + pub const CONFIG: &str = "config"; + pub const GATEWAY_DETAILS: &str = "gateway_details"; pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair"; pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair"; @@ -110,15 +111,32 @@ impl ClientStorage { }) } - pub(crate) async fn read_gateway_config( - &self, - ) -> Result, ClientStorageError> { + // TODO: persist client's config + #[allow(dead_code)] + pub(crate) async fn read_config(&self) -> Result, ClientStorageError> { self.inner - .read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_CONFIG)) + .read_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG)) .await .map_err(Into::into) } + pub(crate) async fn may_read_gateway_details( + &self, + ) -> Result, ClientStorageError> { + self.inner + .read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_DETAILS)) + .await + .map_err(Into::into) + } + + pub(crate) async fn must_read_gateway_details( + &self, + ) -> Result { + self.may_read_gateway_details() + .await? + .ok_or(ClientStorageError::GatewayDetailsNotInStorage) + } + async fn may_read_identity_keypair( &self, ) -> Result, ClientStorageError> { @@ -244,17 +262,33 @@ impl ClientStorage { .map_err(Into::into) } - pub(crate) async fn store_gateway_config( + pub(crate) async fn store_gateway_details( &self, - gateway_endpoint: &GatewayEndpointConfig, + gateway_endpoint: &PersistedGatewayDetails, ) -> Result<(), ClientStorageError> { self.inner .store_value( v1::CORE_STORE, - JsValue::from_str(v1::GATEWAY_CONFIG), + JsValue::from_str(v1::GATEWAY_DETAILS), gateway_endpoint, ) .await .map_err(Into::into) } + + // TODO: persist client's config + #[allow(dead_code)] + pub(crate) async fn store_config(&self, config: &Config) -> Result<(), ClientStorageError> { + self.inner + .store_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG), config) + .await + .map_err(Into::into) + } + + pub(crate) async fn has_full_gateway_info(&self) -> Result { + let has_keys = self.may_read_gateway_shared_key().await?.is_some(); + let has_details = self.may_read_gateway_details().await?.is_some(); + + Ok(has_keys && has_details) + } } diff --git a/clients/webassembly/src/storage/traits.rs b/clients/webassembly/src/storage/traits.rs index 4959e65e1e..ed56dda969 100644 --- a/clients/webassembly/src/storage/traits.rs +++ b/clients/webassembly/src/storage/traits.rs @@ -4,6 +4,9 @@ use crate::storage::errors::ClientStorageError; use crate::storage::ClientStorage; use async_trait::async_trait; +use nym_client_core::client::base_client::storage::gateway_details::{ + GatewayDetailsStore, PersistedGatewayDetails, +}; use nym_client_core::client::base_client::storage::MixnetClientStorage; use nym_client_core::client::key_manager::persistence::KeyStore; use nym_client_core::client::key_manager::KeyManager; @@ -14,7 +17,7 @@ use wasm_utils::console_log; // temporary until other variants are properly implemented (probably it should get changed into `ClientStorage` // implementing all traits and everything getting combined pub struct FullWasmClientStorage { - pub(crate) key_store: ClientStorage, + pub(crate) keys_and_gateway_store: ClientStorage, pub(crate) reply_storage: browser_backend::Backend, pub(crate) credential_storage: EphemeralCredentialStorage, } @@ -24,12 +27,14 @@ impl MixnetClientStorage for FullWasmClientStorage { type ReplyStore = browser_backend::Backend; type CredentialStore = EphemeralCredentialStorage; - fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) { - (self.key_store, self.reply_storage, self.credential_storage) + type GatewayDetailsStore = ClientStorage; + + fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) { + (self.reply_storage, self.credential_storage) } fn key_store(&self) -> &Self::KeyStore { - &self.key_store + &self.keys_and_gateway_store } fn reply_store(&self) -> &Self::ReplyStore { @@ -39,6 +44,10 @@ impl MixnetClientStorage for FullWasmClientStorage { fn credential_store(&self) -> &Self::CredentialStore { &self.credential_storage } + + fn gateway_details_store(&self) -> &Self::GatewayDetailsStore { + &self.keys_and_gateway_store + } } #[async_trait(?Send)] @@ -74,3 +83,19 @@ impl KeyStore for ClientStorage { .await } } + +#[async_trait(?Send)] +impl GatewayDetailsStore for ClientStorage { + type StorageError = ClientStorageError; + + async fn load_gateway_details(&self) -> Result { + self.must_read_gateway_details().await + } + + async fn store_gateway_details( + &self, + details: &PersistedGatewayDetails, + ) -> Result<(), Self::StorageError> { + self.store_gateway_details(details).await + } +} diff --git a/clients/webassembly/src/tester/mod.rs b/clients/webassembly/src/tester/mod.rs index 3931268f33..3b4104e603 100644 --- a/clients/webassembly/src/tester/mod.rs +++ b/clients/webassembly/src/tester/mod.rs @@ -3,7 +3,7 @@ use crate::constants::NODE_TESTER_ID; use crate::error::WasmClientError; -use crate::helpers::{current_network_topology_async, gateway_from_topology}; +use crate::helpers::{current_network_topology_async, setup_from_topology}; use crate::storage::ClientStorage; use crate::tester::ephemeral_receiver::EphemeralTestReceiver; use crate::tester::helpers::{ @@ -15,7 +15,7 @@ use js_sys::Promise; use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient}; use nym_bandwidth_controller::BandwidthController; use nym_client_core::client::key_manager::ManagedKeys; -use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::init::InitialisationDetails; use nym_credential_storage::ephemeral_storage::EphemeralStorage; use nym_gateway_client::GatewayClient; use nym_node_tester_utils::receiver::SimpleMessageReceiver; @@ -28,7 +28,6 @@ use nym_task::TaskManager; use nym_topology::NymTopology; use nym_validator_client::client::IdentityKey; use rand::rngs::OsRng; -use rand::{CryptoRng, Rng}; use std::collections::HashSet; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Arc, Mutex as SyncMutex}; @@ -120,29 +119,26 @@ impl NymNodeTesterBuilder { }) } - async fn gateway_info( + async fn gateway_info( &self, - rng: &mut R, client_store: &ClientStorage, - ) -> Result { - gateway_from_topology( - rng, - self.gateway.as_deref(), - &self.base_topology, - client_store, - ) - .await + ) -> Result { + if let Ok(loaded) = InitialisationDetails::try_load(client_store, client_store).await { + Ok(loaded) + } else { + setup_from_topology(self.gateway.clone(), &self.base_topology, client_store).await + } } async fn _setup_client(mut self) -> Result { - let mut rng = OsRng; let task_manager = TaskManager::default(); let client_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?; - let gateway_endpoint = self.gateway_info(&mut rng, &client_store).await?; + let init_details = self.gateway_info(&client_store).await?; + let gateway_endpoint = init_details.gateway_details; let gateway_identity = gateway_endpoint.try_get_gateway_identity_key()?; - let mut managed_keys = ManagedKeys::load_or_generate(&mut rng, &client_store).await; + let managed_keys = init_details.managed_keys; let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded(); let (ack_sender, ack_receiver) = mpsc::unbounded(); @@ -151,7 +147,7 @@ impl NymNodeTesterBuilder { gateway_endpoint.gateway_listener, managed_keys.identity_keypair(), gateway_identity, - managed_keys.gateway_shared_key(), + Some(managed_keys.must_get_gateway_shared_key()), mixnet_message_sender, ack_sender, Duration::from_secs(10), @@ -160,14 +156,11 @@ impl NymNodeTesterBuilder { ); gateway_client.set_disabled_credentials_mode(true); - let shared_keys = gateway_client.authenticate_and_start().await?; - managed_keys - .deal_with_gateway_key(shared_keys, &client_store) - .await?; + gateway_client.authenticate_and_start().await?; // TODO: make those values configurable later let tester = NodeTester::new( - rng, + OsRng, self.base_topology, Some(address(&managed_keys, gateway_identity)), PacketSize::default(), diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index 1ddb9a908c..98d7ce6151 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -9,6 +9,7 @@ rust-version = "1.66" [dependencies] async-trait = { workspace = true } +base64 = "0.21.2" dirs = "4.0" dashmap = "5.4.0" futures = "0.3" @@ -17,6 +18,7 @@ log = { workspace = true } rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +sha2 = "0.10.6" tap = "1.0.1" thiserror = "1.0.34" url = { version ="2.2", features = ["serde"] } diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index f2cc5c3d32..aecb7eb5c1 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -42,7 +42,6 @@ use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver}; use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths}; use nym_task::{TaskClient, TaskManager}; use nym_topology::provider_trait::TopologyProvider; -use rand::rngs::OsRng; use std::sync::Arc; use tap::TapFallible; use url::Url; @@ -50,6 +49,8 @@ use url::Url; #[cfg(target_arch = "wasm32")] use nym_bandwidth_controller::wasm_mockups::DkgQueryClient; +use crate::client::base_client::storage::gateway_details::GatewayDetailsStore; +use crate::init::{setup_gateway, GatewaySetup, InitialisationDetails}; #[cfg(not(target_arch = "wasm32"))] use nym_validator_client::nyxd::traits::DkgQueryClient; @@ -157,13 +158,11 @@ impl From for CredentialsToggle { } pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> { - // due to wasm limitations I had to split it like this : ( - gateway_config: &'a GatewayEndpointConfig, config: &'a Config, - nym_api_endpoints: Vec, client_store: S, dkg_query_client: Option, custom_topology_provider: Option>, + setup_method: GatewaySetup, } impl<'a, C, S> BaseClientBuilder<'a, C, S> @@ -177,15 +176,19 @@ where dkg_query_client: Option, ) -> BaseClientBuilder<'a, C, S> { BaseClientBuilder { - gateway_config: base_config.get_gateway_endpoint_config(), config: base_config, - nym_api_endpoints: base_config.get_nym_api_endpoints(), client_store, dkg_query_client, custom_topology_provider: None, + setup_method: GatewaySetup::MustLoad, } } + pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self { + self.setup_method = setup; + self + } + pub fn with_topology_provider( mut self, provider: Box, @@ -288,12 +291,10 @@ where controller.start_with_shutdown(shutdown) } - #[allow(clippy::too_many_arguments)] async fn start_gateway_client( - managed_keys: &mut ManagedKeys, - key_store: &S::KeyStore, - gateway_details: &GatewayEndpointConfig, config: &Config, + gateway_config: GatewayEndpointConfig, + managed_keys: &ManagedKeys, bandwidth_controller: Option>, mixnet_message_sender: MixnetMessageSender, ack_sender: AcknowledgementSender, @@ -303,15 +304,10 @@ where ::StorageError: Send + Sync + 'static, ::StorageError: Send + Sync + 'static, { - let gateway_id = gateway_details.gateway_id.clone(); - if gateway_id.is_empty() { - return Err(ClientCoreError::GatewayIdUnknown); - } - let gateway_address = gateway_details.gateway_listener.clone(); - if gateway_address.is_empty() { - return Err(ClientCoreError::GatewayAddressUnknown); - } + let gateway_address = gateway_config.gateway_listener.clone(); + let gateway_id = gateway_config.gateway_id; + // TODO: in theory, at this point, this should be infallible let gateway_identity = identity::PublicKey::from_base58_string(gateway_id) .map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?; @@ -319,7 +315,7 @@ where gateway_address, managed_keys.identity_keypair(), gateway_identity, - managed_keys.gateway_shared_key(), + Some(managed_keys.must_get_gateway_shared_key()), mixnet_message_sender, ack_sender, config.debug.gateway_connection.gateway_response_timeout, @@ -336,12 +332,7 @@ where log::error!("Could not authenticate and start up the gateway connection - {err}") })?; - managed_keys - .deal_with_gateway_key(shared_key, key_store) - .await - .map_err(|source| ClientCoreError::KeyStoreError { - source: Box::new(source), - })?; + managed_keys.ensure_gateway_key(shared_key); Ok(gateway_client) } @@ -447,22 +438,38 @@ where Ok(mem_store) } - async fn initial_key_setup(key_store: &S::KeyStore) -> ManagedKeys { - let mut rng = OsRng; - ManagedKeys::load_or_generate(&mut rng, key_store).await + async fn initialise_keys_and_gateway(&self) -> Result + where + ::StorageError: Sync + Send, + ::StorageError: Sync + Send, + { + setup_gateway( + &self.setup_method, + self.client_store.key_store(), + self.client_store.gateway_details_store(), + false, + Some(&self.config.client.nym_api_urls), + ) + .await } pub async fn start_base(mut self) -> Result where - ::StorageError: Sync + Send, S::ReplyStore: Send + Sync, ::StorageError: Send + Sync, + ::StorageError: Sync + Send, ::StorageError: Send + Sync + 'static, + ::StorageError: Sync + Send, { info!("Starting nym client"); - let (key_store, reply_storage_backend, credential_store) = self.client_store.into_split(); - let mut managed_keys = Self::initial_key_setup(&key_store).await; + // derive (or load) client keys and gateway configuration + let details = self.initialise_keys_and_gateway().await?; + let gateway_config = details.gateway_details; + let managed_keys = details.managed_keys; + + let (reply_storage_backend, credential_store) = self.client_store.into_runtime_stores(); + let bandwidth_controller = self .dkg_query_client .map(|client| BandwidthController::new(credential_store, client)); @@ -493,15 +500,14 @@ where let (reply_controller_sender, reply_controller_receiver) = reply_controller::requests::new_control_channels(); - let self_address = Self::mix_address(&managed_keys, self.gateway_config); + let self_address = Self::mix_address(&managed_keys, &gateway_config); // the components are started in very specific order. Unless you know what you are doing, // do not change that. let gateway_client = Self::start_gateway_client( - &mut managed_keys, - &key_store, - self.gateway_config, self.config, + gateway_config, + &managed_keys, bandwidth_controller, mixnet_messages_sender, ack_sender, @@ -515,7 +521,7 @@ where let topology_provider = Self::setup_topology_provider( self.custom_topology_provider.take(), - self.nym_api_endpoints, + self.config.get_nym_api_endpoints(), ); Self::start_topology_refresher( topology_provider, diff --git a/common/client-core/src/client/base_client/storage/gateway_details.rs b/common/client-core/src/client/base_client/storage/gateway_details.rs index 671860ddb9..bcacdf731e 100644 --- a/common/client-core/src/client/base_client/storage/gateway_details.rs +++ b/common/client-core/src/client/base_client/storage/gateway_details.rs @@ -5,10 +5,12 @@ use crate::config::GatewayEndpointConfig; use async_trait::async_trait; use nym_gateway_requests::registration::handshake::SharedKeys; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use std::error::Error; -use std::sync::Arc; +use std::ops::Deref; +use tokio::sync::Mutex; +use zeroize::Zeroizing; -// TODO: to incorporate into `MixnetClientStorage` #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait GatewayDetailsStore { @@ -22,22 +24,178 @@ pub trait GatewayDetailsStore { ) -> Result<(), Self::StorageError>; } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct PersistedGatewayDetails { - /// (to be determined), hash, ciphertext or tag derived from the details and the shared key - /// to ensure they correspond to the same instance. - magic_crypto_field: (), + // TODO: should we also verify correctness of the details themselves? + // i.e. we could include a checksum or tag (via the shared keys) + // counterargument: if we wanted to modify, say, the host information in the stored file on disk, + // in order to actually use it, we'd have to recompute the whole checksum which would be a huge pain. + /// The hash of the shared keys to ensure the correct ones are used with those gateway details. + #[serde(with = "base64")] + key_hash: Vec, /// Actual gateway details being persisted. - details: GatewayEndpointConfig, + pub(crate) details: GatewayEndpointConfig, +} + +impl From for GatewayEndpointConfig { + fn from(value: PersistedGatewayDetails) -> Self { + value.details + } } impl PersistedGatewayDetails { - pub fn new(details: GatewayEndpointConfig, shared_key: Arc) -> Self { - todo!() + pub fn new(details: GatewayEndpointConfig, shared_key: &SharedKeys) -> Self { + let key_bytes = Zeroizing::new(shared_key.to_bytes()); + + let mut key_hasher = Sha256::new(); + key_hasher.update(&key_bytes); + let key_hash = key_hasher.finalize().to_vec(); + + PersistedGatewayDetails { key_hash, details } } - pub fn verify(&self, shared_key: Arc) -> bool { - todo!() + pub fn verify(&self, shared_key: &SharedKeys) -> bool { + let key_bytes = Zeroizing::new(shared_key.to_bytes()); + + let mut key_hasher = Sha256::new(); + key_hasher.update(&key_bytes); + let key_hash = key_hasher.finalize(); + + self.key_hash == key_hash.deref() + } +} + +// helper to make Vec serialization use base64 representation to make it human readable +// so that it would be easier for users to copy contents from the disk if they wanted to use it elsewhere +mod base64 { + use base64::{engine::general_purpose::STANDARD, Engine as _}; + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(bytes: &[u8], serializer: S) -> Result { + serializer.serialize_str(&STANDARD.encode(bytes)) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + let s = ::deserialize(deserializer)?; + STANDARD.decode(s).map_err(serde::de::Error::custom) + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[derive(Debug, thiserror::Error)] +pub enum OnDiskGatewayDetailsError { + #[error("JSON failure: {0}")] + SerializationFailure(#[from] serde_json::Error), + + #[error("failed to store gateway details to {path}: {err}")] + StoreFailure { + path: String, + #[source] + err: std::io::Error, + }, + + #[error("failed to load gateway details from {path}: {err}")] + LoadFailure { + path: String, + #[source] + err: std::io::Error, + }, +} + +#[cfg(not(target_arch = "wasm32"))] +pub struct OnDiskGatewayDetails { + file_location: std::path::PathBuf, +} + +#[cfg(not(target_arch = "wasm32"))] +impl OnDiskGatewayDetails { + pub fn new>(path: P) -> Self { + OnDiskGatewayDetails { + file_location: path.as_ref().to_owned(), + } + } + + pub fn load_from_disk(&self) -> Result { + let file = std::fs::File::open(&self.file_location).map_err(|err| { + OnDiskGatewayDetailsError::LoadFailure { + path: self.file_location.display().to_string(), + err, + } + })?; + + Ok(serde_json::from_reader(file)?) + } + + pub fn store_to_disk( + &self, + details: &PersistedGatewayDetails, + ) -> Result<(), OnDiskGatewayDetailsError> { + // ensure the whole directory structure exists + if let Some(parent_dir) = &self.file_location.parent() { + std::fs::create_dir_all(parent_dir).map_err(|err| { + OnDiskGatewayDetailsError::StoreFailure { + path: self.file_location.display().to_string(), + err, + } + })? + } + + let file = std::fs::File::create(&self.file_location).map_err(|err| { + OnDiskGatewayDetailsError::StoreFailure { + path: self.file_location.display().to_string(), + err, + } + })?; + + Ok(serde_json::to_writer_pretty(file, details)?) + } +} + +#[cfg(not(target_arch = "wasm32"))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl GatewayDetailsStore for OnDiskGatewayDetails { + type StorageError = OnDiskGatewayDetailsError; + + async fn load_gateway_details(&self) -> Result { + self.load_from_disk() + } + + async fn store_gateway_details( + &self, + gateway_details: &PersistedGatewayDetails, + ) -> Result<(), Self::StorageError> { + self.store_to_disk(gateway_details) + } +} + +#[derive(Default)] +pub struct InMemGatewayDetails { + details: Mutex>, +} + +#[derive(Debug, thiserror::Error)] +#[error("old ephemeral gateway details can't be loaded from storage")] +pub struct EphemeralGatewayDetailsError; + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl GatewayDetailsStore for InMemGatewayDetails { + type StorageError = EphemeralGatewayDetailsError; + + async fn load_gateway_details(&self) -> Result { + self.details + .lock() + .await + .clone() + .ok_or(EphemeralGatewayDetailsError) + } + + async fn store_gateway_details( + &self, + gateway_details: &PersistedGatewayDetails, + ) -> Result<(), Self::StorageError> { + *self.details.lock().await = Some(gateway_details.clone()); + Ok(()) } } 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 caf8942143..3d05044d4f 100644 --- a/common/client-core/src/client/base_client/storage/mod.rs +++ b/common/client-core/src/client/base_client/storage/mod.rs @@ -4,17 +4,20 @@ // TODO: combine those more closely. Perhaps into a single underlying store. // Like for persistent, on-disk, storage, what's the point of having 3 different databases? +use crate::client::base_client::storage::gateway_details::{ + GatewayDetailsStore, InMemGatewayDetails, +}; use crate::client::key_manager::persistence::{InMemEphemeralKeys, KeyStore}; use crate::client::replies::reply_storage; use crate::client::replies::reply_storage::ReplyStorageBackend; -use nym_credential_storage::ephemeral_storage::{ - EphemeralStorage as EphemeralCredentialStorage, EphemeralStorage, -}; +use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage; use nym_credential_storage::storage::Storage as CredentialStorage; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] use crate::client::base_client::non_wasm_helpers; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] +use crate::client::base_client::storage::gateway_details::OnDiskGatewayDetails; +#[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::client::replies::reply_storage::fs_backend; @@ -25,24 +28,33 @@ use crate::error::ClientCoreError; #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage; +pub mod gateway_details; + +// TODO: ideally this should be changed into +// `MixnetClientStorage: KeyStore + ReplyStorageBackend + CredentialStorage + GatewayDetailsStore` pub trait MixnetClientStorage { type KeyStore: KeyStore; type ReplyStore: ReplyStorageBackend; type CredentialStore: CredentialStorage; + type GatewayDetailsStore: GatewayDetailsStore; // this is a TERRIBLE name... - fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore); + // fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore, Self::GatewayDetailsStore); + + fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore); fn key_store(&self) -> &Self::KeyStore; fn reply_store(&self) -> &Self::ReplyStore; fn credential_store(&self) -> &Self::CredentialStore; + fn gateway_details_store(&self) -> &Self::GatewayDetailsStore; } #[derive(Default)] pub struct Ephemeral { key_store: InMemEphemeralKeys, reply_store: reply_storage::Empty, - credential_store: EphemeralStorage, + credential_store: EphemeralCredentialStorage, + gateway_details_store: InMemGatewayDetails, } impl Ephemeral { @@ -55,9 +67,10 @@ impl MixnetClientStorage for Ephemeral { type KeyStore = InMemEphemeralKeys; type ReplyStore = reply_storage::Empty; type CredentialStore = EphemeralCredentialStorage; + type GatewayDetailsStore = InMemGatewayDetails; - fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) { - (self.key_store, self.reply_store, self.credential_store) + fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) { + (self.reply_store, self.credential_store) } fn key_store(&self) -> &Self::KeyStore { @@ -71,6 +84,10 @@ impl MixnetClientStorage for Ephemeral { fn credential_store(&self) -> &Self::CredentialStore { &self.credential_store } + + fn gateway_details_store(&self) -> &Self::GatewayDetailsStore { + &self.gateway_details_store + } } #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] @@ -78,6 +95,7 @@ pub struct OnDiskPersistent { pub(crate) key_store: OnDiskKeys, pub(crate) reply_store: fs_backend::Backend, pub(crate) credential_store: PersistentCredentialStorage, + pub(crate) gateway_details_store: OnDiskGatewayDetails, } #[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))] @@ -86,11 +104,13 @@ impl OnDiskPersistent { key_store: OnDiskKeys, reply_store: fs_backend::Backend, credential_store: PersistentCredentialStorage, + gateway_details_store: OnDiskGatewayDetails, ) -> Self { Self { key_store, reply_store, credential_store, + gateway_details_store, } } @@ -109,10 +129,13 @@ impl OnDiskPersistent { let credential_store = nym_credential_storage::initialise_persistent_storage(paths.credentials_database).await; + let gateway_details_store = OnDiskGatewayDetails::new(paths.gateway_details); + Ok(OnDiskPersistent { key_store, reply_store, credential_store, + gateway_details_store, }) } } @@ -122,9 +145,10 @@ impl MixnetClientStorage for OnDiskPersistent { type KeyStore = OnDiskKeys; type ReplyStore = fs_backend::Backend; type CredentialStore = PersistentCredentialStorage; + type GatewayDetailsStore = OnDiskGatewayDetails; - fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) { - (self.key_store, self.reply_store, self.credential_store) + fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) { + (self.reply_store, self.credential_store) } fn key_store(&self) -> &Self::KeyStore { @@ -138,4 +162,8 @@ impl MixnetClientStorage for OnDiskPersistent { fn credential_store(&self) -> &Self::CredentialStore { &self.credential_store } + + fn gateway_details_store(&self) -> &Self::GatewayDetailsStore { + &self.gateway_details_store + } } diff --git a/common/client-core/src/client/key_manager/mod.rs b/common/client-core/src/client/key_manager/mod.rs index c67d87a6b5..4c77f23632 100644 --- a/common/client-core/src/client/key_manager/mod.rs +++ b/common/client-core/src/client/key_manager/mod.rs @@ -6,6 +6,7 @@ use nym_crypto::asymmetric::{encryption, identity}; use nym_gateway_requests::registration::handshake::SharedKeys; use nym_sphinx::acknowledgements::AckKey; use rand::{CryptoRng, RngCore}; +use std::fmt::{Debug, Formatter}; use std::sync::Arc; use zeroize::ZeroizeOnDrop; @@ -20,6 +21,16 @@ pub enum ManagedKeys { Invalidated, } +impl Debug for ManagedKeys { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ManagedKeys::Initial(_) => write!(f, "initial"), + ManagedKeys::FullyDerived(_) => write!(f, "fully derived"), + ManagedKeys::Invalidated => write!(f, "invalidated"), + } + } +} + impl From for ManagedKeys { fn from(value: KeyManagerBuilder) -> Self { ManagedKeys::Initial(value) @@ -84,6 +95,11 @@ impl ManagedKeys { } } + pub fn must_get_gateway_shared_key(&self) -> Arc { + self.gateway_shared_key() + .expect("failed to extract gateway shared key") + } + pub fn gateway_shared_key(&self) -> Option> { match self { ManagedKeys::Initial(_) => None, @@ -108,6 +124,17 @@ impl ManagedKeys { } } + pub fn ensure_gateway_key(&self, gateway_shared_key: Arc) { + if let ManagedKeys::FullyDerived(key_manager) = &self { + if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key) + || key_manager.gateway_shared_key != gateway_shared_key + { + // this should NEVER happen thus panic here + panic!("derived fresh gateway shared key whilst already holding one!") + } + } + } + pub async fn deal_with_gateway_key( &mut self, gateway_shared_key: Arc, @@ -120,12 +147,7 @@ impl ManagedKeys { key_manager } ManagedKeys::FullyDerived(key_manager) => { - if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key) - || key_manager.gateway_shared_key != gateway_shared_key - { - // this should NEVER happen thus panic here - panic!("derived fresh gateway shared key whilst already holding one!") - } + self.ensure_gateway_key(gateway_shared_key); key_manager } ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"), diff --git a/common/client-core/src/client/key_manager/persistence.rs b/common/client-core/src/client/key_manager/persistence.rs index 9f5d482787..0475de3990 100644 --- a/common/client-core/src/client/key_manager/persistence.rs +++ b/common/client-core/src/client/key_manager/persistence.rs @@ -4,6 +4,7 @@ use crate::client::key_manager::KeyManager; use async_trait::async_trait; use std::error::Error; +use tokio::sync::Mutex; #[cfg(not(target_arch = "wasm32"))] use crate::config::disk_persistence::keys_paths::ClientKeysPaths; @@ -36,6 +37,7 @@ pub enum OnDiskKeysError { KeyPairLoadFailure { keys: String, paths: nym_pemstore::KeyPairPath, + #[source] err: std::io::Error, }, @@ -43,6 +45,7 @@ pub enum OnDiskKeysError { KeyPairStoreFailure { keys: String, paths: nym_pemstore::KeyPairPath, + #[source] err: std::io::Error, }, @@ -50,6 +53,7 @@ pub enum OnDiskKeysError { KeyLoadFailure { key: String, path: String, + #[source] err: std::io::Error, }, @@ -57,6 +61,7 @@ pub enum OnDiskKeysError { KeyStoreFailure { key: String, path: String, + #[source] err: std::io::Error, }, } @@ -79,11 +84,21 @@ impl OnDiskKeys { OnDiskKeys { paths } } + #[doc(hidden)] + pub fn ephemeral_load_gateway_keys( + &self, + ) -> Result, OnDiskKeysError> { + self.load_key(self.paths.gateway_shared_key(), "gateway shared keys") + .map(zeroize::Zeroizing::new) + } + + #[doc(hidden)] pub fn load_encryption_keypair(&self) -> Result { let encryption_paths = self.paths.encryption_key_pair_path(); self.load_keypair(encryption_paths, "encryption keys") } + #[doc(hidden)] pub fn load_identity_keypair(&self) -> Result { let identity_paths = self.paths.identity_key_pair_path(); self.load_keypair(identity_paths, "identity keys") @@ -198,10 +213,12 @@ impl KeyStore for OnDiskKeys { } #[derive(Default)] -pub struct InMemEphemeralKeys; +pub struct InMemEphemeralKeys { + keys: Mutex>, +} #[derive(Debug, thiserror::Error)] -#[error("ephemeral keys can't be loaded from storage")] +#[error("old ephemeral keys can't be loaded from storage")] pub struct EphemeralKeysError; #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] @@ -210,10 +227,11 @@ impl KeyStore for InMemEphemeralKeys { type StorageError = EphemeralKeysError; async fn load_keys(&self) -> Result { - Err(EphemeralKeysError) + self.keys.lock().await.clone().ok_or(EphemeralKeysError) } - async fn store_keys(&self, _keys: &KeyManager) -> Result<(), Self::StorageError> { + async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> { + *self.keys.lock().await = Some(keys.clone()); Ok(()) } } diff --git a/common/client-core/src/config/disk_persistence/mod.rs b/common/client-core/src/config/disk_persistence/mod.rs index 9b4532e153..6527283117 100644 --- a/common/client-core/src/config/disk_persistence/mod.rs +++ b/common/client-core/src/config/disk_persistence/mod.rs @@ -6,16 +6,21 @@ use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; pub mod keys_paths; +pub mod old_v1_1_20_2; +pub const DEFAULT_GATEWAY_DETAILS_FILENAME: &str = "gateway_details.json"; 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)] +#[serde(deny_unknown_fields)] pub struct CommonClientPaths { pub keys: ClientKeysPaths, - // TODO: - // pub gateway_config_pathfinder: (), + /// Path to the file containing information about gateway used by this client, + /// i.e. details such as its public key, owner address or the network information. + pub gateway_details: PathBuf, + /// Path to the database containing bandwidth credentials of this client. pub credentials_database: PathBuf, @@ -30,6 +35,7 @@ impl CommonClientPaths { CommonClientPaths { credentials_database: base_dir.join(DEFAULT_CREDENTIALS_DB_FILENAME), reply_surb_database: base_dir.join(DEFAULT_REPLY_SURB_DB_FILENAME), + gateway_details: base_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME), keys: ClientKeysPaths::new_default(base_data_directory), } } diff --git a/common/client-core/src/config/disk_persistence/old_v1_1_20_2.rs b/common/client-core/src/config/disk_persistence/old_v1_1_20_2.rs new file mode 100644 index 0000000000..3346dca6ca --- /dev/null +++ b/common/client-core/src/config/disk_persistence/old_v1_1_20_2.rs @@ -0,0 +1,30 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::disk_persistence::keys_paths::ClientKeysPaths; +use crate::config::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAY_DETAILS_FILENAME}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct CommonClientPathsV1_1_20_2 { + pub keys: ClientKeysPaths, + pub credentials_database: PathBuf, + pub reply_surb_database: PathBuf, +} + +impl CommonClientPathsV1_1_20_2 { + pub fn upgrade_default(self) -> CommonClientPaths { + let data_dir = self + .reply_surb_database + .parent() + .expect("client paths upgrade failure"); + CommonClientPaths { + keys: self.keys, + gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME), + credentials_database: self.credentials_database, + reply_surb_database: self.reply_surb_database, + } + } +} diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index 2d91f5a392..36c7aca4eb 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -15,6 +15,7 @@ use wasm_bindgen::prelude::*; pub mod disk_persistence; pub mod old_config_v1_1_13; pub mod old_config_v1_1_20; +pub mod old_config_v1_1_20_2; // 'DEBUG' const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5; @@ -77,7 +78,7 @@ impl Config { } pub fn validate(&self) -> bool { - self.client.validate() && self.debug.validate() + self.debug.validate() } pub fn with_debug_config(mut self, debug: DebugConfig) -> Self { @@ -90,19 +91,6 @@ impl Config { self } - pub fn set_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) { - self.client.gateway_endpoint = gateway_endpoint; - } - - pub fn with_gateway_endpoint(mut self, gateway_endpoint: GatewayEndpointConfig) -> Self { - self.client.gateway_endpoint = gateway_endpoint; - self - } - - pub fn with_gateway_id>(&mut self, id: S) { - self.client.gateway_endpoint.gateway_id = id.into(); - } - pub fn with_custom_nyxd(mut self, urls: Vec) -> Self { self.client.nyxd_urls = urls; self @@ -178,22 +166,6 @@ impl Config { pub fn get_nym_api_endpoints(&self) -> Vec { self.client.nym_api_urls.clone() } - - pub fn get_gateway_id(&self) -> String { - self.client.gateway_endpoint.gateway_id.clone() - } - - pub fn get_gateway_owner(&self) -> String { - self.client.gateway_endpoint.gateway_owner.clone() - } - - pub fn get_gateway_listener(&self) -> String { - self.client.gateway_endpoint.gateway_listener.clone() - } - - pub fn get_gateway_endpoint_config(&self) -> &GatewayEndpointConfig { - &self.client.gateway_endpoint - } } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] @@ -246,6 +218,8 @@ impl From for GatewayEndpointConfig { } #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2 +#[serde(deny_unknown_fields)] pub struct Client { /// Version of the client for which this configuration was created. pub version: String, @@ -266,11 +240,6 @@ pub struct Client { /// Addresses to APIs running on validator from which the client gets the view of the network. #[serde(alias = "validator_api_urls")] pub nym_api_urls: Vec, - - /// 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, } impl Client { @@ -293,7 +262,6 @@ impl Client { disabled_credentials_mode: true, nyxd_urls, nym_api_urls, - gateway_endpoint: Default::default(), } } @@ -302,7 +270,6 @@ impl Client { disabled_credentials_mode: bool, nyxd_urls: Vec, nym_api_urls: Vec, - gateway_endpoint: GatewayEndpointConfig, ) -> Self { Client { version: env!("CARGO_PKG_VERSION").to_string(), @@ -310,15 +277,8 @@ impl Client { disabled_credentials_mode, nyxd_urls, nym_api_urls, - gateway_endpoint, } } - - 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, Copy, Deserialize, PartialEq, Serialize)] 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 index c2bc27b89b..b3a4a189d8 100644 --- a/common/client-core/src/config/old_config_v1_1_20.rs +++ b/common/client-core/src/config/old_config_v1_1_20.rs @@ -1,9 +1,10 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::{ - Acknowledgements, CoverTraffic, DebugConfig, GatewayConnection, GatewayEndpointConfig, - ReplySurbs, Topology, Traffic, +use crate::config::old_config_v1_1_20_2::{ + AcknowledgementsV1_1_20_2, CoverTrafficV1_1_20_2, DebugConfigV1_1_20_2, + GatewayConnectionV1_1_20_2, GatewayEndpointConfigV1_1_20_2, ReplySurbsV1_1_20_2, + TopologyV1_1_20_2, TrafficV1_1_20_2, }; use nym_sphinx::params::{PacketSize, PacketType}; use serde::{Deserialize, Serialize}; @@ -71,9 +72,9 @@ pub struct GatewayEndpointConfigV1_1_20 { pub gateway_listener: String, } -impl From for GatewayEndpointConfig { +impl From for GatewayEndpointConfigV1_1_20_2 { fn from(value: GatewayEndpointConfigV1_1_20) -> Self { - GatewayEndpointConfig { + GatewayEndpointConfigV1_1_20_2 { gateway_id: value.gateway_id, gateway_owner: value.gateway_owner, gateway_listener: value.gateway_listener, @@ -123,9 +124,9 @@ pub struct TrafficV1_1_20 { pub secondary_packet_size: Option, } -impl From for Traffic { +impl From for TrafficV1_1_20_2 { fn from(value: TrafficV1_1_20) -> Self { - Traffic { + TrafficV1_1_20_2 { average_packet_delay: value.average_packet_delay, message_sending_average_delay: value.message_sending_average_delay, disable_main_poisson_packet_distribution: value @@ -158,9 +159,9 @@ pub struct CoverTrafficV1_1_20 { pub disable_loop_cover_traffic_stream: bool, } -impl From for CoverTraffic { +impl From for CoverTrafficV1_1_20_2 { fn from(value: CoverTrafficV1_1_20) -> Self { - CoverTraffic { + CoverTrafficV1_1_20_2 { 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, @@ -185,9 +186,9 @@ pub struct GatewayConnectionV1_1_20 { pub gateway_response_timeout: Duration, } -impl From for GatewayConnection { +impl From for GatewayConnectionV1_1_20_2 { fn from(value: GatewayConnectionV1_1_20) -> Self { - GatewayConnection { + GatewayConnectionV1_1_20_2 { gateway_response_timeout: value.gateway_response_timeout, } } @@ -211,9 +212,9 @@ pub struct AcknowledgementsV1_1_20 { pub ack_wait_addition: Duration, } -impl From for Acknowledgements { +impl From for AcknowledgementsV1_1_20_2 { fn from(value: AcknowledgementsV1_1_20) -> Self { - Acknowledgements { + AcknowledgementsV1_1_20_2 { average_ack_delay: value.average_ack_delay, ack_wait_multiplier: value.ack_wait_multiplier, ack_wait_addition: value.ack_wait_addition, @@ -241,9 +242,9 @@ pub struct TopologyV1_1_20 { pub disable_refreshing: bool, } -impl From for Topology { +impl From for TopologyV1_1_20_2 { fn from(value: TopologyV1_1_20) -> Self { - Topology { + TopologyV1_1_20_2 { topology_refresh_rate: value.topology_refresh_rate, topology_resolution_timeout: value.topology_resolution_timeout, disable_refreshing: value.disable_refreshing, @@ -279,9 +280,9 @@ pub struct ReplySurbsV1_1_20 { pub maximum_reply_key_age: Duration, } -impl From for ReplySurbs { +impl From for ReplySurbsV1_1_20_2 { fn from(value: ReplySurbsV1_1_20) -> Self { - ReplySurbs { + ReplySurbsV1_1_20_2 { 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, @@ -313,7 +314,7 @@ impl Default for ReplySurbsV1_1_20 { } } -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] pub struct DebugConfigV1_1_20 { pub traffic: TrafficV1_1_20, @@ -324,9 +325,9 @@ pub struct DebugConfigV1_1_20 { pub reply_surbs: ReplySurbsV1_1_20, } -impl From for DebugConfig { +impl From for DebugConfigV1_1_20_2 { fn from(value: DebugConfigV1_1_20) -> Self { - DebugConfig { + DebugConfigV1_1_20_2 { traffic: value.traffic.into(), cover_traffic: value.cover_traffic.into(), gateway_connection: value.gateway_connection.into(), @@ -336,19 +337,3 @@ impl From for DebugConfig { } } } - -// 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/old_config_v1_1_20_2.rs b/common/client-core/src/config/old_config_v1_1_20_2.rs new file mode 100644 index 0000000000..ed0cd0356a --- /dev/null +++ b/common/client-core/src/config/old_config_v1_1_20_2.rs @@ -0,0 +1,348 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::{ + Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection, + GatewayEndpointConfig, ReplySurbs, Topology, Traffic, +}; +use nym_sphinx::params::{PacketSize, PacketType}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use url::Url; + +// 'DEBUG' +const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5; + +const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500); +const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200); +const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20); +const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50); +const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min +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 +const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60); + +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 +const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10; +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 +const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10; +const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100; + +const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500; + +const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10); +const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60); + +// 12 hours +const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60); + +// 24 hours +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_2 { + pub client: ClientV1_1_20_2, + + #[serde(default)] + pub debug: DebugConfigV1_1_20_2, +} + +impl From for Config { + fn from(value: ConfigV1_1_20_2) -> Self { + Config { + client: value.client.into(), + debug: value.debug.into(), + } + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +pub struct GatewayEndpointConfigV1_1_20_2 { + /// gateway_id specifies ID of the gateway to which the client should send messages. + /// If initially omitted, a random gateway will be chosen from the available topology. + pub gateway_id: String, + + /// Address of the gateway owner to which the client should send messages. + pub gateway_owner: String, + + /// Address of the gateway listener to which all client requests should be sent. + pub gateway_listener: String, +} + +impl From for GatewayEndpointConfig { + fn from(value: GatewayEndpointConfigV1_1_20_2) -> 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_2 { + 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 gateway_endpoint: GatewayEndpointConfigV1_1_20_2, +} + +impl From for Client { + fn from(value: ClientV1_1_20_2) -> Self { + Client { + version: value.version, + id: value.id, + disabled_credentials_mode: value.disabled_credentials_mode, + nyxd_urls: value.nyxd_urls, + nym_api_urls: value.nym_api_urls, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct TrafficV1_1_20_2 { + #[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, + pub packet_type: PacketType, +} + +impl From for Traffic { + fn from(value: TrafficV1_1_20_2) -> 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_2 { + fn default() -> Self { + TrafficV1_1_20_2 { + 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, + packet_type: PacketType::Mix, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct CoverTrafficV1_1_20_2 { + #[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_2) -> 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_2 { + fn default() -> Self { + CoverTrafficV1_1_20_2 { + 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_2 { + #[serde(with = "humantime_serde")] + pub gateway_response_timeout: Duration, +} + +impl From for GatewayConnection { + fn from(value: GatewayConnectionV1_1_20_2) -> Self { + GatewayConnection { + gateway_response_timeout: value.gateway_response_timeout, + } + } +} + +impl Default for GatewayConnectionV1_1_20_2 { + fn default() -> Self { + GatewayConnectionV1_1_20_2 { + gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct AcknowledgementsV1_1_20_2 { + #[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_2) -> 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_2 { + fn default() -> Self { + AcknowledgementsV1_1_20_2 { + 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_2 { + #[serde(with = "humantime_serde")] + pub topology_refresh_rate: Duration, + #[serde(with = "humantime_serde")] + pub topology_resolution_timeout: Duration, + pub disable_refreshing: bool, +} + +impl Default for TopologyV1_1_20_2 { + fn default() -> Self { + TopologyV1_1_20_2 { + topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE, + topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT, + disable_refreshing: false, + } + } +} + +impl From for Topology { + fn from(value: TopologyV1_1_20_2) -> Self { + Topology { + topology_refresh_rate: value.topology_refresh_rate, + topology_resolution_timeout: value.topology_resolution_timeout, + disable_refreshing: value.disable_refreshing, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct ReplySurbsV1_1_20_2 { + 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 Default for ReplySurbsV1_1_20_2 { + fn default() -> Self { + ReplySurbsV1_1_20_2 { + 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, + } + } +} + +impl From for ReplySurbs { + fn from(value: ReplySurbsV1_1_20_2) -> 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, + } + } +} + +#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct DebugConfigV1_1_20_2 { + pub traffic: TrafficV1_1_20_2, + pub cover_traffic: CoverTrafficV1_1_20_2, + pub gateway_connection: GatewayConnectionV1_1_20_2, + pub acknowledgements: AcknowledgementsV1_1_20_2, + pub topology: TopologyV1_1_20_2, + pub reply_surbs: ReplySurbsV1_1_20_2, +} + +impl From for DebugConfig { + fn from(value: DebugConfigV1_1_20_2) -> 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(), + } + } +} diff --git a/common/client-core/src/error.rs b/common/client-core/src/error.rs index b898461c1d..ff3f36e4cb 100644 --- a/common/client-core/src/error.rs +++ b/common/client-core/src/error.rs @@ -50,6 +50,11 @@ pub enum ClientCoreError { source: Box, }, + #[error("experienced a failure with our gateway details storage: {source}")] + GatewayDetailsStoreError { + source: Box, + }, + #[error("The gateway id is invalid - {0}")] UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError), @@ -97,6 +102,20 @@ pub enum ClientCoreError { "This operation would have resulted in clients keys being overwritten without permission" )] ForbiddenKeyOverwrite, + + #[error("gateway details are unavailable")] + UnavailableGatewayDetails { + source: Box, + }, + + #[error("gateway shared key is unavailable whilst we have full node information")] + UnavailableSharedKey, + + #[error("attempted to obtain fresh gateway details whilst already knowing about one")] + UnexpectedGatewayDetails, + + #[error("the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys")] + MismatchedGatewayDetails { gateway_id: String }, } /// Set of messages that the client can send to listeners via the task manager diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index ac28eba405..6ad04678f8 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -44,18 +44,18 @@ const MEASUREMENTS: usize = 3; const CONN_TIMEOUT: Duration = Duration::from_millis(1500); const PING_TIMEOUT: Duration = Duration::from_millis(1000); -struct GatewayWithLatency { - gateway: gateway::Node, +struct GatewayWithLatency<'a> { + gateway: &'a gateway::Node, latency: Duration, } -impl GatewayWithLatency { - fn new(gateway: gateway::Node, latency: Duration) -> Self { +impl<'a> GatewayWithLatency<'a> { + fn new(gateway: &'a gateway::Node, latency: Duration) -> Self { GatewayWithLatency { gateway, latency } } } -pub(super) async fn current_gateways( +pub async fn current_gateways( rng: &mut R, nym_apis: &[Url], ) -> Result, ClientCoreError> { @@ -64,7 +64,7 @@ pub(super) async fn current_gateways( .ok_or(ClientCoreError::ListOfNymApisIsEmpty)?; let client = nym_validator_client::client::NymApiClient::new(nym_api.clone()); - log::trace!("Fetching list of gateways from: {}", nym_api); + log::trace!("Fetching list of gateways from: {nym_api}"); let gateways = client.get_cached_gateways().await?; let valid_gateways = gateways @@ -91,7 +91,7 @@ async fn connect(endpoint: &str) -> Result { JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure) } -async fn measure_latency(gateway: gateway::Node) -> Result { +async fn measure_latency(gateway: &gateway::Node) -> Result { let addr = gateway.clients_address(); trace!( "establishing connection to {} ({addr})...", @@ -156,7 +156,7 @@ async fn measure_latency(gateway: gateway::Node) -> Result( rng: &mut R, - gateways: Vec, + gateways: &[gateway::Node], ) -> Result { info!("choosing gateway by latency..."); @@ -189,7 +189,7 @@ pub(super) async fn choose_gateway_by_latency( pub(super) fn uniformly_random_gateway( rng: &mut R, - gateways: Vec, + gateways: &[gateway::Node], ) -> Result { gateways .choose(rng) diff --git a/common/client-core/src/init/mod.rs b/common/client-core/src/init/mod.rs index 5139fb7dbd..6ebc123b4b 100644 --- a/common/client-core/src/init/mod.rs +++ b/common/client-core/src/init/mod.rs @@ -3,26 +3,82 @@ //! Collection of initialization steps used by client implementations -use crate::client::base_client::storage::MixnetClientStorage; +use crate::client::base_client::storage::gateway_details::{ + GatewayDetailsStore, PersistedGatewayDetails, +}; use crate::client::key_manager::persistence::KeyStore; -use crate::client::key_manager::{KeyManager, ManagedKeys}; +use crate::client::key_manager::ManagedKeys; use crate::init::helpers::{choose_gateway_by_latency, current_gateways, uniformly_random_gateway}; use crate::{ - config::{disk_persistence::keys_paths::ClientKeysPaths, Config, GatewayEndpointConfig}, + config::{Config, GatewayEndpointConfig}, error::ClientCoreError, }; -use nym_crypto::asymmetric::{encryption, identity}; +use nym_crypto::asymmetric::identity; use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity}; +use nym_topology::gateway; use nym_validator_client::client::IdentityKey; use rand::rngs::OsRng; use serde::Serialize; use std::fmt::{Debug, Display}; use url::Url; -mod helpers; +pub mod helpers; -#[derive(Clone)] +// TODO: rename to something better... +#[derive(Debug)] +pub struct InitialisationDetails { + pub gateway_details: GatewayEndpointConfig, + pub managed_keys: ManagedKeys, +} + +impl InitialisationDetails { + pub fn new(gateway_details: GatewayEndpointConfig, managed_keys: ManagedKeys) -> Self { + InitialisationDetails { + gateway_details, + managed_keys, + } + } + + pub async fn try_load(key_store: &K, details_store: &D) -> Result + where + K: KeyStore, + D: GatewayDetailsStore, + K::StorageError: Send + Sync + 'static, + D::StorageError: Send + Sync + 'static, + { + let loaded_details = _load_gateway_details(details_store).await?; + let loaded_keys = _load_managed_keys(key_store).await?; + + if !loaded_details.verify(&loaded_keys.must_get_gateway_shared_key()) { + return Err(ClientCoreError::MismatchedGatewayDetails { + gateway_id: loaded_details.details.gateway_id, + }); + } + + Ok(InitialisationDetails { + gateway_details: loaded_details.into(), + managed_keys: loaded_keys, + }) + } + + pub fn client_address(&self) -> Result { + let client_recipient = Recipient::new( + *self.managed_keys.identity_public_key(), + *self.managed_keys.encryption_public_key(), + // TODO: below only works under assumption that gateway address == gateway id + // (which currently is true) + NodeIdentity::from_base58_string(&self.gateway_details.gateway_id)?, + ); + + Ok(client_recipient) + } +} + +#[derive(Debug, Clone)] pub enum GatewaySetup { + /// The gateway specification MUST BE loaded from the underlying storage. + MustLoad, + /// Specifies usage of a new, random, gateway. New { /// Should the new gateway be selected based on latency. @@ -34,13 +90,13 @@ pub enum GatewaySetup { }, Predefined { /// Full gateway configuration - config: GatewayEndpointConfig, + details: PersistedGatewayDetails, }, } -impl From for GatewaySetup { - fn from(config: GatewayEndpointConfig) -> Self { - GatewaySetup::Predefined { config } +impl From for GatewaySetup { + fn from(details: PersistedGatewayDetails) -> Self { + GatewaySetup::Predefined { details } } } @@ -57,14 +113,11 @@ impl Default for GatewaySetup { } impl GatewaySetup { - pub fn new( - full_config: Option, - gateway_identity: Option, + pub fn new_fresh( + gateway_identity: Option, latency_based_selection: Option, ) -> Self { - if let Some(config) = full_config { - GatewaySetup::Predefined { config } - } else if let Some(gateway_identity) = gateway_identity { + if let Some(gateway_identity) = gateway_identity { GatewaySetup::Specified { gateway_identity } } else { GatewaySetup::New { @@ -73,15 +126,22 @@ impl GatewaySetup { } } - pub async fn try_get_gateway_details( - self, - validator_servers: &[Url], + pub fn is_must_load(&self) -> bool { + matches!(self, GatewaySetup::MustLoad) + } + + pub fn has_full_details(&self) -> bool { + matches!(self, GatewaySetup::Predefined { .. }) || self.is_must_load() + } + + pub async fn choose_gateway( + &self, + gateways: &[gateway::Node], ) -> Result { match self { GatewaySetup::New { by_latency } => { let mut rng = OsRng; - let gateways = current_gateways(&mut rng, validator_servers).await?; - if by_latency { + if *by_latency { choose_gateway_by_latency(&mut rng, gateways).await } else { uniformly_random_gateway(&mut rng, gateways) @@ -89,20 +149,28 @@ impl GatewaySetup { } .map(Into::into), GatewaySetup::Specified { gateway_identity } => { - let user_gateway = identity::PublicKey::from_base58_string(&gateway_identity) + let user_gateway = identity::PublicKey::from_base58_string(gateway_identity) .map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?; - let mut rng = OsRng; - let gateways = current_gateways(&mut rng, validator_servers).await?; gateways - .into_iter() + .iter() .find(|gateway| gateway.identity_key == user_gateway) .ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string())) + .cloned() } .map(Into::into), - GatewaySetup::Predefined { config } => Ok(config), + _ => Err(ClientCoreError::UnexpectedGatewayDetails), } } + + pub async fn try_get_new_gateway_details( + &self, + validator_servers: &[Url], + ) -> Result { + let mut rng = OsRng; + let gateways = current_gateways(&mut rng, validator_servers).await?; + self.choose_gateway(&gateways).await + } } /// Struct describing the results of the client initialization procedure. @@ -117,14 +185,14 @@ pub struct InitResults { } impl InitResults { - pub fn new(config: &Config, address: &Recipient) -> Self { + pub fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self { Self { 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(), - gateway_listener: config.get_gateway_listener(), + gateway_id: gateway.gateway_id.clone(), + gateway_listener: gateway.gateway_listener.clone(), } } } @@ -140,38 +208,164 @@ impl Display for InitResults { } } -/// Recovers the already present gateway information or attempts to register with new gateway -/// and stores the newly obtained key -pub async fn get_registered_gateway( - validator_servers: Vec, - key_store: &S::KeyStore, - setup: GatewaySetup, - overwrite_keys: bool, -) -> Result<(GatewayEndpointConfig, ManagedKeys), ClientCoreError> +// helpers for error wrapping +async fn _store_gateway_details( + details_store: &D, + details: &PersistedGatewayDetails, +) -> Result<(), ClientCoreError> where - S: MixnetClientStorage, - ::StorageError: Send + Sync + 'static, + D: GatewayDetailsStore, + D::StorageError: Send + Sync + 'static, +{ + details_store + .store_gateway_details(details) + .await + .map_err(|source| ClientCoreError::GatewayDetailsStoreError { + source: Box::new(source), + }) +} + +async fn _load_gateway_details( + details_store: &D, +) -> Result +where + D: GatewayDetailsStore, + D::StorageError: Send + Sync + 'static, +{ + details_store + .load_gateway_details() + .await + .map_err(|source| ClientCoreError::UnavailableGatewayDetails { + source: Box::new(source), + }) +} + +async fn _load_managed_keys(key_store: &K) -> Result +where + K: KeyStore, + K::StorageError: Send + Sync + 'static, +{ + ManagedKeys::try_load(key_store) + .await + .map_err(|source| ClientCoreError::KeyStoreError { + source: Box::new(source), + }) +} + +fn ensure_valid_details( + details: &PersistedGatewayDetails, + loaded_keys: &ManagedKeys, +) -> Result<(), ClientCoreError> { + if !details.verify(&loaded_keys.must_get_gateway_shared_key()) { + Err(ClientCoreError::MismatchedGatewayDetails { + gateway_id: details.details.gateway_id.clone(), + }) + } else { + Ok(()) + } +} + +pub async fn setup_gateway_from( + setup: &GatewaySetup, + key_store: &K, + details_store: &D, + overwrite_data: bool, + gateways: Option<&[gateway::Node]>, +) -> Result +where + K: KeyStore, + D: GatewayDetailsStore, + K::StorageError: Send + Sync + 'static, + D::StorageError: Send + Sync + 'static, { let mut rng = OsRng; - // try load keys + // try load gateway details + let loaded_details = _load_gateway_details(details_store).await; + + // try load keys and decide what to do based on the GatewaySetup let mut managed_keys = match ManagedKeys::try_load(key_store).await { Ok(loaded_keys) => { - // if we loaded something and we don't have full gateway details, check if we can overwrite the data - if let GatewaySetup::Predefined { config } = setup { - // we already have defined gateway details AND a shared key, so nothing more for us to do - return Ok((config, loaded_keys)); - } else if overwrite_keys { - ManagedKeys::generate_new(&mut rng) - } else { - return Err(ClientCoreError::ForbiddenKeyOverwrite); + match setup { + GatewaySetup::MustLoad => { + // get EVERYTHING from the storage + let details = loaded_details?; + ensure_valid_details(&details, &loaded_keys)?; + + // no need to persist anything as we got everything from the storage + return Ok(InitialisationDetails::new(details.into(), loaded_keys)); + } + GatewaySetup::Predefined { details } => { + // we already have defined gateway details AND a shared key + ensure_valid_details(details, &loaded_keys)?; + + // if nothing was stored or we're allowed to overwrite what's there, just persist the passed data + if overwrite_data || loaded_details.is_err() { + _store_gateway_details(details_store, details).await?; + } + + return Ok(InitialisationDetails::new( + details.clone().into(), + loaded_keys, + )); + } + GatewaySetup::Specified { gateway_identity } => { + // if that data was already stored... + if let Ok(existing_gateway) = loaded_details { + ensure_valid_details(&existing_gateway, &loaded_keys)?; + if &existing_gateway.details.gateway_id != gateway_identity + && !overwrite_data + { + // if our loaded details don't match requested value and we CANT overwrite it... + return Err(ClientCoreError::UnexpectedGatewayDetails); + } else if &existing_gateway.details.gateway_id == gateway_identity { + // if they do match up, just return it + return Ok(InitialisationDetails::new( + existing_gateway.into(), + loaded_keys, + )); + } + } + + // we didn't get full details from the store and we have loaded some keys + // so we can only continue if we're allowed to overwrite keys + if overwrite_data { + ManagedKeys::generate_new(&mut rng) + } else { + return Err(ClientCoreError::ForbiddenKeyOverwrite); + } + } + GatewaySetup::New { .. } => { + if let Ok(existing_gateway) = loaded_details { + ensure_valid_details(&existing_gateway, &loaded_keys)?; + return Ok(InitialisationDetails::new( + existing_gateway.into(), + loaded_keys, + )); + } + + // we didn't get full details from the store and we have loaded some keys + // so we can only continue if we're allowed to overwrite keys + if overwrite_data { + ManagedKeys::generate_new(&mut rng) + } else { + return Err(ClientCoreError::ForbiddenKeyOverwrite); + } + } } } - Err(_) => ManagedKeys::generate_new(&mut rng), + Err(_) => { + // if we failed to load the keys, ensure we didn't provide gateway details in some form + // (in that case we CAN'T generate new keys + if setup.has_full_details() { + return Err(ClientCoreError::UnavailableSharedKey); + } + ManagedKeys::generate_new(&mut rng) + } }; // choose gateway - let gateway_details = setup.try_get_gateway_details(&validator_servers).await?; + let gateway_details = setup.choose_gateway(gateways.unwrap_or_default()).await?; // get our identity key let our_identity = managed_keys.identity_keypair(); @@ -179,6 +373,9 @@ where // Establish connection, authenticate and generate keys for talking with the gateway let shared_keys = helpers::register_with_gateway(&gateway_details, our_identity).await?; + let persisted_details = PersistedGatewayDetails::new(gateway_details, &shared_keys); + + // persist gateway keys managed_keys .deal_with_gateway_key(shared_keys, key_store) .await @@ -186,109 +383,39 @@ where source: Box::new(source), })?; - // TODO: here we should be probably persisting gateway details as opposed to returning them + // persist gateway config + _store_gateway_details(details_store, &persisted_details).await?; - Ok((gateway_details, managed_keys)) + Ok(InitialisationDetails::new( + persisted_details.into(), + managed_keys, + )) } -/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the -/// arguments given it will do the sensible thing. Either it will -/// -/// a. Reuse existing gateway configuration from storage. -/// 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( - key_store: &KSt, - register_gateway: bool, - user_chosen_gateway_id: Option, - config: &Config, - old_gateway_config: Option, - by_latency: bool, -) -> Result +pub async fn setup_gateway( + setup: &GatewaySetup, + key_store: &K, + details_store: &D, + overwrite_data: bool, + validator_servers: Option<&[Url]>, +) -> Result where - KSt: KeyStore, - ::StorageError: Send + Sync + 'static, + K: KeyStore, + D: GatewayDetailsStore, + K::StorageError: Send + Sync + 'static, + D::StorageError: Send + Sync + 'static, { - // 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 old_gateway_config.ok_or(ClientCoreError::FailedToSetupGateway); - } - - let gateway_setup = GatewaySetup::new( - None, - user_chosen_gateway_id.map(|id| id.to_base58_string()), - Some(by_latency), - ); - // Else, we proceed by querying the nym-api - let gateway = gateway_setup - .try_get_gateway_details(&config.get_nym_api_endpoints()) - .await?; - log::debug!("Querying gateway gives: {:?}", gateway); - - // If we are not registering, just return this and assume the caller has the keys already and - // wants to keep the, - if !register_gateway && user_chosen_gateway_id.is_some() { - eprintln!("Using gateway provided by user, keeping existing keys"); - return Ok(gateway); - } - let mut rng = OsRng; - let mut managed_keys = - crate::client::key_manager::ManagedKeys::load_or_generate(&mut rng, key_store).await; + let gateways = current_gateways(&mut rng, validator_servers.unwrap_or_default()).await?; - // Create new keys and derive our identity - let our_identity = managed_keys.identity_keypair(); - - // Establish connection, authenticate and generate keys for talking with the gateway - eprintln!("Registering with new gateway"); - let shared_keys = helpers::register_with_gateway(&gateway, our_identity).await?; - managed_keys - .deal_with_gateway_key(shared_keys, key_store) - .await - .map_err(|source| ClientCoreError::KeyStoreError { - source: Box::new(source), - })?; - - Ok(gateway) -} - -/// Get the full client address from the client keys and the gateway identity -pub fn get_client_address( - key_manager: &KeyManager, - gateway_config: &GatewayEndpointConfig, -) -> Recipient { - Recipient::new( - *key_manager.identity_keypair().public_key(), - *key_manager.encryption_keypair().public_key(), - // TODO: below only works under assumption that gateway address == gateway id - // (which currently is true) - NodeIdentity::from_base58_string(&gateway_config.gateway_id).unwrap(), + setup_gateway_from( + setup, + key_store, + details_store, + overwrite_data, + Some(&gateways), ) -} - -/// Get the client address by loading the keys from stored files. -// TODO: rethink that sucker -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( - public_identity, - public_encryption, - // TODO: below only works under assumption that gateway address == gateway id - // (which currently is true) - NodeIdentity::from_base58_string(&gateway_config.gateway_id)?, - ); - - Ok(client_recipient) + .await } pub fn output_to_json(init_results: &T, output_file: &str) { diff --git a/common/socks5-client-core/src/config/mod.rs b/common/socks5-client-core/src/config/mod.rs index 2411f2fbbc..4dd377a76b 100644 --- a/common/socks5-client-core/src/config/mod.rs +++ b/common/socks5-client-core/src/config/mod.rs @@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize}; use std::fmt::Debug; use std::str::FromStr; +pub mod old_config_v1_1_20_2; + pub use nym_service_providers_common::interface::ProviderInterfaceVersion; pub use nym_socks5_requests::Socks5ProtocolVersion; diff --git a/common/socks5-client-core/src/config/old_config_v1_1_20_2.rs b/common/socks5-client-core/src/config/old_config_v1_1_20_2.rs new file mode 100644 index 0000000000..2269a0fa01 --- /dev/null +++ b/common/socks5-client-core/src/config/old_config_v1_1_20_2.rs @@ -0,0 +1,87 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseClientConfigV1_1_20_2; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; + +use crate::config::{Config, Socks5, Socks5Debug}; +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, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_20_2 { + #[serde(flatten)] + pub base: BaseClientConfigV1_1_20_2, + + pub socks5: Socks5V1_1_20_2, +} + +impl From for Config { + fn from(value: ConfigV1_1_20_2) -> Self { + Config { + base: value.base.into(), + socks5: value.socks5.into(), + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Socks5V1_1_20_2 { + 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_2, +} + +impl From for Socks5 { + fn from(value: Socks5V1_1_20_2) -> 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, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct Socks5DebugV1_1_20_2 { + /// Number of reply SURBs attached to each `Request::Connect` message. + pub connection_start_surbs: u32, + + /// Number of reply SURBs attached to each `Request::Send` message. + pub per_request_surbs: u32, +} + +impl From for Socks5Debug { + fn from(value: Socks5DebugV1_1_20_2) -> Self { + Socks5Debug { + connection_start_surbs: value.connection_start_surbs, + per_request_surbs: value.per_request_surbs, + } + } +} + +impl Default for Socks5DebugV1_1_20_2 { + fn default() -> Self { + Socks5DebugV1_1_20_2 { + connection_start_surbs: DEFAULT_CONNECTION_START_SURBS, + per_request_surbs: DEFAULT_PER_REQUEST_SURBS, + } + } +} diff --git a/common/socks5-client-core/src/lib.rs b/common/socks5-client-core/src/lib.rs index 35c4e5187e..ae2e9c6cbb 100644 --- a/common/socks5-client-core/src/lib.rs +++ b/common/socks5-client-core/src/lib.rs @@ -11,6 +11,7 @@ use futures::channel::mpsc; use futures::StreamExt; use log::*; use nym_client_core::client::base_client::non_wasm_helpers::default_query_dkg_client_from_config; +use nym_client_core::client::base_client::storage::gateway_details::GatewayDetailsStore; use nym_client_core::client::base_client::storage::MixnetClientStorage; use nym_client_core::client::base_client::{ BaseClientBuilder, ClientInput, ClientOutput, ClientState, @@ -60,6 +61,7 @@ where S::ReplyStore: Send + Sync, ::StorageError: Sync + Send, ::StorageError: Send + Sync, + ::StorageError: Sync + Send, ::StorageError: Send + Sync, { pub fn new(config: Config, storage: S) -> Self { diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index a9c3469a66..4692527b4e 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -207,9 +207,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" @@ -3238,6 +3238,7 @@ name = "nym-client-core" version = "1.1.14" dependencies = [ "async-trait", + "base64 0.21.2", "dashmap", "dirs 4.0.0", "futures", @@ -3260,6 +3261,7 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", + "sha2 0.10.6", "sqlx 0.6.3", "tap", "thiserror", @@ -4427,7 +4429,7 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "indexmap", "line-wrap", "quick-xml 0.28.1", @@ -4778,7 +4780,7 @@ version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -4972,7 +4974,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", ] [[package]] diff --git a/nym-connect/desktop/src-tauri/src/config/mod.rs b/nym-connect/desktop/src-tauri/src/config/mod.rs index f9caf60ee9..79dc15e5af 100644 --- a/nym-connect/desktop/src-tauri/src/config/mod.rs +++ b/nym-connect/desktop/src-tauri/src/config/mod.rs @@ -4,7 +4,10 @@ use crate::config::persistence::NymConnectPaths; use crate::config::template::CONFIG_TEMPLATE; use crate::error::{BackendError, Result}; +use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails; use nym_client_core::client::key_manager::persistence::OnDiskKeys; +use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::init::GatewaySetup; 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, @@ -127,13 +130,15 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str // 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 _validated = identity::PublicKey::from_base58_string(&chosen_gateway_id) + .map_err(|_| BackendError::UnableToParseGateway)?; - let old_config = if default_config_filepath(&id).exists() { + let already_init = if default_config_filepath(&id).exists() { eprintln!("SOCKS5 client \"{id}\" was already initialised before"); - Some(Config::read_from_default_path(&id)?) + true } else { init_paths(&id)?; - None + false }; // Future proofing. This flag exists for the other clients @@ -142,7 +147,7 @@ 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 = old_config.is_none() || user_wants_force_register; + let register_gateway = !already_init || user_wants_force_register; log::trace!("Creating config for id: {id}"); let mut config = Config::new(&id, &provider_address); @@ -151,49 +156,40 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str 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)?; + let gateway_setup = GatewaySetup::new_fresh(Some(chosen_gateway_id), None); // Setup gateway by either registering a new one, or reusing exiting keys let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone()); - let gateway = nym_client_core::init::setup_gateway_from_config::<_>( + let details_store = + OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details); + let init_details = nym_client_core::init::setup_gateway( + &gateway_setup, &key_store, + &details_store, register_gateway, - Some(chosen_gateway_id), - &config.core.base, - old_config.map(|cfg| cfg.core.base.client.gateway_endpoint), - // TODO: another instance where this setting should probably get used - false, + Some(&config.core.base.client.nym_api_urls), ) .await?; - config.core.base.set_gateway_endpoint(gateway); - config.save_to_default_location().tap_err(|_| { log::error!("Failed to save the config file"); })?; - print_saved_config(&config); + print_saved_config(&config, &init_details.gateway_details); - 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); + let address = init_details.client_address()?; + log::info!("The address of this client is: {address}"); Ok(()) } -fn print_saved_config(config: &Config) { +fn print_saved_config(config: &Config, gateway_details: &GatewayEndpointConfig) { log::info!( "Saved configuration file to {}", config.default_location().display() ); - 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.core.base.get_gateway_listener() - ); + log::info!("Gateway id: {}", gateway_details.gateway_id); + log::info!("Gateway owner: {}", gateway_details.gateway_owner); + log::info!("Gateway listener: {}", gateway_details.gateway_listener); log::info!( "Service provider address: {}", config.core.socks5.provider_mix_address diff --git a/nym-connect/desktop/src-tauri/src/config/template.rs b/nym-connect/desktop/src-tauri/src/config/template.rs index fd4aec5f52..3394f6c80e 100644 --- a/nym-connect/desktop/src-tauri/src/config/template.rs +++ b/nym-connect/desktop/src-tauri/src/config/template.rs @@ -64,16 +64,9 @@ 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 }}' +# Path to the file containing information about gateway used by this client, +# i.e. details such as its public key, owner address or the network information. +gateway_details = '{{ storage_paths.gateway_details }}' ##### socket config options ##### diff --git a/nym-connect/desktop/src-tauri/src/state.rs b/nym-connect/desktop/src-tauri/src/state.rs index eb03a4b86a..9e4ba9c10b 100644 --- a/nym-connect/desktop/src-tauri/src/state.rs +++ b/nym-connect/desktop/src-tauri/src/state.rs @@ -176,7 +176,7 @@ impl State { } // Kick off the main task and get the channel for controlling it - self.start_nym_socks5_client() + self.start_nym_socks5_client().await } /// Create a configuration file @@ -196,12 +196,12 @@ impl State { } /// Spawn a new thread running the SOCKS5 client - fn start_nym_socks5_client( + async fn start_nym_socks5_client( &mut self, ) -> Result<(nym_task::StatusReceiver, ExitStatusReceiver)> { let id = self.get_config_id()?; let (control_tx, msg_rx, exit_status_rx, used_gateway) = - tasks::start_nym_socks5_client(&id)?; + tasks::start_nym_socks5_client(&id).await?; self.socks5_client_sender = Some(control_tx); self.gateway = Some(used_gateway.gateway_id); Ok((msg_rx, exit_status_rx)) diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index 2e89864db9..9e70500dd0 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -1,5 +1,6 @@ use futures::{channel::mpsc, StreamExt}; -use nym_client_core::client::base_client::storage::OnDiskPersistent; +use nym_client_core::client::base_client::storage::gateway_details::GatewayDetailsStore; +use nym_client_core::client::base_client::storage::{MixnetClientStorage, OnDiskPersistent}; use nym_client_core::{config::GatewayEndpointConfig, error::ClientCoreStatusMessage}; use nym_socks5_client_core::NymClient as Socks5NymClient; use nym_socks5_client_core::Socks5ControlMessageSender; @@ -29,7 +30,7 @@ pub enum Socks5ExitStatusMessage { } /// The main SOCKS5 client task. It loads the configuration from file determined by the `id`. -pub fn start_nym_socks5_client( +pub async fn start_nym_socks5_client( id: &str, ) -> Result<( Socks5ControlMessageSender, @@ -40,7 +41,17 @@ pub fn start_nym_socks5_client( log::info!("Loading config from file: {id}"); let config = Config::read_from_default_path(id) .tap_err(|_| log::warn!("Failed to load configuration file"))?; - let used_gateway = config.core.base.client.gateway_endpoint.clone(); + + let storage = + OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug) + .await?; + + let used_gateway = storage + .gateway_details_store() + .load_gateway_details() + .await + .expect("failed to load gateway details") + .into(); log::info!("Starting socks5 client"); @@ -61,11 +72,6 @@ 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_paths( - config.storage_paths.common_paths, - &config.core.base.debug, - ) - .await?; let socks5_client = Socks5NymClient::new(config.core, storage); socks5_client diff --git a/sdk/lib/socks5-listener/src/lib.rs b/sdk/lib/socks5-listener/src/lib.rs index ff4e41283a..9a9c9c841b 100644 --- a/sdk/lib/socks5-listener/src/lib.rs +++ b/sdk/lib/socks5-listener/src/lib.rs @@ -8,8 +8,6 @@ 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_socks5_client_core::NymClient as Socks5NymClient; use safer_ffi::char_p::char_p_boxed; @@ -288,30 +286,10 @@ async fn setup_new_client_config( .set_custom_nym_apis(nym_config_common::parse_urls(&raw_validators)); } - 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? + if let Some(_storage_paths) = &new_config.storage_paths { + println!("persistent storage is not implemented"); }; - new_config.core.base.set_gateway_endpoint(gateway); - if let Some(storage_dir) = storage_dir { new_config.save_to_default_location(storage_dir)?; } diff --git a/sdk/lib/socks5-listener/src/persistence.rs b/sdk/lib/socks5-listener/src/persistence.rs index e6ef9fd634..68455fad27 100644 --- a/sdk/lib/socks5-listener/src/persistence.rs +++ b/sdk/lib/socks5-listener/src/persistence.rs @@ -1,40 +1,30 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::config::Config; +use nym_client_core::client::base_client::storage::gateway_details::InMemGatewayDetails; use nym_client_core::client::base_client::storage::MixnetClientStorage; +use nym_client_core::client::key_manager::persistence::InMemEphemeralKeys; use nym_client_core::client::replies::reply_storage; use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage; -#[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; - pub struct MobileClientStorage { - #[cfg(not(target_os = "android"))] - key_store: OnDiskKeys, - - #[cfg(target_os = "android")] + // the key storage is now useless without gateway details store. so use ephemeral for everything. key_store: InMemEphemeralKeys, + gateway_details_store: InMemGatewayDetails, reply_store: reply_storage::Empty, credential_store: EphemeralCredentialStorage, } impl MixnetClientStorage for MobileClientStorage { - #[cfg(not(target_os = "android"))] - type KeyStore = OnDiskKeys; - - #[cfg(target_os = "android")] type KeyStore = InMemEphemeralKeys; - type ReplyStore = reply_storage::Empty; type CredentialStore = EphemeralCredentialStorage; + type GatewayDetailsStore = InMemGatewayDetails; - fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) { - (self.key_store, self.reply_store, self.credential_store) + fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) { + (self.reply_store, self.credential_store) } fn key_store(&self) -> &Self::KeyStore { @@ -48,28 +38,17 @@ impl MixnetClientStorage for MobileClientStorage { fn credential_store(&self) -> &Self::CredentialStore { &self.credential_store } + + fn gateway_details_store(&self) -> &Self::GatewayDetailsStore { + &self.gateway_details_store + } } impl MobileClientStorage { - pub fn new(config: &Config) -> Self { - #[cfg(target_os = "android")] - let key_store = { - let _ = config; - InMemEphemeralKeys - }; - - #[cfg(not(target_os = "android"))] - let key_store = OnDiskKeys::new( - config - .storage_paths - .clone() - .expect("storage paths unavailable") - .common_paths - .keys, - ); - + pub fn new(_config: &Config) -> Self { MobileClientStorage { - key_store, + key_store: Default::default(), + gateway_details_store: Default::default(), reply_store: Default::default(), credential_store: Default::default(), } diff --git a/sdk/rust/nym-sdk/examples/manually_handle_storage.rs b/sdk/rust/nym-sdk/examples/manually_handle_storage.rs index 3f7e5998db..3c5ef4f450 100644 --- a/sdk/rust/nym-sdk/examples/manually_handle_storage.rs +++ b/sdk/rust/nym-sdk/examples/manually_handle_storage.rs @@ -1,3 +1,6 @@ +use nym_client_core::client::base_client::storage::gateway_details::{ + GatewayDetailsStore, PersistedGatewayDetails, +}; use nym_sdk::mixnet::{ self, EmptyReplyStorage, EphemeralCredentialStorage, KeyManager, KeyStore, MixnetClientStorage, }; @@ -10,37 +13,13 @@ async fn main() { // Just some plain data to pretend we have some external storage that the application // implementer is using. let mock_storage = MockClientStorage::empty(); - let mut mock_gw_storage = MockGatewayConfigStorage::empty(); - - let first_run = true; - - let client = if first_run { - // Create a client without a storage backend - let mut client = mixnet::MixnetClientBuilder::new_with_storage(mock_storage) - .build() - .await - .unwrap(); - - // In this we want to provide our own gateway config struct, and handle persisting this info to disk - // ourselves (e.g., as part of our own configuration file). - // during registration, our key storage will be automatically called to persist the keys - client.register_and_authenticate_gateway().await.unwrap(); - mock_gw_storage.write_config(client.get_gateway_endpoint().unwrap()); - client - } else { - let gateway_config = mock_gw_storage.read_config(); - - // Create a client with a storage backend, so that our keys could be loaded. - // This creates the client in a registered state. - mixnet::MixnetClientBuilder::new_with_storage(mock_storage) - .registered_gateway(gateway_config) - .build() - .await - .unwrap() - }; - - // Connect to the mixnet, now we're listening for incoming - let mut client = client.connect_to_mixnet().await.unwrap(); + let mut client = mixnet::MixnetClientBuilder::new_with_storage(mock_storage) + .build() + .await + .unwrap() + .connect_to_mixnet() + .await + .unwrap(); // Be able to get our client address let our_address = client.nym_address(); @@ -59,30 +38,10 @@ async fn main() { client.disconnect().await; } -#[allow(unused)] -struct MockGatewayConfigStorage { - pub gateway_config: Option, -} - -impl MockGatewayConfigStorage { - fn read_config(&self) -> mixnet::GatewayEndpointConfig { - todo!(); - } - - fn write_config(&mut self, _gateway_config: &mixnet::GatewayEndpointConfig) { - log::info!("todo"); - } - - fn empty() -> Self { - Self { - gateway_config: None, - } - } -} - #[allow(unused)] struct MockClientStorage { pub key_store: MockKeyStore, + pub gateway_details_store: MockGatewayDetailsStore, pub reply_store: EmptyReplyStorage, pub credential_store: EphemeralCredentialStorage, } @@ -91,6 +50,7 @@ impl MockClientStorage { fn empty() -> Self { Self { key_store: MockKeyStore, + gateway_details_store: MockGatewayDetailsStore, reply_store: EmptyReplyStorage::default(), credential_store: EphemeralCredentialStorage::default(), } @@ -101,9 +61,10 @@ impl MixnetClientStorage for MockClientStorage { type KeyStore = MockKeyStore; type ReplyStore = EmptyReplyStorage; type CredentialStore = EphemeralCredentialStorage; + type GatewayDetailsStore = MockGatewayDetailsStore; - fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) { - (self.key_store, self.reply_store, self.credential_store) + fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) { + (self.reply_store, self.credential_store) } fn key_store(&self) -> &Self::KeyStore { @@ -117,6 +78,10 @@ impl MixnetClientStorage for MockClientStorage { fn credential_store(&self) -> &Self::CredentialStore { &self.credential_store } + + fn gateway_details_store(&self) -> &Self::GatewayDetailsStore { + &self.gateway_details_store + } } struct MockKeyStore; @@ -137,6 +102,29 @@ impl KeyStore for MockKeyStore { Ok(()) } } + +struct MockGatewayDetailsStore; + +#[async_trait] +impl GatewayDetailsStore for MockGatewayDetailsStore { + type StorageError = MyError; + + async fn load_gateway_details(&self) -> Result { + println!("loading stored gateway details"); + + Err(MyError) + } + + async fn store_gateway_details( + &self, + _details: &PersistedGatewayDetails, + ) -> Result<(), Self::StorageError> { + println!("storing gateway details"); + + Ok(()) + } +} + // // struct MockReplyStore; // diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index ccf21bb12c..33185ccd88 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -8,25 +8,24 @@ use crate::mixnet::{CredentialStorage, MixnetClient, Recipient}; use crate::{Error, Result}; use futures::channel::mpsc; use futures::StreamExt; +use nym_client_core::client::base_client::storage::gateway_details::GatewayDetailsStore; use nym_client_core::client::base_client::storage::{ Ephemeral, MixnetClientStorage, OnDiskPersistent, }; use nym_client_core::client::base_client::BaseClient; use nym_client_core::client::key_manager::persistence::KeyStore; -use nym_client_core::client::key_manager::ManagedKeys; -use nym_client_core::config::{Client as ClientConfig, DebugConfig}; +use nym_client_core::config::DebugConfig; use nym_client_core::init::GatewaySetup; use nym_client_core::{ client::{base_client::BaseClientBuilder, replies::reply_storage::ReplyStorageBackend}, config::GatewayEndpointConfig, }; use nym_network_defaults::NymNetworkDetails; -use nym_socks5_client_core::config::{BaseClientConfig, Socks5}; +use nym_socks5_client_core::config::Socks5; use nym_task::manager::TaskStatus; use nym_topology::provider_trait::TopologyProvider; use nym_validator_client::nyxd::QueryNyxdClient; use nym_validator_client::Client; -use rand::thread_rng; use std::path::Path; use std::path::PathBuf; use url::Url; @@ -34,8 +33,6 @@ use url::Url; // The number of surbs to include in a message by default const DEFAULT_NUMBER_OF_SURBS: u32 = 5; -const DEFAULT_SDK_CLIENT_ID: &str = "_default-nym-sdk-client"; - #[derive(Default)] pub struct MixnetClientBuilder { config: Config, @@ -85,10 +82,11 @@ impl MixnetClientBuilder { impl MixnetClientBuilder where S: MixnetClientStorage + 'static, + S::ReplyStore: Send + Sync, ::StorageError: Sync + Send, ::StorageError: Send + Sync, - S::ReplyStore: Send + Sync, ::StorageError: Send + Sync, + ::StorageError: Send + Sync, { /// Creates a client builder with the provided client storage implementation. #[must_use] @@ -155,13 +153,6 @@ where self } - /// Use a gateway that you previously registered with. - #[must_use] - pub fn registered_gateway(mut self, gateway_config: GatewayEndpointConfig) -> Self { - self.gateway_config = Some(gateway_config); - self - } - /// Configure the SOCKS5 mode. #[must_use] pub fn socks5_config(mut self, socks5_config: Socks5) -> Self { @@ -187,21 +178,14 @@ where /// Construct a [`DisconnectedMixnetClient`] from the setup specified. pub async fn build(self) -> Result> { - let mut client = DisconnectedMixnetClient::new( + let client = DisconnectedMixnetClient::new( self.config, self.socks5_config, self.storage, self.custom_topology_provider, - self.gateway_endpoint_config_path, ) .await?; - // If we have a gateway config, we can move the client into a registered state. This will - // fail if no gateway key is set. - if let Some(gateway_config) = self.gateway_config { - client.register_gateway_with_config(gateway_config)?; - } - Ok(client) } } @@ -232,13 +216,6 @@ where /// dkg and coconut contracts dkg_query_client: Option>, - /// Keys handled by the client - managed_keys: ManagedKeys, - - // TODO: incorporate it properly into `MixnetClientStorage` (I will need it in wasm anyway) - /// Path to optionally persist gateway configuration. Note that it's required if one were to use persistent keys. - gateway_endpoint_config_path: Option, - /// Alternative provider of network topology used for constructing sphinx packets. custom_topology_provider: Option>, } @@ -246,10 +223,11 @@ where impl DisconnectedMixnetClient where S: MixnetClientStorage + 'static, + S::ReplyStore: Send + Sync, ::StorageError: Sync + Send, ::StorageError: Send + Sync, - S::ReplyStore: Send + Sync, ::StorageError: Send + Sync, + ::StorageError: Send + Sync, { /// Create a new mixnet client in a disconnected state. The default configuration, /// creates a new mainnet client with ephemeral keys stored in RAM, which will be discarded at @@ -263,7 +241,6 @@ where socks5_config: Option, storage: S, custom_topology_provider: Option>, - gateway_endpoint_config_path: Option, ) -> Result> { // don't create dkg client for the bandwidth controller if credentials are disabled let dkg_query_client = if config.enabled_credentials_mode { @@ -276,9 +253,6 @@ where None }; - let mut rng = thread_rng(); - let managed_keys = ManagedKeys::load_or_generate(&mut rng, storage.key_store()).await; - Ok(DisconnectedMixnetClient { config, socks5_config, @@ -286,8 +260,6 @@ where dkg_query_client, storage, custom_topology_provider, - managed_keys, - gateway_endpoint_config_path, }) } @@ -314,35 +286,17 @@ where /// Client keys are generated at client creation if none were found. The gateway shared /// key, however, is created during the gateway registration handshake so it might not /// necessarily be available. - fn has_gateway_key(&self) -> bool { - matches!(self.managed_keys, ManagedKeys::FullyDerived(..)) - } + /// Furthermore, it has to be coupled with particular gateway's config. + async fn has_gateway_info(&self) -> bool { + let has_keys = self.storage.key_store().load_keys().await.is_ok(); + let has_gateway_details = self + .storage + .gateway_details_store() + .load_gateway_details() + .await + .is_ok(); - fn remove_gateway_key(&mut self) { - assert!(self.has_gateway_key()); - let ManagedKeys::FullyDerived(keys) = std::mem::replace(&mut self.managed_keys, ManagedKeys::Invalidated) else { - unreachable!() - }; - self.managed_keys = ManagedKeys::Initial(keys.remove_gateway_key()) - } - - /// Sets the gateway endpoint of this [`MixnetClientBuilder`]. - /// - /// NOTE: this will mark this builder as `Registered`, and the it is assumed that the keys are - /// also explicitly set. - pub fn register_gateway_with_config( - &mut self, - gateway_endpoint_config: GatewayEndpointConfig, - ) -> Result<()> { - if !self.has_gateway_key() { - return Err(Error::NoGatewayKeySet); - } - - self.state = BuilderState::Registered { - gateway_endpoint_config, - }; - - Ok(()) + has_keys && has_gateway_details } /// Register with a gateway. If a gateway is provided in the config then that will try to be @@ -353,59 +307,31 @@ where /// This function will return an error if you try to re-register when in an already registered /// state. pub async fn register_and_authenticate_gateway(&mut self) -> Result<()> { - if self.state != BuilderState::New { + if !matches!(self.state, BuilderState::New) { return Err(Error::ReregisteringGatewayNotSupported); } + log::debug!("Registering with gateway"); let api_endpoints = self.get_api_endpoints(); - let gateway_setup = GatewaySetup::new(None, self.config.user_chosen_gateway.clone(), None); - let (gateway_config, managed_keys) = nym_client_core::init::get_registered_gateway::( - api_endpoints, + let gateway_setup = if self.has_gateway_info().await { + GatewaySetup::MustLoad + } else { + GatewaySetup::new_fresh(self.config.user_chosen_gateway.clone(), None) + }; + + // this will perform necessary key and details load and optional store + let _init_result = nym_client_core::init::setup_gateway( + &gateway_setup, self.storage.key_store(), - gateway_setup, + self.storage.gateway_details_store(), !self.config.key_mode.is_keep(), + Some(&api_endpoints), ) .await?; - self.state = BuilderState::Registered { - gateway_endpoint_config: gateway_config, - }; - self.managed_keys = managed_keys; - Ok(()) - } - - /// Returns the get gateway endpoint of this [`MixnetClientBuilder`]. - pub fn get_gateway_endpoint(&self) -> Option<&GatewayEndpointConfig> { - self.state.gateway_endpoint_config() - } - - fn write_gateway_endpoint_config(&self, gateway_endpoint_config_path: &Path) -> Result<()> { - let gateway_endpoint_config = toml::to_string( - self.get_gateway_endpoint() - .ok_or(Error::GatewayNotAvailableForWriting)?, - )?; - - // Ensure the whole directory structure exists - if let Some(parent_dir) = gateway_endpoint_config_path.parent() { - std::fs::create_dir_all(parent_dir)?; - } - std::fs::write(gateway_endpoint_config_path, gateway_endpoint_config)?; - Ok(()) - } - - fn read_gateway_endpoint_config>( - &mut self, - gateway_endpoint_config_path: P, - ) -> Result<()> { - let gateway_endpoint_config: GatewayEndpointConfig = - std::fs::read_to_string(gateway_endpoint_config_path) - .map(|str| toml::from_str(&str))??; - - self.state = BuilderState::Registered { - gateway_endpoint_config, - }; + self.state = BuilderState::Registered {}; Ok(()) } @@ -426,61 +352,40 @@ where } async fn connect_to_mixnet_common(mut self) -> Result<(BaseClient, Recipient)> { - // For some simple cases we can figure how to setup gateway without it having to have been - // called in advance. - if matches!(self.state, BuilderState::New) { - let already_registered = self.has_gateway_key(); - if already_registered { - if let Some(gateway_endpoint_path) = self.gateway_endpoint_config_path.clone() { - self.read_gateway_endpoint_config(gateway_endpoint_path)?; - } else if !self.config.key_mode.is_keep() { - // if we don't have gateway configuration available and we're not keeping the keys, - // purge them - self.remove_gateway_key(); - } - } else { - // TODO: that is redundant since the base client will perform gateway registration - self.register_and_authenticate_gateway().await?; - if let Some(gateway_endpoint_path) = &self.gateway_endpoint_config_path { - self.write_gateway_endpoint_config(gateway_endpoint_path)?; - } - } + // if we don't care about our keys, explicitly register + if !self.config.key_mode.is_keep() { + self.register_and_authenticate_gateway().await?; } + // otherwise, the whole key setup and gateway selection dance will be done for us + // when we start the base client + let nyxd_endpoints = self.get_nyxd_endpoints(); let nym_api_endpoints = self.get_api_endpoints(); - // If the gateway is in a registered state, but without the gateway key set. - if matches!(self.state, BuilderState::Registered { .. }) && !self.has_gateway_key() { - return Err(Error::NoGatewayKeySet); - } - - // At this point we should be in a registered state, either at function entry or by the - // above convenience logic. - let BuilderState::Registered { gateway_endpoint_config } = self.state else { - return Err(Error::FailedToTransitionToRegisteredState); - }; - // a temporary workaround - let base_config = BaseClientConfig::from_client_config( - ClientConfig::new( - DEFAULT_SDK_CLIENT_ID, - !self.config.enabled_credentials_mode, - nyxd_endpoints, - nym_api_endpoints, - gateway_endpoint_config, - ), - self.config.debug_config, - ); + let base_config = self + .config + .as_base_client_config(nyxd_endpoints, nym_api_endpoints); + + let known_gateway = self.has_gateway_info().await; let mut base_builder: BaseClientBuilder<_, _> = BaseClientBuilder::new(&base_config, self.storage, self.dkg_query_client); + if !known_gateway { + base_builder = base_builder.with_gateway_setup(GatewaySetup::new_fresh( + self.config.user_chosen_gateway, + None, + )) + } + if let Some(topology_provider) = self.custom_topology_provider { base_builder = base_builder.with_topology_provider(topology_provider); } let started_client = base_builder.start_base().await?; + self.state = BuilderState::Registered {}; let nym_address = started_client.address; Ok((started_client, nym_address)) diff --git a/sdk/rust/nym-sdk/src/mixnet/config.rs b/sdk/rust/nym-sdk/src/mixnet/config.rs index 3945b7b7f3..264f857413 100644 --- a/sdk/rust/nym-sdk/src/mixnet/config.rs +++ b/sdk/rust/nym-sdk/src/mixnet/config.rs @@ -1,5 +1,9 @@ -use nym_client_core::config::DebugConfig; +use nym_client_core::config::{Client as ClientConfig, DebugConfig}; use nym_network_defaults::NymNetworkDetails; +use nym_socks5_client_core::config::BaseClientConfig; +use url::Url; + +const DEFAULT_SDK_CLIENT_ID: &str = "_default-nym-sdk-client"; #[derive(Clone, Debug, Default)] pub enum KeyMode { @@ -35,3 +39,22 @@ pub struct Config { /// Changing these risk compromising network anonymity! pub debug_config: DebugConfig, } + +impl Config { + // I really dislike this workaround. + pub fn as_base_client_config( + &self, + nyxd_endpoints: Vec, + nym_api_endpoints: Vec, + ) -> BaseClientConfig { + BaseClientConfig::from_client_config( + ClientConfig::new( + DEFAULT_SDK_CLIENT_ID, + !self.enabled_credentials_mode, + nyxd_endpoints, + nym_api_endpoints, + ), + self.debug_config, + ) + } +} diff --git a/sdk/rust/nym-sdk/src/mixnet/connection_state.rs b/sdk/rust/nym-sdk/src/mixnet/connection_state.rs index 259c001508..74bc5919c6 100644 --- a/sdk/rust/nym-sdk/src/mixnet/connection_state.rs +++ b/sdk/rust/nym-sdk/src/mixnet/connection_state.rs @@ -1,24 +1,8 @@ // Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_client_core::config::GatewayEndpointConfig; - -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug)] pub(super) enum BuilderState { New, - Registered { - gateway_endpoint_config: GatewayEndpointConfig, - }, -} - -impl BuilderState { - pub(super) fn gateway_endpoint_config(&self) -> Option<&GatewayEndpointConfig> { - match self { - BuilderState::New => None, - BuilderState::Registered { - gateway_endpoint_config, - .. - } => Some(gateway_endpoint_config), - } - } + Registered {}, } diff --git a/sdk/rust/nym-sdk/src/mixnet/paths.rs b/sdk/rust/nym-sdk/src/mixnet/paths.rs index f005b6bd63..5c09752280 100644 --- a/sdk/rust/nym-sdk/src/mixnet/paths.rs +++ b/sdk/rust/nym-sdk/src/mixnet/paths.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::{Error, Result}; +use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails; 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; @@ -38,6 +39,9 @@ pub struct StoragePaths { /// The database storing reply surbs in-between sessions pub reply_surb_database_path: PathBuf, + + /// Details of the used gateway + pub gateway_details_path: PathBuf, } impl StoragePaths { @@ -61,6 +65,7 @@ impl StoragePaths { gateway_shared_key: dir.join("gateway_shared.pem"), credential_database_path: dir.join("db.sqlite"), reply_surb_database_path: dir.join("persistent_reply_store.sqlite"), + gateway_details_path: dir.join("gateway_details.json"), }) } @@ -72,6 +77,7 @@ impl StoragePaths { self.on_disk_key_storage_spec(), self.default_persistent_fs_reply_backend().await?, self.persistent_credential_storage().await?, + self.on_disk_gateway_details_storage(), )) } @@ -85,6 +91,7 @@ impl StoragePaths { self.persistent_fs_reply_backend(&config.reply_surbs) .await?, self.persistent_credential_storage().await?, + self.on_disk_gateway_details_storage(), )) } @@ -121,6 +128,10 @@ impl StoragePaths { OnDiskKeys::new(self.client_keys_paths()) } + pub fn on_disk_gateway_details_storage(&self) -> OnDiskGatewayDetails { + OnDiskGatewayDetails::new(&self.gateway_details_path) + } + fn client_keys_paths(&self) -> ClientKeysPaths { ClientKeysPaths { private_identity_key_file: self.private_identity.clone(), @@ -144,6 +155,7 @@ impl From for CommonClientPaths { gateway_shared_key_file: value.gateway_shared_key, ack_key_file: value.ack_key, }, + gateway_details: value.gateway_details_path, credentials_database: value.credential_database_path, reply_surb_database: value.reply_surb_database_path, } @@ -161,6 +173,7 @@ impl From for StoragePaths { gateway_shared_key: value.keys.gateway_shared_key_file, credential_database_path: value.credentials_database, reply_surb_database_path: value.reply_surb_database, + gateway_details_path: value.gateway_details, } } } diff --git a/service-providers/network-requester/src/cli/init.rs b/service-providers/network-requester/src/cli/init.rs index bef82badce..0a2162033f 100644 --- a/service-providers/network-requester/src/cli/init.rs +++ b/service-providers/network-requester/src/cli/init.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::cli::try_load_current_config; +use crate::cli::try_upgrade_config; use crate::config::{default_config_directory, default_config_filepath, default_data_directory}; use crate::{ cli::{override_config, OverrideConfig}, @@ -10,7 +10,10 @@ use crate::{ }; use clap::Args; use nym_bin_common::output_format::OutputFormat; +use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails; use nym_client_core::client::key_manager::persistence::OnDiskKeys; +use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::init::GatewaySetup; use nym_crypto::asymmetric::identity; use nym_sphinx::addressing::clients::Recipient; use serde::Serialize; @@ -77,9 +80,9 @@ pub struct InitResults { } impl InitResults { - fn new(config: &Config, address: &Recipient) -> Self { + fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self { Self { - client_core: nym_client_core::init::InitResults::new(&config.base, address), + client_core: nym_client_core::init::InitResults::new(&config.base, address, gateway), client_address: address.to_string(), } } @@ -106,13 +109,15 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { let id = &args.id; - let old_config = if default_config_filepath(id).exists() { + let already_init = if default_config_filepath(id).exists() { + // 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_config(id)?; 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)?) + true } else { init_paths(&args.id)?; - None + false }; // Usually you only register with the gateway on the first init, however you can force @@ -125,32 +130,32 @@ 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 = old_config.is_none() || user_wants_force_register; + let register_gateway = !already_init || user_wants_force_register; // Attempt to use a user-provided gateway, if possible let user_chosen_gateway_id = args.gateway; + let gateway_setup = GatewaySetup::new_fresh( + user_chosen_gateway_id.map(|id| id.to_base58_string()), + Some(args.latency_based_selection), + ); // Load and potentially override config - let mut config = override_config(Config::new(id), OverrideConfig::from(args.clone())); + let config = override_config(Config::new(id), OverrideConfig::from(args.clone())); // 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::new(config.storage_paths.common_paths.keys.clone()); - let gateway = nym_client_core::init::setup_gateway_from_config::<_>( + let details_store = + OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details); + let init_details = nym_client_core::init::setup_gateway( + &gateway_setup, &key_store, + &details_store, register_gateway, - user_chosen_gateway_id, - &config.base, - old_config.map(|cfg| cfg.base.client.gateway_endpoint), - args.latency_based_selection, + Some(&config.base.client.nym_api_urls), ) .await - .map_err(|source| { - eprintln!("Failed to setup gateway\nError: {source}"); - NetworkRequesterError::FailedToSetupGateway { source } - })?; - - config.base.set_gateway_endpoint(gateway); + .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?; let config_save_location = config.default_location(); config.save_to_default_location().tap_err(|_| { @@ -161,14 +166,11 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { config_save_location.display() ); - let address = nym_client_core::init::get_client_address_from_stored_ondisk_keys( - &config.storage_paths.common_paths.keys, - &config.base.client.gateway_endpoint, - )?; + let address = init_details.client_address()?; eprintln!("Client configuration completed.\n"); - let init_results = InitResults::new(&config, &address); + let init_results = InitResults::new(&config, &address, &init_details.gateway_details); println!("{}", args.output.format(&init_results)); Ok(()) diff --git a/service-providers/network-requester/src/cli/mod.rs b/service-providers/network-requester/src/cli/mod.rs index 237f3a38ed..7b5d2efc33 100644 --- a/service-providers/network-requester/src/cli/mod.rs +++ b/service-providers/network-requester/src/cli/mod.rs @@ -3,6 +3,7 @@ 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::old_config_v1_1_20_2::ConfigV1_1_20_2; use crate::{ config::{BaseClientConfig, Config}, error::NetworkRequesterError, @@ -12,6 +13,12 @@ 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_client_core::client::base_client::storage::gateway_details::{ + OnDiskGatewayDetails, PersistedGatewayDetails, +}; +use nym_client_core::client::key_manager::persistence::OnDiskKeys; +use nym_client_core::config::GatewayEndpointConfig; +use nym_client_core::error::ClientCoreError; mod init; mod run; @@ -105,6 +112,28 @@ pub(crate) async fn execute(args: Cli) -> Result<(), NetworkRequesterError> { Ok(()) } +fn persist_gateway_details( + config: &Config, + details: GatewayEndpointConfig, +) -> Result<(), NetworkRequesterError> { + let details_store = + OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details); + let keys_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone()); + let shared_keys = keys_store.ephemeral_load_gateway_keys().map_err(|source| { + NetworkRequesterError::ClientCoreError(ClientCoreError::KeyStoreError { + source: Box::new(source), + }) + })?; + let persisted_details = PersistedGatewayDetails::new(details, &shared_keys); + details_store + .store_to_disk(&persisted_details) + .map_err(|source| { + NetworkRequesterError::ClientCoreError(ClientCoreError::GatewayDetailsStoreError { + source: Box::new(source), + }) + }) +} + fn try_upgrade_v1_1_13_config(id: &str) -> Result { use nym_config::legacy_helpers::nym_config::MigrationNymConfig; @@ -118,7 +147,9 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result { 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(); + let updated_step2: ConfigV1_1_20_2 = updated_step1.into(); + let (updated, gateway_config) = updated_step2.upgrade(); + persist_gateway_details(&updated, gateway_config)?; updated.save_to_default_location()?; Ok(true) @@ -137,21 +168,56 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result { 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(); + let updated_step1: ConfigV1_1_20_2 = old_config.into(); + let (updated, gateway_config) = updated_step1.upgrade(); + persist_gateway_details(&updated, gateway_config)?; + + updated.save_to_default_location()?; + Ok(true) +} + +fn try_upgrade_v1_1_20_2_config(id: &str) -> Result { + // explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21) + let Ok(old_config) = ConfigV1_1_20_2::read_from_default_path(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_2 config template."); + info!("It is going to get updated to the current specification."); + + let (updated, gateway_config) = old_config.upgrade(); + persist_gateway_details(&updated, gateway_config)?; + 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)?; + if try_upgrade_v1_1_13_config(id)? { + return Ok(()); + } + if try_upgrade_v1_1_20_config(id)? { + return Ok(()); + } + if try_upgrade_v1_1_20_2_config(id)? { + return Ok(()); } Ok(()) } fn try_load_current_config(id: &str) -> Result { + // try to load the config as is + if let Ok(cfg) = Config::read_from_default_path(id) { + return if !cfg.validate() { + Err(NetworkRequesterError::ConfigValidationFailure) + } else { + Ok(cfg) + }; + } + + // we couldn't load it - try upgrading it from older revisions try_upgrade_config(id)?; let config = match Config::read_from_default_path(id) { diff --git a/service-providers/network-requester/src/config/mod.rs b/service-providers/network-requester/src/config/mod.rs index 428d4d4e3c..dc3c8eb23b 100644 --- a/service-providers/network-requester/src/config/mod.rs +++ b/service-providers/network-requester/src/config/mod.rs @@ -1,6 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::config::persistence::NetworkRequesterPaths; use crate::config::template::CONFIG_TEMPLATE; use nym_bin_common::logging::LoggingSettings; use nym_config::{ @@ -14,12 +15,12 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Duration; -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; +pub mod old_config_v1_1_20_2; mod persistence; mod template; diff --git a/service-providers/network-requester/src/config/old_config_v1_1_19.rs b/service-providers/network-requester/src/config/old_config_v1_1_19.rs new file mode 100644 index 0000000000..5da0601669 --- /dev/null +++ b/service-providers/network-requester/src/config/old_config_v1_1_19.rs @@ -0,0 +1,120 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::old_config_v1_1_20::{ + ConfigV1_1_20, DebugV1_1_20, NetworkRequesterPathsV1_1_20, +}; +use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths; +use nym_client_core::config::disk_persistence::old_v1_1_20::CommonClientPathsV1_1_20; +use nym_client_core::config::old_config_v1_1_19::ConfigV1_1_19 as BaseConfigV1_1_19; +use nym_client_core::config::old_config_v1_1_20::{ + ClientV1_1_20, ConfigV1_1_20 as BaseClientConfigV1_1_20, +}; +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_19 { + #[serde(flatten)] + pub base: BaseConfigV1_1_19, + + #[serde(default)] + pub network_requester: NetworkRequster, + + #[serde(default)] + pub network_requester_debug: DebugV1_1_19, +} + +impl From for ConfigV1_1_20 { + fn from(value: ConfigV1_1_19) -> Self { + ConfigV1_1_20 { + base: BaseClientConfigV1_1_20 { + client: ClientV1_1_20 { + 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: NetworkRequesterPathsV1_1_20 { + common_paths: CommonClientPathsV1_1_20 { + 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_19 { + 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_19 { + #[serde(with = "humantime_serde")] + pub standard_list_update_interval: Duration, +} + +impl From for DebugV1_1_20 { + fn from(value: DebugV1_1_19) -> Self { + DebugV1_1_20 { + standard_list_update_interval: value.standard_list_update_interval, + } + } +} + +impl Default for DebugV1_1_19 { + fn default() -> Self { + DebugV1_1_19 { + standard_list_update_interval: DEFAULT_STANDARD_LIST_UPDATE_INTERVAL, + } + } +} 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 index 412fb71133..71b77a8f49 100644 --- 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 @@ -1,12 +1,15 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::persistence::NetworkRequesterPaths; -use crate::config::{BaseClientConfig, Config, Debug}; +use crate::config::old_config_v1_1_20_2::{ + ConfigV1_1_20_2, DebugV1_1_20_2, NetworkRequesterPathsV1_1_20_2, +}; use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths; -use nym_client_core::config::disk_persistence::CommonClientPaths; +use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2; 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_client_core::config::old_config_v1_1_20_2::{ + ClientV1_1_20_2, ConfigV1_1_20_2 as BaseClientConfigV1_1_20_2, +}; use nym_config::legacy_helpers::nym_config::MigrationNymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -27,11 +30,11 @@ pub struct ConfigV1_1_20 { pub network_requester_debug: DebugV1_1_20, } -impl From for Config { +impl From for ConfigV1_1_20_2 { fn from(value: ConfigV1_1_20) -> Self { - Config { - base: BaseClientConfig { - client: Client { + ConfigV1_1_20_2 { + base: BaseClientConfigV1_1_20_2 { + client: ClientV1_1_20_2 { version: value.base.client.version, id: value.base.client.id, disabled_credentials_mode: value.base.client.disabled_credentials_mode, @@ -42,8 +45,8 @@ impl From for Config { debug: Default::default(), }, network_requester: Default::default(), - storage_paths: NetworkRequesterPaths { - common_paths: CommonClientPaths { + storage_paths: NetworkRequesterPathsV1_1_20_2 { + common_paths: CommonClientPathsV1_1_20_2 { keys: ClientKeysPaths { private_identity_key_file: value.base.client.private_identity_key_file, public_identity_key_file: value.base.client.public_identity_key_file, @@ -100,9 +103,9 @@ pub struct DebugV1_1_20 { pub standard_list_update_interval: Duration, } -impl From for Debug { +impl From for DebugV1_1_20_2 { fn from(value: DebugV1_1_20) -> Self { - Debug { + DebugV1_1_20_2 { standard_list_update_interval: value.standard_list_update_interval, } } diff --git a/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs b/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs new file mode 100644 index 0000000000..92defcd702 --- /dev/null +++ b/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs @@ -0,0 +1,108 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::NetworkRequesterPaths; +use crate::config::{default_config_filepath, Config, Debug, NetworkRequester}; +use nym_bin_common::logging::LoggingSettings; +use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2; +use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseClientConfigV1_1_20_2; +use nym_client_core::config::GatewayEndpointConfig; +use nym_config::read_config_from_toml_file; +use serde::{Deserialize, Serialize}; +use std::io; +use std::path::{Path, PathBuf}; +use std::time::Duration; + +pub const DEFAULT_STANDARD_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30 * 60); + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)] +pub struct NetworkRequesterPathsV1_1_20_2 { + #[serde(flatten)] + pub common_paths: CommonClientPathsV1_1_20_2, + + /// 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, +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_20_2 { + #[serde(flatten)] + pub base: BaseClientConfigV1_1_20_2, + + #[serde(default)] + pub network_requester: NetworkRequesterV1_1_20_2, + + pub storage_paths: NetworkRequesterPathsV1_1_20_2, + + #[serde(default)] + pub network_requester_debug: DebugV1_1_20_2, + + pub logging: LoggingSettings, +} + +impl ConfigV1_1_20_2 { + 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)) + } + + // in this upgrade, gateway endpoint configuration was moved out of the config file, + // so its returned to be stored elsewhere. + pub fn upgrade(self) -> (Config, GatewayEndpointConfig) { + let gateway_details = self.base.client.gateway_endpoint.clone().into(); + let config = Config { + base: self.base.into(), + storage_paths: NetworkRequesterPaths { + common_paths: self.storage_paths.common_paths.upgrade_default(), + allowed_list_location: self.storage_paths.allowed_list_location, + unknown_list_location: self.storage_paths.unknown_list_location, + }, + network_requester_debug: self.network_requester_debug.into(), + logging: self.logging, + network_requester: self.network_requester.into(), + }; + + (config, gateway_details) + } +} + +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct NetworkRequesterV1_1_20_2 {} + +impl From for NetworkRequester { + fn from(_value: NetworkRequesterV1_1_20_2) -> Self { + NetworkRequester {} + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct DebugV1_1_20_2 { + /// Defines how often the standard allow list should get updated + #[serde(with = "humantime_serde")] + pub standard_list_update_interval: Duration, +} + +impl From for Debug { + fn from(value: DebugV1_1_20_2) -> Self { + Debug { + standard_list_update_interval: value.standard_list_update_interval, + } + } +} + +impl Default for DebugV1_1_20_2 { + fn default() -> Self { + DebugV1_1_20_2 { + standard_list_update_interval: DEFAULT_STANDARD_LIST_UPDATE_INTERVAL, + } + } +} diff --git a/service-providers/network-requester/src/config/template.rs b/service-providers/network-requester/src/config/template.rs index 628a521f9d..34df005821 100644 --- a/service-providers/network-requester/src/config/template.rs +++ b/service-providers/network-requester/src/config/template.rs @@ -62,6 +62,10 @@ 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 file containing information about gateway used by this client, +# i.e. details such as its public key, owner address or the network information. +gateway_details = '{{ storage_paths.gateway_details }}' + # Path to the persistent store for received reply surbs, unused encryption keys and used sender tags. reply_surb_database = '{{ storage_paths.reply_surb_database }}' @@ -72,17 +76,6 @@ allowed_list_location = '{{ storage_paths.allowed_list_location }}' 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 }}' - -# Address of the gateway owner to which the client should send messages. -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 }}' - ##### logging configuration options ##### [logging] diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs index f460f188ff..a53b620f33 100644 --- a/service-providers/network-requester/src/core.rs +++ b/service-providers/network-requester/src/core.rs @@ -513,8 +513,7 @@ async fn create_mixnet_client( .await .map_err(|err| NetworkRequesterError::FailedToSetupMixnetClient { source: err })? .network_details(NymNetworkDetails::new_from_env()) - .debug_config(debug_config) - .registered_gateway(config.get_gateway_endpoint_config().clone()); + .debug_config(debug_config); if !config.get_disabled_credentials_mode() { client_builder = client_builder.enable_credentials_mode(); } diff --git a/service-providers/network-requester/src/error.rs b/service-providers/network-requester/src/error.rs index 2a6bd4d5d6..9248273d4b 100644 --- a/service-providers/network-requester/src/error.rs +++ b/service-providers/network-requester/src/error.rs @@ -15,9 +15,6 @@ pub enum NetworkRequesterError { source: Socks5RequestError, }, - #[error("failed to setup gateway: {source}")] - FailedToSetupGateway { source: ClientCoreError }, - #[error("failed to load configuration file: {0}")] FailedToLoadConfig(String),