From eecd685c7722fb9a2b9fec8cd2f8a0abfaf2ab68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Mar 2022 15:18:43 +0100 Subject: [PATCH] wallet: simplify connection test and set nymd_url and api_url independently --- nym-wallet/Cargo.lock | 1 + nym-wallet/src-tauri/Cargo.toml | 1 + nym-wallet/src-tauri/src/config/mod.rs | 23 ++- .../src/operations/mixnet/account.rs | 148 ++++-------------- nym-wallet/src-tauri/src/state.rs | 4 +- 5 files changed, 53 insertions(+), 124 deletions(-) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 967422946e..2cbbf6bbb0 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -2851,6 +2851,7 @@ dependencies = [ "dirs", "eyre", "futures", + "itertools", "log", "mixnet-contract-common", "pretty_env_logger", diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index a4d76edaf0..f3250d3d82 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -23,6 +23,7 @@ bip39 = "1.0" dirs = "4.0" eyre = "0.6.5" futures = "0.3.15" +itertools = "0.10" log = "0.4" pretty_env_logger = "0.4" rand = "0.6.5" diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index ed93d1da46..f2255d0835 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -4,6 +4,7 @@ use crate::{error::BackendError, network::Network as WalletNetwork}; use config::defaults::{all::SupportedNetworks, ValidatorDetails}; use config::NymConfig; +use itertools::Itertools; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -115,8 +116,10 @@ impl Config { .chain(self.network.validators(network)) .cloned() .chain(base_validators) + .unique() } + #[allow(unused)] pub fn get_validators_with_api_endpoint( &self, network: WalletNetwork, @@ -164,6 +167,10 @@ impl Config { let client = reqwest::Client::builder() .timeout(Duration::from_secs(3)) .build()?; + log::debug!( + "Fetching validator urls from: {}", + REMOTE_SOURCE_OF_VALIDATOR_URLS + ); let response = client .get(REMOTE_SOURCE_OF_VALIDATOR_URLS.to_string()) .send() @@ -183,6 +190,7 @@ impl Config { let validator_urls = validators_to_query().map(|v| { let mut health_url = v.nymd_url.clone(); health_url.set_path("health"); + log::debug!("Checking health of: {health_url}"); (v, health_url) }); @@ -193,13 +201,18 @@ impl Config { let requests = validator_urls.map(|(_, url)| client.get(url).send()); let responses = futures::future::join_all(requests).await; - let validators_responding_success = - zip(validators_to_query(), responses).filter_map(|(v, r)| match r { + let validators_responding_success = zip(validators_to_query(), responses) + .filter_map(|(v, r)| match r { Ok(r) if r.status().is_success() => Some((v, r.status())), _ => None, - }); + }) + .collect::>(); - Ok(validators_responding_success.collect::>()) + for validator in &validators_responding_success { + log::debug!("Returned success: {}", validator.0.nymd_url); + } + + Ok(validators_responding_success) } #[allow(unused)] @@ -223,7 +236,7 @@ impl Config { } } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq)] pub struct ValidatorUrl { pub nymd_url: Url, pub api_url: Option, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index e369f9c976..af28500d5e 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -1,20 +1,21 @@ use crate::coin::{Coin, Denom}; -use crate::config::{Config, ValidatorUrlWithApiEndpoint}; +use crate::config::{Config, ValidatorUrl}; use crate::error::BackendError; use crate::network::Network; use crate::nymd_client; use crate::state::State; use bip39::{Language, Mnemonic}; +use rand::seq::SliceRandom; +use reqwest::StatusCode; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::convert::TryInto; use std::str::FromStr; use std::sync::Arc; -use std::time::Duration; use strum::IntoEnumIterator; use tokio::sync::RwLock; -use validator_client::nymd::error::NymdError; +use url::Url; use validator_client::nymd::SigningNymdClient; use validator_client::Client; @@ -101,7 +102,7 @@ pub async fn create_new_account( } #[tauri::command] -pub async fn create_new_mnemonic() -> Result { +pub fn create_new_mnemonic() -> Result { let rand_mnemonic = random_mnemonic(); Ok(rand_mnemonic.to_string()) } @@ -155,9 +156,9 @@ async fn _connect_with_mnemonic( state: tauri::State<'_, Arc>>, ) -> Result { update_validator_urls(state.clone()).await?; - let validators = choose_validators(mnemonic.clone(), &state).await?; - let config = state.read().await.config(); + let config = state.read().await.config().clone(); + let validators = config.check_validator_health_for_all_networks().await?; let clients = create_clients(&validators, &mnemonic, &config)?; // Set the default account @@ -187,17 +188,22 @@ async fn _connect_with_mnemonic( } fn create_clients( - validators: &HashMap, + validators: &HashMap>, mnemonic: &Mnemonic, config: &Config, ) -> Result>, BackendError> { let mut clients = Vec::new(); for network in Network::iter() { + let nymd_url = select_validator_nymd_url(network, validators)?; + let api_url = select_validator_api_url(network, validators)?; + log::info!("{network}: nymd_url: connecting to {nymd_url}"); + log::info!("{network}: api_url: connecting to {api_url}"); + let client = validator_client::Client::new_signing( validator_client::Config::new( network.into(), - validators[&network].nymd_url.clone(), - validators[&network].api_url.clone(), + nymd_url, + api_url, config.get_mixnet_contract_address(network), config.get_vesting_contract_address(network), config.get_bandwidth_claim_contract_address(network), @@ -209,116 +215,24 @@ fn create_clients( Ok(clients) } -async fn choose_validators( - mnemonic: Mnemonic, - state: &tauri::State<'_, Arc>>, -) -> Result, BackendError> { - let config = state.read().await.config(); - - // Try to connect to validators on all networks - let mut validators = select_responding_validators(&config, &mnemonic).await?; - - // If for a network we didn't manage to connect to any validators, just go ahead and try with the - // first in the list - for network in Network::iter() { - validators.entry(network).or_insert_with(|| { - let default_validator = config - .get_validators_with_api_endpoint(network) - .next() - // We always have at least one hardcoded default validator - .unwrap(); - println!( - "Using default for {network}: {}, {}", - default_validator.nymd_url, default_validator.api_url, - ); - default_validator - }); - } - Ok(validators) -} - -// For each network, try the list of available validators one by one and use the first responding -// one. -async fn select_responding_validators( - config: &Config, - mnemonic: &Mnemonic, -) -> Result, BackendError> { - use tokio::time::timeout; - let validators = futures::future::join_all(Network::iter().map(|network| { - timeout( - Duration::from_millis(3000), - try_connect_to_validators( - config.get_validators_with_api_endpoint(network), - config, - network, - mnemonic.clone(), - ), - ) - })) - .await; - - // Drop networks that failed the global timeout - let validators = validators.into_iter().filter_map(Result::ok); - - // Rewrap to return any errors during client creation - let validators = validators.collect::, _>>()?; - - // Filter out networks where we exhausted all listed validators - let validators = validators.into_iter().flatten(); - - Ok(validators.collect::>()) -} - -async fn try_connect_to_validators( - validators: impl Iterator, - config: &Config, +fn select_validator_nymd_url( network: Network, - mnemonic: Mnemonic, -) -> Result, BackendError> { - for validator in validators { - if let Some(responding_validator) = - try_connect_to_validator(&validator, config, network, mnemonic.clone()).await? - { - // Pick the first successful one - return Ok(Some(responding_validator)); - } - } - Ok(None) + validators: &HashMap>, +) -> Result { + // For the nymd url we pick one at random + validators[&network] + .choose(&mut rand::thread_rng()) + .map(|v| v.0.nymd_url.clone()) + .ok_or(BackendError::NoNymdValidatorConfigured) } -async fn try_connect_to_validator( - validator: &ValidatorUrlWithApiEndpoint, - config: &Config, +fn select_validator_api_url( network: Network, - mnemonic: Mnemonic, -) -> Result, BackendError> { - let client = validator_client::Client::new_signing( - validator_client::Config::new( - network.into(), - validator.nymd_url.clone(), - validator.api_url.clone(), - config.get_mixnet_contract_address(network), - config.get_vesting_contract_address(network), - config.get_bandwidth_claim_contract_address(network), - ), - mnemonic, - )?; - - if is_validator_connection_ok(&client).await { - println!( - "Connection ok for {network}: {}, {}", - validator.nymd_url, validator.api_url - ); - Ok(Some((network, validator.clone()))) - } else { - Ok(None) - } -} - -// The criteria used to determina if a validator endpoint is to be used -async fn is_validator_connection_ok(client: &Client) -> bool { - match client.get_mixnet_contract_version().await { - Err(NymdError::TendermintError(_)) => false, - Err(_) | Ok(_) => true, - } + validators: &HashMap>, +) -> Result { + // For the validator api we always just pick the first one + validators[&network] + .get(0) + .and_then(|v| v.0.api_url.clone()) + .ok_or(BackendError::NoValidatorApiUrlConfigured) } diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index 82da416be5..2c5d937218 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -28,8 +28,8 @@ impl State { .ok_or(BackendError::ClientNotInitialized) } - pub fn config(&self) -> Config { - self.config.clone() + pub fn config(&self) -> &Config { + &self.config } pub fn add_client(&mut self, network: Network, client: Client) {