Different entry/exit selection criteria

This commit is contained in:
dynco-nym
2025-06-11 00:07:45 +02:00
parent 40d31e0cd0
commit eae677a898
4 changed files with 79 additions and 53 deletions
@@ -11,7 +11,6 @@ use axum::{
Json, Router,
};
use itertools::Itertools;
use nym_validator_client::nym_nodes::NodeRole;
use tracing::instrument;
pub(crate) fn routes() -> Router<AppState> {
@@ -43,11 +42,8 @@ pub async fn get_entry_gateways(state: State<AppState>) -> HttpResult<Json<Vec<D
Ok(Json(
state
.cache()
.get_dvpn_gateway_list(state.db_pool(), &MIN_SUPPORTED_VERSION)
.await
.into_iter()
.filter(|gw| matches!(gw.role, NodeRole::EntryGateway))
.collect(),
.get_entry_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
.await,
))
}
@@ -67,16 +63,10 @@ pub async fn get_entry_gateway_countries(state: State<AppState>) -> HttpResult<J
Ok(Json(
state
.cache()
.get_dvpn_gateway_list(state.db_pool(), &MIN_SUPPORTED_VERSION)
.get_entry_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
.await
.into_iter()
.filter_map(|gw| {
if matches!(gw.role, NodeRole::EntryGateway) {
Some(gw.location.two_letter_iso_country_code.to_string())
} else {
None
}
})
.map(|gw| gw.location.two_letter_iso_country_code.to_string())
// dedup relies on iterator being sorted by country, but we already do that
.dedup()
.collect(),
@@ -108,13 +98,10 @@ pub async fn get_entry_gateways_by_country(
Ok(Json(
state
.cache()
.get_dvpn_gateway_list(state.db_pool(), &MIN_SUPPORTED_VERSION)
.get_entry_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
.await
.into_iter()
.filter(|gw| {
matches!(gw.role, NodeRole::EntryGateway)
&& gw.location.two_letter_iso_country_code.to_lowercase() == country_filter
})
.filter(|gw| gw.location.two_letter_iso_country_code.to_lowercase() == country_filter)
.collect(),
))
}
@@ -11,7 +11,6 @@ use axum::{
Json, Router,
};
use itertools::Itertools;
use nym_validator_client::nym_nodes::NodeRole;
use tracing::instrument;
pub(crate) fn routes() -> Router<AppState> {
@@ -43,11 +42,8 @@ pub async fn get_exit_gateways(state: State<AppState>) -> HttpResult<Json<Vec<DV
Ok(Json(
state
.cache()
.get_dvpn_gateway_list(state.db_pool(), &MIN_SUPPORTED_VERSION)
.await
.into_iter()
.filter(|gw| matches!(gw.role, NodeRole::ExitGateway))
.collect(),
.get_exit_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
.await,
))
}
@@ -67,16 +63,10 @@ pub async fn get_entry_gateway_countries(state: State<AppState>) -> HttpResult<J
Ok(Json(
state
.cache()
.get_dvpn_gateway_list(state.db_pool(), &MIN_SUPPORTED_VERSION)
.get_exit_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
.await
.into_iter()
.filter_map(|gw| {
if matches!(gw.role, NodeRole::ExitGateway) {
Some(gw.location.two_letter_iso_country_code.to_string())
} else {
None
}
})
.map(|gw| gw.location.two_letter_iso_country_code.to_string())
// dedup relies on iterator being sorted by country, but we already do that
.dedup()
.collect(),
@@ -108,13 +98,10 @@ pub async fn get_exit_gateways_by_country(
Ok(Json(
state
.cache()
.get_dvpn_gateway_list(state.db_pool(), &MIN_SUPPORTED_VERSION)
.get_exit_dvpn_gateways(state.db_pool(), &MIN_SUPPORTED_VERSION)
.await
.into_iter()
.filter(|gw| {
matches!(gw.role, NodeRole::ExitGateway)
&& gw.location.two_letter_iso_country_code.to_lowercase() == country_filter
})
.filter(|gw| gw.location.two_letter_iso_country_code.to_lowercase() == country_filter)
.collect(),
))
}
@@ -1,3 +1,4 @@
use crate::monitor::ExplorerPrettyBond;
use cosmwasm_std::{Addr, Coin, Decimal};
use nym_mixnet_contract_common::CoinSchema;
use nym_node_requests::api::v1::node::models::NodeDescription;
@@ -15,8 +16,6 @@ use utoipa::ToSchema;
pub(crate) use nym_node_status_client::models::TestrunAssignment;
use crate::monitor::ExplorerPrettyBond;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct Gateway {
pub gateway_identity_key: String,
@@ -65,6 +64,37 @@ pub struct DVpnGateway {
pub build_information: BinaryBuildInformationOwned,
}
impl DVpnGateway {
pub fn can_route_entry(&self) -> bool {
self.last_probe
.as_ref()
.map(|probe| match &probe.outcome.as_entry {
directory_gw_probe_outcome::Entry::Tested(entry_test_result) => {
entry_test_result.can_route
}
directory_gw_probe_outcome::Entry::NotTested
| directory_gw_probe_outcome::Entry::EntryFailure => false,
})
.unwrap_or(false)
}
pub fn can_route_exit(&self) -> bool {
self.last_probe
.as_ref()
.map(|probe| {
probe
.outcome
.as_exit
.as_ref()
.map(|outcome| {
outcome.can_route_ip_external_v4 && outcome.can_route_ip_external_v6
})
.unwrap_or(false)
})
.unwrap_or(false)
}
}
/// based on
/// https://github.com/nymtech/nym-vpn-client/blob/nym-vpn-core-v1.10.0/nym-vpn-core/crates/nym-gateway-probe/src/types.rs
/// TODO: long term types should be moved into this repo because nym-vpn-client
@@ -109,11 +139,11 @@ pub mod directory_gw_probe_outcome {
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct Exit {
can_connect: bool,
can_route_ip_v4: bool,
can_route_ip_external_v4: bool,
can_route_ip_v6: bool,
can_route_ip_external_v6: bool,
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,
}
}
@@ -81,6 +81,7 @@ impl AppState {
}
static GATEWAYS_LIST_KEY: &str = "gateways";
static DVPN_GATEWAYS_LIST_KEY: &str = "dvpn_gateways";
static MIXNODES_LIST_KEY: &str = "mixnodes";
static NYM_NODES_LIST_KEY: &str = "nym_nodes";
static MIXSTATS_LIST_KEY: &str = "mixstats";
@@ -92,8 +93,7 @@ const MIXNODE_STATS_HISTORY_DAYS: usize = 30;
#[derive(Debug, Clone)]
pub(crate) struct HttpCache {
gateways: Cache<String, Arc<RwLock<Vec<Gateway>>>>,
// each min_version has their own cached list
dvpn_gateways: Cache<Version, Arc<RwLock<Vec<DVpnGateway>>>>,
dvpn_gateways: Cache<String, Arc<RwLock<Vec<DVpnGateway>>>>,
mixnodes: Cache<String, Arc<RwLock<Vec<Mixnode>>>>,
nym_nodes: Cache<String, Arc<RwLock<Vec<ExtendedNymNode>>>>,
mixstats: Cache<String, Arc<RwLock<Vec<DailyStats>>>>,
@@ -183,10 +183,9 @@ impl HttpCache {
pub async fn upsert_dvpn_gateway_list(
&self,
new_gateway_list: Vec<DVpnGateway>,
min_node_version: &Version,
) -> Entry<Version, Arc<RwLock<Vec<DVpnGateway>>>> {
) -> Entry<String, Arc<RwLock<Vec<DVpnGateway>>>> {
self.dvpn_gateways
.entry_by_ref(min_node_version)
.entry_by_ref(DVPN_GATEWAYS_LIST_KEY)
.and_upsert_with(|maybe_entry| async {
if let Some(entry) = maybe_entry {
let v = entry.into_value();
@@ -205,7 +204,7 @@ impl HttpCache {
db: &DbPool,
min_node_version: &Version,
) -> Vec<DVpnGateway> {
match self.dvpn_gateways.get(min_node_version).await {
match self.dvpn_gateways.get(DVPN_GATEWAYS_LIST_KEY).await {
Some(guard) => {
tracing::trace!("Fetching from cache...");
let read_lock = guard.read().await;
@@ -297,8 +296,7 @@ impl HttpCache {
if res_gws.is_empty() && started_with > 0 {
tracing::warn!("Started with {}, got 0 gateways", started_with);
} else {
self.upsert_dvpn_gateway_list(res_gws.clone(), min_node_version)
.await;
self.upsert_dvpn_gateway_list(res_gws.clone()).await;
}
res_gws
@@ -306,6 +304,30 @@ impl HttpCache {
}
}
pub async fn get_entry_dvpn_gateways(
&self,
db: &DbPool,
min_node_version: &Version,
) -> Vec<DVpnGateway> {
self.get_dvpn_gateway_list(db, min_node_version)
.await
.into_iter()
.filter(DVpnGateway::can_route_entry)
.collect()
}
pub async fn get_exit_dvpn_gateways(
&self,
db: &DbPool,
min_node_version: &Version,
) -> Vec<DVpnGateway> {
self.get_dvpn_gateway_list(db, min_node_version)
.await
.into_iter()
.filter(DVpnGateway::can_route_exit)
.collect()
}
pub async fn upsert_mixnode_list(
&self,
new_mixnode_list: Vec<Mixnode>,