diff --git a/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs b/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs index c9c196aad9..ed215d30af 100644 --- a/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs +++ b/sdk/rust/nym-sdk/examples/manually_handle_keys_and_config.rs @@ -10,7 +10,7 @@ async fn main() { let config = mixnet::Config::new(user_chosen_gateway_id, nym_api_endpoints); - let mut client = mixnet::Client::new(Some(config), None).unwrap(); + let mut client = mixnet::ClientBuilder::new(Some(config), None).unwrap(); // Just some plain data to pretend we have some external storage that the application // implementer is using. @@ -29,10 +29,10 @@ async fn main() { } // Connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet().await.unwrap(); + let client = client.connect_to_mixnet().await.unwrap(); // Be able to get our client address - println!("Our client address is {}", client.nym_address().unwrap()); + println!("Our client address is {}", client.nym_address()); // Send important info up the pipe to a buddy client.send_str("foo.bar@blah", "flappappa").await; diff --git a/sdk/rust/nym-sdk/examples/persist_keys.rs b/sdk/rust/nym-sdk/examples/persist_keys.rs index 32d97d1cc8..0e88293764 100644 --- a/sdk/rust/nym-sdk/examples/persist_keys.rs +++ b/sdk/rust/nym-sdk/examples/persist_keys.rs @@ -14,13 +14,13 @@ async fn main() { let keys = mixnet::StoragePaths::new_from_dir(mixnet::KeyMode::Keep, &config_dir); // Provide key paths for the client to read/write keys to. - let mut client = mixnet::Client::new(None, Some(keys)).unwrap(); + let client = mixnet::ClientBuilder::new(None, Some(keys)).unwrap(); // Connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet().await.unwrap(); + let mut client = client.connect_to_mixnet().await.unwrap(); // Be able to get our client address - let our_address = client.nym_address().unwrap(); + let our_address = client.nym_address(); println!("Our client nym address is: {our_address}"); // Send a message throught the mixnet to ourselves diff --git a/sdk/rust/nym-sdk/examples/simple.rs b/sdk/rust/nym-sdk/examples/simple.rs index 35a2af1fae..a61833cb0e 100644 --- a/sdk/rust/nym-sdk/examples/simple.rs +++ b/sdk/rust/nym-sdk/examples/simple.rs @@ -5,13 +5,10 @@ async fn main() { logging::setup_logging(); // Passing no config makes the client fire up an ephemeral session and figure shit out on its own - let mut client = mixnet::Client::new(None, None).unwrap(); - - // Connect to the mixnet, now we're listening for incoming - client.connect_to_mixnet().await.unwrap(); + let mut client = mixnet::Client::connect().await.unwrap(); // Be able to get our client address - let our_address = client.nym_address().unwrap(); + let our_address = client.nym_address(); println!("Our client nym address is: {our_address}"); // Send a message throught the mixnet to ourselves diff --git a/sdk/rust/nym-sdk/src/mixnet.rs b/sdk/rust/nym-sdk/src/mixnet.rs index a717b022ab..21d9369790 100644 --- a/sdk/rust/nym-sdk/src/mixnet.rs +++ b/sdk/rust/nym-sdk/src/mixnet.rs @@ -13,5 +13,5 @@ pub use nymsphinx::{ pub use keys::Keys; pub use paths::{GatewayKeyMode, KeyMode, StoragePaths}; -pub use client::Client; +pub use client::{Client, ClientBuilder}; pub use config::Config; diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 70b64ad9f2..bcda8f5603 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -1,27 +1,29 @@ use std::path::Path; +use client_connections::TransmissionLane; +use client_core::{ + client::{ + base_client::{ + non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState, + CredentialsToggle, + }, + inbound_messages::InputMessage, + key_manager::KeyManager, + received_buffer::ReconstructedMessagesReceiver, + }, + config::{persistence::key_pathfinder::ClientKeyPathfinder, GatewayEndpointConfig}, +}; use futures::StreamExt; use nymsphinx::{ addressing::clients::{ClientIdentity, Recipient}, receiver::ReconstructedMessage, }; -use tap::TapOptional; - -use client_connections::TransmissionLane; -use client_core::{ - client::{ - base_client::{non_wasm_helpers, BaseClientBuilder, CredentialsToggle}, - inbound_messages::InputMessage, - key_manager::KeyManager, - }, - config::{persistence::key_pathfinder::ClientKeyPathfinder, GatewayEndpointConfig}, -}; +use task::TaskManager; +use super::{connection_state::BuilderState, Config, GatewayKeyMode, Keys, StoragePaths}; use crate::error::{Error, Result}; -use super::{connection_state::ConnectionState, Config, GatewayKeyMode, Keys, StoragePaths}; - -pub struct Client { +pub struct ClientBuilder { /// Keys handled by the client key_manager: KeyManager, @@ -33,16 +35,35 @@ pub struct Client { /// The client can be in one of multiple states, depending on how it is created and if it's /// connected to the mixnet. - connection_state: ConnectionState, + state: BuilderState, } -impl Client { +pub struct Client { + nym_address: Recipient, + + /// Keys handled by the client + key_manager: KeyManager, + + client_input: ClientInput, + + #[allow(dead_code)] + client_output: ClientOutput, + + #[allow(dead_code)] + client_state: ClientState, + + reconstructed_receiver: ReconstructedMessagesReceiver, + + task_manager: TaskManager, +} + +impl ClientBuilder { /// Create a new mixnet client. If no config options are supplied, creates a new client with /// ephemeral keys stored in RAM, which will be discarded at application close. /// /// Callers have the option of supplying futher parameters to store persistent identities at a /// location on-disk, if desired. - pub fn new(config_option: Option, paths: Option) -> Result { + pub fn new(config_option: Option, paths: Option) -> Result { let config = config_option.unwrap_or_default(); // If we are provided paths to keys, use them if they are available. And if they are @@ -71,29 +92,18 @@ impl Client { client_core::init::new_client_keys() }; - Ok(Client { + Ok(Self { key_manager, config, storage_paths: paths, - connection_state: ConnectionState::New, + state: BuilderState::New, }) } - /// Get the client identity, which is the public key of the identity key pair. - pub fn identity(&self) -> ClientIdentity { - *self.key_manager.identity_keypair().public_key() - } - - /// Get the nym address for this client, if it is available. The nym address is composed of the - /// client identity, the client encryption key, and the gateway identity. - pub fn nym_address(&self) -> Option<&Recipient> { - self.connection_state.nym_address() - } - /// 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. - pub fn has_gateway_key(&self) -> bool { + fn has_gateway_key(&self) -> bool { self.key_manager.gateway_key_set() } @@ -110,12 +120,12 @@ impl Client { } pub fn get_gateway_endpoint(&self) -> Option<&GatewayEndpointConfig> { - self.connection_state.gateway_endpoint_config() + self.state.gateway_endpoint_config() } pub async fn register_with_gateway(&mut self) -> Result<()> { assert!( - matches!(self.connection_state, ConnectionState::New), + matches!(self.state, BuilderState::New), "can only setup gateway when in `New` connection state" ); @@ -126,11 +136,8 @@ impl Client { ) .await?; - let nym_address = client_core::init::get_client_address(&self.key_manager, &gateway_config); - - self.connection_state = ConnectionState::Registered { + self.state = BuilderState::Registered { gateway_endpoint_config: gateway_config, - nym_address, }; Ok(()) } @@ -163,21 +170,17 @@ impl Client { std::fs::read_to_string(gateway_endpoint_config_path) .map(|str| toml::from_str(&str))??; - let nym_address = - client_core::init::get_client_address(&self.key_manager, &gateway_endpoint_config); - - self.connection_state = ConnectionState::Registered { + self.state = BuilderState::Registered { gateway_endpoint_config, - nym_address, }; Ok(()) } /// Connects to the mixnet via the gateway in the client config - pub async fn connect_to_mixnet(&mut self) -> Result<()> { + pub async fn connect_to_mixnet(mut self) -> Result { // For some simple cases we can figure how to setup gateway without it having to have been // called in advance. - if matches!(self.connection_state, ConnectionState::New) { + if matches!(self.state, BuilderState::New) { if let Some(paths) = &self.storage_paths { let paths = paths.clone(); if self.has_gateway_key() { @@ -201,12 +204,12 @@ impl Client { // At this point we should be in a registered state, either at function entry or by the // above convenience logic. - assert!(matches!( - self.connection_state, - ConnectionState::Registered { .. } - )); + let BuilderState::Registered { gateway_endpoint_config } = self.state else { + todo!(); + }; - let gateway_config = self.connection_state.gateway_endpoint_config().unwrap(); + let nym_address = + client_core::init::get_client_address(&self.key_manager, &gateway_endpoint_config); // TODO: we currently don't support having a bandwidth controller let bandwidth_controller = None; @@ -216,7 +219,7 @@ impl Client { non_wasm_helpers::setup_empty_reply_surb_backend(&self.config.debug_config); let base_builder = BaseClientBuilder::new( - gateway_config, + &gateway_endpoint_config, &self.config.debug_config, self.key_manager.clone(), bandwidth_controller, @@ -225,8 +228,6 @@ impl Client { self.config.nym_api_endpoints.clone(), ); - let nym_address = base_builder.as_mix_recipient(); - let mut started_client = base_builder.start_base().await.unwrap(); let client_input = started_client.client_input.register_producer(); let mut client_output = started_client.client_output.register_consumer(); @@ -235,15 +236,33 @@ impl Client { // Register our receiver let reconstructed_receiver = client_output.register_receiver().unwrap(); - self.connection_state = ConnectionState::Connected { + Ok(Client { nym_address, + key_manager: self.key_manager, client_input, client_output, client_state, reconstructed_receiver, task_manager: started_client.task_manager, - }; - Ok(()) + }) + } +} + +impl Client { + pub async fn connect() -> Result { + let client = ClientBuilder::new(None, None)?; + client.connect_to_mixnet().await + } + + /// Get the client identity, which is the public key of the identity key pair. + pub fn identity(&self) -> ClientIdentity { + *self.key_manager.identity_keypair().public_key() + } + + /// Get the nym address for this client, if it is available. The nym address is composed of the + /// client identity, the client encryption key, and the gateway identity. + pub fn nym_address(&self) -> &Recipient { + &self.nym_address } /// Sends stringy data to the supplied Nym address @@ -256,25 +275,20 @@ impl Client { /// Sends bytes to the supplied Nym address pub async fn send_bytes(&self, address: &str, message: Vec) { log::debug!("send_bytes"); - let Some(client_input) = self.connection_state.client_input() else { - log::error!("Error: trying to send without being connected"); - return; - }; let lane = TransmissionLane::General; let recipient = Recipient::try_from_base58_string(address).unwrap(); let input_msg = InputMessage::new_regular(recipient, message, lane); - client_input.input_sender.send(input_msg).await.unwrap(); + self.client_input + .input_sender + .send(input_msg) + .await + .unwrap(); } /// Wait for messages from the mixnet pub async fn wait_for_messages(&mut self) -> Option> { - let receiver = self - .connection_state - .reconstructed_receiver() - .tap_none(|| log::error!("Error: trying to wait without being connected"))?; - - receiver.next().await + self.reconstructed_receiver.next().await } pub fn wait_for_messages_split(&mut self) -> Option { @@ -295,13 +309,7 @@ impl Client { /// Disconnect from the mixnet. Currently it is not supported to reconnect a disconnected /// client. pub async fn disconnect(&mut self) { - let Some(task_manager) = self.connection_state.task_manager() else { - log::error!("Trying to disconnect when not connected!"); - return; - }; - - task_manager.signal_shutdown().ok(); - task_manager.wait_for_shutdown().await; - self.connection_state = ConnectionState::Disconnected; + self.task_manager.signal_shutdown().ok(); + self.task_manager.wait_for_shutdown().await; } } diff --git a/sdk/rust/nym-sdk/src/mixnet/connection_state.rs b/sdk/rust/nym-sdk/src/mixnet/connection_state.rs index 5336143dc5..2988948a60 100644 --- a/sdk/rust/nym-sdk/src/mixnet/connection_state.rs +++ b/sdk/rust/nym-sdk/src/mixnet/connection_state.rs @@ -1,115 +1,21 @@ -use client_core::{ - client::{ - base_client::{ClientInput, ClientOutput, ClientState}, - received_buffer::ReconstructedMessagesReceiver, - }, - config::GatewayEndpointConfig, -}; -use nymsphinx::addressing::clients::Recipient; -use task::TaskManager; +use client_core::config::GatewayEndpointConfig; -/// States that the client connection can be in. Currently the states can only progress linearly -/// from New -> Registered -> Connected -> Disconnected. In the future it could be useful to be able -/// to re-connect a disconnected client. -// WIP(JON): consider adding inner types -#[allow(clippy::large_enum_variant)] -pub(super) enum ConnectionState { +#[derive(Debug)] +pub(super) enum BuilderState { New, Registered { - nym_address: Recipient, gateway_endpoint_config: GatewayEndpointConfig, }, - Connected { - nym_address: Recipient, - client_input: ClientInput, - #[allow(dead_code)] - client_output: ClientOutput, - #[allow(dead_code)] - client_state: ClientState, - reconstructed_receiver: ReconstructedMessagesReceiver, - task_manager: TaskManager, - }, - Disconnected, } -impl std::fmt::Debug for ConnectionState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::New => write!(f, "New"), - Self::Registered { - nym_address, - gateway_endpoint_config: gateway_config, - } => f - .debug_struct("Registered") - .field("nym_address", nym_address) - .field("gateway_config", gateway_config) - .finish(), - Self::Connected { - nym_address, - client_input: _, - client_output: _, - client_state: _, - reconstructed_receiver, - task_manager, - } => f - .debug_struct("Connected") - .field("nym_address", nym_address) - .field("reconstructed_receiver", reconstructed_receiver) - .field("task_manager", task_manager) - .finish(), - Self::Disconnected => write!(f, "Disconnected"), - } - } -} - -impl ConnectionState { - pub(super) fn client_input(&self) -> Option<&ClientInput> { - match self { - ConnectionState::New - | ConnectionState::Registered { .. } - | ConnectionState::Disconnected => None, - ConnectionState::Connected { client_input, .. } => Some(client_input), - } - } - - pub(super) fn reconstructed_receiver(&mut self) -> Option<&mut ReconstructedMessagesReceiver> { - match self { - ConnectionState::New - | ConnectionState::Registered { .. } - | ConnectionState::Disconnected => None, - ConnectionState::Connected { - reconstructed_receiver, - .. - } => Some(reconstructed_receiver), - } - } - +impl BuilderState { pub(super) fn gateway_endpoint_config(&self) -> Option<&GatewayEndpointConfig> { match self { - ConnectionState::New - | ConnectionState::Connected { .. } - | ConnectionState::Disconnected => None, - ConnectionState::Registered { - gateway_endpoint_config: gateway_config, + BuilderState::New => None, + BuilderState::Registered { + gateway_endpoint_config, .. - } => Some(gateway_config), - } - } - - pub(super) fn nym_address(&self) -> Option<&Recipient> { - match self { - ConnectionState::New | ConnectionState::Disconnected => None, - ConnectionState::Registered { nym_address, .. } - | ConnectionState::Connected { nym_address, .. } => Some(nym_address), - } - } - - pub(super) fn task_manager(&mut self) -> Option<&mut TaskManager> { - match self { - ConnectionState::New - | ConnectionState::Registered { .. } - | ConnectionState::Disconnected => None, - ConnectionState::Connected { task_manager, .. } => Some(task_manager), + } => Some(gateway_endpoint_config), } } }