diff --git a/Cargo.lock b/Cargo.lock index 6419e1e296..3494de31af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,7 +667,9 @@ dependencies = [ "rand 0.7.3", "serde", "sled", + "tap", "tempfile", + "thiserror", "tokio", "topology", "url", diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index c628d8bd44..ca11dfd240 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -14,6 +14,7 @@ log = "0.4" rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } sled = "0.34" +thiserror = "1.0.34" tokio = { version = "1.19.1", features = ["macros"] } url = { version ="2.2", features = ["serde"] } @@ -27,6 +28,7 @@ nymsphinx = { path = "../../common/nymsphinx" } pemstore = { path = "../../common/pemstore" } topology = { path = "../../common/topology" } validator-client = { path = "../../common/client-libs/validator-client" } +tap = "1.0.1" [dev-dependencies] tempfile = "3.1.0" diff --git a/clients/client-core/src/error.rs b/clients/client-core/src/error.rs new file mode 100644 index 0000000000..12e27fdd58 --- /dev/null +++ b/clients/client-core/src/error.rs @@ -0,0 +1,27 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crypto::asymmetric::identity::Ed25519RecoveryError; +use gateway_client::error::GatewayClientError; +use validator_client::ValidatorClientError; + +#[derive(thiserror::Error, Debug)] +pub enum ClientCoreError { + #[error("I/O error: {0}")] + IoError(#[from] std::io::Error), + #[error("Gateway client error: {0}")] + GatewayClientError(#[from] GatewayClientError), + #[error("Ed25519 error: {0}")] + Ed25519RecoveryError(#[from] Ed25519RecoveryError), + #[error("Validator client error: {0}")] + ValidatorClientError(#[from] ValidatorClientError), + + #[error("No gateway with id: {0}")] + NoGatewayWithId(String), + #[error("No gateways on network")] + NoGatewaysOnNetwork, + #[error("List of validator apis is empty")] + ListOfValidatorApisIsEmpty, + #[error("Could not load existing gateway configuration: {0}")] + CouldNotLoadExistingGatewayConfiguration(std::io::Error), +} diff --git a/clients/client-core/src/init.rs b/clients/client-core/src/init.rs index 16921cd3f5..66ae845b04 100644 --- a/clients/client-core/src/init.rs +++ b/clients/client-core/src/init.rs @@ -14,25 +14,27 @@ use nymsphinx::addressing::nodes::NodeIdentity; use rand::rngs::OsRng; use rand::seq::SliceRandom; use rand::thread_rng; +use tap::TapFallible; use topology::{filter::VersionFilterable, gateway}; use url::Url; use crate::{ client::key_manager::KeyManager, config::{persistence::key_pathfinder::ClientKeyPathfinder, Config}, + error::ClientCoreError, }; pub async fn query_gateway_details( validator_servers: Vec, chosen_gateway_id: Option<&str>, -) -> gateway::Node { +) -> Result { let validator_api = validator_servers .choose(&mut thread_rng()) - .expect("The list of validator apis is empty"); + .ok_or(ClientCoreError::ListOfValidatorApisIsEmpty)?; let validator_client = validator_client::ApiClient::new(validator_api.clone()); log::trace!("Fetching list of gateways from: {}", validator_api); - let gateways = validator_client.get_cached_gateways().await.unwrap(); + let gateways = validator_client.get_cached_gateways().await?; let valid_gateways = gateways .into_iter() .filter_map(|gateway| gateway.try_into().ok()) @@ -47,38 +49,40 @@ pub async fn query_gateway_details( filtered_gateways .iter() .find(|gateway| gateway.identity_key.to_base58_string() == gateway_id) - .expect(&*format!("no gateway with id {} exists!", gateway_id)) - .clone() + .ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_id.to_string())) + .cloned() } else { filtered_gateways .choose(&mut rand::thread_rng()) - .expect("there are no gateways on the network!") - .clone() + .ok_or(ClientCoreError::NoGatewaysOnNetwork) + .cloned() } } pub async fn register_with_gateway_and_store_keys( gateway_details: gateway::Node, config: &Config, -) where +) -> Result<(), ClientCoreError> +where T: NymConfig, { let mut rng = OsRng; let mut key_manager = KeyManager::new(&mut rng); - let shared_keys = register_with_gateway(&gateway_details, key_manager.identity_keypair()).await; + let shared_keys = + register_with_gateway(&gateway_details, key_manager.identity_keypair()).await?; key_manager.insert_gateway_shared_key(shared_keys); let pathfinder = ClientKeyPathfinder::new_from_config(config); - key_manager + Ok(key_manager .store_keys(&pathfinder) - .expect("Failed to generated keys"); + .tap_err(|err| log::error!("Failed to generate keys: {err}"))?) } async fn register_with_gateway( gateway: &gateway::Node, our_identity: Arc, -) -> Arc { +) -> Result, ClientCoreError> { let timeout = Duration::from_millis(1500); let mut gateway_client = GatewayClient::new_init( gateway.clients_address(), @@ -90,48 +94,54 @@ async fn register_with_gateway( gateway_client .establish_connection() .await - .expect("failed to establish connection with the gateway!"); - gateway_client + .tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?; + let shared_keys = gateway_client .perform_initial_authentication() .await - .expect("failed to register with the gateway!") + .tap_err(|_| log::warn!("Failed to register with the gateway!"))?; + Ok(shared_keys) } -pub fn show_address(config: &Config) +pub fn show_address(config: &Config) -> Result<(), ClientCoreError> where T: config::NymConfig, { - fn load_identity_keys(pathfinder: &ClientKeyPathfinder) -> identity::KeyPair { + fn load_identity_keys( + pathfinder: &ClientKeyPathfinder, + ) -> Result { let identity_keypair: identity::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new( pathfinder.private_identity_key().to_owned(), pathfinder.public_identity_key().to_owned(), )) - .expect("Failed to read stored identity key files"); - identity_keypair + .tap_err(|_| log::error!("Failed to read stored identity key files"))?; + Ok(identity_keypair) } - fn load_sphinx_keys(pathfinder: &ClientKeyPathfinder) -> encryption::KeyPair { + fn load_sphinx_keys( + pathfinder: &ClientKeyPathfinder, + ) -> Result { let sphinx_keypair: encryption::KeyPair = pemstore::load_keypair(&pemstore::KeyPairPath::new( pathfinder.private_encryption_key().to_owned(), pathfinder.public_encryption_key().to_owned(), )) - .expect("Failed to read stored sphinx key files"); - sphinx_keypair + .tap_err(|_| log::error!("Failed to read stored sphinx key files"))?; + Ok(sphinx_keypair) } let pathfinder = ClientKeyPathfinder::new_from_config(config); - let identity_keypair = load_identity_keys(&pathfinder); - let sphinx_keypair = load_sphinx_keys(&pathfinder); + let identity_keypair = load_identity_keys(&pathfinder)?; + let sphinx_keypair = load_sphinx_keys(&pathfinder)?; let client_recipient = Recipient::new( *identity_keypair.public_key(), *sphinx_keypair.public_key(), // TODO: below only works under assumption that gateway address == gateway id // (which currently is true) - NodeIdentity::from_base58_string(config.get_gateway_id()).unwrap(), + NodeIdentity::from_base58_string(config.get_gateway_id())?, ); println!("\nThe address of this client is: {}", client_recipient); + Ok(()) } diff --git a/clients/client-core/src/lib.rs b/clients/client-core/src/lib.rs index 6bbed6b087..e36cea9276 100644 --- a/clients/client-core/src/lib.rs +++ b/clients/client-core/src/lib.rs @@ -1,3 +1,4 @@ pub mod client; pub mod config; +pub mod error; pub mod init; diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index 55a57a935b..8052ceeac7 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use clap::Args; -use client_core::config::GatewayEndpoint; +use client_core::{config::GatewayEndpoint, error::ClientCoreError}; use config::NymConfig; use crate::{ @@ -120,7 +120,12 @@ pub(crate) async fn execute(args: &Init) { let override_config_fields = OverrideConfig::from(args.clone()); config = override_config(config, override_config_fields); - let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await; + let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config) + .await + .unwrap_or_else(|err| { + eprintln!("Failed to setup gateway\nError: {err}"); + std::process::exit(1) + }); config.get_base_mut().with_gateway_endpoint(gateway); let config_save_location = config.get_config_file_save_location(); @@ -138,7 +143,10 @@ pub(crate) async fn execute(args: &Init) { ); println!("Client configuration completed."); - client_core::init::show_address(config.get_base()); + client_core::init::show_address(config.get_base()).unwrap_or_else(|err| { + eprintln!("Failed to show address\nError: {err}"); + std::process::exit(1) + }); } async fn setup_gateway( @@ -146,7 +154,7 @@ async fn setup_gateway( register: bool, user_chosen_gateway_id: Option<&str>, config: &Config, -) -> GatewayEndpoint { +) -> Result { if register { // Get the gateway details by querying the validator-api. Either pick one at random or use // the chosen one if it's among the available ones. @@ -155,16 +163,16 @@ async fn setup_gateway( config.get_base().get_validator_api_endpoints(), user_chosen_gateway_id, ) - .await; + .await?; log::debug!("Querying gateway gives: {}", gateway); // Registering with gateway by setting up and writing shared keys to disk log::trace!("Registering gateway"); client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base()) - .await; + .await?; println!("Saved all generated keys"); - gateway.into() + Ok(gateway.into()) } else if user_chosen_gateway_id.is_some() { // Just set the config, don't register or create any keys // This assumes that the user knows what they are doing, and that the existing keys are @@ -174,22 +182,22 @@ async fn setup_gateway( config.get_base().get_validator_api_endpoints(), user_chosen_gateway_id, ) - .await; + .await?; log::debug!("Querying gateway gives: {}", gateway); - gateway.into() + Ok(gateway.into()) } else { println!("Not registering gateway, will reuse existing config and keys"); - match Config::load_from_file(Some(id)) { - Ok(existing_config) => existing_config.get_base().get_gateway_endpoint().clone(), - Err(err) => { - panic!( - "Unable to configure gateway: {err}. \n - Seems like the client was already initialized but it was not possible to read \ - the existing configuration file. \n - CAUTION: Consider backing up your gateway keys and try force gateway registration, or \ - removing the existing configuration and starting over." - ) - } - } + let existing_config = Config::load_from_file(Some(id)).map_err(|err| { + log::error!( + "Unable to configure gateway: {err}. \n + Seems like the client was already initialized but it was not possible to read \ + the existing configuration file. \n + CAUTION: Consider backing up your gateway keys and try force gateway registration, or \ + removing the existing configuration and starting over." + ); + ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err) + })?; + + Ok(existing_config.get_base().get_gateway_endpoint().clone()) } } diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 7d4f731d0d..bb2135fa9f 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use clap::Args; -use client_core::config::GatewayEndpoint; +use client_core::{config::GatewayEndpoint, error::ClientCoreError}; use config::NymConfig; use crate::{ @@ -120,7 +120,12 @@ pub(crate) async fn execute(args: &Init) { let override_config_fields = OverrideConfig::from(args.clone()); config = override_config(config, override_config_fields); - let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await; + let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config) + .await + .unwrap_or_else(|err| { + eprintln!("Failed to setup gateway\nError: {err}"); + std::process::exit(1) + }); config.get_base_mut().with_gateway_endpoint(gateway); let config_save_location = config.get_config_file_save_location(); @@ -138,7 +143,10 @@ pub(crate) async fn execute(args: &Init) { ); println!("Client configuration completed."); - client_core::init::show_address(config.get_base()); + client_core::init::show_address(config.get_base()).unwrap_or_else(|err| { + eprintln!("Failed to show address\nError: {err}"); + std::process::exit(1) + }); } async fn setup_gateway( @@ -146,7 +154,7 @@ async fn setup_gateway( register: bool, user_chosen_gateway_id: Option<&str>, config: &Config, -) -> GatewayEndpoint { +) -> Result { if register { // Get the gateway details by querying the validator-api. Either pick one at random or use // the chosen one if it's among the available ones. @@ -155,16 +163,16 @@ async fn setup_gateway( config.get_base().get_validator_api_endpoints(), user_chosen_gateway_id, ) - .await; + .await?; log::debug!("Querying gateway gives: {}", gateway); // Registering with gateway by setting up and writing shared keys to disk log::trace!("Registering gateway"); client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base()) - .await; + .await?; println!("Saved all generated keys"); - gateway.into() + Ok(gateway.into()) } else if user_chosen_gateway_id.is_some() { // Just set the config, don't register or create any keys // This assumes that the user knows what they are doing, and that the existing keys are @@ -174,22 +182,21 @@ async fn setup_gateway( config.get_base().get_validator_api_endpoints(), user_chosen_gateway_id, ) - .await; + .await?; log::debug!("Querying gateway gives: {}", gateway); - gateway.into() + Ok(gateway.into()) } else { println!("Not registering gateway, will reuse existing config and keys"); - match Config::load_from_file(Some(id)) { - Ok(existing_config) => existing_config.get_base().get_gateway_endpoint().clone(), - Err(err) => { - panic!( - "Unable to configure gateway: {err}. \n - Seems like the client was already initialized but it was not possible to read \ - the existing configuration file. \n - CAUTION: Consider backing up your gateway keys and try force gateway registration, or \ - removing the existing configuration and starting over." - ) - } - } + let existing_config = Config::load_from_file(Some(id)).map_err(|err| { + log::error!( + "Unable to configure gateway: {err}. \n + Seems like the client was already initialized but it was not possible to read \ + the existing configuration file. \n + CAUTION: Consider backing up your gateway keys and try force gateway registration, or \ + removing the existing configuration and starting over." + ); + ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err) + })?; + Ok(existing_config.get_base().get_gateway_endpoint().clone()) } } diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 1270461520..30857c6fd8 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -144,7 +144,7 @@ pub fn execute( } /// Update locked_pledge_cap, the hard cap for staking/bonding with unvested tokens. -/// +/// /// Callable by ADMIN only, see [instantiate]. pub fn try_update_locked_pledge_cap( amount: Uint128, @@ -169,7 +169,7 @@ pub fn try_update_mixnode_config( } /// Updates mixnet contract address, for cases when a new mixnet contract is deployed. -/// +/// /// Callable by ADMIN only, see [instantiate]. pub fn try_update_mixnet_address( address: String, @@ -449,7 +449,7 @@ fn try_undelegate_from_mixnode( } /// Creates a new periodic vesting account, and deposits funds to vest into the contract. -/// +/// /// Callable by ADMIN only, see [instantiate]. fn try_create_periodic_vesting_account( owner_address: &str, diff --git a/nym-connect/Cargo.lock b/nym-connect/Cargo.lock index d6bf186f83..008d909765 100644 --- a/nym-connect/Cargo.lock +++ b/nym-connect/Cargo.lock @@ -629,6 +629,8 @@ dependencies = [ "rand 0.7.3", "serde", "sled", + "tap", + "thiserror", "tokio", "topology", "url", @@ -5944,18 +5946,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" dependencies = [ "proc-macro2", "quote", diff --git a/nym-connect/src-tauri/src/config/mod.rs b/nym-connect/src-tauri/src/config/mod.rs index 92556c29e0..ee4cb690fb 100644 --- a/nym-connect/src-tauri/src/config/mod.rs +++ b/nym-connect/src-tauri/src/config/mod.rs @@ -172,7 +172,7 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str ); log::info!("Client configuration completed."); - client_core::init::show_address(config.get_base()); + client_core::init::show_address(config.get_base())?; Ok(()) } @@ -191,13 +191,13 @@ async fn setup_gateway( config.get_base().get_validator_api_endpoints(), user_chosen_gateway_id, ) - .await; + .await?; log::debug!("Querying gateway gives: {}", gateway); // Registering with gateway by setting up and writing shared keys to disk log::trace!("Registering gateway"); client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base()) - .await; + .await?; println!("Saved all generated keys"); Ok(gateway.into()) @@ -210,23 +210,21 @@ async fn setup_gateway( config.get_base().get_validator_api_endpoints(), user_chosen_gateway_id, ) - .await; + .await?; log::debug!("Querying gateway gives: {}", gateway); Ok(gateway.into()) } else { println!("Not registering gateway, will reuse existing config and keys"); - match Socks5Config::load_from_file(Some(id)) { - Ok(existing_config) => Ok(existing_config.get_base().get_gateway_endpoint().clone()), - Err(err) => { - log::error!( - "Unable to configure gateway: {err}. \n - Seems like the client was already initialized but it was not possible to read \ - the existing configuration file. \n - CAUTION: Consider backing up your gateway keys and try force gateway registration, or \ - removing the existing configuration and starting over." - ); - Err(BackendError::CouldNotLoadExistingGatewayConfiguration(err)) - } - } + let existing_config = Socks5Config::load_from_file(Some(id)).map_err(|err| { + log::error!( + "Unable to configure gateway: {err}. \n + Seems like the client was already initialized but it was not possible to read \ + the existing configuration file. \n + CAUTION: Consider backing up your gateway keys and try force gateway registration, or \ + removing the existing configuration and starting over." + ); + BackendError::CouldNotLoadExistingGatewayConfiguration(err) + })?; + Ok(existing_config.get_base().get_gateway_endpoint().clone()) } } diff --git a/nym-connect/src-tauri/src/error.rs b/nym-connect/src-tauri/src/error.rs index 27a10d58e3..9ffaba93a6 100644 --- a/nym-connect/src-tauri/src/error.rs +++ b/nym-connect/src-tauri/src/error.rs @@ -1,3 +1,4 @@ +use client_core::error::ClientCoreError; use serde::{Serialize, Serializer}; use thiserror::Error; @@ -29,6 +30,11 @@ pub enum BackendError { #[from] source: serde_json::Error, }, + #[error("{source}")] + ClientCoreError { + #[from] + source: ClientCoreError, + }, #[error("State error")] StateError,