diff --git a/nym-connect/desktop/src-tauri/src/models/mod.rs b/nym-connect/desktop/src-tauri/src/models/mod.rs index 1b9ed11e71..92b8512ec5 100644 --- a/nym-connect/desktop/src-tauri/src/models/mod.rs +++ b/nym-connect/desktop/src-tauri/src/models/mod.rs @@ -1,4 +1,4 @@ -use core::fmt; +use std::fmt; use serde::{Deserialize, Serialize}; @@ -115,3 +115,9 @@ pub struct PagedResult { pub struct Gateway { pub identity: String, } + +impl fmt::Display for Gateway { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Gateway({})", self.identity) + } +} diff --git a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs index 2f63fcf8a2..cb8e7e577c 100644 --- a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs @@ -15,6 +15,7 @@ use nym_topology::gateway; use nym_validator_client::nym_api::Client as ApiClient; use std::str::FromStr; use std::sync::Arc; +use tap::TapFallible; use tokio::sync::RwLock; use url::Url; @@ -43,7 +44,6 @@ pub async fn get_services( log::trace!("Fetching services"); let all_services_with_category = fetch_services(&privacy_level).await?; - log::trace!("Received: {:#?}", all_services_with_category); // Flatten all services into a single vector (get rid of categories) // We currently don't care about categories, but we might in the future... @@ -51,34 +51,47 @@ pub async fn get_services( .into_iter() .flat_map(|sp| sp.items) .collect_vec(); + log::debug!("Received {} services", all_services.len()); + log::trace!("Received: {:#?}", all_services); // Early return if we're running with medium toggle enabled if let PrivacyLevel::Medium = privacy_level { return Ok(all_services); } + // If there is a failure getting the active services, just return all of them // TODO: get paged log::trace!("Fetching active services"); - let active_services = fetch_active_services().await?; + let Ok(active_services) = query_active_services().await else { + log::warn!("Using all services instead as fallback"); + return Ok(all_services); + }; + log::debug!( + "Received {} active services from harbourmaster", + active_services.items.len() + ); log::trace!("Active: {:#?}", active_services); - if active_services.items.is_empty() { - log::warn!("No active services found! Using all services instead as fallback"); - return Ok(all_services); - } - - log::trace!("Filter out inactive"); + // From the list of all services, filter out the ones that are inactive + log::trace!("Filter out inactive and low performance"); let filtered_services = filter_out_inactive_services(&all_services, active_services); - log::trace!("After filtering: {:#?}", filtered_services); - if let Ok(services) = filtered_services { - Ok(services) - } else { - log::warn!( - "After filtering, no active services found! Using all services instead as fallback" - ); - Ok(all_services) - } + // If there is a failure filtering out inactive services, just return all of them + filtered_services + .tap_ok(|services| { + log::debug!( + "After filtering out inactive and low performance: {}", + services.len() + ); + log::trace!("After filtering: {:#?}", services); + }) + .or_else(|_| { + // If for some reason harbourmaster is done, we want things to still sort of work. + log::warn!( + "After filtering, no active services found! Using all services instead as fallback" + ); + Ok(all_services) + }) } async fn fetch_services(privacy_level: &PrivacyLevel) -> Result> { @@ -101,7 +114,7 @@ async fn fetch_services(privacy_level: &PrivacyLevel) -> Result Result> { +async fn query_active_services() -> Result> { let active_services = reqwest::get(HARBOUR_MASTER_URL) .await? .json::>() @@ -145,7 +158,7 @@ async fn fetch_all_gateways() -> Result> { } } -async fn fetch_compatible_gateways() -> Result> { +async fn fetch_only_compatible_gateways() -> Result> { let gateways = fetch_all_gateways().await?; let our_version = env!("CARGO_PKG_VERSION"); log::debug!( @@ -168,18 +181,35 @@ async fn fetch_compatible_gateways() -> Result> { fn filter_out_low_performance_gateways( gateways: Vec, ) -> Result> { - let gateways: Vec<_> = gateways - .into_iter() + let mut filtered_gateways: Vec<_> = gateways + .iter() .filter(|g| { g.node_performance.most_recent > Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap() }) + .cloned() .collect(); - if gateways.is_empty() { + + // Sometimes the most_recent is zero for all gateways (bug in nym-api?) + if filtered_gateways.is_empty() { + log::warn!( + "No gateways with recent performance score above threshold found! Using \ + last hour performance scores instead as fallback" + ); + filtered_gateways = gateways + .into_iter() + .filter(|g| { + g.node_performance.last_hour + > Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap() + }) + .collect(); + } + + if filtered_gateways.is_empty() { log::error!("No gateways found! (with high enough performance score)"); Err(BackendError::NoGatewayWithAcceptablePerformanceFound) } else { - Ok(gateways) + Ok(filtered_gateways) } } @@ -200,7 +230,8 @@ async fn select_gateway_by_latency(gateways: Vec) -> Resul #[tauri::command] pub async fn get_gateways() -> Result> { log::trace!("Fetching gateways"); - let all_gateways = fetch_compatible_gateways().await?; + let all_gateways = fetch_only_compatible_gateways().await?; + log::debug!("Received {} gateways", all_gateways.len()); log::trace!("Received: {:#?}", all_gateways); let gateways_filtered = filter_out_low_performance_gateways(all_gateways.clone())? @@ -209,15 +240,24 @@ pub async fn get_gateways() -> Result> { identity: g.identity().clone(), }) .collect_vec(); - log::trace!("Filtered: {:#?}", gateways_filtered); + log::debug!( + "After filtering out low-performance gateways: {}", + gateways_filtered.len() + ); + log::trace!( + "Filtered: [\n\t{}\n]", + gateways_filtered.iter().join(",\n\t") + ); + Ok(gateways_filtered) } // Lookup and select a single gateway with low latency. #[tauri::command] pub async fn get_gateway_with_low_latency() -> Result { - log::trace!("Fetching gateways"); - let all_gateways = fetch_compatible_gateways().await?; + log::trace!("Fetching gateways with low latency"); + let all_gateways = fetch_only_compatible_gateways().await?; + log::debug!("Received {} gateways", all_gateways.len()); log::trace!("Received: {:#?}", all_gateways); let gateways_filtered = filter_out_low_performance_gateways(all_gateways)?; @@ -233,7 +273,7 @@ pub async fn get_gateway_with_low_latency() -> Result { pub async fn select_gateway_with_low_latency_from_list(gateways: Vec) -> Result { log::debug!("Selecting a gateway with low latency"); let gateways = gateways.into_iter().map(|g| g.identity).collect_vec(); - let all_gateways = fetch_compatible_gateways().await?; + let all_gateways = fetch_only_compatible_gateways().await?; let gateways_union_set: Vec = all_gateways .into_iter() .filter(|g| gateways.contains(g.identity()))