Merge pull request #3814 from nymtech/jon/include-gateways-in-georouting

In geoaware routing, choose mixnodes close to exit gateway
This commit is contained in:
Tommy Verrall
2023-08-25 16:56:07 +02:00
committed by GitHub
6 changed files with 157 additions and 40 deletions
+12 -4
View File
@@ -17,7 +17,7 @@ use nym_client_core::client::base_client::storage::gateway_details::{
};
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
use nym_client_core::config::{GatewayEndpointConfig, TopologyStructure};
use nym_client_core::config::{GatewayEndpointConfig, GroupBy, TopologyStructure};
use nym_client_core::error::ClientCoreError;
use nym_config::OptionalSet;
use nym_sphinx::params::{PacketSize, PacketType};
@@ -101,9 +101,17 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
let secondary_packet_size = args.medium_toggle.then_some(PacketSize::ExtendedPacket16);
let no_per_hop_delays = args.medium_toggle;
let topology_structure = if args.medium_toggle || args.geo_routing.is_some() {
// TODO: rethink the default group. I just picked one for now.
TopologyStructure::GeoAware(args.geo_routing.unwrap_or(CountryGroup::Europe))
let topology_structure = if args.medium_toggle {
// Use the location of the network-requester
let address = config
.core
.socks5
.provider_mix_address
.parse()
.expect("failed to parse provider mix address");
TopologyStructure::GeoAware(GroupBy::NymAddress(address))
} else if let Some(code) = args.geo_routing {
TopologyStructure::GeoAware(GroupBy::CountryGroup(code))
} else {
TopologyStructure::default()
};
@@ -354,11 +354,13 @@ where
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
)),
config::TopologyStructure::GeoAware(group) => Box::new(GeoAwareTopologyProvider::new(
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
group,
)),
config::TopologyStructure::GeoAware(group_by) => {
Box::new(GeoAwareTopologyProvider::new(
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
group_by,
))
}
})
}
@@ -1,20 +1,36 @@
use std::{collections::HashMap, fmt};
use log::{debug, error, info};
use nym_explorer_api_requests::PrettyDetailedMixNodeBond;
use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
use nym_network_defaults::var_names::EXPLORER_API;
use nym_topology::{
nym_topology_from_detailed,
provider_trait::{async_trait, TopologyProvider},
NymTopology,
};
use nym_validator_client::client::MixId;
use nym_validator_client::client::{IdentityKey, MixId};
use rand::{prelude::SliceRandom, thread_rng};
use serde::{Deserialize, Serialize};
use tap::TapOptional;
use url::Url;
use crate::config::GroupBy;
const MIN_NODES_PER_LAYER: usize = 1;
#[cfg(target_arch = "wasm32")]
fn reqwest_client() -> Option<reqwest::Client> {
reqwest::Client::builder().build().ok()
}
#[cfg(not(target_arch = "wasm32"))]
fn reqwest_client() -> Option<reqwest::Client> {
reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.ok()
}
// TODO: create a explorer-api-client
async fn fetch_mixnodes_from_explorer_api() -> Option<Vec<PrettyDetailedMixNodeBond>> {
let explorer_api_url = std::env::var(EXPLORER_API).ok()?;
@@ -24,20 +40,7 @@ async fn fetch_mixnodes_from_explorer_api() -> Option<Vec<PrettyDetailedMixNodeB
.ok()?;
debug!("Fetching: {}", explorer_api_url);
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
let client = reqwest::Client::builder()
.build()
.ok()?;
} else {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.ok()?;
}
}
client
reqwest_client()?
.get(explorer_api_url)
.send()
.await
@@ -47,6 +50,25 @@ async fn fetch_mixnodes_from_explorer_api() -> Option<Vec<PrettyDetailedMixNodeB
.ok()
}
// TODO: create a explorer-api-client
async fn fetch_gateways_from_explorer_api() -> Option<Vec<PrettyDetailedGatewayBond>> {
let explorer_api_url = std::env::var(EXPLORER_API).ok()?;
let explorer_api_url = Url::parse(&explorer_api_url)
.ok()?
.join("v1/gateways")
.ok()?;
debug!("Fetching: {}", explorer_api_url);
reqwest_client()?
.get(explorer_api_url)
.send()
.await
.ok()?
.json::<Vec<PrettyDetailedGatewayBond>>()
.await
.ok()
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum CountryGroup {
Europe,
@@ -213,6 +235,23 @@ fn group_mixnodes_by_country_code(
})
}
fn group_gateways_by_country_code(
gateways: Vec<PrettyDetailedGatewayBond>,
) -> HashMap<CountryGroup, Vec<IdentityKey>> {
gateways.into_iter().fold(
HashMap::<CountryGroup, Vec<IdentityKey>>::new(),
|mut acc, g| {
if let Some(ref location) = g.location {
let country_code = location.two_letter_iso_country_code.clone();
let group_code = CountryGroup::new(country_code.as_str());
let gateways = acc.entry(group_code).or_insert_with(Vec::new);
gateways.push(g.gateway.identity_key)
}
acc
},
)
}
fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<MixId>>) {
let mixnode_distribution = mixnodes
.iter()
@@ -222,6 +261,15 @@ fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<MixId>>) {
debug!("Mixnode distribution - {}", mixnode_distribution);
}
fn log_gateway_distribution(gateways: &HashMap<CountryGroup, Vec<IdentityKey>>) {
let gateway_distribution = gateways
.iter()
.map(|(k, v)| format!("{}: {}", k, v.len()))
.collect::<Vec<_>>()
.join(", ");
debug!("Gateway distribution - {}", gateway_distribution);
}
fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
let mixes = topology.mixes();
if mixes.keys().len() < 3 {
@@ -244,7 +292,7 @@ fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
pub struct GeoAwareTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient,
filter_on: CountryGroup,
filter_on: GroupBy,
client_version: String,
}
@@ -252,7 +300,7 @@ impl GeoAwareTopologyProvider {
pub fn new(
mut nym_api_urls: Vec<Url>,
client_version: String,
filter_on: CountryGroup,
filter_on: GroupBy,
) -> GeoAwareTopologyProvider {
log::info!(
"Creating geo-aware topology provider with filter on {:?}",
@@ -294,6 +342,38 @@ impl GeoAwareTopologyProvider {
return None;
};
debug!("Fetching gateways from explorer-api...");
let Some(gateways_from_explorer_api) = fetch_gateways_from_explorer_api().await else {
error!("failed to get mixnodes from explorer-api");
return None;
};
// Determine what we should filter around
let filter_on = match self.filter_on {
GroupBy::CountryGroup(group) => group,
GroupBy::NymAddress(recipient) => {
// Convert recipient into a country group by extracting out the gateway part and
// using that as the country code.
let gateway = recipient.gateway().to_base58_string();
// Lookup the location of this gateway by using the location data from the
// explorer-api
let gateway_location = gateways_from_explorer_api
.iter()
.find(|g| g.gateway.identity_key == gateway)
.and_then(|g| g.location.clone())
.map(|location| location.two_letter_iso_country_code)
.tap_none(|| error!("No location found for the gateway: {}", gateway))?;
debug!(
"Filtering on nym-address: {}, with location: {}",
recipient, gateway_location
);
CountryGroup::new(&gateway_location)
}
};
debug!("Filter group: {}", filter_on);
// Partition mixnodes_from_explorer_api according to the value of
// two_letter_iso_country_code.
// NOTE: we construct the full distribution here, but only use the one we're interested in.
@@ -302,8 +382,16 @@ impl GeoAwareTopologyProvider {
let mixnode_distribution = group_mixnodes_by_country_code(mixnodes_from_explorer_api);
log_mixnode_distribution(&mixnode_distribution);
let Some(filtered_mixnode_ids) = mixnode_distribution.get(&self.filter_on) else {
error!("no mixnodes found for: {}", self.filter_on);
let gateway_distribution = group_gateways_by_country_code(gateways_from_explorer_api);
log_gateway_distribution(&gateway_distribution);
let Some(filtered_mixnode_ids) = mixnode_distribution.get(&filter_on) else {
error!("no mixnodes found for: {}", filter_on);
return None;
};
let Some(filtered_gateway_ids) = gateway_distribution.get(&filter_on) else {
error!("no gateways found for: {}", filter_on);
return None;
};
@@ -312,6 +400,11 @@ impl GeoAwareTopologyProvider {
.filter(|m| filtered_mixnode_ids.contains(&m.mix_id()))
.collect::<Vec<_>>();
let gateways = gateways
.into_iter()
.filter(|g| filtered_gateway_ids.contains(g.identity()))
.collect::<Vec<_>>();
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
+13 -2
View File
@@ -3,7 +3,10 @@
use nym_config::defaults::NymNetworkDetails;
use nym_crypto::asymmetric::identity;
use nym_sphinx::params::{PacketSize, PacketType};
use nym_sphinx::{
addressing::clients::Recipient,
params::{PacketSize, PacketType},
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
@@ -480,11 +483,19 @@ pub struct Topology {
pub topology_structure: TopologyStructure,
}
#[allow(clippy::large_enum_variant)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TopologyStructure {
#[default]
NymApi,
GeoAware(CountryGroup),
GeoAware(GroupBy),
}
#[allow(clippy::large_enum_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupBy {
CountryGroup(CountryGroup),
NymAddress(Recipient),
}
impl Default for Topology {
+1
View File
@@ -3884,6 +3884,7 @@ version = "1.1.15"
dependencies = [
"async-trait",
"base64 0.21.2",
"cfg-if",
"dashmap",
"dirs 4.0.0",
"futures",
+11 -9
View File
@@ -1,12 +1,9 @@
use futures::{channel::mpsc, StreamExt};
use nym_client_core::{
client::{
base_client::storage::{
gateway_details::GatewayDetailsStore, MixnetClientStorage, OnDiskPersistent,
},
topology_control::geo_aware_provider::CountryGroup,
client::base_client::storage::{
gateway_details::GatewayDetailsStore, MixnetClientStorage, OnDiskPersistent,
},
config::{GatewayEndpointConfig, TopologyStructure},
config::{GatewayEndpointConfig, GroupBy, TopologyStructure},
error::ClientCoreStatusMessage,
};
use nym_socks5_client_core::{NymClient as Socks5NymClient, Socks5ControlMessageSender};
@@ -54,12 +51,17 @@ fn override_config_from_env(config: &mut Config, privacy_level: &PrivacyLevel) {
config.core.base.set_no_per_hop_delays();
// TODO: selectable in the UI
let default_country_group = CountryGroup::Europe;
log::warn!("Using geo-aware mixnode selection: {default_country_group}");
let address = config
.core
.socks5
.provider_mix_address
.parse()
.expect("failed to parse provider mix address");
log::warn!("Using geo-aware mixnode selection baseon the location of: {address}");
config
.core
.base
.set_topology_structure(TopologyStructure::GeoAware(default_country_group));
.set_topology_structure(TopologyStructure::GeoAware(GroupBy::NymAddress(address)));
}
}