Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added hacky routes to return nymnodes alongside legacy nodes #5051

Merged
merged 5 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion common/client-libs/validator-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ use time::Date;
use url::Url;

pub use crate::nym_api::NymApiClientExt;
use nym_mixnet_contract_common::NymNodeDetails;
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId,
};

// re-export the type to not break existing imports
pub use crate::coconut::EcashApiClient;

Expand Down Expand Up @@ -106,7 +106,9 @@ impl Config {

pub struct Client<C, S = NoSigner> {
// ideally they would have been read-only, but unfortunately rust doesn't have such features
// #[deprecated(note = "please use `nym_api_client` instead")]
pub nym_api: nym_api::Client,
// pub nym_api_client: NymApiClient,
pub nyxd: NyxdClient<C, S>,
}

Expand Down Expand Up @@ -243,6 +245,50 @@ impl<C, S> Client<C, S> {
Ok(self.nym_api.get_gateways().await?)
}

// TODO: combine with NymApiClient...
pub async fn get_all_cached_described_nodes(
&self,
) -> Result<Vec<NymNodeDescription>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut descriptions = Vec::new();

loop {
let mut res = self.nym_api.get_nodes_described(Some(page), None).await?;

descriptions.append(&mut res.data);
if descriptions.len() < res.pagination.total {
page += 1
} else {
break;
}
}

Ok(descriptions)
}

// TODO: combine with NymApiClient...
pub async fn get_all_cached_bonded_nym_nodes(
&self,
) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut bonds = Vec::new();

loop {
let mut res = self.nym_api.get_nym_nodes(Some(page), None).await?;

bonds.append(&mut res.data);
if bonds.len() < res.pagination.total {
page += 1
} else {
break;
}
}

Ok(bonds)
}

pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
Expand Down Expand Up @@ -418,6 +464,27 @@ impl NymApiClient {
Ok(descriptions)
}

pub async fn get_all_bonded_nym_nodes(
&self,
) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut bonds = Vec::new();

loop {
let mut res = self.nym_api.get_nym_nodes(Some(page), None).await?;

bonds.append(&mut res.data);
if bonds.len() < res.pagination.total {
page += 1
} else {
break;
}
}

Ok(bonds)
}

pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
Expand Down
21 changes: 20 additions & 1 deletion common/client-libs/validator-client/src/nym_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use nym_contracts_common::IdentityKey;
pub use nym_http_api_client::Client;
use nym_http_api_client::{ApiClient, NO_PARAMS};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId};
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
use time::format_description::BorrowedFormatItem;
use time::Date;

Expand Down Expand Up @@ -139,6 +139,25 @@ pub trait NymApiClientExt: ApiClient {
.await
}

async fn get_nym_nodes(
&self,
page: Option<u32>,
per_page: Option<u32>,
) -> Result<PaginatedResponse<NymNodeDetails>, NymAPIError> {
let mut params = Vec::new();

if let Some(page) = page {
params.push(("page", page.to_string()))
}

if let Some(per_page) = per_page {
params.push(("per_page", per_page.to_string()))
}

self.get_json(&[routes::API_VERSION, "nym-nodes", "bonded"], &params)
.await
}

async fn get_basic_mixnodes(
&self,
semver_compatibility: Option<String>,
Expand Down
15 changes: 15 additions & 0 deletions explorer-api/explorer-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub use nym_explorer_api_requests::{

// Paths
const API_VERSION: &str = "v1";
const TMP: &str = "tmp";
const UNSTABLE: &str = "unstable";
const MIXNODES: &str = "mix-nodes";
const GATEWAYS: &str = "gateways";

Expand Down Expand Up @@ -53,6 +55,12 @@ impl ExplorerClient {
Ok(Self { client, url })
}

#[cfg(not(target_arch = "wasm32"))]
pub fn new_with_timeout(url: url::Url, timeout: Duration) -> Result<Self, ExplorerApiError> {
let client = reqwest::Client::builder().timeout(timeout).build()?;
Ok(Self { client, url })
}

async fn send_get_request(
&self,
paths: &[&str],
Expand Down Expand Up @@ -86,6 +94,13 @@ impl ExplorerClient {
pub async fn get_gateways(&self) -> Result<Vec<PrettyDetailedGatewayBond>, ExplorerApiError> {
self.query_explorer_api(&[API_VERSION, GATEWAYS]).await
}

pub async fn unstable_get_gateways(
&self,
) -> Result<Vec<PrettyDetailedGatewayBond>, ExplorerApiError> {
self.query_explorer_api(&[API_VERSION, TMP, UNSTABLE, GATEWAYS])
.await
}
}

fn combine_url(mut base_url: Url, paths: &[&str]) -> Result<Url, ExplorerApiError> {
Expand Down
79 changes: 79 additions & 0 deletions explorer-api/src/country_statistics/geolocate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use crate::state::ExplorerApiStateContext;
use log::{info, warn};
use nym_explorer_api_requests::Location;
use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_task::TaskClient;

pub(crate) struct GeoLocateTask {
Expand All @@ -25,6 +26,7 @@ impl GeoLocateTask {
_ = interval_timer.tick() => {
self.locate_mix_nodes().await;
self.locate_gateways().await;
self.locate_nym_nodes().await;
}
_ = self.shutdown.recv() => {
trace!("Listener: Received shutdown");
Expand Down Expand Up @@ -109,6 +111,83 @@ impl GeoLocateTask {
trace!("All mix nodes located");
}

async fn locate_nym_nodes(&mut self) {
// I'm unwrapping to the default value to get rid of an extra indentation level from the `if let Some(...) = ...`
// If the value is None, we'll unwrap to an empty hashmap and the `values()` loop won't do any work anyway
let nym_nodes = self.state.inner.nymnodes.get_bonded_nymnodes().await;

let geo_ip = self.state.inner.geo_ip.0.clone();

for (i, cache_item) in nym_nodes.values().enumerate() {
if self
.state
.inner
.nymnodes
.is_location_valid(cache_item.node_id())
.await
{
// when the cached location is valid, don't locate and continue to next mix node
continue;
}

let bonded_host = &cache_item.bond_information.node.host;

match geo_ip.query(
bonded_host,
Some(
cache_item
.bond_information
.node
.custom_http_port
.unwrap_or(DEFAULT_NYM_NODE_HTTP_PORT),
),
) {
Ok(opt) => match opt {
Some(location) => {
let location: Location = location.into();

trace!(
"{} mix nodes already located. host {} is located in {:#?}",
i,
bonded_host,
location.three_letter_iso_country_code,
);

if i > 0 && (i % 100) == 0 {
info!("Located {} nym-nodes...", i + 1,);
}

self.state
.inner
.nymnodes
.set_location(cache_item.node_id(), Some(location))
.await;

// one node has been located, so return out of the loop
return;
}
None => {
warn!("❌ Location for {bonded_host} not found.");
self.state
.inner
.nymnodes
.set_location(cache_item.node_id(), None)
.await;
}
},
Err(_e) => {
// warn!(
// "❌ Oh no! Location for {} failed. Error: {:#?}",
// cache_item.mix_node().host,
// e
// );
}
};
}

trace!("All nym-nodes nodes located");
}

async fn locate_gateways(&mut self) {
let gateways = self.state.inner.gateways.get_gateways().await;

Expand Down
2 changes: 2 additions & 0 deletions explorer-api/src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::gateways::http::gateways_make_default_routes;
use crate::http::swagger::get_docs;
use crate::mix_node::http::mix_node_make_default_routes;
use crate::mix_nodes::http::mix_nodes_make_default_routes;
use crate::nym_nodes::http::unstable_temp_nymnodes_make_default_routes;
use crate::overview::http::overview_make_default_routes;
use crate::ping::http::ping_make_default_routes;
use crate::service_providers::http::service_providers_make_default_routes;
Expand Down Expand Up @@ -58,6 +59,7 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
"/ping" => ping_make_default_routes(&openapi_settings),
"/validators" => validators_make_default_routes(&openapi_settings),
"/service-providers" => service_providers_make_default_routes(&openapi_settings),
"/tmp/unstable" => unstable_temp_nymnodes_make_default_routes(&openapi_settings),
};

building_rocket
Expand Down
1 change: 1 addition & 0 deletions explorer-api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod http;
mod location;
mod mix_node;
pub(crate) mod mix_nodes;
mod nym_nodes;
mod overview;
mod ping;
pub(crate) mod service_providers;
Expand Down
26 changes: 26 additions & 0 deletions explorer-api/src/nym_nodes/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2024 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: Apache-2.0

use crate::state::ExplorerApiStateContext;
use nym_explorer_api_requests::PrettyDetailedGatewayBond;
use okapi::openapi3::OpenApi;
use rocket::serde::json::Json;
use rocket::{Route, State};
use rocket_okapi::settings::OpenApiSettings;

pub fn unstable_temp_nymnodes_make_default_routes(
settings: &OpenApiSettings,
) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: all_gateways]
}

#[openapi(tag = "UNSTABLE")]
#[get("/gateways")]
pub(crate) async fn all_gateways(
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<PrettyDetailedGatewayBond>> {
let mut gateways = state.inner.gateways.get_detailed_gateways().await;
gateways.append(&mut state.inner.nymnodes.pretty_gateways().await);

Json(gateways)
}
8 changes: 8 additions & 0 deletions explorer-api/src/nym_nodes/location.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2024 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: Apache-2.0

use nym_mixnet_contract_common::NodeId;

use crate::location::LocationCache;

pub(crate) type NymNodeLocationCache = LocationCache<NodeId>;
10 changes: 10 additions & 0 deletions explorer-api/src/nym_nodes/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2024 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: Apache-2.0

use std::time::Duration;

pub(crate) mod http;
pub(crate) mod location;
pub(crate) mod models;

pub(crate) const CACHE_ENTRY_TTL: Duration = Duration::from_secs(1200);
Loading
Loading