Endpoints for average mixnode uptime (#1238)

This commit is contained in:
Jon Häggblad
2022-05-04 11:28:41 +02:00
committed by GitHub
parent 8eb3f6f862
commit 139e89643c
9 changed files with 161 additions and 2 deletions
@@ -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";
+1
View File
@@ -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 {
+26
View File
@@ -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);
}
}
}
+30
View File
@@ -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");
}
});
+2
View File
@@ -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,
],
)
})
+55 -1
View File
@@ -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(