Endpoints for average mixnode uptime (#1238)
This commit is contained in:
@@ -5,6 +5,9 @@ use crate::{validator_api, ValidatorClientError};
|
||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use validator_api_requests::models::UptimeResponse;
|
||||
use validator_api_requests::models::{
|
||||
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse,
|
||||
@@ -582,6 +585,12 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_avg_uptimes(
|
||||
&self,
|
||||
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
|
||||
Ok(self.validator_api.get_mixnode_avg_uptimes().await?)
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::collections::HashMap;
|
||||
use url::Url;
|
||||
use validator_api_requests::models::{
|
||||
CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
|
||||
};
|
||||
|
||||
pub mod error;
|
||||
@@ -253,6 +253,36 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_avg_uptime(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Result<UptimeResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
identity,
|
||||
routes::AVG_UPTIME,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_avg_uptimes(&self) -> Result<Vec<UptimeResponse>, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODES,
|
||||
routes::AVG_UPTIME,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
|
||||
@@ -26,5 +26,6 @@ pub const SINCE_ARG: &str = "since";
|
||||
|
||||
pub const STATUS: &str = "status";
|
||||
pub const REWARD_ESTIMATION: &str = "reward-estimation";
|
||||
pub const AVG_UPTIME: &str = "avg_uptime";
|
||||
pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
||||
|
||||
@@ -28,6 +28,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
|
||||
pub owner: Addr,
|
||||
pub layer: Layer,
|
||||
pub mix_node: MixNode,
|
||||
pub avg_uptime: Option<u8>,
|
||||
}
|
||||
|
||||
pub(crate) struct MixNodeCache {
|
||||
|
||||
@@ -9,7 +9,9 @@ use serde::Serialize;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use mixnet_contract_common::MixNodeBond;
|
||||
use validator_client::models::UptimeResponse;
|
||||
|
||||
use crate::cache::Cache;
|
||||
use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond};
|
||||
use crate::mix_nodes::location::{Location, LocationCache, LocationCacheItem};
|
||||
use crate::mix_nodes::CACHE_ENTRY_TTL;
|
||||
@@ -76,10 +78,16 @@ impl MixNodesResult {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct MixNodeHealth {
|
||||
avg_uptime: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ThreadsafeMixNodesCache {
|
||||
mixnodes: Arc<RwLock<MixNodesResult>>,
|
||||
locations: Arc<RwLock<LocationCache>>,
|
||||
mixnode_health: Arc<RwLock<Cache<MixNodeHealth>>>,
|
||||
}
|
||||
|
||||
impl ThreadsafeMixNodesCache {
|
||||
@@ -87,6 +95,7 @@ impl ThreadsafeMixNodesCache {
|
||||
ThreadsafeMixNodesCache {
|
||||
mixnodes: Arc::new(RwLock::new(MixNodesResult::new())),
|
||||
locations: Arc::new(RwLock::new(LocationCache::new())),
|
||||
mixnode_health: Arc::new(RwLock::new(Cache::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +103,7 @@ impl ThreadsafeMixNodesCache {
|
||||
ThreadsafeMixNodesCache {
|
||||
mixnodes: Arc::new(RwLock::new(MixNodesResult::new())),
|
||||
locations: Arc::new(RwLock::new(locations)),
|
||||
mixnode_health: Arc::new(RwLock::new(Cache::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,9 +142,11 @@ impl ThreadsafeMixNodesCache {
|
||||
) -> Option<PrettyDetailedMixNodeBond> {
|
||||
let mixnodes_guard = self.mixnodes.read().await;
|
||||
let location_guard = self.locations.read().await;
|
||||
let mixnode_health_guard = self.mixnode_health.read().await;
|
||||
|
||||
let bond = mixnodes_guard.get_mixnode(identity_key);
|
||||
let location = location_guard.get(identity_key);
|
||||
let health = mixnode_health_guard.get(identity_key);
|
||||
|
||||
match bond {
|
||||
Some(bond) => Some(PrettyDetailedMixNodeBond {
|
||||
@@ -145,6 +157,7 @@ impl ThreadsafeMixNodesCache {
|
||||
owner: bond.owner,
|
||||
layer: bond.layer,
|
||||
mix_node: bond.mix_node,
|
||||
avg_uptime: health.map(|m| m.avg_uptime),
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
@@ -153,6 +166,7 @@ impl ThreadsafeMixNodesCache {
|
||||
pub(crate) async fn get_detailed_mixnodes(&self) -> Vec<PrettyDetailedMixNodeBond> {
|
||||
let mixnodes_guard = self.mixnodes.read().await;
|
||||
let location_guard = self.locations.read().await;
|
||||
let mixnode_health_guard = self.mixnode_health.read().await;
|
||||
|
||||
mixnodes_guard
|
||||
.all_mixnodes
|
||||
@@ -160,6 +174,7 @@ impl ThreadsafeMixNodesCache {
|
||||
.map(|bond| {
|
||||
let location = location_guard.get(&bond.mix_node.identity_key);
|
||||
let copy = bond.clone();
|
||||
let health = mixnode_health_guard.get(&bond.mix_node.identity_key);
|
||||
PrettyDetailedMixNodeBond {
|
||||
location: location.and_then(|l| l.location.clone()),
|
||||
status: mixnodes_guard.determine_node_status(&bond.mix_node.identity_key),
|
||||
@@ -168,6 +183,7 @@ impl ThreadsafeMixNodesCache {
|
||||
owner: copy.owner,
|
||||
layer: copy.layer,
|
||||
mix_node: copy.mix_node,
|
||||
avg_uptime: health.map(|m| m.avg_uptime),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -188,4 +204,14 @@ impl ThreadsafeMixNodesCache {
|
||||
guard.active_mixnodes = active_nodes;
|
||||
guard.valid_until = SystemTime::now() + CACHE_ENTRY_TTL;
|
||||
}
|
||||
|
||||
pub(crate) async fn update_health_cache(&self, all_uptimes: Vec<UptimeResponse>) {
|
||||
let mut mixnode_health = self.mixnode_health.write().await;
|
||||
for uptime in all_uptimes {
|
||||
let health = MixNodeHealth {
|
||||
avg_uptime: uptime.avg_uptime,
|
||||
};
|
||||
mixnode_health.set(&uptime.identity, health);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use std::future::Future;
|
||||
|
||||
use mixnet_contract_common::{GatewayBond, MixNodeBond};
|
||||
use validator_client::models::UptimeResponse;
|
||||
use validator_client::nymd::error::NymdError;
|
||||
use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse};
|
||||
use validator_client::ValidatorClientError;
|
||||
@@ -88,6 +89,17 @@ impl ExplorerApiTasks {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn retrieve_all_mixnode_avg_uptimes(
|
||||
&self,
|
||||
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
|
||||
self.state
|
||||
.inner
|
||||
.validator_client
|
||||
.0
|
||||
.get_mixnode_avg_uptimes()
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_mixnode_cache(&self) {
|
||||
let all_bonds = self.retrieve_all_mixnodes().await;
|
||||
let rewarded_nodes = self
|
||||
@@ -109,6 +121,21 @@ impl ExplorerApiTasks {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn update_mixnode_health_cache(&self) {
|
||||
match self.retrieve_all_mixnode_avg_uptimes().await {
|
||||
Ok(response) => {
|
||||
self.state
|
||||
.inner
|
||||
.mixnodes
|
||||
.update_health_cache(response)
|
||||
.await
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get mixnode avg uptimes: {:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_validators_cache(&self) {
|
||||
match self.retrieve_all_validators().await {
|
||||
Ok(response) => self.state.inner.validators.update_cache(response).await,
|
||||
@@ -145,6 +172,9 @@ impl ExplorerApiTasks {
|
||||
|
||||
info!("Updating mix node cache...");
|
||||
self.update_mixnode_cache().await;
|
||||
|
||||
info!("Updating mix node health cache...");
|
||||
self.update_mixnode_health_cache().await;
|
||||
info!("Done");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -30,6 +30,8 @@ pub(crate) fn stage_full() -> AdHoc {
|
||||
routes::get_mixnode_reward_estimation,
|
||||
routes::get_mixnode_stake_saturation,
|
||||
routes::get_mixnode_inclusion_probability,
|
||||
routes::get_mixnode_avg_uptime,
|
||||
routes::get_mixnode_avg_uptimes,
|
||||
],
|
||||
)
|
||||
})
|
||||
|
||||
@@ -8,13 +8,14 @@ use crate::node_status_api::models::{
|
||||
use crate::storage::ValidatorApiStorage;
|
||||
use crate::ValidatorCache;
|
||||
use mixnet_contract_common::reward_params::{NodeRewardParams, RewardParams};
|
||||
use mixnet_contract_common::Interval;
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
use validator_api_requests::models::{
|
||||
CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
|
||||
};
|
||||
|
||||
use super::models::Uptime;
|
||||
@@ -237,3 +238,56 @@ pub(crate) async fn get_mixnode_inclusion_probability(
|
||||
Json(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn average_mixnode_uptime(
|
||||
identity: &str,
|
||||
current_epoch: Option<Interval>,
|
||||
storage: &State<ValidatorApiStorage>,
|
||||
) -> Result<Uptime, ErrorResponse> {
|
||||
Ok(if let Some(epoch) = current_epoch {
|
||||
storage
|
||||
.get_average_mixnode_uptime_in_the_last_24hrs(identity, epoch.end_unix_timestamp())
|
||||
.await
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))?
|
||||
} else {
|
||||
Uptime::default()
|
||||
})
|
||||
}
|
||||
|
||||
#[openapi(tag = "mixnode")]
|
||||
#[get("/mixnode/<identity>/avg_uptime")]
|
||||
pub(crate) async fn get_mixnode_avg_uptime(
|
||||
cache: &State<ValidatorCache>,
|
||||
storage: &State<ValidatorApiStorage>,
|
||||
identity: String,
|
||||
) -> Result<Json<UptimeResponse>, ErrorResponse> {
|
||||
let current_epoch = cache.current_epoch().await.into_inner();
|
||||
let uptime = average_mixnode_uptime(&identity, current_epoch, storage).await?;
|
||||
|
||||
Ok(Json(UptimeResponse {
|
||||
identity,
|
||||
avg_uptime: uptime.u8(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[openapi(tag = "mixnode")]
|
||||
#[get("/mixnodes/avg_uptime")]
|
||||
pub(crate) async fn get_mixnode_avg_uptimes(
|
||||
cache: &State<ValidatorCache>,
|
||||
storage: &State<ValidatorApiStorage>,
|
||||
) -> Result<Json<Vec<UptimeResponse>>, ErrorResponse> {
|
||||
let mixnodes = cache.mixnodes().await;
|
||||
let current_epoch = cache.current_epoch().await.into_inner();
|
||||
|
||||
let mut response = Vec::new();
|
||||
for mixnode in mixnodes {
|
||||
let uptime = average_mixnode_uptime(mixnode.identity(), current_epoch, storage).await?;
|
||||
|
||||
response.push(UptimeResponse {
|
||||
identity: mixnode.identity().to_string(),
|
||||
avg_uptime: uptime.u8(),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
@@ -63,6 +63,12 @@ pub struct RewardEstimationResponse {
|
||||
pub as_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct UptimeResponse {
|
||||
pub identity: String,
|
||||
pub avg_uptime: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
|
||||
Reference in New Issue
Block a user