Compare commits

...

1 Commits

Author SHA1 Message Date
Jon Häggblad 15ef250263 explorer-api: add stake saturation for all mixnodes 2022-05-25 16:52:31 +02:00
11 changed files with 158 additions and 41 deletions
+2 -1
View File
@@ -4,7 +4,8 @@
### Added
- wallet: require password to switch accounts
- explorer-api and validator-api: endpoints for getting stake saturation for all mixnodes.
- wallet: require password to switch accounts.
- wallet: add simple CLI tool for decrypting and recovering the wallet file.
- wallet: added support for multiple accounts ([#1265])
- wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint.
Generated
+2
View File
@@ -1181,6 +1181,7 @@ dependencies = [
"byteorder",
"digest 0.9.0",
"rand_core 0.5.1",
"serde",
"subtle 2.4.1",
"zeroize",
]
@@ -6628,6 +6629,7 @@ checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f"
dependencies = [
"curve25519-dalek",
"rand_core 0.5.1",
"serde",
"zeroize",
]
@@ -10,7 +10,7 @@ use url::Url;
use validator_api_requests::models::UptimeResponse;
use validator_api_requests::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
StakeSaturationResponse, StakeSaturationResponseWithId, StakeStats,
};
#[cfg(feature = "nymd-client")]
@@ -591,6 +591,16 @@ impl<C> Client<C> {
Ok(self.validator_api.get_mixnode_avg_uptimes().await?)
}
pub async fn get_mixnode_stake_saturations(
&self,
) -> Result<Vec<StakeSaturationResponseWithId>, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_stake_saturations().await?)
}
pub async fn get_mixnode_stake_stats(&self) -> Result<Vec<StakeStats>, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_stake_stats().await?)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -10,7 +10,8 @@ use std::collections::HashMap;
use url::Url;
use validator_api_requests::models::{
CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
RewardEstimationResponse, StakeSaturationResponse, StakeSaturationResponseWithId, StakeStats,
UptimeResponse,
};
pub mod error;
@@ -236,6 +237,34 @@ impl Client {
.await
}
pub async fn get_mixnode_stake_saturations(
&self,
) -> Result<Vec<StakeSaturationResponseWithId>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODES,
routes::STAKE_SATURATION,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_stake_stats(&self) -> Result<Vec<StakeStats>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODES,
routes::STAKE_STATS,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_inclusion_probability(
&self,
identity: IdentityKeyRef<'_>,
@@ -28,4 +28,5 @@ 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 STAKE_STATS: &str = "stake-stats";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
+1
View File
@@ -29,6 +29,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
pub layer: Layer,
pub mix_node: MixNode,
pub avg_uptime: Option<u8>,
pub stake_saturation: Option<f32>,
}
pub(crate) struct MixNodeCache {
+11 -7
View File
@@ -9,7 +9,7 @@ use serde::Serialize;
use tokio::sync::RwLock;
use mixnet_contract_common::MixNodeBond;
use validator_client::models::UptimeResponse;
use validator_client::models::StakeStats;
use crate::cache::Cache;
use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond};
@@ -81,6 +81,7 @@ impl MixNodesResult {
#[derive(Clone, Debug)]
pub(crate) struct MixNodeHealth {
avg_uptime: u8,
stake_saturation: f32,
}
#[derive(Clone)]
@@ -157,7 +158,8 @@ impl ThreadsafeMixNodesCache {
owner: bond.owner,
layer: bond.layer,
mix_node: bond.mix_node,
avg_uptime: health.map(|m| m.avg_uptime),
avg_uptime: health.as_ref().map(|m| m.avg_uptime),
stake_saturation: health.map(|m| m.stake_saturation),
}),
None => None,
}
@@ -183,7 +185,8 @@ impl ThreadsafeMixNodesCache {
owner: copy.owner,
layer: copy.layer,
mix_node: copy.mix_node,
avg_uptime: health.map(|m| m.avg_uptime),
avg_uptime: health.as_ref().map(|m| m.avg_uptime),
stake_saturation: health.map(|m| m.stake_saturation),
}
})
.collect()
@@ -205,13 +208,14 @@ impl ThreadsafeMixNodesCache {
guard.valid_until = SystemTime::now() + CACHE_ENTRY_TTL;
}
pub(crate) async fn update_health_cache(&self, all_uptimes: Vec<UptimeResponse>) {
pub(crate) async fn update_health_cache(&self, stake_stats: Vec<StakeStats>) {
let mut mixnode_health = self.mixnode_health.write().await;
for uptime in all_uptimes {
for stat in stake_stats {
let health = MixNodeHealth {
avg_uptime: uptime.avg_uptime,
avg_uptime: stat.avg_uptime,
stake_saturation: stat.saturation,
};
mixnode_health.set(&uptime.identity, health);
mixnode_health.set(&stat.identity, health);
}
}
}
+15 -14
View File
@@ -4,7 +4,7 @@
use std::future::Future;
use mixnet_contract_common::{GatewayBond, MixNodeBond};
use validator_client::models::UptimeResponse;
use validator_client::models::StakeStats;
use validator_client::nymd::error::NymdError;
use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse};
use validator_client::ValidatorClientError;
@@ -89,14 +89,14 @@ impl ExplorerApiTasks {
.await
}
async fn retrieve_all_mixnode_avg_uptimes(
async fn retrieve_all_mixnode_stake_stats(
&self,
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
) -> Result<Vec<StakeStats>, ValidatorClientError> {
self.state
.inner
.validator_client
.0
.get_mixnode_avg_uptimes()
.get_mixnode_stake_stats()
.await
}
@@ -122,18 +122,19 @@ impl ExplorerApiTasks {
}
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
}
let response = match self.retrieve_all_mixnode_stake_stats().await {
Ok(response) => response,
Err(e) => {
error!("Failed to get mixnode avg uptimes: {:?}", e)
error!("Failed to get mixnode stake stats: {:?}", e);
return;
}
}
};
self.state
.inner
.mixnodes
.update_health_cache(response)
.await
}
async fn update_validators_cache(&self) {
+4 -1
View File
@@ -33,7 +33,9 @@ pub(crate) fn node_status_routes(
routes::get_mixnode_stake_saturation,
routes::get_mixnode_inclusion_probability,
routes::get_mixnode_avg_uptime,
routes::get_mixnode_avg_uptimes,
routes::get_all_mixnode_avg_uptimes,
routes::get_all_mixnode_stake_saturations,
routes::get_all_mixnode_stake_stats,
]
} else {
// in the minimal variant we would not have access to endpoints relying on existence
@@ -42,6 +44,7 @@ pub(crate) fn node_status_routes(
routes::get_mixnode_status,
routes::get_mixnode_stake_saturation,
routes::get_mixnode_inclusion_probability,
routes::get_all_mixnode_stake_saturations,
]
}
}
+66 -16
View File
@@ -8,14 +8,15 @@ 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 mixnet_contract_common::{Interval, MixNodeBond};
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, UptimeResponse,
RewardEstimationResponse, StakeSaturationResponse, StakeSaturationResponseWithId, StakeStats,
UptimeResponse,
};
use super::models::Uptime;
@@ -175,6 +176,25 @@ pub(crate) async fn get_mixnode_reward_estimation(
}
}
async fn get_state_saturation(
bond: &MixNodeBond,
cache: &State<ValidatorCache>,
) -> StakeSaturationResponse {
let interval_reward_params = cache.epoch_reward_params().await;
let as_at = interval_reward_params.timestamp();
let interval_reward_params = interval_reward_params.into_inner();
let saturation = bond.stake_saturation(
interval_reward_params.circulating_supply(),
interval_reward_params.rewarded_set_size() as u32,
);
StakeSaturationResponse {
saturation: saturation.to_num(),
as_at,
}
}
#[openapi(tag = "status")]
#[get("/mixnode/<identity>/stake-saturation")]
pub(crate) async fn get_mixnode_stake_saturation(
@@ -183,19 +203,7 @@ pub(crate) async fn get_mixnode_stake_saturation(
) -> Result<Json<StakeSaturationResponse>, ErrorResponse> {
let (bond, _) = cache.mixnode_details(&identity).await;
if let Some(bond) = bond {
let interval_reward_params = cache.epoch_reward_params().await;
let as_at = interval_reward_params.timestamp();
let interval_reward_params = interval_reward_params.into_inner();
let saturation = bond.stake_saturation(
interval_reward_params.circulating_supply(),
interval_reward_params.rewarded_set_size() as u32,
);
Ok(Json(StakeSaturationResponse {
saturation: saturation.to_num(),
as_at,
}))
Ok(Json(get_state_saturation(&bond, cache).await))
} else {
Err(ErrorResponse::new(
"mixnode bond not found",
@@ -204,6 +212,48 @@ pub(crate) async fn get_mixnode_stake_saturation(
}
}
#[openapi(tag = "status")]
#[get("/mixnodes/stake-saturation")]
pub(crate) async fn get_all_mixnode_stake_saturations(
cache: &State<ValidatorCache>,
) -> Result<Json<Vec<StakeSaturationResponseWithId>>, ErrorResponse> {
let mixnode_bonds = cache.mixnodes().await;
let mut stake_saturations = Vec::new();
for bond in mixnode_bonds {
let stake_saturation = get_state_saturation(&bond, cache).await;
stake_saturations.push(StakeSaturationResponseWithId {
identity: bond.identity().to_string(),
saturation: stake_saturation.saturation,
as_at: stake_saturation.as_at,
});
}
Ok(Json(stake_saturations))
}
#[openapi(tag = "status")]
#[get("/mixnodes/staking-stats")]
pub(crate) async fn get_all_mixnode_stake_stats(
cache: &State<ValidatorCache>,
storage: &State<ValidatorApiStorage>,
) -> Result<Json<Vec<StakeStats>>, ErrorResponse> {
let mixnode_bonds = cache.mixnodes().await;
let current_epoch = cache.current_epoch().await.into_inner();
let mut response = Vec::new();
for bond in mixnode_bonds {
let uptime = average_mixnode_uptime(bond.identity(), current_epoch, storage).await?;
let stake_saturation = get_state_saturation(&bond, cache).await;
response.push(StakeStats {
identity: bond.identity().to_string(),
avg_uptime: uptime.u8(),
saturation: stake_saturation.saturation,
as_at: stake_saturation.as_at,
});
}
Ok(Json(response))
}
#[openapi(tag = "status")]
#[get("/mixnode/<identity>/inclusion-probability")]
pub(crate) async fn get_mixnode_inclusion_probability(
@@ -272,7 +322,7 @@ pub(crate) async fn get_mixnode_avg_uptime(
#[openapi(tag = "status")]
#[get("/mixnodes/avg_uptime")]
pub(crate) async fn get_mixnode_avg_uptimes(
pub(crate) async fn get_all_mixnode_avg_uptimes(
cache: &State<ValidatorCache>,
storage: &State<ValidatorApiStorage>,
) -> Result<Json<Vec<UptimeResponse>>, ErrorResponse> {
@@ -83,6 +83,21 @@ pub struct StakeSaturationResponse {
pub as_at: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct StakeSaturationResponseWithId {
pub identity: String,
pub saturation: f32,
pub as_at: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct StakeStats {
pub identity: String,
pub avg_uptime: u8,
pub saturation: f32,
pub as_at: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(