From abdd960b20db68c52cfcc88ea17f6f990ae4ac8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 24 Oct 2025 15:06:06 +0100 Subject: [PATCH] removed dependency on nym-gateway-directory --- Cargo.lock | 26 - Cargo.toml | 1 - nym-gateway-directory/Cargo.toml | 36 - nym-gateway-directory/README.md | 3 - .../src/entries/auth_addresses.rs | 39 - nym-gateway-directory/src/entries/country.rs | 31 - .../src/entries/entry_point.rs | 127 --- .../src/entries/exit_point.rs | 150 ---- nym-gateway-directory/src/entries/gateway.rs | 729 ------------------ .../src/entries/ipr_addresses.rs | 56 -- nym-gateway-directory/src/entries/mod.rs | 7 - nym-gateway-directory/src/entries/score.rs | 34 - nym-gateway-directory/src/error.rs | 123 --- nym-gateway-directory/src/gateway_client.rs | 345 --------- nym-gateway-directory/src/helpers.rs | 110 --- nym-gateway-directory/src/lib.rs | 28 - sdk/rust/nym-sdk/src/error.rs | 5 +- .../nym-sdk/src/ip_packet_client/connect.rs | 16 +- .../mixnet_stream_wrapper_ipr.rs | 130 ++-- 19 files changed, 91 insertions(+), 1905 deletions(-) delete mode 100644 nym-gateway-directory/Cargo.toml delete mode 100644 nym-gateway-directory/README.md delete mode 100644 nym-gateway-directory/src/entries/auth_addresses.rs delete mode 100644 nym-gateway-directory/src/entries/country.rs delete mode 100644 nym-gateway-directory/src/entries/entry_point.rs delete mode 100644 nym-gateway-directory/src/entries/exit_point.rs delete mode 100644 nym-gateway-directory/src/entries/gateway.rs delete mode 100644 nym-gateway-directory/src/entries/ipr_addresses.rs delete mode 100644 nym-gateway-directory/src/entries/mod.rs delete mode 100644 nym-gateway-directory/src/entries/score.rs delete mode 100644 nym-gateway-directory/src/error.rs delete mode 100644 nym-gateway-directory/src/gateway_client.rs delete mode 100644 nym-gateway-directory/src/helpers.rs delete mode 100644 nym-gateway-directory/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index c9606cea26..edc741823f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5929,31 +5929,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nym-gateway-directory" -version = "0.0.0" -dependencies = [ - "itertools 0.14.0", - "nym-client-core", - "nym-contracts-common", - "nym-crypto", - "nym-http-api-client", - "nym-network-defaults", - "nym-sphinx", - "nym-sphinx-addressing", - "nym-topology", - "nym-validator-client", - "rand 0.8.5", - "serde", - "serde_json", - "strum", - "thiserror 2.0.17", - "tokio", - "tokio-util", - "tracing", - "url", -] - [[package]] name = "nym-gateway-probe" version = "1.18.0" @@ -6912,7 +6887,6 @@ dependencies = [ "nym-credentials", "nym-credentials-interface", "nym-crypto", - "nym-gateway-directory", "nym-gateway-requests", "nym-http-api-client", "nym-ip-packet-requests", diff --git a/Cargo.toml b/Cargo.toml index a20444e7c0..1d09c6ef63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,7 +128,6 @@ members = [ "nym-credential-proxy/nym-credential-proxy", "nym-credential-proxy/nym-credential-proxy-requests", "nym-data-observatory", - "nym-gateway-directory", "nym-network-monitor", "nym-node", "nym-node-status-api/nym-node-status-agent", diff --git a/nym-gateway-directory/Cargo.toml b/nym-gateway-directory/Cargo.toml deleted file mode 100644 index ecdf99d8aa..0000000000 --- a/nym-gateway-directory/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "nym-gateway-directory" -authors.workspace = true -repository.workspace = true -homepage.workspace = true -license.workspace = true -edition.workspace = true - -[lints] -workspace = true - -[dependencies] -itertools.workspace = true -nym-client-core = { path = "../common/client-core" } -nym-http-api-client = { path = "../common/http-api-client", features = ["network-defaults"] } -nym-sphinx = { path = "../common/nymsphinx" } -nym-topology = { path = "../common/topology" } -nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-crypto = { path = "../common/crypto" } -nym-sphinx-addressing = { path = "../common/nymsphinx/addressing" } -nym-network-defaults = { path = "../common/network-defaults" } -nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } - -rand.workspace = true -serde.workspace = true -strum.workspace = true -thiserror.workspace = true -tokio.workspace = true -tokio-util.workspace = true -tracing.workspace = true -url.workspace = true - -serde_json.workspace = true - -[dev-dependencies] -tokio = { workspace = true, features = ["full"] } diff --git a/nym-gateway-directory/README.md b/nym-gateway-directory/README.md deleted file mode 100644 index a5b4ab276a..0000000000 --- a/nym-gateway-directory/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `nym-gateway-directory` - -This is a stripped down version of the `nym-gateway-directory` crate from the `nym-vpn-client` repository exposing functions and types related to IPRs. This is for use by the Rust SDK's `stream_wrapper` module. diff --git a/nym-gateway-directory/src/entries/auth_addresses.rs b/nym-gateway-directory/src/entries/auth_addresses.rs deleted file mode 100644 index 388ee041cb..0000000000 --- a/nym-gateway-directory/src/entries/auth_addresses.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use nym_sphinx::addressing::clients::Recipient; - -use crate::{Error, error::Result}; - -#[derive(Debug, Copy, Clone)] -pub struct AuthAddress(Recipient); - -impl AuthAddress { - pub(crate) fn try_from_base58_string(address: &str) -> Result { - let recipient = Recipient::try_from_base58_string(address).map_err(|source| { - Error::RecipientFormattingError { - address: address.to_string(), - source, - } - })?; - Ok(AuthAddress(recipient)) - } -} - -impl std::fmt::Display for AuthAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for AuthAddress { - fn from(recipient: Recipient) -> Self { - Self(recipient) - } -} - -impl From for Recipient { - fn from(auth_address: AuthAddress) -> Self { - auth_address.0 - } -} diff --git a/nym-gateway-directory/src/entries/country.rs b/nym-gateway-directory/src/entries/country.rs deleted file mode 100644 index 537c3deb6b..0000000000 --- a/nym-gateway-directory/src/entries/country.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::{Location, NymDirectoryCountry}; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Country { - iso_code: String, -} - -impl Country { - pub fn iso_code(&self) -> &str { - &self.iso_code - } -} - -impl From for Country { - fn from(country: NymDirectoryCountry) -> Self { - Self { - iso_code: country.iso_code().to_string(), - } - } -} - -impl From for Country { - fn from(location: Location) -> Self { - Self { - iso_code: location.two_letter_iso_country_code, - } - } -} diff --git a/nym-gateway-directory/src/entries/entry_point.rs b/nym-gateway-directory/src/entries/entry_point.rs deleted file mode 100644 index e85a7e905a..0000000000 --- a/nym-gateway-directory/src/entries/entry_point.rs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use nym_sphinx::addressing::nodes::NodeIdentity; -use std::fmt::{Display, Formatter}; - -use serde::{Deserialize, Serialize}; -use tracing::debug; - -use crate::{ - Error, ScoreValue, - entries::gateway::{COUNTRY_WITH_REGION_SELECTOR, Gateway, GatewayFilter, GatewayList}, - error::Result, -}; - -// The entry point is always a gateway identity, or some other entry that can be resolved to a -// gateway identity. -#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] -pub enum EntryPoint { - // An explicit entry gateway identity. - Gateway { identity: NodeIdentity }, - // Select a random entry gateway in a specific country. - Country { two_letter_iso_country_code: String }, - // Select a random entry gateway in a specific region/state. - Region { region: String }, - // Select an entry gateway at random. - Random, -} - -impl Display for EntryPoint { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - EntryPoint::Gateway { identity } => write!(f, "Gateway: {identity}"), - EntryPoint::Country { - two_letter_iso_country_code, - } => write!(f, "Country: {two_letter_iso_country_code}"), - EntryPoint::Region { region } => write!(f, "Region/state: {region}"), - EntryPoint::Random => write!(f, "Random"), - } - } -} - -impl EntryPoint { - pub fn from_base58_string(base58: &str) -> Result { - let identity = NodeIdentity::from_base58_string(base58).map_err(|source| { - Error::NodeIdentityFormattingError { - identity: base58.to_string(), - source, - } - })?; - Ok(EntryPoint::Gateway { identity }) - } - - pub fn lookup_gateway( - &self, - gateways: &GatewayList, - min_score: Option, - ) -> Result { - match &self { - EntryPoint::Gateway { identity } => { - debug!("Selecting gateway by identity: {identity}"); - gateways - .gateway_with_identity(identity) - .ok_or_else(|| Error::NoMatchingGateway { - requested_identity: identity.to_string(), - }) - .cloned() - } - EntryPoint::Country { - two_letter_iso_country_code, - } => { - debug!("Selecting gateway by country: {two_letter_iso_country_code}"); - - let filters = Self::build_filters( - vec![GatewayFilter::Country(two_letter_iso_country_code.clone())], - min_score, - ); - - gateways.choose_random(&filters).ok_or_else(|| { - Error::NoMatchingEntryGatewayForLocation { - requested_location: two_letter_iso_country_code.clone(), - available_countries: gateways.all_iso_codes(), - } - }) - } - EntryPoint::Region { region } => { - debug!("Selecting gateway by region/state: {region}"); - - // Currently only supported in the US - let filters = Self::build_filters( - vec![ - GatewayFilter::Country(COUNTRY_WITH_REGION_SELECTOR.to_string()), - GatewayFilter::Region(region.to_string()), - ], - min_score, - ); - - gateways.choose_random(&filters).ok_or_else(|| { - Error::NoMatchingEntryGatewayForLocation { - requested_location: region.clone(), - available_countries: gateways.all_iso_codes(), - } - }) - } - EntryPoint::Random => { - debug!("Selecting a random gateway"); - - let filters = Self::build_filters(vec![], min_score); - - gateways - .choose_random(&filters) - .ok_or_else(|| Error::FailedToSelectGatewayRandomly) - } - } - } - - #[inline] - fn build_filters( - mut base_filters: Vec, - min_score: Option, - ) -> Vec { - if let Some(min_score) = min_score { - base_filters.push(GatewayFilter::MinScore(min_score)); - } - base_filters - } -} diff --git a/nym-gateway-directory/src/entries/exit_point.rs b/nym-gateway-directory/src/entries/exit_point.rs deleted file mode 100644 index a7213b30a0..0000000000 --- a/nym-gateway-directory/src/entries/exit_point.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use nym_sphinx::addressing::clients::Recipient; -use nym_sphinx::addressing::nodes::NodeIdentity; -use serde::{Deserialize, Serialize}; -use std::fmt::{Display, Formatter}; - -use crate::{ - Error, IpPacketRouterAddress, ScoreValue, - entries::gateway::{COUNTRY_WITH_REGION_SELECTOR, Gateway, GatewayFilter, GatewayList}, - error::Result, -}; - -// The exit point is a nym-address, but if the exit ip-packet-router is running embedded on a -// gateway, we can refer to it by the gateway identity. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub enum ExitPoint { - // An explicit exit address. This is useful when the exit ip-packet-router is running as a - // standalone entity (private). - Address { address: Box }, - - // An explicit exit gateway identity. This is useful when the exit ip-packet-router is running - // embedded on a gateway. - Gateway { identity: NodeIdentity }, - - // Select a random entry gateway in a specific country. - Country { two_letter_iso_country_code: String }, - - // Select a random entry gateway in a specific region/state. - Region { region: String }, - - // Select an exit gateway at random. - Random, -} - -impl Display for ExitPoint { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ExitPoint::Address { address } => write!(f, "Address: {address}"), - ExitPoint::Gateway { identity } => write!(f, "Gateway: {identity}"), - ExitPoint::Country { - two_letter_iso_country_code, - } => write!(f, "Country: {two_letter_iso_country_code}"), - ExitPoint::Region { region } => write!(f, "Region/state: {region}"), - ExitPoint::Random => write!(f, "Random"), - } - } -} - -impl ExitPoint { - pub fn lookup_gateway( - &self, - gateways: &GatewayList, - min_score: Option, - residential_exit: bool, - ) -> Result { - match &self { - ExitPoint::Address { address } => { - tracing::debug!("Selecting gateway by address: {address}"); - // There is no validation done when a ip packet router is specified by address - // since it might be private and not available in any directory. - let ipr_address = IpPacketRouterAddress::from(**address); - let gateway_address = ipr_address.gateway(); - - // Now fetch the gateway that the IPR is connected to, and override its IPR address - let mut gateway = gateways - .gateway_with_identity(&gateway_address) - .ok_or_else(|| Error::NoMatchingGateway { - requested_identity: gateway_address.to_string(), - }) - .cloned()?; - gateway.ipr_address = Some(ipr_address); - Ok(gateway) - } - ExitPoint::Gateway { identity } => { - tracing::debug!("Selecting gateway by identity: {identity}"); - gateways - .gateway_with_identity(identity) - .ok_or_else(|| Error::NoMatchingGateway { - requested_identity: identity.to_string(), - }) - .cloned() - } - ExitPoint::Country { - two_letter_iso_country_code, - } => { - tracing::debug!("Selecting gateway by country: {two_letter_iso_country_code}"); - - let filters = Self::build_filters( - vec![GatewayFilter::Country(two_letter_iso_country_code.clone())], - min_score, - residential_exit, - ); - - gateways.choose_random(&filters).ok_or_else(|| { - Error::NoMatchingExitGatewayForLocation { - requested_location: two_letter_iso_country_code.clone(), - available_countries: gateways.all_iso_codes(), - } - }) - } - ExitPoint::Region { region } => { - tracing::debug!("Selecting gateway by region/state: {region}"); - - let filters = Self::build_filters( - vec![ - // Currently only supported in the US - GatewayFilter::Country(COUNTRY_WITH_REGION_SELECTOR.to_string()), - GatewayFilter::Region(region.to_string()), - ], - min_score, - residential_exit, - ); - - gateways.choose_random(&filters).ok_or_else(|| { - Error::NoMatchingExitGatewayForLocation { - requested_location: region.clone(), - available_countries: gateways.all_iso_codes(), - } - }) - } - ExitPoint::Random => { - tracing::debug!("Selecting a random exit gateway"); - - let filters = Self::build_filters(vec![], min_score, residential_exit); - - gateways - .choose_random(&filters) - .ok_or_else(|| Error::FailedToSelectGatewayRandomly) - } - } - } - - #[inline] - fn build_filters( - mut base_filters: Vec, - min_score: Option, - residential_exit: bool, - ) -> Vec { - if let Some(min_score) = min_score { - base_filters.push(GatewayFilter::MinScore(min_score)); - } - if residential_exit { - base_filters.push(GatewayFilter::Residential); - base_filters.push(GatewayFilter::Exit); - } - base_filters - } -} diff --git a/nym-gateway-directory/src/entries/gateway.rs b/nym-gateway-directory/src/entries/gateway.rs deleted file mode 100644 index c64f5ee262..0000000000 --- a/nym-gateway-directory/src/entries/gateway.rs +++ /dev/null @@ -1,729 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only -use itertools::Itertools; -use nym_contracts_common::Percent; -use nym_sphinx::addressing::nodes::NodeIdentity; -use nym_topology::{NodeId, RoutingNode}; -use nym_validator_client::models::{KeyRotationId, NymNodeDescription}; -use rand::seq::IteratorRandom; -use serde::{Deserialize, Serialize}; -use std::{ - fmt::{self, Display}, - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - str::FromStr, -}; -use tracing::error; - -use crate::{ - AuthAddress, BridgeInformation, BridgeParameters, Country, Error, IpPacketRouterAddress, - ScoreThresholds, - entries::score::{HIGH_SCORE_THRESHOLD, LOW_SCORE_THRESHOLD, MEDIUM_SCORE_THRESHOLD, Score}, - error::Result, - helpers, -}; - -pub const COUNTRY_WITH_REGION_SELECTOR: &str = "US"; - -#[derive(Clone)] -pub struct Gateway { - pub identity: NodeIdentity, - pub moniker: String, - pub location: Option, - pub ipr_address: Option, - pub authenticator_address: Option, - pub bridge_params: Option, - pub last_probe: Option, - pub ips: Vec, - pub host: Option, - pub clients_ws_port: Option, - pub clients_wss_port: Option, - pub mixnet_performance: Option, - pub mixnet_score: Option, - pub wg_performance: Option, - pub version: Option, -} - -impl fmt::Debug for Gateway { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Gateway") - .field("identity", &self.identity.to_base58_string()) - .field("location", &self.location) - .field("ipr_address", &self.ipr_address) - .field("authenticator_address", &self.authenticator_address) - .field("last_probe", &self.last_probe) - .field("host", &self.host) - .field("clients_ws_port", &self.clients_ws_port) - .field("clients_wss_port", &self.clients_wss_port) - .field("mixnet_performance", &self.mixnet_performance) - .field("mixnet_score", &self.mixnet_score) - .field("wg_performance", &self.wg_performance) - .field("version", &self.version) - .finish() - } -} - -impl Gateway { - pub fn try_from_node_description( - node_description: NymNodeDescription, - current_key_rotation: KeyRotationId, - ) -> Result { - let identity = node_description.description.host_information.keys.ed25519; - let location = node_description - .description - .auxiliary_details - .location - .map(|l| Location { - two_letter_iso_country_code: l.alpha2.to_string(), - ..Default::default() - }); - let ipr_address = node_description - .description - .ip_packet_router - .as_ref() - .and_then(|ipr| { - IpPacketRouterAddress::try_from_base58_string(&ipr.address) - .inspect_err(|err| error!("Failed to parse IPR address: {err}")) - .ok() - }); - let authenticator_address = node_description - .description - .authenticator - .as_ref() - .and_then(|a| { - AuthAddress::try_from_base58_string(&a.address) - .inspect_err(|err| error!("Failed to parse authenticator address: {err}")) - .ok() - }); - let version = Some(node_description.version().to_string()); - let role = if node_description.description.declared_role.entry { - nym_validator_client::nym_nodes::NodeRole::EntryGateway - } else if node_description.description.declared_role.exit_ipr - || node_description.description.declared_role.exit_nr - { - nym_validator_client::nym_nodes::NodeRole::ExitGateway - } else { - nym_validator_client::nym_nodes::NodeRole::Inactive - }; - - let gateway = RoutingNode::try_from(&node_description.to_skimmed_node( - current_key_rotation, - role, - Default::default(), - )) - .map_err(|_| Error::MalformedGateway)?; - - let host = gateway.ws_entry_address(false); - let entry_info = &gateway.entry; - let clients_ws_port = entry_info.as_ref().map(|g| g.clients_ws_port); - let clients_wss_port = entry_info.as_ref().and_then(|g| g.clients_wss_port); - let ips = node_description.description.host_information.ip_address; - Ok(Gateway { - identity, - moniker: String::new(), - location, - ipr_address, - authenticator_address, - bridge_params: None, - last_probe: None, - ips, - host, - clients_ws_port, - clients_wss_port, - mixnet_performance: None, - mixnet_score: None, - wg_performance: None, - version, - }) - } - - pub fn identity(&self) -> NodeIdentity { - self.identity - } - - pub fn two_letter_iso_country_code(&self) -> Option<&str> { - self.location - .as_ref() - .map(|l| l.two_letter_iso_country_code.as_str()) - } - - pub fn is_in_country(&self, two_letter_iso_country_code: &str) -> bool { - self.location - .as_ref() - .map(|loc| loc.two_letter_iso_country_code == two_letter_iso_country_code) - .unwrap_or(false) - } - - pub fn region(&self) -> Option<&str> { - self.location.as_ref().map(|l| l.region.as_str()) - } - - pub fn is_in_region(&self, region: &str) -> bool { - self.location - .as_ref() - .map(|loc| loc.region == region) - .unwrap_or(false) - } - - pub fn is_residential_asn(&self) -> bool { - self.location - .as_ref() - .and_then(|loc| loc.asn.as_ref()) - .map(|asn| asn.kind == AsnKind::Residential) - .unwrap_or(false) - } - - pub fn is_exit_node(&self) -> bool { - self.ipr_address.is_some() - } - - pub fn is_vpn_node(&self) -> bool { - self.authenticator_address.is_some() - } - - pub fn host(&self) -> Option<&String> { - self.host.as_ref() - } - - pub fn lookup_ip(&self) -> Option { - self.ips.first().copied() - } - - pub fn split_ips(&self) -> (Vec, Vec) { - helpers::split_ips(self.ips.clone()) - } - - pub fn clients_address_no_tls(&self) -> Option { - match (&self.host, &self.clients_ws_port) { - (Some(host), Some(port)) => Some(format!("ws://{host}:{port}")), - _ => None, - } - } - - pub fn clients_address_tls(&self) -> Option { - match (&self.host, &self.clients_wss_port) { - (Some(host), Some(port)) => Some(format!("wss://{host}:{port}")), - _ => None, - } - } - - pub fn update_to_new_thresholds(&mut self, mix_thresholds: Option) { - if let (Some(mix_thresholds), Some(score)) = (mix_thresholds, self.mixnet_score.as_mut()) { - score.update_to_new_thresholds(mix_thresholds); - } - } - - pub fn meets_score(&self, gw_type: Option, min_score: ScoreValue) -> bool { - match gw_type { - Some(GatewayType::MixnetEntry) | Some(GatewayType::MixnetExit) => self - .mixnet_performance - .is_some_and(|p| p.round_to_integer() >= min_score.threshold()), - Some(GatewayType::Wg) => self - .wg_performance - .as_ref() - .is_some_and(|p| p.score >= min_score), - None => false, - } - } - - /// Tests whether the gateway matches a specific filter. - pub fn matches_filter(&self, gw_type: Option, filter: &GatewayFilter) -> bool { - match filter { - GatewayFilter::MinScore(score) => self.meets_score(gw_type, *score), - GatewayFilter::Country(code) => self.is_in_country(code), - GatewayFilter::Region(region) => self.is_in_region(region), - GatewayFilter::Residential => self.is_residential_asn(), - GatewayFilter::Exit => self.is_exit_node(), - GatewayFilter::Vpn => self.is_vpn_node(), - } - } - - /// Tests whether the gateway matches all of the filters. - pub fn matches_all_filters( - &self, - gw_type: Option, - filters: &[GatewayFilter], - ) -> bool { - filters - .iter() - .all(|filter| self.matches_filter(gw_type, filter)) - } - - pub fn get_bridge_params(&self) -> Option { - if let Some(all_params) = &self.bridge_params { - all_params.transports.first().cloned() - } else { - None - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum AsnKind { - Residential, - Other, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Asn { - pub asn: String, - pub name: String, - pub kind: AsnKind, -} - -#[derive(Debug, Default, Clone, PartialEq)] -pub struct Location { - pub two_letter_iso_country_code: String, - pub latitude: f64, - pub longitude: f64, - - pub city: String, - pub region: String, - - pub asn: Option, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum ScoreValue { - Offline, - Low, - Medium, - High, -} - -impl ScoreValue { - fn priority(&self) -> u8 { - match self { - ScoreValue::Offline => 0, - ScoreValue::Low => 1, - ScoreValue::Medium => 2, - ScoreValue::High => 3, - } - } - - pub fn threshold(&self) -> u8 { - match self { - ScoreValue::Offline => 0, - ScoreValue::Low => LOW_SCORE_THRESHOLD, - ScoreValue::Medium => MEDIUM_SCORE_THRESHOLD, - ScoreValue::High => HIGH_SCORE_THRESHOLD, - } - } -} - -impl PartialOrd for ScoreValue { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.priority().cmp(&other.priority())) - } -} - -impl FromStr for ScoreValue { - type Err = crate::Error; - - fn from_str(s: &str) -> std::result::Result { - match s.to_lowercase().as_str() { - "offline" => Ok(ScoreValue::Offline), - "low" => Ok(ScoreValue::Low), - "medium" => Ok(ScoreValue::Medium), - "high" => Ok(ScoreValue::High), - _ => Err(crate::Error::InvalidScoreValue(s.to_string())), - } - } -} - -impl Display for ScoreValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - ScoreValue::Offline => "Offline", - ScoreValue::Low => "Low", - ScoreValue::Medium => "Medium", - ScoreValue::High => "High", - }; - write!(f, "{s}") - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Performance { - pub last_updated_utc: String, - pub score: ScoreValue, - pub load: ScoreValue, - pub uptime_percentage_last_24_hours: f32, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Probe { - pub last_updated_utc: String, - pub outcome: ProbeOutcome, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ProbeOutcome { - pub as_entry: Entry, - pub as_exit: Option, - pub wg: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Entry { - pub can_connect: bool, - pub can_route: bool, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Exit { - pub can_connect: bool, - pub can_route_ip_v4: bool, - pub can_route_ip_external_v4: bool, - pub can_route_ip_v6: bool, - pub can_route_ip_external_v6: bool, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct WgProbeResults { - pub can_register: bool, - pub can_handshake: bool, - pub can_resolve_dns: bool, - pub ping_hosts_performance: f32, - pub ping_ips_performance: f32, -} - -impl From for AsnKind { - fn from(value: helpers::AsnKind) -> Self { - match value { - helpers::AsnKind::Residential => AsnKind::Residential, - helpers::AsnKind::Other => AsnKind::Other, - } - } -} - -impl From for Asn { - fn from(location: helpers::Asn) -> Self { - Asn { - asn: location.asn, - name: location.name, - kind: location.kind.into(), - } - } -} - -impl From for Location { - fn from(location: helpers::Location) -> Self { - Location { - two_letter_iso_country_code: location.two_letter_iso_country_code, - latitude: location.latitude, - longitude: location.longitude, - city: location.city, - region: location.region, - asn: location.asn.map(Into::into), - } - } -} - -// impl From for ScoreValue { -// fn from(value: nym_vpn_api_client::response::ScoreValue) -> Self { -// match value { -// nym_vpn_api_client::response::ScoreValue::Offline => ScoreValue::Offline, -// nym_vpn_api_client::response::ScoreValue::Low => ScoreValue::Low, -// nym_vpn_api_client::response::ScoreValue::Medium => ScoreValue::Medium, -// nym_vpn_api_client::response::ScoreValue::High => ScoreValue::High, -// } -// } -// } - -// impl From for Performance { -// fn from(value: nym_vpn_api_client::response::DVpnGatewayPerformance) -> Self { -// Performance { -// last_updated_utc: value.last_updated_utc, -// score: value.score.into(), -// load: value.load.into(), -// uptime_percentage_last_24_hours: value.uptime_percentage_last_24_hours, -// } -// } -// } - -// impl From for Probe { -// fn from(probe: nym_vpn_api_client::response::Probe) -> Self { -// Probe { -// last_updated_utc: probe.last_updated_utc, -// outcome: ProbeOutcome::from(probe.outcome), -// } -// } -// } - -impl From for Score { - fn from(percent: Percent) -> Self { - let rounded_percent = percent.round_to_integer(); - if rounded_percent >= HIGH_SCORE_THRESHOLD { - Score::High(rounded_percent) - } else if rounded_percent >= MEDIUM_SCORE_THRESHOLD { - Score::Medium(rounded_percent) - } else if rounded_percent > LOW_SCORE_THRESHOLD { - Score::Low(rounded_percent) - } else { - Score::None - } - } -} - -// impl From for ProbeOutcome { -// fn from(outcome: nym_vpn_api_client::response::ProbeOutcome) -> Self { -// ProbeOutcome { -// as_entry: Entry::from(outcome.as_entry), -// as_exit: outcome.as_exit.map(Exit::from), -// wg: outcome.wg.map(WgProbeResults::from), -// } -// } -// } - -// impl From for Entry { -// fn from(entry: nym_vpn_api_client::response::Entry) -> Self { -// Entry { -// can_connect: entry.can_connect, -// can_route: entry.can_route, -// } -// } -// } - -// impl From for Exit { -// fn from(exit: nym_vpn_api_client::response::Exit) -> Self { -// Exit { -// can_connect: exit.can_connect, -// can_route_ip_v4: exit.can_route_ip_v4, -// can_route_ip_external_v4: exit.can_route_ip_external_v4, -// can_route_ip_v6: exit.can_route_ip_v6, -// can_route_ip_external_v6: exit.can_route_ip_external_v6, -// } -// } -// } - -// impl From for WgProbeResults { -// fn from(results: nym_vpn_api_client::response::WgProbeResults) -> Self { -// WgProbeResults { -// can_register: results.can_register, -// can_handshake: results.can_handshake, -// can_resolve_dns: results.can_resolve_dns, -// ping_hosts_performance: results.ping_hosts_performance, -// ping_ips_performance: results.ping_ips_performance, -// } -// } -// } - -// impl TryFrom for Gateway { -// type Error = Error; - -// fn try_from(gateway: nym_vpn_api_client::response::NymDirectoryGateway) -> Result { -// let identity = -// NodeIdentity::from_base58_string(&gateway.identity_key).map_err(|source| { -// Error::NodeIdentityFormattingError { -// identity: gateway.identity_key, -// source, -// } -// })?; - -// let ipr_address = gateway -// .ip_packet_router -// .and_then(|ipr| IpPacketRouterAddress::try_from_base58_string(&ipr.address).ok()); - -// let authenticator_address = gateway -// .authenticator -// .and_then(|auth| AuthAddress::try_from_base58_string(&auth.address).ok()); - -// let hostname = gateway.entry.hostname; -// let first_ip_address = gateway -// .ip_addresses -// .first() -// .cloned() -// .map(|ip| ip.to_string()); -// let host = hostname.or(first_ip_address); - -// Ok(Gateway { -// identity, -// moniker: gateway.name, -// location: Some(gateway.location.into()), -// ipr_address, -// authenticator_address, -// bridge_params: gateway.bridges, -// last_probe: gateway.last_probe.map(Probe::from), -// ips: gateway.ip_addresses, -// host, -// clients_ws_port: Some(gateway.entry.ws_port), -// clients_wss_port: gateway.entry.wss_port, -// mixnet_performance: Some(gateway.performance), -// mixnet_score: Some(Score::from(gateway.performance)), -// wg_performance: gateway.performance_v2.map(Performance::from), -// version: gateway.build_information.map(|info| info.build_version), -// }) -// } -// } - -pub type NymNodeList = GatewayList; - -#[derive(Debug, Clone)] -pub struct GatewayList { - /// If None, then the list contains mixed types. - gw_type: Option, - gateways: Vec, -} - -impl GatewayList { - pub fn new(gw_type: Option, gateways: Vec) -> Self { - GatewayList { gw_type, gateways } - } - - // Returns a list of all locations of the gateways, including duplicates - fn all_locations(&self) -> impl Iterator { - self.gateways - .iter() - .filter_map(|gateway| gateway.location.as_ref()) - } - - pub fn all_countries(&self) -> Vec { - self.all_locations() - .cloned() - .map(Country::from) - .unique() - .collect() - } - - pub fn all_iso_codes(&self) -> Vec { - self.all_countries() - .into_iter() - .map(|country| country.iso_code().to_string()) - .collect() - } - - pub fn filter(&self, filters: &[GatewayFilter]) -> Vec { - self.gateways - .iter() - .filter(|gateway| gateway.matches_all_filters(self.gw_type, filters)) - .cloned() - .collect() - } - - pub fn node_with_identity(&self, identity: &NodeIdentity) -> Option<&Gateway> { - // Not using self.filter() here as find() will stop at the first match - self.gateways - .iter() - .find(|node| &node.identity() == identity) - } - - pub fn gateway_with_identity(&self, identity: &NodeIdentity) -> Option<&Gateway> { - self.node_with_identity(identity) - } - - pub fn choose_random(&self, filters: &[GatewayFilter]) -> Option { - self.filter(filters) - .into_iter() - .choose(&mut rand::thread_rng()) - } - - pub fn remove_gateway(&mut self, entry_gateway: &Gateway) { - self.gateways - .retain(|gateway| gateway.identity() != entry_gateway.identity()); - } - - pub fn gw_type(&self) -> Option { - self.gw_type - } - - pub fn len(&self) -> usize { - self.gateways.len() - } - - pub fn is_empty(&self) -> bool { - self.gateways.is_empty() - } - - pub fn into_exit_gateways(self) -> GatewayList { - Self::new(self.gw_type, self.filter(&[GatewayFilter::Exit])) - } - - pub fn into_vpn_gateways(self) -> GatewayList { - Self::new(self.gw_type, self.filter(&[GatewayFilter::Vpn])) - } - - pub fn into_inner(self) -> Vec { - self.gateways - } -} - -impl IntoIterator for GatewayList { - type Item = Gateway; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.gateways.into_iter() - } -} - -impl nym_client_core::init::helpers::ConnectableGateway for Gateway { - #[allow(unconditional_recursion)] - fn node_id(&self) -> NodeId { - self.node_id() - } - - fn identity(&self) -> NodeIdentity { - self.identity() - } - - fn clients_address(&self, _prefer_ipv6: bool) -> Option { - // This is a bit of a sharp edge, but temporary until we can remove Option from host - // and tls port when we add these to the vpn API endpoints. - Some( - self.clients_address_tls() - .or(self.clients_address_no_tls()) - .unwrap_or("ws://".to_string()), - ) - } - - fn is_wss(&self) -> bool { - self.clients_address_tls().is_some() - } -} - -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, strum::EnumIter)] -pub enum GatewayType { - MixnetEntry, - MixnetExit, - Wg, -} - -impl fmt::Display for GatewayType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - GatewayType::MixnetEntry => write!(f, "mixnet entry"), - GatewayType::MixnetExit => write!(f, "mixnet exit"), - GatewayType::Wg => write!(f, "vpn"), - } - } -} - -impl From for GatewayType { - fn from(gateway_type: helpers::GatewayType) -> Self { - match gateway_type { - helpers::GatewayType::MixnetEntry => GatewayType::MixnetEntry, - helpers::GatewayType::MixnetExit => GatewayType::MixnetExit, - helpers::GatewayType::Wg => GatewayType::Wg, - } - } -} - -impl From for helpers::GatewayType { - fn from(gateway_type: GatewayType) -> Self { - match gateway_type { - GatewayType::MixnetEntry => helpers::GatewayType::MixnetEntry, - GatewayType::MixnetExit => helpers::GatewayType::MixnetExit, - GatewayType::Wg => helpers::GatewayType::Wg, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum GatewayFilter { - MinScore(ScoreValue), // Mixnet or Wg score - Country(String), // Two-letter ISO country code - Region(String), // Region name - Residential, // Has a residential ASN - Exit, // Has an IPR address - Vpn, // Has an authenticator address -} diff --git a/nym-gateway-directory/src/entries/ipr_addresses.rs b/nym-gateway-directory/src/entries/ipr_addresses.rs deleted file mode 100644 index 42de5ef878..0000000000 --- a/nym-gateway-directory/src/entries/ipr_addresses.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only -use nym_sphinx::addressing::clients::Recipient; -use nym_sphinx::addressing::nodes::NodeIdentity; -use nym_validator_client::models::NymNodeData; - -use crate::{Error, error::Result}; - -#[derive(Debug, Copy, Clone)] -pub struct IpPacketRouterAddress(Recipient); - -impl IpPacketRouterAddress { - pub fn try_from_base58_string(ip_packet_router_nym_address: &str) -> Result { - Ok(Self( - Recipient::try_from_base58_string(ip_packet_router_nym_address).map_err(|source| { - Error::RecipientFormattingError { - address: ip_packet_router_nym_address.to_string(), - source, - } - })?, - )) - } - - pub fn try_from_described_gateway(gateway: &NymNodeData) -> Result { - let address = gateway - .clone() - .ip_packet_router - .map(|ipr| ipr.address) - .ok_or(Error::MissingIpPacketRouterAddress)?; - Ok(Self(Recipient::try_from_base58_string(&address).map_err( - |source| Error::RecipientFormattingError { address, source }, - )?)) - } - - pub fn gateway(&self) -> NodeIdentity { - self.0.gateway() - } -} - -impl std::fmt::Display for IpPacketRouterAddress { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for IpPacketRouterAddress { - fn from(recipient: Recipient) -> Self { - Self(recipient) - } -} - -impl From for Recipient { - fn from(ipr_address: IpPacketRouterAddress) -> Self { - ipr_address.0 - } -} diff --git a/nym-gateway-directory/src/entries/mod.rs b/nym-gateway-directory/src/entries/mod.rs deleted file mode 100644 index 0a74d0afed..0000000000 --- a/nym-gateway-directory/src/entries/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub(crate) mod auth_addresses; -pub(crate) mod country; -pub(crate) mod entry_point; -pub(crate) mod exit_point; -pub(crate) mod gateway; -pub(crate) mod ipr_addresses; -pub(crate) mod score; diff --git a/nym-gateway-directory/src/entries/score.rs b/nym-gateway-directory/src/entries/score.rs deleted file mode 100644 index 5024ffab3d..0000000000 --- a/nym-gateway-directory/src/entries/score.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use crate::helpers::ScoreThresholds; - -pub(crate) const HIGH_SCORE_THRESHOLD: u8 = 80; -pub(crate) const MEDIUM_SCORE_THRESHOLD: u8 = 60; -pub(crate) const LOW_SCORE_THRESHOLD: u8 = 0; - -#[derive(Debug, Clone)] -pub enum Score { - High(u8), - Medium(u8), - Low(u8), - None, -} - -impl Score { - pub fn update_to_new_thresholds(&mut self, thresholds: ScoreThresholds) { - let score = match self { - Score::None => return, - Score::High(score) | Score::Medium(score) | Score::Low(score) => *score, - }; - *self = if score > thresholds.high { - Score::High(score) - } else if score > thresholds.medium { - Score::Medium(score) - } else if score > thresholds.low { - Score::Low(score) - } else { - Score::None - }; - } -} diff --git a/nym-gateway-directory/src/error.rs b/nym-gateway-directory/src/error.rs deleted file mode 100644 index 94c4f5e60d..0000000000 --- a/nym-gateway-directory/src/error.rs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use nym_http_api_client::HttpClientError; -use nym_validator_client::nym_api::error::NymAPIError; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("identity not formatted correctly: {identity}")] - NodeIdentityFormattingError { - identity: String, - source: nym_crypto::asymmetric::ed25519::Ed25519RecoveryError, - }, - - #[error("recipient is not formatted correctly: {address}")] - RecipientFormattingError { - address: String, - source: nym_sphinx::addressing::clients::RecipientFormattingError, - }, - - #[error(transparent)] - ValidatorClientError(#[from] nym_validator_client::ValidatorClientError), - - #[error("failed to resolve gateway hostname: {hostname}")] - FailedToDnsResolveGateway { - hostname: String, - source: nym_http_api_client::HickoryDnsError, - }, - - #[error("resolved hostname {0} but no IP address found")] - ResolvedHostnameButNoIp(String), - - #[error("timed out while attempting to resolve hostname: {hostname}")] - HostnameResolutionTimeout { hostname: String }, - - #[error("failed to lookup described gateways")] - FailedToLookupDescribedGateways(#[source] nym_validator_client::ValidatorClientError), - - #[error("failed to lookup skimmed gateways")] - FailedToLookupSkimmedGateways(#[source] nym_validator_client::ValidatorClientError), - - #[error("failed to lookup skimmed nodes")] - FailedToLookupSkimmedNodes(#[source] nym_validator_client::ValidatorClientError), - - #[error("requested gateway not found in the remote list: {0}")] - RequestedGatewayIdNotFound(String), - - #[error("missing ip packet router address for gateway")] - MissingIpPacketRouterAddress, - - #[error("missing hostname or ip address for gateway")] - MissingHostnameOrIpAddress { gateway_identity: String }, - - #[error("no matching gateway found: {requested_identity}")] - NoMatchingGateway { requested_identity: String }, - - #[error( - "no entry gateway available for location {requested_location}, available countries: {available_countries:?}" - )] - NoMatchingEntryGatewayForLocation { - requested_location: String, - available_countries: Vec, - }, - - #[error( - "no exit gateway available for location {requested_location}, available countries: {available_countries:?}" - )] - NoMatchingExitGatewayForLocation { - requested_location: String, - available_countries: Vec, - }, - - #[error("no matching gateway found after selecting low latency: {requested_identity}")] - NoMatchingGatewayAfterSelectingLowLatency { requested_identity: String }, - - #[error("failed to select gateway randomly")] - FailedToSelectGatewayRandomly, - - #[error("gateway {0} doesn't have a description available")] - NoGatewayDescriptionAvailable(String), - - #[error("failed to lookup gateway ip for gateway {0}")] - FailedToLookupIp(String), - - #[error("the url {url} doesn't parse to a host and/or a port: {reason}")] - UrlError { url: url::Url, reason: String }, - - #[error("the provided gateway information is malformed")] - MalformedGateway, - - #[error("no connectivity")] - Offline, - - #[error("HTTP client error: {0}")] - HttpClient(#[from] Box), - - #[error("Nym API error: {source}")] - NymApi { source: Box }, - - #[error("operation cancelled")] - Cancelled, - - #[error("invalid score value: {0}. Valid values are: offline, low, medium, high")] - InvalidScoreValue(String), - - #[error("ips vec empty")] - NoIpsAvailable, -} - -impl Error { - /// Returns true when no gateways matching the search criteria could be found, except when the gateway is constrained to identity - pub fn is_unmatched_non_specific_gateway(&self) -> bool { - matches!( - self, - Error::NoMatchingEntryGatewayForLocation { .. } - | Error::NoMatchingExitGatewayForLocation { .. } - | Error::FailedToSelectGatewayRandomly - ) - } -} - -// Result type based on our error type -pub type Result = std::result::Result; diff --git a/nym-gateway-directory/src/gateway_client.rs b/nym-gateway-directory/src/gateway_client.rs deleted file mode 100644 index dfb9b7d0c2..0000000000 --- a/nym-gateway-directory/src/gateway_client.rs +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright 2023-2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use std::{ - fmt, - net::{IpAddr, SocketAddr}, -}; - -use crate::{ - Error, GatewayMinPerformance, ScoreThresholds, - entries::gateway::{Gateway, GatewayList, GatewayType, NymNodeList}, - error::Result, -}; -use nym_contracts_common::Percent; -use nym_http_api_client::UserAgent; -use nym_validator_client::{ - models::NymNodeDescription, nym_api::NymApiClientExt, nym_nodes::SkimmedNodesWithMetadata, -}; -use rand::{prelude::SliceRandom, thread_rng}; -use tracing::{debug, error, warn}; -use url::Url; - -#[derive(Clone, Debug)] -pub struct Config { - pub nyxd_url: Url, - pub api_url: Url, - pub min_gateway_performance: Option, - pub mix_score_thresholds: Option, - pub wg_score_thresholds: Option, -} - -impl fmt::Display for Config { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "nyxd_url: {}, api_url: {}", self.nyxd_url, self.api_url,) - } -} - -impl Config { - pub fn nyxd_url(&self) -> &Url { - &self.nyxd_url - } - - pub fn with_custom_nyxd_url(mut self, nyxd_url: Url) -> Self { - self.nyxd_url = nyxd_url; - self - } - - pub fn api_url(&self) -> &Url { - &self.api_url - } - - pub fn with_custom_api_url(mut self, api_url: Url) -> Self { - self.api_url = api_url; - self - } - - pub fn with_min_gateway_performance( - mut self, - min_gateway_performance: GatewayMinPerformance, - ) -> Self { - self.min_gateway_performance = Some(min_gateway_performance); - self - } -} - -#[derive(Clone)] -pub struct GatewayClient { - api_client: nym_http_api_client::Client, - nyxd_url: Url, - min_gateway_performance: Option, - mix_score_thresholds: Option, - wg_score_thresholds: Option, -} - -impl GatewayClient { - pub fn new(config: Config, user_agent: UserAgent) -> Result { - Self::new_with_resolver_overrides(config, user_agent, None) - } - - pub fn new_with_resolver_overrides( - config: Config, - user_agent: UserAgent, - _static_nym_api_ip_addresses: Option<&[SocketAddr]>, - ) -> Result { - let api_client = nym_http_api_client::Client::builder(config.api_url.clone()) - .map_err(|e| Error::FailedToLookupDescribedGateways(e.into()))? - .with_user_agent(user_agent.clone()) - .build() - .map_err(|e| Error::FailedToLookupDescribedGateways(e.into()))?; - - Ok(GatewayClient { - api_client, - nyxd_url: config.nyxd_url, - min_gateway_performance: config.min_gateway_performance, - mix_score_thresholds: config.mix_score_thresholds, - wg_score_thresholds: config.wg_score_thresholds, - }) - } - - pub fn from_network_with_resolver_overrides( - config: Config, - network_details: &nym_network_defaults::NymNetworkDetails, - user_agent: UserAgent, - _static_nym_api_ip_addresses: Option<&[SocketAddr]>, - ) -> Result { - #[allow(deprecated)] - let api_client = nym_http_api_client::ClientBuilder::from_network(network_details) - .map_err(Box::new)? - .with_user_agent(user_agent.clone()) - .build() - .map_err(Box::new)?; - - Ok(GatewayClient { - api_client, - nyxd_url: config.nyxd_url, - min_gateway_performance: config.min_gateway_performance, - mix_score_thresholds: config.mix_score_thresholds, - wg_score_thresholds: config.wg_score_thresholds, - }) - } - - /// Return the config of this instance. - pub fn get_config(&self) -> Config { - Config { - api_url: self.api_client.api_url().clone(), - nyxd_url: self.nyxd_url.clone(), - min_gateway_performance: self.min_gateway_performance, - mix_score_thresholds: self.mix_score_thresholds, - wg_score_thresholds: self.wg_score_thresholds, - } - } - - pub fn mixnet_min_performance(&self) -> Option { - self.min_gateway_performance - .as_ref() - .and_then(|min_performance| min_performance.mixnet_min_performance) - } - - pub fn vpn_min_performance(&self) -> Option { - self.min_gateway_performance - .as_ref() - .and_then(|min_performance| min_performance.vpn_min_performance) - } - - async fn lookup_described_nodes(&self) -> Result> { - debug!("Fetching all described nodes from nym-api..."); - self.api_client - .get_all_described_nodes() - .await - .map_err(|e| Error::NymApi { - source: Box::new(e), - }) - } - - async fn lookup_skimmed_gateways(&self) -> Result { - debug!("Fetching skimmed entry assigned nodes from nym-api..."); - self.api_client - .get_all_basic_entry_assigned_nodes_with_metadata() - .await - .map_err(|e| Error::NymApi { - source: Box::new(e), - }) - } - - async fn lookup_skimmed_nodes(&self) -> Result { - debug!("Fetching skimmed entry assigned nodes from nym-api..."); - self.api_client - .get_all_basic_nodes_with_metadata() - .await - .map_err(|e| Error::NymApi { - source: Box::new(e), - }) - } - - pub async fn lookup_gateway_ip_from_nym_api(&self, gateway_identity: &str) -> Result { - debug!("Fetching gateway ip from nym-api..."); - let mut ips = self - .api_client - .get_all_described_nodes() - .await - .map_err(|e| Error::NymApi { - source: Box::new(e), - })? - .iter() - .find_map(|node| { - if node - .description - .host_information - .keys - .ed25519 - .to_base58_string() - == gateway_identity - { - Some(node.description.host_information.ip_address.clone()) - } else { - None - } - }) - .ok_or(Error::RequestedGatewayIdNotFound( - gateway_identity.to_string(), - ))?; - - if ips.is_empty() { - // nym-api should forbid this from ever happening, but we don't want to accidentally panic - // if this assumption fails - warn!("somehow {gateway_identity} hasn't provided any ip addresses!"); - return Err(Error::RequestedGatewayIdNotFound( - gateway_identity.to_string(), - )); - } - - debug!("found the following ips for {gateway_identity}: {ips:?}"); - if ips.len() == 1 { - // SAFETY: the vector is not empty, so unwrap is fine - #[allow(clippy::unwrap_used)] - Ok(ips.pop().unwrap()) - } else { - // chose a random one if there's more than one - // SAFETY: the vector is not empty, so unwrap is fine - let mut rng = thread_rng(); - let ip = ips.choose(&mut rng).ok_or_else(|| Error::NoIpsAvailable)?; - Ok(*ip) - } - } - - pub async fn lookup_all_gateways_from_nym_api(&self) -> Result { - let skimmed_gateways = self.lookup_skimmed_gateways().await?; - let key_rotation_id = skimmed_gateways.metadata.rotation_id; - - let mut gateways = self - .lookup_described_nodes() - .await? - .into_iter() - .filter(|node| node.description.declared_role.entry) - .filter_map(|gw| { - Gateway::try_from_node_description(gw, key_rotation_id) - .inspect_err(|err| error!("Failed to parse gateway: {err}")) - .ok() - }) - .collect::>(); - append_performance(&mut gateways, skimmed_gateways.nodes); - filter_on_mixnet_min_performance(&mut gateways, &self.min_gateway_performance); - update_thresholds(&mut gateways, self.mix_score_thresholds); - Ok(GatewayList::new(None, gateways)) - } - - pub async fn lookup_all_nymnodes(&self) -> Result { - let skimmed_nodes = self.lookup_skimmed_nodes().await?; - let key_rotation_id = skimmed_nodes.metadata.rotation_id; - - let mut nodes = self - .lookup_described_nodes() - .await? - .into_iter() - .filter_map(|gw| { - Gateway::try_from_node_description(gw, key_rotation_id) - .inspect_err(|err| error!("Failed to parse node: {err}")) - .ok() - }) - .collect::>(); - append_performance(&mut nodes, skimmed_nodes.nodes); - filter_on_mixnet_min_performance(&mut nodes, &self.min_gateway_performance); - update_thresholds(&mut nodes, self.mix_score_thresholds); - Ok(GatewayList::new(None, nodes)) - } - - pub async fn lookup_gateways_from_nym_api(&self, gw_type: GatewayType) -> Result { - match gw_type { - GatewayType::MixnetEntry => self.lookup_entry_gateways_from_nym_api().await, - GatewayType::MixnetExit => self.lookup_exit_gateways_from_nym_api().await, - GatewayType::Wg => self.lookup_vpn_gateways_from_nym_api().await, - } - } - - async fn lookup_entry_gateways_from_nym_api(&self) -> Result { - self.lookup_all_gateways_from_nym_api().await - } - - async fn lookup_exit_gateways_from_nym_api(&self) -> Result { - self.lookup_all_gateways_from_nym_api() - .await - .map(GatewayList::into_exit_gateways) - } - - async fn lookup_vpn_gateways_from_nym_api(&self) -> Result { - self.lookup_all_gateways_from_nym_api() - .await - .map(GatewayList::into_vpn_gateways) - } - - pub async fn lookup_gateway_ip(&self, gateway_identity: &str) -> Result { - self.lookup_gateway_ip_from_nym_api(gateway_identity).await - } - - pub async fn lookup_all_gateways(&self) -> Result { - self.lookup_all_gateways_from_nym_api().await - } - - pub async fn lookup_gateways(&self, gw_type: GatewayType) -> Result { - self.lookup_gateways_from_nym_api(gw_type).await - } -} - -fn append_performance( - gateways: &mut [Gateway], - basic_gw: Vec, -) { - debug!("Appending mixnet_performance to gateways"); - for gateway in gateways.iter_mut() { - if let Some(basic_gw) = basic_gw - .iter() - .find(|bgw| bgw.ed25519_identity_pubkey == gateway.identity()) - { - gateway.mixnet_performance = Some(basic_gw.performance); - } else { - tracing::warn!( - "Failed to append mixnet_performance, node {} not found among the skimmed nodes", - gateway.identity() - ); - } - } -} - -fn filter_on_mixnet_min_performance( - gateways: &mut Vec, - min_gateway_performance: &Option, -) { - if let Some(min_performance) = min_gateway_performance - && let Some(mixnet_min_performance) = min_performance.mixnet_min_performance - { - tracing::debug!( - "Filtering gateways based on mixnet_min_performance: {:?}", - min_performance - ); - gateways.retain(|gateway| { - gateway.mixnet_performance.unwrap_or_default() >= mixnet_min_performance - }); - } -} - -fn update_thresholds(gateways: &mut [Gateway], mix_score_thresholds: Option) { - for gateway in gateways.iter_mut() { - gateway.update_to_new_thresholds(mix_score_thresholds); - } -} diff --git a/nym-gateway-directory/src/helpers.rs b/nym-gateway-directory/src/helpers.rs deleted file mode 100644 index 3583868a90..0000000000 --- a/nym-gateway-directory/src/helpers.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use nym_contracts_common::Percent; -use serde::{Deserialize, Serialize}; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - -pub fn split_ips(ips: Vec) -> (Vec, Vec) { - ips.into_iter() - .fold((vec![], vec![]), |(mut v4, mut v6), ip| { - match ip { - IpAddr::V4(ipv4_addr) => v4.push(ipv4_addr), - IpAddr::V6(ipv6_addr) => v6.push(ipv6_addr), - } - (v4, v6) - }) -} - -// Types copied in from nym-vpn-client/nym-vpn-core/crates/nym-vpn-api-client - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NymDirectoryCountry(String); - -impl NymDirectoryCountry { - pub fn iso_code(&self) -> &str { - &self.0 - } - pub fn into_string(self) -> String { - self.0 - } -} - -impl From for NymDirectoryCountry { - fn from(s: String) -> Self { - Self(s) - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ScoreThresholds { - pub high: u8, - pub medium: u8, - pub low: u8, -} - -#[derive(Clone, Debug)] -pub enum GatewayType { - MixnetEntry, - MixnetExit, - Wg, -} - -#[derive(Clone, Copy, Default, Debug, Eq, PartialEq)] -pub struct GatewayMinPerformance { - pub mixnet_min_performance: Option, - pub vpn_min_performance: Option, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct BridgeInformation { - pub version: String, - pub transports: Vec, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(tag = "transport_type", content = "args")] -#[serde(rename_all = "snake_case")] -pub enum BridgeParameters { - QuicPlain(QuicClientOptions), -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct QuicClientOptions { - /// Address describing the remote transport server. This is a vec to support multiple addresses - /// so as to support both IPv4 and IPv6. These addresses are meant to describe a single bridge - /// as the key material should not be used across multiple instances. - pub addresses: Vec, - - /// Override hostname used for certificate verification - pub host: Option, - - /// Use identity public key to verify server self signed certificate - pub id_pubkey: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "snake_case")] -pub enum AsnKind { - Residential, - Other, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Asn { - pub asn: String, - pub name: String, - pub kind: AsnKind, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Location { - pub two_letter_iso_country_code: String, - pub latitude: f64, - pub longitude: f64, - - pub city: String, - pub region: String, - - pub asn: Option, -} diff --git a/nym-gateway-directory/src/lib.rs b/nym-gateway-directory/src/lib.rs deleted file mode 100644 index 25b97bcb22..0000000000 --- a/nym-gateway-directory/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -mod entries; -mod error; -mod gateway_client; -mod helpers; - -pub use crate::{ - entries::{ - auth_addresses::AuthAddress, - country::Country, - entry_point::EntryPoint, - exit_point::ExitPoint, - gateway::{ - Asn, AsnKind, Entry, Exit, Gateway, GatewayFilter, GatewayList, GatewayType, Location, - Performance, Probe, ProbeOutcome, ScoreValue, - }, - ipr_addresses::IpPacketRouterAddress, - score::Score, - }, - error::Error, - gateway_client::{Config, GatewayClient}, - helpers::{ - BridgeInformation, BridgeParameters, GatewayMinPerformance, NymDirectoryCountry, - QuicClientOptions, ScoreThresholds, split_ips, - }, -}; diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs index 31ac246d49..582f76f909 100644 --- a/sdk/rust/nym-sdk/src/error.rs +++ b/sdk/rust/nym-sdk/src/error.rs @@ -2,6 +2,7 @@ use nym_validator_client::nyxd::error::NyxdError; use std::path::PathBuf; use nym_ip_packet_requests::v8::response::{ConnectFailureReason, IpPacketResponseData}; +use nym_validator_client::nym_api::error::NymAPIError; /// Top-level Error enum for the mixnet client and its relevant types. #[derive(Debug, thiserror::Error)] @@ -132,8 +133,8 @@ pub enum Error { #[error("connect denied: {0:?}")] ConnectDenied(ConnectFailureReason), - #[error("gateway directory error: {0}")] - GatewayDirectoryError(#[from] nym_gateway_directory::Error), + #[error("api directory error: {0}")] + GatewayDirectoryError(#[from] NymAPIError), #[error("did not receive Validator endpoint details")] NoValidatorDetailsAvailable, diff --git a/sdk/rust/nym-sdk/src/ip_packet_client/connect.rs b/sdk/rust/nym-sdk/src/ip_packet_client/connect.rs index a7491fe9d7..a8fdca86b3 100644 --- a/sdk/rust/nym-sdk/src/ip_packet_client/connect.rs +++ b/sdk/rust/nym-sdk/src/ip_packet_client/connect.rs @@ -7,7 +7,6 @@ pub use crate::mixnet::{ InputMessage, MixnetClient, MixnetClientSender, MixnetMessageSender, Recipient, TransmissionLane, }; -use nym_gateway_directory::IpPacketRouterAddress; use nym_ip_packet_requests::IpPair; use tokio::time::sleep; use tokio_util::sync::CancellationToken; @@ -59,10 +58,7 @@ impl IprClientConnect { } } - pub async fn connect( - &mut self, - ip_packet_router_address: IpPacketRouterAddress, - ) -> Result { + pub async fn connect(&mut self, ip_packet_router_address: Recipient) -> Result { if self.connected != ConnectionState::Disconnected { return Err(Error::AlreadyConnected); } @@ -83,20 +79,14 @@ impl IprClientConnect { } } - async fn connect_inner( - &mut self, - ip_packet_router_address: IpPacketRouterAddress, - ) -> Result { + async fn connect_inner(&mut self, ip_packet_router_address: Recipient) -> Result { let request_id = self.send_connect_request(ip_packet_router_address).await?; debug!("Waiting for reply..."); self.listen_for_connect_response(request_id).await } - async fn send_connect_request( - &mut self, - ip_packet_router_address: IpPacketRouterAddress, - ) -> Result { + async fn send_connect_request(&mut self, ip_packet_router_address: Recipient) -> Result { let (request, request_id) = IpPacketRequest::new_connect_request(None); // We use 20 surbs for the connect request because typically the IPR is configured to have diff --git a/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs b/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs index 743b1fb5ab..d940a053b0 100644 --- a/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs +++ b/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs @@ -7,11 +7,9 @@ use crate::ip_packet_client::{ }; use crate::UserAgent; use crate::{mixnet::Recipient, Error}; +use std::collections::HashMap; use bytes::Bytes; -use nym_gateway_directory::{ - Config as GatewayConfig, GatewayClient, GatewayType, IpPacketRouterAddress, -}; use nym_ip_packet_requests::{ v8::{ request::IpPacketRequest, @@ -22,6 +20,9 @@ use nym_ip_packet_requests::{ use nym_sphinx::receiver::ReconstructedMessageCodec; use futures::StreamExt; +use nym_crypto::asymmetric::ed25519; +use nym_network_defaults::ApiUrl; +use nym_validator_client::nym_api::NymApiClientExt; use std::io; use std::pin::Pin; use std::task::{Context, Poll}; @@ -33,6 +34,13 @@ use tracing::{debug, error, info}; const IPR_CONNECT_TIMEOUT: Duration = Duration::from_secs(60); +#[derive(Clone)] +pub struct IprWithPerformance { + pub(crate) address: Recipient, + pub(crate) identity: ed25519::PublicKey, + pub(crate) performance: u8, +} + #[derive(Debug, Clone, PartialEq)] pub enum ConnectionState { Disconnected, @@ -40,7 +48,7 @@ pub enum ConnectionState { Connected, } -fn create_gateway_client() -> Result { +fn create_nym_api_client(nym_api_urls: Vec) -> Result { // TODO do something proper with this let user_agent = UserAgent { application: "nym-ipr-streamer".to_string(), @@ -49,55 +57,81 @@ fn create_gateway_client() -> Result { git_commit: "max/sdk-streamer".to_string(), }; - let mainnet_network_defaults = crate::NymNetworkDetails::default(); - let api_url = mainnet_network_defaults - .endpoints - .first() - .ok_or_else(|| Error::NoValidatorDetailsAvailable)? - .api_url() - .ok_or_else(|| Error::NoValidatorAPIUrl)?; + let urls = nym_api_urls + .into_iter() + .map(|url| url.url.parse()) + .collect::, _>>() + .map_err(|err| { + error!("malformed nym-api url: {err}"); + Error::NoNymAPIUrl + })?; - let nyxd_url = mainnet_network_defaults - .endpoints - .first() - .ok_or_else(|| Error::NoValidatorDetailsAvailable)? - .nyxd_url(); + if urls.is_empty() { + return Err(Error::NoNymAPIUrl); + } - let config = GatewayConfig { - nyxd_url, - api_url, - min_gateway_performance: None, - mix_score_thresholds: None, - wg_score_thresholds: None, - }; + let client = nym_http_api_client::ClientBuilder::new_with_urls(urls) + .with_user_agent(user_agent) + .build()?; - Ok(GatewayClient::new(config, user_agent)?) + Ok(client) } -async fn get_ipr_addr(client: GatewayClient) -> Result { - let exit_gateways = client.lookup_gateways(GatewayType::MixnetExit).await?; - - info!("Found {} Exit Gateways", exit_gateways.len()); - - let selected_gateway = exit_gateways +async fn retrieve_exit_nodes_with_performance( + client: nym_http_api_client::Client, +) -> Result, Error> { + // retrieve all nym-nodes on the network + let all_nodes = client + .get_all_described_nodes() + .await? .into_iter() - .filter(|gw| gw.ipr_address.is_some()) - .max_by_key(|gw| { - gw.mixnet_performance - .map(|p| p.round_to_integer()) - .unwrap_or(0) - }) + .map(|described| (described.ed25519_identity_key(), described)) + .collect::>(); + + // annoyingly there's no convenient way of doing this in a single query + // retrieve performance scores of all exit gateways + let exit_gateways = client + .get_all_basic_exit_assigned_nodes_with_metadata() + .await? + .nodes; + + let mut described = Vec::new(); + + for exit in exit_gateways { + if let Some(ipr_info) = all_nodes + .get(&exit.ed25519_identity_pubkey) + .and_then(|n| n.description.ip_packet_router.clone()) + { + if let Ok(parsed_address) = ipr_info.address.parse() { + described.push(IprWithPerformance { + address: parsed_address, + identity: exit.ed25519_identity_pubkey, + performance: exit.performance.round_to_integer(), + }) + } + } + } + + Ok(described) +} + +async fn get_random_ipr(client: nym_http_api_client::Client) -> Result { + let nodes = retrieve_exit_nodes_with_performance(client).await?; + info!("Found {} Exit Gateways", nodes.len()); + + // JS: I'm leaving the old behaviour here of choosing node with the highest performance, + // but I think you should reconsider making a pseudorandom selection weighted by some scaled performance + // otherwise all clients might end up choosing exactly the same node (I will leave this as PR comment when I get here : D) + let selected_gateway = nodes + .into_iter() + .max_by_key(|gw| gw.performance) .ok_or_else(|| Error::NoGatewayAvailable)?; - let ipr_address = selected_gateway - .ipr_address - .ok_or_else(|| Error::NoIPRAvailable)?; + let ipr_address = selected_gateway.address; info!( "Using IPR: {} (Gateway: {}, Performance: {:?})", - ipr_address, - selected_gateway.identity(), - selected_gateway.mixnet_performance + ipr_address, selected_gateway.identity, selected_gateway.performance ); Ok(ipr_address) @@ -106,7 +140,7 @@ async fn get_ipr_addr(client: GatewayClient) -> Result, connection_state: ConnectionState, @@ -115,8 +149,14 @@ pub struct IpMixStream { impl IpMixStream { // TODO be able to pass in DisconnectedMixnetClient to use as MixStream inner client. pub async fn new() -> Result { - let gw_client = create_gateway_client()?; - let ipr_address = get_ipr_addr(gw_client).await?; + // JS: I think you need some bootstrapping here, something would need to be passed to `new()` + // to indicate what endpoints to use + // again, for now I'm default to mainnet as you did before + let mainnet_network_defaults = crate::NymNetworkDetails::new_mainnet(); + + let api_client = + create_nym_api_client(mainnet_network_defaults.nym_api_urls.unwrap_or_default())?; + let ipr_address = get_random_ipr(api_client).await?; let stream = MixStream::new(None, Recipient::from(ipr_address)).await; Ok(Self {