add semi-skimmed endpoint to distribute noise key

This commit is contained in:
Simon Wicky
2025-05-20 16:07:20 +02:00
committed by Georgio Nicolas
parent fb874d699b
commit 74b2aab446
6 changed files with 199 additions and 63 deletions
Generated
+19 -19
View File
@@ -4892,18 +4892,18 @@ dependencies = [
"getset",
"hex",
"humantime-serde",
"nym-compact-ecash 0.1.0",
"nym-config 0.1.0",
"nym-contracts-common 0.5.0",
"nym-credentials-interface 0.1.0",
"nym-crypto 0.4.0",
"nym-ecash-time 0.1.0",
"nym-mixnet-contract-common 0.6.0",
"nym-network-defaults 0.1.0",
"nym-node-requests 0.1.0",
"nym-compact-ecash",
"nym-config",
"nym-contracts-common",
"nym-credentials-interface",
"nym-crypto",
"nym-ecash-time",
"nym-mixnet-contract-common",
"nym-network-defaults",
"nym-node-requests",
"nym-noise-keys",
"nym-serde-helpers 0.1.0",
"nym-ticketbooks-merkle 0.1.0",
"nym-serde-helpers",
"nym-ticketbooks-merkle",
"rand_chacha 0.3.1",
"schemars",
"serde",
@@ -6530,7 +6530,7 @@ dependencies = [
"nym-mixnet-client",
"nym-network-requester",
"nym-node-metrics",
"nym-node-requests 0.1.0",
"nym-node-requests",
"nym-noise",
"nym-noise-keys",
"nym-nonexhaustive-delayqueue",
@@ -6593,12 +6593,12 @@ dependencies = [
"celes",
"humantime",
"humantime-serde",
"nym-bin-common 0.6.0",
"nym-crypto 0.4.0",
"nym-exit-policy 0.1.0",
"nym-http-api-client 0.1.0",
"nym-bin-common",
"nym-crypto",
"nym-exit-policy",
"nym-http-api-client",
"nym-noise-keys",
"nym-wireguard-types 0.1.0",
"nym-wireguard-types",
"rand_chacha 0.3.1",
"schemars",
"serde",
@@ -6785,7 +6785,7 @@ dependencies = [
"nym-noise-keys",
"pin-project",
"serde",
"sha2 0.10.8",
"sha2 0.10.9",
"snow",
"thiserror 2.0.12",
"tokio",
@@ -10095,7 +10095,7 @@ dependencies = [
"curve25519-dalek",
"rand_core 0.6.4",
"rustc_version 0.4.1",
"sha2 0.10.8",
"sha2 0.10.9",
"subtle 2.6.1",
]
@@ -25,9 +25,7 @@ use nym_api_requests::models::{
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
};
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::{
NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNode, SkimmedNodesWithMetadata,
};
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SemiSkimmedNode, SkimmedNode};
use nym_coconut_dkg_common::types::EpochId;
use nym_http_api_client::UserAgent;
use nym_mixnet_contract_common::EpochRewardedSet;
@@ -567,6 +565,31 @@ impl NymApiClient {
Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
}
/// retrieve expanded information for all bonded nodes on the network
pub async fn get_all_expanded_nodes(
&self,
) -> Result<Vec<SemiSkimmedNode>, 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 nodes = Vec::new();
loop {
let mut res = self
.nym_api
.get_expanded_nodes(false, Some(page), None)
.await?;
nodes.append(&mut res.nodes.data);
if nodes.len() < res.nodes.pagination.total {
page += 1
} else {
break;
}
}
Ok(nodes)
}
pub async fn health(&self) -> Result<ApiHealthResponse, ValidatorClientError> {
Ok(self.nym_api.health().await?)
}
@@ -18,8 +18,8 @@ use nym_api_requests::models::{
NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
};
use nym_api_requests::nym_nodes::{
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponseV1,
PaginatedCachedNodesResponseV2,
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
SemiSkimmedNode,
};
use nym_api_requests::pagination::PaginatedResponse;
pub use nym_api_requests::{
@@ -676,6 +676,39 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn get_expanded_nodes(
&self,
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
) -> Result<PaginatedCachedNodesResponse<SemiSkimmedNode>, NymAPIError> {
let mut params = Vec::new();
if no_legacy {
params.push(("no_legacy", "true".to_string()))
}
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,
"unstable",
routes::NYM_NODES_ROUTES,
"semi-skimmed",
],
&params,
)
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
+3 -30
View File
@@ -896,29 +896,7 @@ pub struct HostKeys {
pub current_x25519_sphinx_key: SphinxKey,
#[serde(default)]
pub pre_announced_x25519_sphinx_key: Option<SphinxKey>,
#[serde(default)]
pub x25519_versioned_noise: Option<VersionedNoiseKey>,
}
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct SphinxKey {
pub rotation_id: u32,
#[serde(with = "bs58_x25519_pubkey")]
#[schemars(with = "String")]
#[schema(value_type = String)]
pub public_key: x25519::PublicKey,
}
impl From<nym_node_requests::api::v1::node::models::SphinxKey> for SphinxKey {
fn from(value: nym_node_requests::api::v1::node::models::SphinxKey) -> Self {
SphinxKey {
rotation_id: value.rotation_id,
public_key: value.public_key,
}
}
pub x25519_noise: Option<VersionedNoiseKey>,
}
impl From<nym_node_requests::api::v1::node::models::HostKeys> for HostKeys {
@@ -1108,19 +1086,14 @@ impl NymNodeDescription {
pub fn to_semi_skimmed_node(
&self,
current_rotation_id: u32,
role: NodeRole,
performance: Performance,
) -> SemiSkimmedNode {
let skimmed_node = self.to_skimmed_node(current_rotation_id, role, performance);
let skimmed_node = self.to_skimmed_node(role, performance);
SemiSkimmedNode {
basic: skimmed_node,
x25519_noise_versioned_key: self
.description
.host_information
.keys
.x25519_versioned_noise,
x25519_noise_versioned_key: self.description.host_information.keys.x25519_noise,
}
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ 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::{EpochId, Interval, NodeId};
use nym_mixnet_contract_common::{Interval, NodeId};
use nym_noise_keys::VersionedNoiseKey;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -1,13 +1,92 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::node_describe_cache::DescribedNodes;
use crate::node_status_api::models::AxumResult;
use crate::nym_nodes::handlers::unstable::helpers::{refreshed_at, LegacyAnnotation};
use crate::nym_nodes::handlers::unstable::NodesParamsWithRole;
use crate::support::http::state::AppState;
use crate::unstable_routes::v1::nym_nodes::helpers::NodesParamsWithRole;
use axum::extract::{Query, State};
use nym_api_requests::nym_nodes::{CachedNodesResponse, SemiSkimmedNode};
use axum::Json;
use nym_api_requests::models::{
NodeAnnotation, NymNodeDescription, OffsetDateTimeJsonSchemaWrapper,
};
use nym_api_requests::nym_nodes::{NodeRole, PaginatedCachedNodesResponse, SemiSkimmedNode};
use nym_api_requests::pagination::PaginatedResponse;
use nym_http_api_common::FormattedResponse;
use nym_mixnet_contract_common::NodeId;
use nym_topology::CachedEpochRewardedSet;
use std::collections::HashMap;
use tracing::trace;
use utoipa::ToSchema;
pub type PaginatedSemiSkimmedNodes =
AxumResult<FormattedResponse<PaginatedCachedNodesResponse<SemiSkimmedNode>>>;
//SW TODO : this is copied from skimmed nodes, surely we can do better than that
fn build_nym_nodes_response<'a, NI>(
rewarded_set: &CachedEpochRewardedSet,
nym_nodes_subset: NI,
annotations: &HashMap<NodeId, NodeAnnotation>,
) -> Vec<SemiSkimmedNode>
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;
let role: NodeRole = rewarded_set.role(node_id).into();
// 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_semi_skimmed_node(role, annotation.last_24h_performance));
}
nodes
}
//SW TODO : this is copied from skimmed nodes, surely we can do better than that
/// Given all relevant caches, add appropriate legacy nodes to the part of the response
fn add_legacy<LN>(
nodes: &mut Vec<SemiSkimmedNode>,
rewarded_set: &CachedEpochRewardedSet,
describe_cache: &DescribedNodes,
annotated_legacy_nodes: &HashMap<NodeId, LN>,
) where
LN: LegacyAnnotation,
{
for (node_id, legacy) in annotated_legacy_nodes.iter() {
let role: NodeRole = rewarded_set.role(*node_id).into();
// 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_semi_skimmed_node(role, legacy.performance()))
} else {
match legacy.try_to_semi_skimmed_node(role) {
Ok(node) => nodes.push(node),
Err(err) => {
let id = legacy.identity();
trace!("node {id} is malformed: {err}")
}
}
}
}
}
#[allow(dead_code)] // not dead, used in OpenAPI docs
#[derive(ToSchema)]
#[schema(title = "PaginatedCachedNodesExpandedResponseSchema")]
pub struct PaginatedCachedNodesExpandedResponseSchema {
pub refreshed_at: OffsetDateTimeJsonSchemaWrapper,
#[schema(value_type = SemiSkimmedNode)]
pub nodes: PaginatedResponse<SemiSkimmedNode>,
}
/// Return all Nym Nodes and optionally legacy mixnodes/gateways (if `no-legacy` flag is not used)
/// that are currently bonded.
#[utoipa::path(
tag = "Unstable Nym Nodes",
get,
@@ -15,13 +94,41 @@ use nym_http_api_common::FormattedResponse;
path = "",
context_path = "/v1/unstable/nym-nodes/semi-skimmed",
responses(
// (status = 200, body = CachedNodesResponse<SemiSkimmedNode>)
(status = 501)
(status = 200, body = PaginatedCachedNodesExpandedResponseSchema)
)
)]
pub(crate) async fn nodes_expanded(
_state: State<AppState>,
pub(super) async fn nodes_expanded(
state: State<AppState>,
_query_params: Query<NodesParamsWithRole>,
) -> AxumResult<FormattedResponse<CachedNodesResponse<SemiSkimmedNode>>> {
Err(AxumErrorResponse::not_implemented())
) -> PaginatedSemiSkimmedNodes {
// 1. grab all relevant described nym-nodes
let rewarded_set = state.rewarded_set().await?;
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, all_nym_nodes, &annotations);
// add legacy gateways to the response
add_legacy(&mut nodes, &rewarded_set, &describe_cache, &legacy_gateways);
// add legacy mixnodes to the response
add_legacy(&mut nodes, &rewarded_set, &describe_cache, &legacy_mixnodes);
// 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,
)))
}