Files
nym/nym-api/src/node_status_api/helpers.rs
T
2025-08-08 13:03:05 +01:00

382 lines
12 KiB
Rust

// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::storage::NymApiStorage;
use crate::support::caching::Cache;
use crate::{MixnetContractCache, NodeStatusCache};
use nym_api_requests::models::{
ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, GatewayUptimeResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
use nym_mixnet_contract_common::rewarding::RewardEstimate;
use nym_mixnet_contract_common::NodeId;
async fn gateway_identity_to_node_id(
cache: &NodeStatusCache,
identity: &str,
) -> AxumResult<NodeId> {
let node_id = cache
.map_identity_to_node_id(identity)
.await
.ok_or(AxumErrorResponse::not_found("gateway bond not found"))?;
Ok(node_id)
}
async fn get_gateway_bond_annotated(
cache: &NodeStatusCache,
node_id: NodeId,
) -> AxumResult<GatewayBondAnnotated> {
cache
.gateway_annotated(node_id)
.await
.ok_or(AxumErrorResponse::not_found("gateway bond not found"))
}
async fn get_gateway_bond_annotated_by_identity(
cache: &NodeStatusCache,
identity: &str,
) -> AxumResult<GatewayBondAnnotated> {
let node_id = gateway_identity_to_node_id(cache, identity).await?;
get_gateway_bond_annotated(cache, node_id).await
}
async fn get_mixnode_bond_annotated(
cache: &NodeStatusCache,
mix_id: NodeId,
) -> AxumResult<MixNodeBondAnnotated> {
cache
.mixnode_annotated(mix_id)
.await
.ok_or(AxumErrorResponse::not_found("mixnode bond not found"))
}
pub(crate) async fn _gateway_report(
cache: &NodeStatusCache,
identity: &str,
) -> AxumResult<GatewayStatusReportResponse> {
let gateway = get_gateway_bond_annotated_by_identity(cache, identity).await?;
Ok(GatewayStatusReportResponse {
identity: gateway.identity().to_owned(),
owner: gateway.owner().to_string(),
most_recent: gateway.node_performance.most_recent.round_to_integer(),
last_hour: gateway.node_performance.last_hour.round_to_integer(),
last_day: gateway.node_performance.last_24h.round_to_integer(),
})
}
pub(crate) async fn _gateway_uptime_history(
storage: &NymApiStorage,
nym_contract_cache: &MixnetContractCache,
identity: &str,
) -> AxumResult<GatewayUptimeHistoryResponse> {
let history = storage
.get_gateway_uptime_history_by_identity(identity)
.await
.map_err(AxumErrorResponse::not_found)?;
let owner = nym_contract_cache
.legacy_gateway_owner(history.node_id)
.await
.ok_or_else(|| AxumErrorResponse::not_found("could not determine gateway owner"))?;
Ok(GatewayUptimeHistoryResponse {
identity: history.identity,
owner,
history: history.history.into_iter().map(Into::into).collect(),
})
}
pub(crate) async fn _gateway_core_status_count(
storage: &NymApiStorage,
identity: &str,
since: Option<i64>,
) -> AxumResult<GatewayCoreStatusResponse> {
let count = storage
.get_core_gateway_status_count_by_identity(identity, since)
.await
.map_err(AxumErrorResponse::not_found)?;
Ok(GatewayCoreStatusResponse {
identity: identity.to_string(),
count,
})
}
pub(crate) async fn _mixnode_report(
cache: &NodeStatusCache,
mix_id: NodeId,
) -> AxumResult<MixnodeStatusReportResponse> {
let mixnode = get_mixnode_bond_annotated(cache, mix_id).await?;
Ok(MixnodeStatusReportResponse {
mix_id,
identity: mixnode.identity_key().to_owned(),
owner: mixnode.owner().to_string(),
most_recent: mixnode.node_performance.most_recent.round_to_integer(),
last_hour: mixnode.node_performance.last_hour.round_to_integer(),
last_day: mixnode.node_performance.last_24h.round_to_integer(),
})
}
pub(crate) async fn _mixnode_uptime_history(
storage: &NymApiStorage,
nym_contract_cache: &MixnetContractCache,
mix_id: NodeId,
) -> AxumResult<MixnodeUptimeHistoryResponse> {
let history = storage
.get_mixnode_uptime_history(mix_id)
.await
.map_err(AxumErrorResponse::not_found)?;
let owner = nym_contract_cache
.legacy_gateway_owner(mix_id)
.await
.ok_or_else(|| AxumErrorResponse::not_found("could not determine mixnode owner"))?;
Ok(MixnodeUptimeHistoryResponse {
mix_id,
identity: history.identity,
owner,
history: history.history.into_iter().map(Into::into).collect(),
})
}
pub(crate) async fn _mixnode_core_status_count(
storage: &NymApiStorage,
mix_id: NodeId,
since: Option<i64>,
) -> AxumResult<MixnodeCoreStatusResponse> {
let count = storage
.get_core_mixnode_status_count(mix_id, since)
.await
.map_err(AxumErrorResponse::not_found)?;
Ok(MixnodeCoreStatusResponse { mix_id, count })
}
pub(crate) async fn _get_mixnode_status(
cache: &MixnetContractCache,
mix_id: NodeId,
) -> MixnodeStatusResponse {
MixnodeStatusResponse {
status: cache.mixnode_status(mix_id).await,
}
}
pub(crate) async fn _get_mixnode_reward_estimation(
status_cache: &NodeStatusCache,
contract_cache: &MixnetContractCache,
mix_id: NodeId,
) -> AxumResult<RewardEstimationResponse> {
let _ = status_cache
.mixnode_annotated(mix_id)
.await
.ok_or_else(|| AxumErrorResponse::not_found("mixnode bond not found"))?;
// legacy mixnode will never get any rewards
let reward_estimation = RewardEstimate::zero();
let reward_params = contract_cache.interval_reward_params().await?;
let current_interval = contract_cache.current_interval().await?;
// in some very rare edge cases this value might be off (as internals might have got updated between
// queries for `reward_params` and `current_interval`, but timestamp is only informative to begin with)
let as_at = contract_cache.cache_timestamp().await;
Ok(RewardEstimationResponse {
estimation: reward_estimation,
reward_params,
epoch: current_interval,
as_at: as_at.unix_timestamp(),
})
}
pub(crate) async fn _compute_mixnode_reward_estimation(
_: &ComputeRewardEstParam,
status_cache: &NodeStatusCache,
contract_cache: &MixnetContractCache,
mix_id: NodeId,
) -> AxumResult<RewardEstimationResponse> {
let _ = status_cache
.mixnode_annotated(mix_id)
.await
.ok_or_else(|| AxumErrorResponse::not_found("mixnode bond not found"))?;
let reward_estimation = RewardEstimate::zero();
let reward_params = contract_cache.interval_reward_params().await?;
let current_interval = contract_cache.current_interval().await?;
// in some very rare edge cases this value might be off (as internals might have got updated between
// queries for `reward_params` and `current_interval`, but timestamp is only informative to begin with)
let as_at = contract_cache.cache_timestamp().await;
Ok(RewardEstimationResponse {
estimation: reward_estimation,
reward_params,
epoch: current_interval,
as_at: as_at.unix_timestamp(),
})
}
pub(crate) async fn _get_mixnode_stake_saturation(
status_cache: &NodeStatusCache,
contract_cache: &MixnetContractCache,
mix_id: NodeId,
) -> AxumResult<StakeSaturationResponse> {
let mixnode = status_cache
.mixnode_annotated(mix_id)
.await
.ok_or_else(|| AxumErrorResponse::not_found("mixnode bond not found"))?;
// Recompute the stake saturation just so that we can confidently state that the `as_at`
// field is consistent and correct. Luckily this is very cheap.
let rewarding_params = contract_cache.interval_reward_params().await?;
let as_at = contract_cache.cache_timestamp().await;
Ok(StakeSaturationResponse {
saturation: mixnode
.mixnode_details
.rewarding_details
.bond_saturation(&rewarding_params),
uncapped_saturation: mixnode
.mixnode_details
.rewarding_details
.uncapped_bond_saturation(&rewarding_params),
as_at: as_at.unix_timestamp(),
})
}
#[allow(deprecated)]
pub(crate) async fn _get_mixnode_inclusion_probability(
cache: &NodeStatusCache,
mix_id: NodeId,
) -> AxumResult<nym_api_requests::models::InclusionProbabilityResponse> {
cache
.inclusion_probabilities()
.await
.map(Cache::into_inner)
.and_then(|p| p.node(mix_id).cloned())
.map(|p| nym_api_requests::models::InclusionProbabilityResponse {
in_active: p.in_active.into(),
in_reserve: p.in_reserve.into(),
})
.ok_or_else(|| AxumErrorResponse::not_found("mixnode bond not found"))
}
pub(crate) async fn _get_mixnode_avg_uptime(
cache: &NodeStatusCache,
mix_id: NodeId,
) -> AxumResult<UptimeResponse> {
let mixnode = get_mixnode_bond_annotated(cache, mix_id).await?;
Ok(UptimeResponse {
mix_id,
avg_uptime: mixnode.node_performance.last_24h.round_to_integer(),
node_performance: mixnode.node_performance,
})
}
pub(crate) async fn _get_gateway_avg_uptime(
cache: &NodeStatusCache,
identity: &str,
) -> AxumResult<GatewayUptimeResponse> {
let gateway = get_gateway_bond_annotated_by_identity(cache, identity).await?;
Ok(GatewayUptimeResponse {
identity: identity.to_string(),
avg_uptime: gateway.node_performance.last_24h.round_to_integer(),
node_performance: gateway.node_performance,
})
}
#[allow(deprecated)]
pub(crate) async fn _get_mixnode_inclusion_probabilities(
cache: &NodeStatusCache,
) -> AxumResult<nym_api_requests::models::AllInclusionProbabilitiesResponse> {
if let Some(prob) = cache.inclusion_probabilities().await {
let as_at = prob.timestamp();
let prob = prob.into_inner();
Ok(
nym_api_requests::models::AllInclusionProbabilitiesResponse {
inclusion_probabilities: prob.inclusion_probabilities,
samples: prob.samples,
elapsed: prob.elapsed,
delta_max: prob.delta_max,
delta_l2: prob.delta_l2,
as_at: as_at.unix_timestamp(),
},
)
} else {
Err(AxumErrorResponse::service_unavailable())
}
}
pub(crate) async fn _get_legacy_mixnodes_detailed(
cache: &NodeStatusCache,
) -> Vec<MixNodeBondAnnotated> {
cache
.mixnodes_annotated_filtered()
.await
.unwrap_or_default()
}
pub(crate) async fn _get_mixnodes_detailed_unfiltered(
cache: &NodeStatusCache,
) -> Vec<MixNodeBondAnnotated> {
cache.mixnodes_annotated_full().await.unwrap_or_default()
}
pub(crate) async fn _get_rewarded_set_legacy_mixnodes_detailed(
status_cache: &NodeStatusCache,
contract_cache: &MixnetContractCache,
) -> Vec<MixNodeBondAnnotated> {
let Some(rewarded_set) = contract_cache.rewarded_set().await else {
return Vec::new();
};
let Some(mixnodes) = status_cache.mixnodes_annotated_full().await else {
return Vec::new();
};
mixnodes
.into_iter()
.filter(|m| {
rewarded_set.is_active_mixnode(&m.mix_id()) || rewarded_set.is_standby(&m.mix_id())
})
.collect()
}
pub(crate) async fn _get_active_set_legacy_mixnodes_detailed(
status_cache: &NodeStatusCache,
contract_cache: &MixnetContractCache,
) -> Vec<MixNodeBondAnnotated> {
let Some(rewarded_set) = contract_cache.rewarded_set().await else {
return Vec::new();
};
let Some(mixnodes) = status_cache.mixnodes_annotated_full().await else {
return Vec::new();
};
mixnodes
.into_iter()
.filter(|m| rewarded_set.is_active_mixnode(&m.mix_id()))
.collect()
}
pub(crate) async fn _get_legacy_gateways_detailed(
cache: &NodeStatusCache,
) -> Vec<GatewayBondAnnotated> {
cache
.gateways_annotated_filtered()
.await
.unwrap_or_default()
}
pub(crate) async fn _get_legacy_gateways_detailed_unfiltered(
cache: &NodeStatusCache,
) -> Vec<GatewayBondAnnotated> {
cache.gateways_annotated_full().await.unwrap_or_default()
}