Compare commits

...

10 Commits

Author SHA1 Message Date
dynco-nym ab1fc724ae Fix probe types 2025-06-05 23:58:26 +02:00
dynco-nym 775250b0ee Bump package version 2025-06-05 20:30:45 +02:00
dynco-nym e975051566 Filter by semver 2025-06-05 20:30:45 +02:00
dynco-nym 8207a9c38a Return percent string instead of u8 2025-06-05 20:30:45 +02:00
dynco-nym bbdf0f158a Filter gw by country 2025-06-05 20:30:45 +02:00
dynco-nym b276a8bc61 Revert before merge: pin deps to cheddar release 2025-06-05 20:30:43 +02:00
dynco-nym f31e6f2470 Rest of the data for /gateways 2025-06-05 20:29:56 +02:00
dynco-nym ea08345025 /gateways works
- SkimmedNode data still missing
- need to move probe models to monorepo
2025-06-05 20:29:56 +02:00
dynco-nym 3e5537b753 Endpoint & cache 2025-06-05 20:29:56 +02:00
Mark Sinclair 23f86b6c1e wip - dvpn directory cache 2025-06-05 20:29:56 +02:00
14 changed files with 1032 additions and 280 deletions
Generated
+517 -229
View File
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@
set -eu
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"}
probe_git_ref="nym-vpn-core-v1.4.0"
probe_git_ref="nym-vpn-core-v1.10.0"
crate_root=$(dirname $(realpath "$0"))
monorepo_root=$(realpath "${crate_root}/../..")
@@ -3,7 +3,7 @@
[package]
name = "nym-node-status-api"
version = "2.3.3"
version = "2.4.1-rc.1"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -23,23 +23,39 @@ envy = { workspace = true }
futures-util = { workspace = true }
itertools = { workspace = true }
moka = { workspace = true, features = ["future"] }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] }
nym-bin-common = { path = "../../common/bin-common", features = ["models"] }
nym-node-status-client = { path = "../nym-node-status-client" }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
nym-http-api-client = { path = "../../common/http-api-client" }
nym-http-api-common = { path = "../../common/http-api-common", features = ["middleware"]}
nym-network-defaults = { path = "../../common/network-defaults" }
nym-serde-helpers = { path = "../../common/serde-helpers" }
nym-statistics-common = { path = "../../common/statistics" }
nym-validator-client = { path = "../../common/client-libs/validator-client" }
nym-task = { path = "../../common/task" }
nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] }
# TODO dz had to switch to cheddar versions because develop is ahead of current
# Nym API: revert after Cheddar is out
nym-contracts-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
nym-mixnet-contract-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
nym-node-status-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
nym-http-api-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
nym-http-api-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = ["middleware"]}
nym-network-defaults = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
nym-serde-helpers = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
nym-statistics-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
# nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
# nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] }
# nym-bin-common = { path = "../../common/bin-common", features = ["models"] }
# nym-node-status-client = { path = "../nym-node-status-client" }
# nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
# nym-http-api-client = { path = "../../common/http-api-client" }
# nym-http-api-common = { path = "../../common/http-api-common", features = ["middleware"]}
# nym-network-defaults = { path = "../../common/network-defaults" }
# nym-serde-helpers = { path = "../../common/serde-helpers" }
# nym-statistics-common = { path = "../../common/statistics" }
# nym-validator-client = { path = "../../common/client-libs/validator-client" }
nym-validator-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
# nym-task = { path = "../../common/task" }
# nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] }
nym-task = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
nym-node-requests = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" }
rand = { workspace = true }
rand_chacha = { workspace = true }
regex = { workspace = true }
reqwest = { workspace = true }
semver = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_json_path = { workspace = true }
@@ -1,5 +1,3 @@
use std::str::FromStr;
use crate::{
http::{self, models::SummaryHistory},
utils::{decimal_to_i64, unix_timestamp_to_utc_rfc3339, NumericalCheckedCast},
@@ -14,6 +12,7 @@ use nym_validator_client::{
};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use std::str::FromStr;
use strum_macros::{EnumString, FromRepr};
use time::{Date, OffsetDateTime};
use utoipa::ToSchema;
@@ -111,7 +111,8 @@ pub(crate) async fn get_all_gateways(pool: &DbPool) -> anyhow::Result<Vec<Gatewa
error!("Conversion from DTO failed: {e}. Invalidly stored data?");
e
})?;
tracing::trace!("Fetched {} gateways from DB", items.len());
// TODO dz revert to trace
tracing::info!("Fetched {} gateways from DB", items.len());
Ok(items)
}
@@ -36,6 +36,8 @@ pub(crate) async fn get_all_nym_nodes(pool: &DbPool) -> anyhow::Result<Vec<NymNo
bond_info as "bond_info: serde_json::Value"
FROM
nym_nodes
ORDER BY
node_id
"#,
)
.fetch(&mut *conn)
@@ -0,0 +1,111 @@
use axum::{
extract::{Path, Query, State},
Json, Router,
};
use semver::Version;
use serde::Deserialize;
use std::sync::LazyLock;
use tracing::instrument;
use utoipa::IntoParams;
use crate::http::models::DVpnGateway;
use crate::http::{
error::{HttpError, HttpResult},
state::AppState,
};
static MIN_SUPPORTED_VERSION: LazyLock<Version> = LazyLock::new(|| Version::new(1, 6, 2));
pub(crate) fn routes() -> Router<AppState> {
Router::new()
.route("/", axum::routing::get(dvpn_gateways))
.route(
"/country/:two_letter_country_code",
axum::routing::get(gateways_by_country),
)
}
#[derive(Deserialize, IntoParams)]
#[into_params(parameter_in = Query)]
struct MinNodeVersionQuery {
min_node_version: Option<String>,
}
#[utoipa::path(
tag = "dVPN Directory Cache",
get,
params(
MinNodeVersionQuery
),
path = "/directory/gateways",
summary = "Gets available entry and exit gateways from the Nym network directory",
context_path = "/dvpn/v1",
responses(
(status = 200, body = Vec<DVpnGateway>)
)
)]
#[instrument(level = tracing::Level::INFO, skip(state))]
async fn dvpn_gateways(
Query(MinNodeVersionQuery { min_node_version }): Query<MinNodeVersionQuery>,
state: State<AppState>,
) -> HttpResult<Json<Vec<DVpnGateway>>> {
let min_node_version = match min_node_version {
Some(min_version) => semver::Version::parse(&min_version)
.map_err(|_| HttpError::invalid_input("Min version must be valid semver"))?,
None => MIN_SUPPORTED_VERSION.clone(),
};
Ok(Json(
state
.cache()
.get_dvpn_gateway_list(state.db_pool(), &min_node_version)
.await,
))
}
#[allow(dead_code)] // clippy doesn't detect usage in utoipa macros
#[derive(Deserialize, IntoParams)]
#[into_params(parameter_in = Path)]
struct TwoLetterCountryCodeParam {
#[param(min_length = 2, max_length = 2)]
two_letter_country_code: String,
}
#[utoipa::path(
tag = "dVPN Directory Cache",
get,
params(
TwoLetterCountryCodeParam
),
path = "/directory/gateways/country/{two_letter_country_code}",
summary = "Gets available gateways from the Nym network directory by country",
context_path = "/dvpn/v1",
responses(
(status = 200, body = Vec<DVpnGateway>)
)
)]
#[instrument(level = tracing::Level::INFO, skip(state), fields(two_letter_country_code = two_letter_country_code))]
async fn gateways_by_country(
Path(TwoLetterCountryCodeParam {
two_letter_country_code,
}): Path<TwoLetterCountryCodeParam>,
state: State<AppState>,
) -> HttpResult<Json<Vec<DVpnGateway>>> {
let country_filter = two_letter_country_code.to_lowercase();
match two_letter_country_code.len() {
2 => Ok(Json(
state
.cache()
.get_dvpn_gateway_list(state.db_pool(), &MIN_SUPPORTED_VERSION)
.await
.into_iter()
.filter(|gw| {
gw.location.two_letter_iso_country_code.to_lowercase() == country_filter
})
.collect(),
)),
_ => Err(HttpError::invalid_input(
"Only two letter country code is allowed",
)),
}
}
@@ -8,6 +8,7 @@ use utoipa_swagger_ui::SwaggerUi;
use crate::http::{server::HttpServer, state::AppState};
pub(crate) mod dvpn;
pub(crate) mod gateways;
pub(crate) mod metrics;
pub(crate) mod mixnodes;
@@ -44,6 +45,10 @@ impl RouterBuilder {
"/explorer/v3",
Router::new().nest("/nym-nodes", nym_nodes::routes()),
)
.nest(
"/dvpn/v1",
Router::new().nest("/directory/gateways", dvpn::routes()),
)
.nest(
"/internal",
Router::new().nest("/testruns", testruns::routes()),
@@ -1,12 +1,22 @@
use cosmwasm_std::{Addr, Coin, Decimal};
use nym_mixnet_contract_common::CoinSchema;
use nym_node_requests::api::v1::node::models::NodeDescription;
use nym_validator_client::client::NodeId;
use nym_validator_client::{
client::NodeId,
models::{
AuthenticatorDetails, BinaryBuildInformationOwned, IpPacketRouterDetails, NymNodeData,
},
nym_api::SkimmedNode,
nym_nodes::{BasicEntryInformation, NodeRole},
};
use serde::{Deserialize, Serialize};
use tracing::{error, instrument};
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,
@@ -23,6 +33,192 @@ pub struct Gateway {
pub config_score: u32,
}
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct BuildInformation {
pub build_version: String,
pub commit_branch: String,
pub commit_sha: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct Location {
pub two_letter_iso_country_code: String,
pub latitude: f64,
pub longitude: f64,
}
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct DVpnGateway {
pub identity_key: String,
pub name: String,
pub ip_packet_router: Option<IpPacketRouterDetails>,
pub authenticator: Option<AuthenticatorDetails>,
pub location: Location,
pub last_probe: Option<DirectoryGwProbe>,
pub ip_addresses: Vec<String>,
pub mix_port: u16,
pub role: NodeRole,
pub entry: Option<BasicEntryInformation>,
// The performance data here originates from the nym-api, and is effectively mixnet performance
// at the time of writing this
pub performance: String,
pub build_information: BinaryBuildInformationOwned,
}
/// 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
/// could pull it as a dependency and we'd have a single source of truth
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct LastProbeResult {
node: String,
used_entry: String,
outcome: ProbeOutcome,
}
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct DirectoryGwProbe {
last_updated_utc: String,
outcome: ProbeOutcome,
}
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct ProbeOutcome {
as_entry: directory_gw_probe_outcome::Entry,
as_exit: Option<directory_gw_probe_outcome::Exit>,
wg: Option<wg_outcome_versions::ProbeOutcomeV1>,
}
pub mod directory_gw_probe_outcome {
use super::*;
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(untagged)]
#[allow(clippy::enum_variant_names)]
pub enum Entry {
Tested(EntryTestResult),
NotTested,
EntryFailure,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct EntryTestResult {
pub can_connect: bool,
pub can_route: bool,
}
#[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 mod wg_outcome_versions {
use super::*;
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct ProbeOutcomeV1 {
pub can_register: bool,
pub can_handshake_v4: bool,
pub can_resolve_dns_v4: bool,
pub ping_hosts_performance_v4: f32,
pub ping_ips_performance_v4: f32,
pub can_handshake_v6: bool,
pub can_resolve_dns_v6: bool,
pub ping_hosts_performance_v6: f32,
pub ping_ips_performance_v6: f32,
pub download_duration_sec_v4: u64,
pub downloaded_file_v4: String,
pub download_error_v4: String,
pub download_duration_sec_v6: u64,
pub downloaded_file_v6: String,
pub download_error_v6: String,
}
}
impl DVpnGateway {
#[instrument(level = tracing::Level::INFO, name = "dvpn_gw_new", skip_all, fields(gateway_key = gateway.gateway_identity_key, node_id = skimmed_node.node_id))]
pub(crate) fn new(gateway: Gateway, skimmed_node: &SkimmedNode) -> anyhow::Result<Self> {
let location = gateway
.explorer_pretty_bond
.ok_or_else(|| anyhow::anyhow!("Missing explorer_pretty_bond"))
.and_then(|value| {
serde_json::from_value::<ExplorerPrettyBond>(value).map_err(From::from)
})
.map(|bond| bond.location)?;
let self_described = gateway
.self_described
.ok_or_else(|| anyhow::anyhow!("Missing self_described"))
.and_then(|value| serde_json::from_value::<NymNodeData>(value).map_err(From::from))?;
let last_probe_result = match gateway.last_probe_result {
Some(value) => {
let parsed =
serde_json::from_value::<LastProbeResult>(value).inspect_err(|err| {
error!("Failed to deserialize probe result: {err}");
})?;
Some(parsed)
}
None => None,
};
Ok(Self {
identity_key: gateway.gateway_identity_key,
name: gateway.description.moniker,
ip_packet_router: self_described.ip_packet_router,
authenticator: self_described.authenticator,
location: Location {
latitude: location.location.latitude,
longitude: location.location.longitude,
two_letter_iso_country_code: location.two_letter_iso_country_code,
},
last_probe: last_probe_result.map(|res| DirectoryGwProbe {
last_updated_utc: gateway.last_testrun_utc.unwrap_or_default(),
outcome: res.outcome,
}),
ip_addresses: skimmed_node
.ip_addresses
.iter()
.map(|ip| ip.to_string())
.collect(),
mix_port: skimmed_node.mix_port,
role: skimmed_node.role.clone(),
entry: skimmed_node.entry.clone(),
performance: to_percent(gateway.performance),
build_information: self_described.build_information,
})
}
}
fn to_percent(performance: u8) -> String {
let fraction = performance as f32 / 100.0;
format!("{:.2}", fraction)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn to_percent_should_work() {
let starting = [0u8, 33, 50, 99, 100];
let expected = ["0.00", "0.33", "0.50", "0.99", "1.00"];
for (starting, expected) in starting.into_iter().zip(expected) {
assert_eq!(expected, to_percent(starting));
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct GatewaySkinny {
pub gateway_identity_key: String,
@@ -1,16 +1,20 @@
use cosmwasm_std::Decimal;
use itertools::Itertools;
use moka::{future::Cache, Entry};
use nym_contracts_common::NaiveFloat;
use nym_crypto::asymmetric::ed25519::PublicKey;
use nym_mixnet_contract_common::NodeId;
use nym_validator_client::nym_api::SkimmedNode;
use semver::Version;
use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::RwLock;
use tracing::instrument;
use tracing::{error, instrument, warn};
use crate::{
db::{queries, DbPool},
http::models::{DailyStats, ExtendedNymNode, Gateway, Mixnode, NodeGeoData, SummaryHistory},
http::models::{
DVpnGateway, DailyStats, ExtendedNymNode, Gateway, Mixnode, NodeGeoData, SummaryHistory,
},
monitor::{DelegationsCache, NodeGeoCache},
};
@@ -88,6 +92,8 @@ 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>>>>,
mixnodes: Cache<String, Arc<RwLock<Vec<Mixnode>>>>,
nym_nodes: Cache<String, Arc<RwLock<Vec<ExtendedNymNode>>>>,
mixstats: Cache<String, Arc<RwLock<Vec<DailyStats>>>>,
@@ -102,6 +108,10 @@ impl HttpCache {
.max_capacity(2)
.time_to_live(Duration::from_secs(ttl_seconds))
.build(),
dvpn_gateways: Cache::builder()
.max_capacity(6)
.time_to_live(Duration::from_secs(ttl_seconds))
.build(),
mixnodes: Cache::builder()
.max_capacity(2)
.time_to_live(Duration::from_secs(ttl_seconds))
@@ -158,10 +168,11 @@ impl HttpCache {
let gateways = crate::db::queries::get_all_gateways(db)
.await
.unwrap_or_default();
self.upsert_gateway_list(gateways.clone()).await;
if gateways.is_empty() {
tracing::warn!("Database contains 0 gateways");
tracing::warn!("Database: gateway list is empty");
} else {
self.upsert_gateway_list(gateways.clone()).await;
}
gateways
@@ -169,6 +180,132 @@ 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>>>> {
self.dvpn_gateways
.entry_by_ref(min_node_version)
.and_upsert_with(|maybe_entry| async {
if let Some(entry) = maybe_entry {
let v = entry.into_value();
let mut guard = v.write().await;
*guard = new_gateway_list;
v.clone()
} else {
Arc::new(RwLock::new(new_gateway_list))
}
})
.await
}
pub async fn get_dvpn_gateway_list(
&self,
db: &DbPool,
min_node_version: &Version,
) -> Vec<DVpnGateway> {
match self.dvpn_gateways.get(min_node_version).await {
Some(guard) => {
tracing::trace!("Fetching from cache...");
let read_lock = guard.read().await;
read_lock.clone()
}
None => {
tracing::trace!("No gateways (dVPN) in cache, refreshing from DB...");
let gateways = self.get_gateway_list(db).await;
let started_with = gateways.len();
let Ok(skimmed_nodes) = crate::db::queries::get_described_bonded_nym_nodes(db)
.await
.map(|records| {
records
.into_iter()
.filter_map(|dto| SkimmedNode::try_from(dto).ok())
.map(|skimmed_node| {
(
skimmed_node.ed25519_identity_pubkey.to_base58_string(),
skimmed_node,
)
})
.collect::<HashMap<_, _>>()
})
.inspect_err(|err| {
// this would fail only in case of internal error: log and return gracefully
error!("Failed to get nym_nodes from DB: {err}");
})
else {
return Vec::new();
};
let res_gws = gateways
.into_iter()
.filter(|gw| gw.bonded)
.filter_map(|gw| match skimmed_nodes.get(&gw.gateway_identity_key) {
Some(skimmed_node) => Some((gw, skimmed_node)),
None => {
warn!(
"No SkimmedNode data found for GW, identity_key={}",
gw.gateway_identity_key
);
None
}
})
.filter_map(
|(gw, skimmed_node)| match DVpnGateway::new(gw, skimmed_node) {
Ok(gw) => Some(gw),
Err(err) => {
error!(
"Failed to convert GW node_id={} to dVPN form: {}",
skimmed_node.node_id, err
);
None
}
},
)
.filter(|gw| {
let gw_version = &gw.build_information.build_version;
if let Ok(gw_version) = Version::parse(gw_version) {
&gw_version >= min_node_version
} else {
warn!("Failed to parse GW version {}", gw_version);
false
}
})
.filter(|gw| {
// gateways must have a country
if gw.location.two_letter_iso_country_code.len() == 2 {
true
} else {
warn!(
"Invalid country code: {}",
gw.location.two_letter_iso_country_code
);
false
}
})
// sort by country, then by identity key
.sorted_by_key(|item| {
(
item.location.two_letter_iso_country_code.clone(),
item.identity_key.clone(),
)
})
.collect::<Vec<_>>();
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;
}
res_gws
}
}
}
pub async fn upsert_mixnode_list(
&self,
new_mixnode_list: Vec<Mixnode>,
@@ -201,10 +338,11 @@ impl HttpCache {
let mixnodes = crate::db::queries::get_all_mixnodes(db)
.await
.unwrap_or_default();
self.upsert_mixnode_list(mixnodes.clone()).await;
if mixnodes.is_empty() {
tracing::warn!("Database contains 0 mixnodes");
} else {
self.upsert_mixnode_list(mixnodes.clone()).await;
}
mixnodes
@@ -245,14 +383,13 @@ impl HttpCache {
None => {
tracing::trace!("No nym nodes in cache, refreshing cache from DB...");
let nym_nodes = aggregate_node_info_from_db(db, node_geocache)
.await
.inspect(|nym_nodes| {
if nym_nodes.is_empty() {
tracing::warn!("Database contains 0 nym nodes");
}
})?;
self.upsert_nym_node_list(nym_nodes.clone()).await;
let nym_nodes = aggregate_node_info_from_db(db, node_geocache).await?;
if nym_nodes.is_empty() {
tracing::warn!("Database contains 0 nym nodes");
} else {
self.upsert_nym_node_list(nym_nodes.clone()).await;
}
Ok(nym_nodes)
}
@@ -63,7 +63,7 @@ impl IpInfoClient {
}
}
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct ExplorerPrettyBond {
pub(crate) identity_key: String,
pub(crate) owner: Addr,
@@ -71,7 +71,7 @@ pub(crate) struct ExplorerPrettyBond {
pub(crate) location: Location,
}
#[derive(Debug, Clone, Default, Serialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub(crate) struct Location {
pub(crate) two_letter_iso_country_code: String,
#[serde(flatten)]
@@ -7,7 +7,6 @@ use crate::db::models::{
NYMNODES_DESCRIBED_COUNT, NYMNODE_COUNT,
};
use crate::db::{queries, DbPool};
use crate::monitor::geodata::{ExplorerPrettyBond, Location};
use crate::utils::now_utc;
use crate::utils::{decimal_to_i64, LogError, NumericalCheckedCast};
use anyhow::anyhow;
@@ -30,7 +29,7 @@ use std::{
use tokio::{sync::RwLock, time::Duration};
use tracing::instrument;
pub(crate) use geodata::IpInfoClient;
pub(crate) use geodata::{ExplorerPrettyBond, IpInfoClient, Location};
pub(crate) use node_delegations::DelegationsCache;
mod geodata;
@@ -106,11 +105,10 @@ impl Monitor {
.clone()
.expect("rust sdk mainnet default missing api_url");
let nym_api =
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
.no_hickory_dns()
.with_timeout(self.nym_api_client_timeout)
.build::<&str>()?;
let nym_api = nym_http_api_client::ClientBuilder::new_with_url(default_api_url)
.no_hickory_dns()
.with_timeout(self.nym_api_client_timeout)
.build::<&str>()?;
let api_client = NymApiClient::from(nym_api);
@@ -134,6 +132,8 @@ impl Monitor {
})
.collect::<Vec<_>>();
tracing::info!("🟣 🚪 gateway nodes: {}", gateways.len());
let bonded_nym_nodes = api_client
.get_all_bonded_nym_nodes()
.await?
@@ -150,7 +150,8 @@ impl Monitor {
.await
.log_error("get_all_basic_nodes")?;
tracing::info!("🟣 get_all_basic_nodes: {}", nym_nodes.len());
let nym_node_count = nym_nodes.len();
tracing::info!("🟣 get_all_basic_nodes: {}", nym_node_count);
let nym_node_records =
self.prepare_nym_node_data(nym_nodes.clone(), &bonded_nym_nodes, &described_nodes);
@@ -251,7 +252,7 @@ impl Monitor {
//
let nodes_summary = vec![
(NYMNODE_COUNT, nym_nodes.len()),
(NYMNODE_COUNT, nym_node_count),
(ASSIGNED_MIXING_COUNT, assigned_mixing_count),
(MIXNODES_LEGACY_COUNT, count_legacy_mixnodes),
(NYMNODES_DESCRIBED_COUNT, described_nodes.len()),
@@ -267,7 +268,7 @@ impl Monitor {
let last_updated = now_utc();
let last_updated_utc = last_updated.unix_timestamp().to_string();
let network_summary = NetworkSummary {
total_nodes: nym_nodes.len().cast_checked()?,
total_nodes: nym_node_count.cast_checked()?,
mixnodes: mixnode::MixnodeSummary {
bonded: mixnode::MixingNodesSummary {
count: assigned_mixing_count.cast_checked()?,
@@ -457,11 +458,7 @@ impl Monitor {
async fn check_ipinfo_bandwidth(&self) {
match self.ipinfo.check_remaining_bandwidth().await {
Ok(bandwidth) => {
tracing::info!(
"ipinfo monthly bandwidth: {}/{} spent",
bandwidth.month,
bandwidth.limit
);
tracing::info!("ipinfo monthly bandwidth: {} spent", bandwidth.month);
}
Err(err) => {
tracing::debug!("Couldn't check ipinfo bandwidth: {}", err);
@@ -57,7 +57,7 @@ async fn run(
.clone()
.expect("rust sdk mainnet default missing api_url");
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
let nym_api = nym_http_api_client::ClientBuilder::new_with_url(default_api_url)
.no_hickory_dns()
.with_timeout(nym_api_client_timeout)
.build::<&str>()?;
@@ -24,7 +24,7 @@ pub(crate) async fn spawn(pool: DbPool, refresh_interval: Duration) {
});
}
#[instrument(level = "debug", name = "testrun_queue", skip_all)]
#[instrument(level = "info", name = "testrun_queue", skip_all)]
async fn run(pool: &DbPool) -> anyhow::Result<()> {
tracing::info!("Spawning testruns...");
if pool.is_closed() {