extracted common functionalities and implemented remaining skimmed routes

This commit is contained in:
Jędrzej Stuczyński
2024-09-24 18:23:08 +01:00
parent 304b192f52
commit cd706aa67e
7 changed files with 367 additions and 364 deletions
+21
View File
@@ -6,6 +6,7 @@ use crate::pagination::{PaginatedResponse, Pagination};
use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey;
use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_mixnet_contract_common::nym_node::Role;
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::NodeId;
use serde::{Deserialize, Serialize};
@@ -90,6 +91,26 @@ pub enum NodeRole {
Inactive,
}
impl NodeRole {
pub fn is_inactive(&self) -> bool {
matches!(self, NodeRole::Inactive)
}
}
impl From<Option<Role>> for NodeRole {
fn from(role: Option<Role>) -> Self {
match role {
Some(Role::EntryGateway) => NodeRole::EntryGateway,
Some(Role::Layer1) => NodeRole::Mixnode { layer: 1 },
Some(Role::Layer2) => NodeRole::Mixnode { layer: 2 },
Some(Role::Layer3) => NodeRole::Mixnode { layer: 3 },
Some(Role::ExitGateway) => NodeRole::ExitGateway,
Some(Role::Standby) => NodeRole::Standby,
None => NodeRole::Inactive,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct BasicEntryInformation {
pub hostname: Option<String>,
+6
View File
@@ -162,6 +162,12 @@ impl DescribedNodes {
self.nodes.values()
}
pub fn all_nym_nodes(&self) -> impl Iterator<Item = &NymNodeDescription> {
self.nodes
.values()
.filter(|n| n.contract_node_type == DescribedNodeType::NymNode)
}
pub fn mixing_nym_nodes(&self) -> impl Iterator<Item = &NymNodeDescription> {
self.nodes
.values()
@@ -12,7 +12,7 @@ use nym_bin_common::build_information::BinaryBuildInformationOwned;
use nym_config::defaults::mainnet;
use nym_mixnet_contract_common::NodeId;
use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt};
use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, NodeRoles};
use nym_node_requests::api::v1::node::models::AuxiliaryDetails;
use nym_node_requests::api::Client;
use pin_project::pin_project;
use std::future::Future;
@@ -1,10 +1,60 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_api_requests::models::OffsetDateTimeJsonSchemaWrapper;
use nym_api_requests::models::{
GatewayBondAnnotated, MalformedNodeBond, MixNodeBondAnnotated, OffsetDateTimeJsonSchemaWrapper,
};
use nym_api_requests::nym_nodes::{NodeRole, SkimmedNode};
use nym_bin_common::version_checker;
use nym_mixnet_contract_common::reward_params::Performance;
use time::OffsetDateTime;
pub(crate) trait LegacyAnnotation {
fn version(&self) -> &str;
fn performance(&self) -> Performance;
fn identity(&self) -> &str;
fn try_to_skimmed_node(&self, role: NodeRole) -> Result<SkimmedNode, MalformedNodeBond>;
}
impl LegacyAnnotation for MixNodeBondAnnotated {
fn version(&self) -> &str {
self.version()
}
fn performance(&self) -> Performance {
self.node_performance.last_24h
}
fn identity(&self) -> &str {
self.identity_key()
}
fn try_to_skimmed_node(&self, role: NodeRole) -> Result<SkimmedNode, MalformedNodeBond> {
self.try_to_skimmed_node(role)
}
}
impl LegacyAnnotation for GatewayBondAnnotated {
fn version(&self) -> &str {
self.version()
}
fn performance(&self) -> Performance {
self.node_performance.last_24h
}
fn identity(&self) -> &str {
self.identity()
}
fn try_to_skimmed_node(&self, role: NodeRole) -> Result<SkimmedNode, MalformedNodeBond> {
self.try_to_skimmed_node(role)
}
}
pub(crate) fn refreshed_at(
iter: impl IntoIterator<Item = OffsetDateTime>,
) -> OffsetDateTimeJsonSchemaWrapper {
@@ -23,10 +23,9 @@
use crate::nym_nodes::handlers::unstable::full_fat::nodes_detailed;
use crate::nym_nodes::handlers::unstable::semi_skimmed::nodes_expanded;
use crate::nym_nodes::handlers::unstable::skimmed::{
deprecated_gateways_basic, deprecated_mixnodes_basic, entry_gateways_basic_active,
entry_gateways_basic_all, exit_gateways_basic_active, exit_gateways_basic_all,
mixnodes_basic_active, mixnodes_basic_all, nodes_basic, nodes_basic_active,
nodes_basic_standby,
entry_gateways_basic_active, entry_gateways_basic_all, exit_gateways_basic_active,
exit_gateways_basic_all, mixnodes_basic_active, mixnodes_basic_all, nodes_basic_active,
nodes_basic_all,
};
use crate::support::http::helpers::PaginationRequest;
use crate::support::http::state::AppState;
@@ -46,9 +45,8 @@ pub(crate) fn nym_node_routes_unstable() -> Router<AppState> {
.nest(
"/skimmed",
Router::new()
.route("/", get(nodes_basic))
.route("/", get(nodes_basic_all))
.route("/active", get(nodes_basic_active))
.route("/standby", get(nodes_basic_standby))
.nest(
"/mixnodes",
Router::new()
@@ -73,8 +71,8 @@ pub(crate) fn nym_node_routes_unstable() -> Router<AppState> {
Router::new().route("/", get(nodes_expanded)),
)
.nest("/full-fat", Router::new().route("/", get(nodes_detailed)))
.route("/gateways/skimmed", get(deprecated_gateways_basic))
.route("/mixnodes/skimmed", get(deprecated_mixnodes_basic))
.route("/gateways/skimmed", get(skimmed::deprecated_gateways_basic))
.route("/mixnodes/skimmed", get(skimmed::deprecated_mixnodes_basic))
}
#[derive(Debug, Deserialize, utoipa::IntoParams)]
+279 -351
View File
@@ -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)
+3 -3
View File
@@ -111,9 +111,9 @@ impl AppState {
// handler helpers to easily get data or return error response
impl AppState {
pub(crate) async fn describe_nodes_cache_data(
&self,
) -> Result<RwLockReadGuard<Cache<DescribedNodes>>, AxumErrorResponse> {
pub(crate) async fn describe_nodes_cache_data<'a>(
&'a self,
) -> Result<RwLockReadGuard<'a, Cache<DescribedNodes>>, AxumErrorResponse> {
Ok(self.described_nodes_cache().get().await?)
}