diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index dabece05e5..5239f87411 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -303,7 +303,7 @@ impl GeoAwareTopologyProvider { filter_on: GroupBy, ) -> GeoAwareTopologyProvider { log::info!( - "Creating geo-aware topology provider with filter on {:?}", + "Creating geo-aware topology provider with filter on {}", filter_on ); nym_api_urls.shuffle(&mut thread_rng()); diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index f3cee8200f..c1d5b9d3cb 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -498,6 +498,15 @@ pub enum GroupBy { NymAddress(Recipient), } +impl std::fmt::Display for GroupBy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GroupBy::CountryGroup(group) => write!(f, "group: {}", group), + GroupBy::NymAddress(address) => write!(f, "address: {}", address), + } + } +} + impl Default for Topology { fn default() -> Self { Topology { diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index adfd9ad623..6237321a1b 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -149,7 +149,7 @@ async fn measure_latency(gateway: &gateway::Node) -> Result( +pub async fn choose_gateway_by_latency( rng: &mut R, gateways: &[gateway::Node], ) -> Result { diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index ecfe3bf851..e2b2acee27 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4014,8 +4014,10 @@ dependencies = [ "nym-socks5-client-core", "nym-sphinx", "nym-task", + "nym-topology", "nym-validator-client", "pretty_env_logger", + "rand 0.7.3", "rand 0.8.5", "reqwest", "rust-embed", diff --git a/nym-connect/desktop/Cargo.toml b/nym-connect/desktop/Cargo.toml index 3ee3f20be3..5322fc260c 100644 --- a/nym-connect/desktop/Cargo.toml +++ b/nym-connect/desktop/Cargo.toml @@ -1,2 +1,3 @@ [workspace] members = ["src-tauri"] +resolver = "2" diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index ae940cd32d..133b94a921 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -30,6 +30,7 @@ itertools = "0.10.5" log = { version = "0.4", features = ["serde"] } pretty_env_logger = "0.4.0" rand = "0.8" +rand-07 = { package = "rand", version = "0.7.3" } reqwest = { version = "0.11.18", features = ["json", "socks"] } rust-embed = { version = "6.4.2", features = ["include-exclude"] } serde = { version = "1.0", features = ["derive"] } @@ -58,6 +59,7 @@ nym-bin-common = { path = "../../../common/bin-common"} nym-socks5-client-core = { path = "../../../common/socks5-client-core" } nym-sphinx = { path = "../../../common/nymsphinx" } nym-task = { path = "../../../common/task" } +nym-topology = { path = "../../../common/topology" } nym-validator-client = { path = "../../../common/client-libs/validator-client" } [dev-dependencies] diff --git a/nym-connect/desktop/src-tauri/src/main.rs b/nym-connect/desktop/src-tauri/src/main.rs index fc36d44c96..9a3c8c7b6c 100644 --- a/nym-connect/desktop/src-tauri/src/main.rs +++ b/nym-connect/desktop/src-tauri/src/main.rs @@ -86,8 +86,10 @@ fn main() { crate::operations::connection::status::get_connection_status, crate::operations::connection::status::get_gateway_connection_status, crate::operations::connection::status::start_connection_health_check_task, - crate::operations::directory::get_services, + crate::operations::directory::get_gateway_with_low_latency, crate::operations::directory::get_gateways, + crate::operations::directory::get_services, + crate::operations::directory::select_gateway_with_low_latency_from_list, crate::operations::export::export_keys, crate::operations::window::hide_window, crate::operations::growth::test_and_earn::growth_tne_get_client_id, 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 3842720161..05e0b9de6e 100644 --- a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs @@ -11,6 +11,7 @@ use nym_api_requests::models::GatewayBondAnnotated; use nym_bin_common::version_checker::is_minor_version_compatible; use nym_config::defaults::var_names::{NETWORK_NAME, NYM_API}; use nym_contracts_common::types::Percent; +use nym_topology::gateway; use nym_validator_client::nym_api::Client as ApiClient; use std::str::FromStr; use std::sync::Arc; @@ -134,25 +135,47 @@ async fn fetch_gateways() -> Result> { Ok(gateways) } +fn filter_out_low_performance_gateways( + gateways: Vec, +) -> Vec { + gateways + .into_iter() + .filter(|g| { + g.node_performance.most_recent + > Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap() + }) + .collect() +} + +async fn select_gateway_by_latency(gateways: Vec) -> Result { + let gateways_as_nodes: Vec = gateways + .into_iter() + .filter_map(|g| g.gateway_bond.try_into().ok()) + .collect(); + + let mut rng = rand_07::rngs::OsRng; + let selected_gateway = + nym_client_core::init::helpers::choose_gateway_by_latency(&mut rng, &gateways_as_nodes) + .await?; + Ok(selected_gateway) +} + +// Get all gateways satisfying the performance threshold. #[tauri::command] pub async fn get_gateways() -> Result> { log::trace!("Fetching gateways"); let all_gateways = fetch_gateways().await?; log::trace!("Received: {:#?}", all_gateways); - let filtered_gateways = all_gateways - .iter() - .filter(|g| { - g.node_performance.most_recent - > Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap() - }) + let gateways_filtered = filter_out_low_performance_gateways(all_gateways.clone()) + .into_iter() .map(|g| Gateway { identity: g.identity().clone(), }) .collect_vec(); - log::trace!("Filtered: {:#?}", filtered_gateways); + log::trace!("Filtered: {:#?}", gateways_filtered); - if filtered_gateways.is_empty() { + if gateways_filtered.is_empty() { log::warn!("No gateways with high enough performance score found! Using all gateways instead as fallback"); return Ok(all_gateways .iter() @@ -162,5 +185,38 @@ pub async fn get_gateways() -> Result> { .collect_vec()); } - Ok(filtered_gateways) + 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_gateways().await?; + log::trace!("Received: {:#?}", all_gateways); + + let gateways_filtered = filter_out_low_performance_gateways(all_gateways); + let selected_gateway = select_gateway_by_latency(gateways_filtered).await?; + log::debug!("Selected gateway: {}", selected_gateway); + Ok(Gateway { + identity: selected_gateway.identity().to_base58_string(), + }) +} + +// From a given list of gateways, select the one with low latency. +#[tauri::command] +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_gateways().await?; + let gateways_union_set: Vec = all_gateways + .into_iter() + .filter(|g| gateways.contains(g.identity())) + .collect(); + let gateways_filtered = filter_out_low_performance_gateways(gateways_union_set); + let selected_gateway = select_gateway_by_latency(gateways_filtered).await?; + log::debug!("Selected gateway: {}", selected_gateway); + Ok(Gateway { + identity: selected_gateway.identity().to_base58_string(), + }) } diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index 4af366ca48..b18cb8277d 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -57,7 +57,7 @@ fn override_config_from_env(config: &mut Config, privacy_level: &PrivacyLevel) { .provider_mix_address .parse() .expect("failed to parse provider mix address"); - log::warn!("Using geo-aware mixnode selection baseon the location of: {address}"); + log::warn!("Using geo-aware mixnode selection based on the location of: {address}"); config .core .base diff --git a/nym-connect/desktop/src/context/main.tsx b/nym-connect/desktop/src/context/main.tsx index 288c9be5a9..2f553b5153 100644 --- a/nym-connect/desktop/src/context/main.tsx +++ b/nym-connect/desktop/src/context/main.tsx @@ -214,7 +214,12 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { const setGateway = async () => { if (gateways) { - const randomGateway = getRandomFromList(gateways); + let randomGateway; + if (userData?.privacy_level === 'Medium') { + randomGateway = await invoke('select_gateway_with_low_latency_from_list', { gateways }); + } else { + randomGateway = getRandomFromList(gateways); + } const withUserDefinitions = await buildGateway(randomGateway); await invoke('set_gateway', { gateway: shouldUseUserGateway ? userDefinedGateway.address : withUserDefinitions.identity,