f590aad42c
* nym-api: cache updates as node performance * nym-api: update get mixnode avg_uptime endpoint * nym-api: mixnode report to use cached data * nym-api: annotate gateway bond with node performance * nym-api: gateway report to use cached data * wip * Add get_gateway_avg_uptime * Add comment * update NR gateways to include node_performance on frontend * use node_performance values on frontend * fixup select gateway from list * fix up lint errors --------- Co-authored-by: fmtabbara <fmtabbara@hotmail.co.uk>
408 lines
13 KiB
Rust
408 lines
13 KiB
Rust
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use crate::node_status_api::models::ErrorResponse;
|
|
use crate::storage::NymApiStorage;
|
|
use crate::support::caching::Cache;
|
|
use crate::{NodeStatusCache, NymContractCache};
|
|
use cosmwasm_std::Decimal;
|
|
use nym_api_requests::models::{
|
|
AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayBondAnnotated,
|
|
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
|
|
GatewayUptimeResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
|
|
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
|
|
MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse,
|
|
UptimeResponse,
|
|
};
|
|
use nym_mixnet_contract_common::{MixId, RewardedSetNodeStatus};
|
|
use rocket::http::Status;
|
|
use rocket::State;
|
|
|
|
use super::reward_estimate::compute_reward_estimate;
|
|
|
|
async fn get_gateway_bond_annotated(
|
|
cache: &NodeStatusCache,
|
|
identity: &str,
|
|
) -> Result<GatewayBondAnnotated, ErrorResponse> {
|
|
let gateways = cache.gateways_annotated().await.ok_or(ErrorResponse::new(
|
|
"no data available",
|
|
Status::ServiceUnavailable,
|
|
))?;
|
|
|
|
gateways
|
|
.into_inner()
|
|
.into_iter()
|
|
.find(|gateway| gateway.identity() == identity)
|
|
.ok_or(ErrorResponse::new(
|
|
"mixnode bond not found",
|
|
Status::NotFound,
|
|
))
|
|
}
|
|
|
|
async fn get_mixnode_bond_annotated(
|
|
cache: &NodeStatusCache,
|
|
mix_id: MixId,
|
|
) -> Result<MixNodeBondAnnotated, ErrorResponse> {
|
|
let mixnodes = cache.mixnodes_annotated().await.ok_or(ErrorResponse::new(
|
|
"no data available",
|
|
Status::ServiceUnavailable,
|
|
))?;
|
|
|
|
mixnodes
|
|
.into_inner()
|
|
.into_iter()
|
|
.find(|mixnode| mixnode.mix_id() == mix_id)
|
|
.ok_or(ErrorResponse::new(
|
|
"mixnode bond not found",
|
|
Status::NotFound,
|
|
))
|
|
}
|
|
|
|
pub(crate) async fn _gateway_report(
|
|
cache: &NodeStatusCache,
|
|
identity: &str,
|
|
) -> Result<GatewayStatusReportResponse, ErrorResponse> {
|
|
let gateway = get_gateway_bond_annotated(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,
|
|
identity: &str,
|
|
) -> Result<GatewayUptimeHistoryResponse, ErrorResponse> {
|
|
storage
|
|
.get_gateway_uptime_history(identity)
|
|
.await
|
|
.map(GatewayUptimeHistoryResponse::from)
|
|
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
|
|
}
|
|
|
|
pub(crate) async fn _gateway_core_status_count(
|
|
storage: &State<NymApiStorage>,
|
|
identity: &str,
|
|
since: Option<i64>,
|
|
) -> Result<GatewayCoreStatusResponse, ErrorResponse> {
|
|
let count = storage
|
|
.get_core_gateway_status_count(identity, since)
|
|
.await
|
|
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))?;
|
|
|
|
Ok(GatewayCoreStatusResponse {
|
|
identity: identity.to_string(),
|
|
count,
|
|
})
|
|
}
|
|
|
|
pub(crate) async fn _mixnode_report(
|
|
cache: &NodeStatusCache,
|
|
mix_id: MixId,
|
|
) -> Result<MixnodeStatusReportResponse, ErrorResponse> {
|
|
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,
|
|
mix_id: MixId,
|
|
) -> Result<MixnodeUptimeHistoryResponse, ErrorResponse> {
|
|
storage
|
|
.get_mixnode_uptime_history(mix_id)
|
|
.await
|
|
.map(MixnodeUptimeHistoryResponse::from)
|
|
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
|
|
}
|
|
|
|
pub(crate) async fn _mixnode_core_status_count(
|
|
storage: &State<NymApiStorage>,
|
|
mix_id: MixId,
|
|
since: Option<i64>,
|
|
) -> Result<MixnodeCoreStatusResponse, ErrorResponse> {
|
|
let count = storage
|
|
.get_core_mixnode_status_count(mix_id, since)
|
|
.await
|
|
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))?;
|
|
|
|
Ok(MixnodeCoreStatusResponse { mix_id, count })
|
|
}
|
|
|
|
pub(crate) async fn _get_mixnode_status(
|
|
cache: &NymContractCache,
|
|
mix_id: MixId,
|
|
) -> MixnodeStatusResponse {
|
|
MixnodeStatusResponse {
|
|
status: cache.mixnode_status(mix_id).await,
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn _get_mixnode_reward_estimation(
|
|
cache: &State<NodeStatusCache>,
|
|
validator_cache: &State<NymContractCache>,
|
|
mix_id: MixId,
|
|
) -> Result<RewardEstimationResponse, ErrorResponse> {
|
|
let (mixnode, status) = cache.mixnode_details(mix_id).await;
|
|
if let Some(mixnode) = mixnode {
|
|
let reward_params = validator_cache.interval_reward_params().await;
|
|
let as_at = reward_params.timestamp();
|
|
let reward_params = reward_params
|
|
.into_inner()
|
|
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
|
|
let current_interval = validator_cache
|
|
.current_interval()
|
|
.await
|
|
.into_inner()
|
|
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
|
|
|
|
let reward_estimation = compute_reward_estimate(
|
|
&mixnode.mixnode_details,
|
|
mixnode.performance,
|
|
status.into(),
|
|
reward_params,
|
|
current_interval,
|
|
);
|
|
|
|
Ok(RewardEstimationResponse {
|
|
estimation: reward_estimation,
|
|
reward_params,
|
|
epoch: current_interval,
|
|
as_at,
|
|
})
|
|
} else {
|
|
Err(ErrorResponse::new(
|
|
"mixnode bond not found",
|
|
Status::NotFound,
|
|
))
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn _compute_mixnode_reward_estimation(
|
|
user_reward_param: ComputeRewardEstParam,
|
|
cache: &NodeStatusCache,
|
|
validator_cache: &NymContractCache,
|
|
mix_id: MixId,
|
|
) -> Result<RewardEstimationResponse, ErrorResponse> {
|
|
let (mixnode, actual_status) = cache.mixnode_details(mix_id).await;
|
|
if let Some(mut mixnode) = mixnode {
|
|
let reward_params = validator_cache.interval_reward_params().await;
|
|
let as_at = reward_params.timestamp();
|
|
let reward_params = reward_params
|
|
.into_inner()
|
|
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
|
|
let current_interval = validator_cache
|
|
.current_interval()
|
|
.await
|
|
.into_inner()
|
|
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
|
|
|
|
// For these parameters we either use the provided ones, or fall back to the system ones
|
|
let performance = user_reward_param.performance.unwrap_or(mixnode.performance);
|
|
|
|
let status = match user_reward_param.active_in_rewarded_set {
|
|
Some(true) => Some(RewardedSetNodeStatus::Active),
|
|
Some(false) => Some(RewardedSetNodeStatus::Standby),
|
|
None => actual_status.into(),
|
|
};
|
|
|
|
if let Some(pledge_amount) = user_reward_param.pledge_amount {
|
|
mixnode.mixnode_details.rewarding_details.operator =
|
|
Decimal::from_ratio(pledge_amount, 1u64);
|
|
}
|
|
if let Some(total_delegation) = user_reward_param.total_delegation {
|
|
mixnode.mixnode_details.rewarding_details.delegates =
|
|
Decimal::from_ratio(total_delegation, 1u64);
|
|
}
|
|
|
|
if let Some(profit_margin_percent) = user_reward_param.profit_margin_percent {
|
|
mixnode
|
|
.mixnode_details
|
|
.rewarding_details
|
|
.cost_params
|
|
.profit_margin_percent = profit_margin_percent;
|
|
}
|
|
|
|
if let Some(interval_operating_cost) = user_reward_param.interval_operating_cost {
|
|
mixnode
|
|
.mixnode_details
|
|
.rewarding_details
|
|
.cost_params
|
|
.interval_operating_cost = interval_operating_cost;
|
|
}
|
|
|
|
if mixnode.mixnode_details.rewarding_details.operator
|
|
+ mixnode.mixnode_details.rewarding_details.delegates
|
|
> reward_params.interval.staking_supply
|
|
{
|
|
return Err(ErrorResponse::new(
|
|
"Pledge plus delegation too large",
|
|
Status::UnprocessableEntity,
|
|
));
|
|
}
|
|
|
|
let reward_estimation = compute_reward_estimate(
|
|
&mixnode.mixnode_details,
|
|
performance,
|
|
status,
|
|
reward_params,
|
|
current_interval,
|
|
);
|
|
|
|
Ok(RewardEstimationResponse {
|
|
estimation: reward_estimation,
|
|
reward_params,
|
|
epoch: current_interval,
|
|
as_at,
|
|
})
|
|
} else {
|
|
Err(ErrorResponse::new(
|
|
"mixnode bond not found",
|
|
Status::NotFound,
|
|
))
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn _get_mixnode_stake_saturation(
|
|
cache: &NodeStatusCache,
|
|
validator_cache: &NymContractCache,
|
|
mix_id: MixId,
|
|
) -> Result<StakeSaturationResponse, ErrorResponse> {
|
|
let (mixnode, _) = cache.mixnode_details(mix_id).await;
|
|
if let Some(mixnode) = mixnode {
|
|
// 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 reward_params = validator_cache.interval_reward_params().await;
|
|
let as_at = reward_params.timestamp();
|
|
let rewarding_params = reward_params
|
|
.into_inner()
|
|
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
|
|
|
|
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,
|
|
})
|
|
} else {
|
|
Err(ErrorResponse::new(
|
|
"mixnode bond not found",
|
|
Status::NotFound,
|
|
))
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn _get_mixnode_inclusion_probability(
|
|
cache: &NodeStatusCache,
|
|
mix_id: MixId,
|
|
) -> Result<InclusionProbabilityResponse, ErrorResponse> {
|
|
cache
|
|
.inclusion_probabilities()
|
|
.await
|
|
.map(Cache::into_inner)
|
|
.and_then(|p| p.node(mix_id).cloned())
|
|
.map(|p| InclusionProbabilityResponse {
|
|
in_active: p.in_active.into(),
|
|
in_reserve: p.in_reserve.into(),
|
|
})
|
|
.ok_or_else(|| ErrorResponse::new("mixnode bond not found", Status::NotFound))
|
|
}
|
|
|
|
pub(crate) async fn _get_mixnode_avg_uptime(
|
|
cache: &NodeStatusCache,
|
|
mix_id: MixId,
|
|
) -> Result<UptimeResponse, ErrorResponse> {
|
|
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,
|
|
) -> Result<GatewayUptimeResponse, ErrorResponse> {
|
|
let gateway = get_gateway_bond_annotated(cache, identity).await?;
|
|
|
|
Ok(GatewayUptimeResponse {
|
|
identity: identity.to_string(),
|
|
avg_uptime: gateway.node_performance.last_24h.round_to_integer(),
|
|
node_performance: gateway.node_performance,
|
|
})
|
|
}
|
|
|
|
pub(crate) async fn _get_mixnode_inclusion_probabilities(
|
|
cache: &NodeStatusCache,
|
|
) -> Result<AllInclusionProbabilitiesResponse, ErrorResponse> {
|
|
if let Some(prob) = cache.inclusion_probabilities().await {
|
|
let as_at = prob.timestamp();
|
|
let prob = prob.into_inner();
|
|
Ok(AllInclusionProbabilitiesResponse {
|
|
inclusion_probabilities: prob.inclusion_probabilities,
|
|
samples: prob.samples,
|
|
elapsed: prob.elapsed,
|
|
delta_max: prob.delta_max,
|
|
delta_l2: prob.delta_l2,
|
|
as_at,
|
|
})
|
|
} else {
|
|
Err(ErrorResponse::new(
|
|
"No data available",
|
|
Status::ServiceUnavailable,
|
|
))
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn _get_mixnodes_detailed(cache: &NodeStatusCache) -> Vec<MixNodeBondAnnotated> {
|
|
cache
|
|
.mixnodes_annotated()
|
|
.await
|
|
.unwrap_or_default()
|
|
.into_inner()
|
|
}
|
|
|
|
pub(crate) async fn _get_rewarded_set_detailed(
|
|
cache: &NodeStatusCache,
|
|
) -> Vec<MixNodeBondAnnotated> {
|
|
cache
|
|
.rewarded_set_annotated()
|
|
.await
|
|
.unwrap_or_default()
|
|
.into_inner()
|
|
}
|
|
|
|
pub(crate) async fn _get_active_set_detailed(cache: &NodeStatusCache) -> Vec<MixNodeBondAnnotated> {
|
|
cache
|
|
.active_set_annotated()
|
|
.await
|
|
.unwrap_or_default()
|
|
.into_inner()
|
|
}
|
|
|
|
pub(crate) async fn _get_gateways_detailed(cache: &NodeStatusCache) -> Vec<GatewayBondAnnotated> {
|
|
cache
|
|
.gateways_annotated()
|
|
.await
|
|
.unwrap_or_default()
|
|
.into_inner()
|
|
}
|