|
|
|
@@ -1,19 +1,188 @@
|
|
|
|
|
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
|
|
|
|
|
|
use crate::node_describe_cache::DescribedNodes;
|
|
|
|
|
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
|
|
|
|
use crate::nym_nodes::handlers::unstable::helpers::{refreshed_at, semver};
|
|
|
|
|
use crate::nym_contract_cache::cache::CachedRewardedSet;
|
|
|
|
|
use crate::nym_nodes::handlers::unstable::helpers::{refreshed_at, semver, LegacyAnnotation};
|
|
|
|
|
use crate::nym_nodes::handlers::unstable::{NodesParams, NodesParamsWithRole};
|
|
|
|
|
use crate::support::caching::Cache;
|
|
|
|
|
use crate::support::http::state::AppState;
|
|
|
|
|
use axum::extract::{Query, State};
|
|
|
|
|
use axum::Json;
|
|
|
|
|
use nym_api_requests::models::{NodeAnnotation, NymNodeDescription};
|
|
|
|
|
use nym_api_requests::nym_nodes::{
|
|
|
|
|
CachedNodesResponse, NodeRole, NodeRoleQueryParam, PaginatedCachedNodesResponse, SkimmedNode,
|
|
|
|
|
};
|
|
|
|
|
use nym_mixnet_contract_common::NodeId;
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::future::Future;
|
|
|
|
|
use tokio::sync::RwLockReadGuard;
|
|
|
|
|
use tracing::trace;
|
|
|
|
|
|
|
|
|
|
pub type PaginatedSkimmedNodes = AxumResult<Json<PaginatedCachedNodesResponse<SkimmedNode>>>;
|
|
|
|
|
|
|
|
|
|
/// Given all relevant caches, build part of response for JUST Nym Nodes
|
|
|
|
|
fn build_nym_nodes_response<'a, NI>(
|
|
|
|
|
rewarded_set: &CachedRewardedSet,
|
|
|
|
|
required_semver: &Option<String>,
|
|
|
|
|
nym_nodes_subset: NI,
|
|
|
|
|
annotations: &HashMap<NodeId, NodeAnnotation>,
|
|
|
|
|
active_only: bool,
|
|
|
|
|
) -> Vec<SkimmedNode>
|
|
|
|
|
where
|
|
|
|
|
NI: Iterator<Item = &'a NymNodeDescription> + 'a,
|
|
|
|
|
{
|
|
|
|
|
let mut nodes = Vec::new();
|
|
|
|
|
for nym_node in nym_nodes_subset {
|
|
|
|
|
let node_id = nym_node.node_id;
|
|
|
|
|
|
|
|
|
|
// if we have wrong version, ignore
|
|
|
|
|
if !semver(required_semver, nym_node.version()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let role: NodeRole = rewarded_set.role(node_id).into();
|
|
|
|
|
|
|
|
|
|
// if the role is inactive, see if our filter allows it
|
|
|
|
|
if active_only && role.is_inactive() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// honestly, not sure under what exact circumstances this value could be missing,
|
|
|
|
|
// but in that case just use 0 performance
|
|
|
|
|
let annotation = annotations.get(&node_id).copied().unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
nodes.push(nym_node.to_skimmed_node(role, annotation.last_24h_performance));
|
|
|
|
|
}
|
|
|
|
|
nodes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Given all relevant caches, add appropriate legacy nodes to the part of the response
|
|
|
|
|
fn add_legacy<LN>(
|
|
|
|
|
nodes: &mut Vec<SkimmedNode>,
|
|
|
|
|
required_semver: &Option<String>,
|
|
|
|
|
rewarded_set: &CachedRewardedSet,
|
|
|
|
|
describe_cache: &DescribedNodes,
|
|
|
|
|
annotated_legacy_nodes: &HashMap<NodeId, LN>,
|
|
|
|
|
active_only: bool,
|
|
|
|
|
) where
|
|
|
|
|
LN: LegacyAnnotation,
|
|
|
|
|
{
|
|
|
|
|
for (node_id, legacy) in annotated_legacy_nodes.iter() {
|
|
|
|
|
// if we have wrong version, ignore
|
|
|
|
|
if !semver(required_semver, legacy.version()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let role: NodeRole = rewarded_set.role(*node_id).into();
|
|
|
|
|
|
|
|
|
|
// if the role is inactive, see if our filter allows it
|
|
|
|
|
if active_only && role.is_inactive() {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we have self-described info, prefer it over contract data
|
|
|
|
|
if let Some(described) = describe_cache.get_node(node_id) {
|
|
|
|
|
nodes.push(described.to_skimmed_node(role, legacy.performance()))
|
|
|
|
|
} else {
|
|
|
|
|
match legacy.try_to_skimmed_node(role) {
|
|
|
|
|
Ok(node) => nodes.push(node),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
let id = legacy.identity();
|
|
|
|
|
trace!("node {id} is malformed: {err}")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// hehe, what an abomination, but it's used in multiple different places and I hate copy-pasting code,
|
|
|
|
|
// especially if it has multiple loops, etc
|
|
|
|
|
async fn build_skimmed_nodes_response<'a, NI, LG, Fut, LN>(
|
|
|
|
|
state: &'a AppState,
|
|
|
|
|
Query(query_params): Query<NodesParams>,
|
|
|
|
|
nym_nodes_subset: NI,
|
|
|
|
|
annotated_legacy_nodes_getter: LG,
|
|
|
|
|
active_only: bool,
|
|
|
|
|
) -> PaginatedSkimmedNodes
|
|
|
|
|
where
|
|
|
|
|
// iterator returning relevant subset of nym-nodes (like mixing nym-nodes, entries, etc.)
|
|
|
|
|
NI: Iterator<Item = &'a NymNodeDescription> + 'a,
|
|
|
|
|
|
|
|
|
|
// async function that returns cache of appropriate legacy nodes (mixnodes or gateways)
|
|
|
|
|
LG: Fn(&'a AppState) -> Fut,
|
|
|
|
|
Fut:
|
|
|
|
|
Future<Output = Result<RwLockReadGuard<'a, Cache<HashMap<NodeId, LN>>>, AxumErrorResponse>>,
|
|
|
|
|
|
|
|
|
|
// the legacy node (MixNodeBondAnnotated or GatewayBondAnnotated)
|
|
|
|
|
LN: LegacyAnnotation + 'a,
|
|
|
|
|
{
|
|
|
|
|
// TODO: implement it
|
|
|
|
|
let _ = query_params.per_page;
|
|
|
|
|
let _ = query_params.page;
|
|
|
|
|
let semver_compatibility = query_params.semver_compatibility;
|
|
|
|
|
|
|
|
|
|
// 1. get the rewarded set
|
|
|
|
|
let rewarded_set = state.rewarded_set().await?;
|
|
|
|
|
|
|
|
|
|
// 2. grab all annotations so that we could attach scores to the [nym] nodes
|
|
|
|
|
let annotations = state.node_annotations().await?;
|
|
|
|
|
|
|
|
|
|
// 3. implicitly grab the relevant described nodes
|
|
|
|
|
// (ideally it'd be tied directly to the NI iterator, but I couldn't defeat the compiler)
|
|
|
|
|
let describe_cache = state.describe_nodes_cache_data().await?;
|
|
|
|
|
|
|
|
|
|
// 4. start building the response
|
|
|
|
|
let mut nodes = build_nym_nodes_response(
|
|
|
|
|
&rewarded_set,
|
|
|
|
|
&semver_compatibility,
|
|
|
|
|
nym_nodes_subset,
|
|
|
|
|
&annotations,
|
|
|
|
|
active_only,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 5. if we allow legacy nodes, repeat the procedure for them, otherwise return just nym-nodes
|
|
|
|
|
if query_params.no_legacy {
|
|
|
|
|
// min of all caches
|
|
|
|
|
let refreshed_at = refreshed_at([
|
|
|
|
|
rewarded_set.timestamp(),
|
|
|
|
|
annotations.timestamp(),
|
|
|
|
|
describe_cache.timestamp(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return Ok(Json(PaginatedCachedNodesResponse::new_full(
|
|
|
|
|
refreshed_at,
|
|
|
|
|
nodes,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. grab relevant legacy nodes
|
|
|
|
|
// (due to the existence of the legacy endpoints, we already have fully annotated data on them)
|
|
|
|
|
let annotated_legacy_nodes = annotated_legacy_nodes_getter(state).await?;
|
|
|
|
|
add_legacy(
|
|
|
|
|
&mut nodes,
|
|
|
|
|
&semver_compatibility,
|
|
|
|
|
&rewarded_set,
|
|
|
|
|
&describe_cache,
|
|
|
|
|
&annotated_legacy_nodes,
|
|
|
|
|
active_only,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// min of all caches
|
|
|
|
|
let refreshed_at = refreshed_at([
|
|
|
|
|
rewarded_set.timestamp(),
|
|
|
|
|
annotations.timestamp(),
|
|
|
|
|
describe_cache.timestamp(),
|
|
|
|
|
annotated_legacy_nodes.timestamp(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Ok(Json(PaginatedCachedNodesResponse::new_full(
|
|
|
|
|
refreshed_at,
|
|
|
|
|
nodes,
|
|
|
|
|
)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Deprecated query that gets ALL gateways
|
|
|
|
|
#[utoipa::path(
|
|
|
|
|
tag = "Unstable Nym Nodes",
|
|
|
|
@@ -68,6 +237,66 @@ pub(super) async fn deprecated_mixnodes_basic(
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn nodes_basic(
|
|
|
|
|
state: State<AppState>,
|
|
|
|
|
Query(query_params): Query<NodesParams>,
|
|
|
|
|
active_only: bool,
|
|
|
|
|
) -> PaginatedSkimmedNodes {
|
|
|
|
|
// unfortunately we have to build the response semi-manually here as we need to add two sources of legacy nodes
|
|
|
|
|
|
|
|
|
|
// 1. grab all relevant described nym-nodes
|
|
|
|
|
let rewarded_set = state.rewarded_set().await?;
|
|
|
|
|
let semver_compatibility = &query_params.semver_compatibility;
|
|
|
|
|
|
|
|
|
|
let describe_cache = state.describe_nodes_cache_data().await?;
|
|
|
|
|
let all_nym_nodes = describe_cache.all_nym_nodes();
|
|
|
|
|
let annotations = state.node_annotations().await?;
|
|
|
|
|
let legacy_mixnodes = state.legacy_mixnode_annotations().await?;
|
|
|
|
|
let legacy_gateways = state.legacy_gateways_annotations().await?;
|
|
|
|
|
|
|
|
|
|
let mut nodes = build_nym_nodes_response(
|
|
|
|
|
&rewarded_set,
|
|
|
|
|
semver_compatibility,
|
|
|
|
|
all_nym_nodes,
|
|
|
|
|
&annotations,
|
|
|
|
|
active_only,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// add legacy gateways to the response
|
|
|
|
|
add_legacy(
|
|
|
|
|
&mut nodes,
|
|
|
|
|
semver_compatibility,
|
|
|
|
|
&rewarded_set,
|
|
|
|
|
&describe_cache,
|
|
|
|
|
&legacy_gateways,
|
|
|
|
|
active_only,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// add legacy mixnodes to the response
|
|
|
|
|
add_legacy(
|
|
|
|
|
&mut nodes,
|
|
|
|
|
semver_compatibility,
|
|
|
|
|
&rewarded_set,
|
|
|
|
|
&describe_cache,
|
|
|
|
|
&legacy_mixnodes,
|
|
|
|
|
active_only,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// min of all caches
|
|
|
|
|
let refreshed_at = refreshed_at([
|
|
|
|
|
rewarded_set.timestamp(),
|
|
|
|
|
annotations.timestamp(),
|
|
|
|
|
describe_cache.timestamp(),
|
|
|
|
|
legacy_mixnodes.timestamp(),
|
|
|
|
|
legacy_gateways.timestamp(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Ok(Json(PaginatedCachedNodesResponse::new_full(
|
|
|
|
|
refreshed_at,
|
|
|
|
|
nodes,
|
|
|
|
|
)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Return all Nym Nodes and optionally legacy mixnodes/gateways (if `no-legacy` flag is not used)
|
|
|
|
|
/// that are currently bonded.
|
|
|
|
|
#[utoipa::path(
|
|
|
|
@@ -80,7 +309,7 @@ pub(super) async fn deprecated_mixnodes_basic(
|
|
|
|
|
(status = 200, body = PaginatedCachedNodesResponse<SkimmedNode>)
|
|
|
|
|
)
|
|
|
|
|
)]
|
|
|
|
|
pub(super) async fn nodes_basic(
|
|
|
|
|
pub(super) async fn nodes_basic_all(
|
|
|
|
|
state: State<AppState>,
|
|
|
|
|
Query(query_params): Query<NodesParamsWithRole>,
|
|
|
|
|
) -> PaginatedSkimmedNodes {
|
|
|
|
@@ -98,23 +327,9 @@ pub(super) async fn nodes_basic(
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: implement pagination
|
|
|
|
|
|
|
|
|
|
let semver = query_params.semver_compatibility;
|
|
|
|
|
let no_legacy = query_params.no_legacy;
|
|
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
|
/*
|
|
|
|
|
- `/v1/unstable/nym-nodes/skimmed` - now works with `exit` parameter
|
|
|
|
|
- `/v1/unstable/nym-nodes/skimmed` - introduced `no-legacy` flag to ignore legacy mixnodes/gateways (where applicable)
|
|
|
|
|
- `/v1/unstable/nym-nodes/skimmed` - will now return **ALL** nodes if no query parameter is provided
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
Err(AxumErrorResponse::not_implemented())
|
|
|
|
|
nodes_basic(state, Query(query_params.into()), false).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - `/v1/unstable/nym-nodes/skimmed/active` - returns all Nym Nodes **AND** legacy mixnodes **AND** legacy gateways that are currently in the active set, unless `no-legacy` parameter is used
|
|
|
|
|
/// Return Nym Nodes and optionally legacy mixnodes/gateways (if `no-legacy` flag is not used)
|
|
|
|
|
/// that are currently bonded and are in the **active set**
|
|
|
|
|
#[utoipa::path(
|
|
|
|
@@ -129,145 +344,42 @@ pub(super) async fn nodes_basic(
|
|
|
|
|
)]
|
|
|
|
|
pub(super) async fn nodes_basic_active(
|
|
|
|
|
state: State<AppState>,
|
|
|
|
|
query_params: Query<NodesParamsWithRole>,
|
|
|
|
|
Query(query_params): Query<NodesParamsWithRole>,
|
|
|
|
|
) -> PaginatedSkimmedNodes {
|
|
|
|
|
// TODO: implement it
|
|
|
|
|
let _ = query_params.per_page;
|
|
|
|
|
let _ = query_params.page;
|
|
|
|
|
todo!()
|
|
|
|
|
}
|
|
|
|
|
if let Some(role) = query_params.role {
|
|
|
|
|
return match role {
|
|
|
|
|
NodeRoleQueryParam::ActiveMixnode => {
|
|
|
|
|
mixnodes_basic_active(state, Query(query_params.into())).await
|
|
|
|
|
}
|
|
|
|
|
NodeRoleQueryParam::EntryGateway => {
|
|
|
|
|
entry_gateways_basic_active(state, Query(query_params.into())).await
|
|
|
|
|
}
|
|
|
|
|
NodeRoleQueryParam::ExitGateway => {
|
|
|
|
|
exit_gateways_basic_active(state, Query(query_params.into())).await
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Return Nym Nodes and optionally legacy mixnodes/gateways (if `no-legacy` flag is not used)
|
|
|
|
|
/// that are currently bonded and are in the **standby set**
|
|
|
|
|
#[utoipa::path(
|
|
|
|
|
tag = "Unstable Nym Nodes",
|
|
|
|
|
get,
|
|
|
|
|
params(NodesParamsWithRole),
|
|
|
|
|
path = "/skimmed/standby",
|
|
|
|
|
context_path = "/v1/unstable/nym-nodes/skimmed",
|
|
|
|
|
responses(
|
|
|
|
|
(status = 200, body = PaginatedCachedNodesResponse<SkimmedNode>)
|
|
|
|
|
)
|
|
|
|
|
)]
|
|
|
|
|
pub(super) async fn nodes_basic_standby(
|
|
|
|
|
state: State<AppState>,
|
|
|
|
|
query_params: Query<NodesParamsWithRole>,
|
|
|
|
|
) -> PaginatedSkimmedNodes {
|
|
|
|
|
// TODO: implement it
|
|
|
|
|
let _ = query_params.per_page;
|
|
|
|
|
let _ = query_params.page;
|
|
|
|
|
todo!()
|
|
|
|
|
nodes_basic(state, Query(query_params.into()), true).await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn mixnodes_basic(
|
|
|
|
|
state: State<AppState>,
|
|
|
|
|
Query(query_params): Query<NodesParams>,
|
|
|
|
|
query_params: Query<NodesParams>,
|
|
|
|
|
active_only: bool,
|
|
|
|
|
) -> PaginatedSkimmedNodes {
|
|
|
|
|
// TODO: implement it
|
|
|
|
|
let _ = query_params.per_page;
|
|
|
|
|
let _ = query_params.page;
|
|
|
|
|
let semver_compatibility = query_params.semver_compatibility;
|
|
|
|
|
|
|
|
|
|
// 1. get the rewarded set
|
|
|
|
|
let rewarded_set = state.rewarded_set().await?;
|
|
|
|
|
|
|
|
|
|
// 2. grab all annotations so that we could attach scores to the [nym] nodes
|
|
|
|
|
let annotations = state.node_annotations().await?;
|
|
|
|
|
|
|
|
|
|
// 3. grab all relevant described nym-nodes
|
|
|
|
|
// 1. grab all relevant described nym-nodes
|
|
|
|
|
let describe_cache = state.describe_nodes_cache_data().await?;
|
|
|
|
|
let mixing_nym_nodes = describe_cache.mixing_nym_nodes();
|
|
|
|
|
|
|
|
|
|
// 4. start building the response
|
|
|
|
|
let mut nodes = Vec::new();
|
|
|
|
|
|
|
|
|
|
for nym_node in mixing_nym_nodes {
|
|
|
|
|
let node_id = nym_node.node_id;
|
|
|
|
|
|
|
|
|
|
// if this node is not an active mixnode, ignore it
|
|
|
|
|
if active_only && !rewarded_set.is_active_mixnode(&node_id) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we have wrong version, ignore
|
|
|
|
|
if !semver(&semver_compatibility, nym_node.version()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let role = match rewarded_set.try_get_mix_layer(&node_id) {
|
|
|
|
|
Some(layer) => NodeRole::Mixnode { layer },
|
|
|
|
|
None => NodeRole::Inactive,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// honestly, not sure under what exact circumstances this value could be missing,
|
|
|
|
|
// but in that case just use 0 performance
|
|
|
|
|
let annotation = annotations.get(&node_id).copied().unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
nodes.push(nym_node.to_skimmed_node(role, annotation.last_24h_performance));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. if we allow legacy mixnodes, repeat the procedure for mixnodes, otherwise return just nym-nodes
|
|
|
|
|
if query_params.no_legacy {
|
|
|
|
|
// min of all caches
|
|
|
|
|
let refreshed_at = refreshed_at([
|
|
|
|
|
rewarded_set.timestamp(),
|
|
|
|
|
annotations.timestamp(),
|
|
|
|
|
describe_cache.timestamp(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return Ok(Json(PaginatedCachedNodesResponse::new_full(
|
|
|
|
|
refreshed_at,
|
|
|
|
|
nodes,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. grab all legacy mixnodes
|
|
|
|
|
// due to legacy endpoints we already have fully annotated data on them
|
|
|
|
|
let annotated_legacy_mixnodes = state.legacy_mixnode_annotations().await?;
|
|
|
|
|
|
|
|
|
|
for (mix_id, legacy) in annotated_legacy_mixnodes.iter() {
|
|
|
|
|
// if this node is not an active mixnode, ignore it
|
|
|
|
|
if active_only && !rewarded_set.is_active_mixnode(mix_id) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we have wrong version, ignore
|
|
|
|
|
if !semver(&semver_compatibility, legacy.version()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let role = match rewarded_set.try_get_mix_layer(mix_id) {
|
|
|
|
|
Some(layer) => NodeRole::Mixnode { layer },
|
|
|
|
|
None => NodeRole::Inactive,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// if we have self-described info, prefer it over contract data
|
|
|
|
|
if let Some(described) = describe_cache.get_node(mix_id) {
|
|
|
|
|
nodes.push(described.to_skimmed_node(role, legacy.node_performance.last_24h))
|
|
|
|
|
} else {
|
|
|
|
|
match legacy.try_to_skimmed_node(role) {
|
|
|
|
|
Ok(node) => nodes.push(node),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
let id = legacy.identity_key();
|
|
|
|
|
trace!("node {id} is malformed: {err}")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// min of all caches
|
|
|
|
|
let refreshed_at = refreshed_at([
|
|
|
|
|
rewarded_set.timestamp(),
|
|
|
|
|
annotations.timestamp(),
|
|
|
|
|
describe_cache.timestamp(),
|
|
|
|
|
annotated_legacy_mixnodes.timestamp(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Ok(Json(PaginatedCachedNodesResponse::new_full(
|
|
|
|
|
refreshed_at,
|
|
|
|
|
nodes,
|
|
|
|
|
)))
|
|
|
|
|
build_skimmed_nodes_response(
|
|
|
|
|
&state.0,
|
|
|
|
|
query_params,
|
|
|
|
|
mixing_nym_nodes,
|
|
|
|
|
|state| state.legacy_mixnode_annotations(),
|
|
|
|
|
active_only,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns Nym Nodes and optionally legacy mixnodes (if `no-legacy` flag is not used)
|
|
|
|
@@ -310,113 +422,21 @@ pub(super) async fn mixnodes_basic_active(
|
|
|
|
|
|
|
|
|
|
async fn entry_gateways_basic(
|
|
|
|
|
state: State<AppState>,
|
|
|
|
|
Query(query_params): Query<NodesParams>,
|
|
|
|
|
query_params: Query<NodesParams>,
|
|
|
|
|
active_only: bool,
|
|
|
|
|
) -> PaginatedSkimmedNodes {
|
|
|
|
|
// TODO: implement it
|
|
|
|
|
let _ = query_params.per_page;
|
|
|
|
|
let _ = query_params.page;
|
|
|
|
|
let semver_compatibility = query_params.semver_compatibility;
|
|
|
|
|
|
|
|
|
|
// 1. get the rewarded set
|
|
|
|
|
let rewarded_set = state.rewarded_set().await?;
|
|
|
|
|
|
|
|
|
|
// 2. grab all annotations so that we could attach scores to the [nym] nodes
|
|
|
|
|
let annotations = state.node_annotations().await?;
|
|
|
|
|
|
|
|
|
|
// 3. grab all relevant described nym-nodes
|
|
|
|
|
// 1. grab all relevant described nym-nodes
|
|
|
|
|
let describe_cache = state.describe_nodes_cache_data().await?;
|
|
|
|
|
let gateway_capable_nym_nodes = describe_cache.entry_capable_nym_nodes();
|
|
|
|
|
let mixing_nym_nodes = describe_cache.entry_capable_nym_nodes();
|
|
|
|
|
|
|
|
|
|
// 4. start building the response
|
|
|
|
|
let mut nodes = Vec::new();
|
|
|
|
|
|
|
|
|
|
for nym_node in gateway_capable_nym_nodes {
|
|
|
|
|
let node_id = nym_node.node_id;
|
|
|
|
|
|
|
|
|
|
// if this node is not an active gateway, ignore it
|
|
|
|
|
if active_only && !rewarded_set.is_entry(&node_id) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we have wrong version, ignore
|
|
|
|
|
if !semver(&semver_compatibility, nym_node.version()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let role = match rewarded_set.is_entry(&node_id) {
|
|
|
|
|
true => NodeRole::EntryGateway,
|
|
|
|
|
false => NodeRole::Inactive,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// honestly, not sure under what exact circumstances this value could be missing,
|
|
|
|
|
// but in that case just use 0 performance
|
|
|
|
|
let annotation = annotations.get(&node_id).copied().unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
nodes.push(nym_node.to_skimmed_node(role, annotation.last_24h_performance));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. if we allow legacy gateways, repeat the procedure for gateways, otherwise return just nym-nodes
|
|
|
|
|
if query_params.no_legacy {
|
|
|
|
|
// min of all caches
|
|
|
|
|
let refreshed_at = refreshed_at([
|
|
|
|
|
rewarded_set.timestamp(),
|
|
|
|
|
annotations.timestamp(),
|
|
|
|
|
describe_cache.timestamp(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return Ok(Json(PaginatedCachedNodesResponse::new_full(
|
|
|
|
|
refreshed_at,
|
|
|
|
|
nodes,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. grab all legacy gateways
|
|
|
|
|
// due to legacy endpoints we already have fully annotated data on them
|
|
|
|
|
let annotated_legacy_gateways = state.legacy_gateways_annotations().await?;
|
|
|
|
|
|
|
|
|
|
for (node_id, legacy) in annotated_legacy_gateways.iter() {
|
|
|
|
|
// if this node is not an active gateway, ignore it
|
|
|
|
|
if active_only && !rewarded_set.is_entry(&node_id) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we have wrong version, ignore
|
|
|
|
|
if !semver(&semver_compatibility, legacy.version()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let role = match rewarded_set.is_entry(&node_id) {
|
|
|
|
|
true => NodeRole::EntryGateway,
|
|
|
|
|
false => NodeRole::Inactive,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// if we have self-described info, prefer it over contract data
|
|
|
|
|
if let Some(described) = describe_cache.get_node(node_id) {
|
|
|
|
|
nodes.push(described.to_skimmed_node(role, legacy.node_performance.last_24h))
|
|
|
|
|
} else {
|
|
|
|
|
match legacy.try_to_skimmed_node(role) {
|
|
|
|
|
Ok(node) => nodes.push(node),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
let id = legacy.gateway_bond.identity();
|
|
|
|
|
trace!("node {id} is malformed: {err}")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// min of all caches
|
|
|
|
|
let refreshed_at = refreshed_at([
|
|
|
|
|
rewarded_set.timestamp(),
|
|
|
|
|
annotations.timestamp(),
|
|
|
|
|
describe_cache.timestamp(),
|
|
|
|
|
annotated_legacy_gateways.timestamp(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Ok(Json(PaginatedCachedNodesResponse::new_full(
|
|
|
|
|
refreshed_at,
|
|
|
|
|
nodes,
|
|
|
|
|
)))
|
|
|
|
|
build_skimmed_nodes_response(
|
|
|
|
|
&state.0,
|
|
|
|
|
query_params,
|
|
|
|
|
mixing_nym_nodes,
|
|
|
|
|
|state| state.legacy_gateways_annotations(),
|
|
|
|
|
active_only,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns Nym Nodes and optionally legacy gateways (if `no-legacy` flag is not used)
|
|
|
|
@@ -459,113 +479,21 @@ pub(super) async fn entry_gateways_basic_all(
|
|
|
|
|
|
|
|
|
|
async fn exit_gateways_basic(
|
|
|
|
|
state: State<AppState>,
|
|
|
|
|
Query(query_params): Query<NodesParams>,
|
|
|
|
|
query_params: Query<NodesParams>,
|
|
|
|
|
active_only: bool,
|
|
|
|
|
) -> PaginatedSkimmedNodes {
|
|
|
|
|
// TODO: implement it
|
|
|
|
|
let _ = query_params.per_page;
|
|
|
|
|
let _ = query_params.page;
|
|
|
|
|
let semver_compatibility = query_params.semver_compatibility;
|
|
|
|
|
|
|
|
|
|
// 1. get the rewarded set
|
|
|
|
|
let rewarded_set = state.rewarded_set().await?;
|
|
|
|
|
|
|
|
|
|
// 2. grab all annotations so that we could attach scores to the [nym] nodes
|
|
|
|
|
let annotations = state.node_annotations().await?;
|
|
|
|
|
|
|
|
|
|
// 3. grab all relevant described nym-nodes
|
|
|
|
|
// 1. grab all relevant described nym-nodes
|
|
|
|
|
let describe_cache = state.describe_nodes_cache_data().await?;
|
|
|
|
|
let gateway_capable_nym_nodes = describe_cache.exit_capable_nym_nodes();
|
|
|
|
|
let mixing_nym_nodes = describe_cache.exit_capable_nym_nodes();
|
|
|
|
|
|
|
|
|
|
// 4. start building the response
|
|
|
|
|
let mut nodes = Vec::new();
|
|
|
|
|
|
|
|
|
|
for nym_node in gateway_capable_nym_nodes {
|
|
|
|
|
let node_id = nym_node.node_id;
|
|
|
|
|
|
|
|
|
|
// if this node is not an active gateway, ignore it
|
|
|
|
|
if active_only && !rewarded_set.is_exit(&node_id) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we have wrong version, ignore
|
|
|
|
|
if !semver(&semver_compatibility, nym_node.version()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let role = match rewarded_set.is_exit(&node_id) {
|
|
|
|
|
true => NodeRole::ExitGateway,
|
|
|
|
|
false => NodeRole::Inactive,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// honestly, not sure under what exact circumstances this value could be missing,
|
|
|
|
|
// but in that case just use 0 performance
|
|
|
|
|
let annotation = annotations.get(&node_id).copied().unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
nodes.push(nym_node.to_skimmed_node(role, annotation.last_24h_performance));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. if we allow legacy gateways, repeat the procedure for gateways, otherwise return just nym-nodes
|
|
|
|
|
if query_params.no_legacy {
|
|
|
|
|
// min of all caches
|
|
|
|
|
let refreshed_at = refreshed_at([
|
|
|
|
|
rewarded_set.timestamp(),
|
|
|
|
|
annotations.timestamp(),
|
|
|
|
|
describe_cache.timestamp(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return Ok(Json(PaginatedCachedNodesResponse::new_full(
|
|
|
|
|
refreshed_at,
|
|
|
|
|
nodes,
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. grab all legacy gateways
|
|
|
|
|
// due to legacy endpoints we already have fully annotated data on them
|
|
|
|
|
let annotated_legacy_gateways = state.legacy_gateways_annotations().await?;
|
|
|
|
|
|
|
|
|
|
for (node_id, legacy) in annotated_legacy_gateways.iter() {
|
|
|
|
|
// if this node is not an active gateway, ignore it
|
|
|
|
|
if active_only && !rewarded_set.is_exit(&node_id) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we have wrong version, ignore
|
|
|
|
|
if !semver(&semver_compatibility, legacy.version()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let role = match rewarded_set.is_exit(&node_id) {
|
|
|
|
|
true => NodeRole::ExitGateway,
|
|
|
|
|
false => NodeRole::Inactive,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// if we have self-described info, prefer it over contract data
|
|
|
|
|
if let Some(described) = describe_cache.get_node(node_id) {
|
|
|
|
|
nodes.push(described.to_skimmed_node(role, legacy.node_performance.last_24h))
|
|
|
|
|
} else {
|
|
|
|
|
match legacy.try_to_skimmed_node(role) {
|
|
|
|
|
Ok(node) => nodes.push(node),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
let id = legacy.gateway_bond.identity();
|
|
|
|
|
trace!("node {id} is malformed: {err}")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// min of all caches
|
|
|
|
|
let refreshed_at = refreshed_at([
|
|
|
|
|
rewarded_set.timestamp(),
|
|
|
|
|
annotations.timestamp(),
|
|
|
|
|
describe_cache.timestamp(),
|
|
|
|
|
annotated_legacy_gateways.timestamp(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
Ok(Json(PaginatedCachedNodesResponse::new_full(
|
|
|
|
|
refreshed_at,
|
|
|
|
|
nodes,
|
|
|
|
|
)))
|
|
|
|
|
build_skimmed_nodes_response(
|
|
|
|
|
&state.0,
|
|
|
|
|
query_params,
|
|
|
|
|
mixing_nym_nodes,
|
|
|
|
|
|state| state.legacy_gateways_annotations(),
|
|
|
|
|
active_only,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns Nym Nodes and optionally legacy gateways (if `no-legacy` flag is not used)
|
|
|
|
|