Feature/additional mixnode endpoints (#1019)

* Moved mixnode status route to node status api module

* Introduced validator-api endpoint for estimating mixnode's reward

* Stake saturation endpoint
This commit is contained in:
Jędrzej Stuczyński
2022-01-11 09:38:39 +00:00
committed by GitHub
parent 11a458a43d
commit 29340ed00c
9 changed files with 330 additions and 134 deletions
+30 -17
View File
@@ -1,9 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::storage;
use rocket::fairing::AdHoc;
use std::path::PathBuf;
use std::time::Duration;
pub(crate) mod local_guard;
@@ -16,20 +14,35 @@ pub(crate) const FIFTEEN_MINUTES: Duration = Duration::from_secs(900);
pub(crate) const ONE_HOUR: Duration = Duration::from_secs(3600);
pub(crate) const ONE_DAY: Duration = Duration::from_secs(86400);
pub(crate) fn stage(database_path: PathBuf) -> AdHoc {
AdHoc::on_ignite("SQLx Stage", |rocket| async {
rocket
.attach(storage::ValidatorApiStorage::stage(database_path))
.mount(
"/v1/status",
routes![
routes::mixnode_report,
routes::gateway_report,
routes::mixnode_uptime_history,
routes::gateway_uptime_history,
routes::mixnode_core_status_count,
routes::gateway_core_status_count,
],
)
pub(crate) fn stage_full() -> AdHoc {
AdHoc::on_ignite("Node Status API Stage", |rocket| async {
rocket.mount(
"/v1/status",
routes![
routes::mixnode_report,
routes::gateway_report,
routes::mixnode_uptime_history,
routes::gateway_uptime_history,
routes::mixnode_core_status_count,
routes::gateway_core_status_count,
routes::get_mixnode_status,
routes::get_mixnode_reward_estimation,
routes::get_mixnode_stake_saturation,
],
)
})
}
// in the minimal variant we would not have access to endpoints relying on existence
// of the network monitor and the associated storage
pub(crate) fn stage_minimal() -> AdHoc {
AdHoc::on_ignite("Node Status API Stage", |rocket| async {
rocket.mount(
"/v1/status",
routes![
routes::get_mixnode_status,
routes::get_mixnode_stake_saturation,
],
)
})
}
+31 -5
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::cache::MixnodeStatus;
use crate::node_status_api::utils::NodeUptimes;
use crate::storage::models::NodeStatus;
use rocket::http::{ContentType, Status};
@@ -208,22 +209,24 @@ pub struct HistoricalUptime {
}
pub(crate) struct ErrorResponse {
error: ValidatorApiStorageError,
error_message: String,
status: Status,
}
impl ErrorResponse {
pub(crate) fn new(error: ValidatorApiStorageError, status: Status) -> Self {
ErrorResponse { error, status }
pub(crate) fn new(error_message: impl Into<String>, status: Status) -> Self {
ErrorResponse {
error_message: error_message.into(),
status,
}
}
}
impl<'r, 'o: 'r> Responder<'r, 'o> for ErrorResponse {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
let message = format!("{}", self.error);
Response::build()
.header(ContentType::Plain)
.sized_body(message.len(), Cursor::new(message))
.sized_body(self.error_message.len(), Cursor::new(self.error_message))
.status(self.status)
.ok()
}
@@ -275,3 +278,26 @@ pub struct CoreNodeStatus {
pub(crate) identity: String,
pub(crate) count: i32,
}
#[derive(Serialize)]
pub(crate) struct MixnodeStatusResponse {
pub(crate) status: MixnodeStatus,
}
#[derive(Serialize)]
pub(crate) struct RewardEstimationResponse {
pub(crate) estimated_total_node_reward: u128,
pub(crate) estimated_operator_reward: u128,
pub(crate) estimated_delegators_reward: u128,
pub(crate) current_epoch_start: i64,
pub(crate) current_epoch_end: i64,
pub(crate) current_epoch_uptime: Uptime,
pub(crate) as_at: i64,
}
#[derive(Serialize)]
pub(crate) struct StakeSaturationResponse {
pub(crate) saturation: f32,
pub(crate) as_at: i64,
}
+88 -5
View File
@@ -3,12 +3,14 @@
use crate::node_status_api::models::{
CoreNodeStatus, ErrorResponse, GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport,
MixnodeUptimeHistory,
MixnodeStatusResponse, MixnodeUptimeHistory, RewardEstimationResponse, StakeSaturationResponse,
};
use crate::storage::ValidatorApiStorage;
use crate::{Epoch, ValidatorCache};
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
use time::OffsetDateTime;
#[get("/mixnode/<pubkey>/report")]
pub(crate) async fn mixnode_report(
@@ -19,7 +21,7 @@ pub(crate) async fn mixnode_report(
.construct_mixnode_report(pubkey)
.await
.map(Json)
.map_err(|err| ErrorResponse::new(err, Status::NotFound))
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
}
#[get("/gateway/<pubkey>/report")]
@@ -31,7 +33,7 @@ pub(crate) async fn gateway_report(
.construct_gateway_report(pubkey)
.await
.map(Json)
.map_err(|err| ErrorResponse::new(err, Status::NotFound))
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
}
#[get("/mixnode/<pubkey>/history")]
@@ -43,7 +45,7 @@ pub(crate) async fn mixnode_uptime_history(
.get_mixnode_uptime_history(pubkey)
.await
.map(Json)
.map_err(|err| ErrorResponse::new(err, Status::NotFound))
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
}
#[get("/gateway/<pubkey>/history")]
@@ -55,7 +57,7 @@ pub(crate) async fn gateway_uptime_history(
.get_gateway_uptime_history(pubkey)
.await
.map(Json)
.map_err(|err| ErrorResponse::new(err, Status::NotFound))
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
}
#[get("/mixnode/<pubkey>/core-status-count?<since>")]
@@ -91,3 +93,84 @@ pub(crate) async fn gateway_core_status_count(
count,
})
}
#[get("/mixnode/<identity>/status")]
pub(crate) async fn get_mixnode_status(
cache: &State<ValidatorCache>,
identity: String,
) -> Json<MixnodeStatusResponse> {
Json(MixnodeStatusResponse {
status: cache.mixnode_status(identity).await,
})
}
#[get("/mixnode/<identity>/reward_estimation")]
pub(crate) async fn get_mixnode_reward_estimation(
cache: &State<ValidatorCache>,
storage: &State<ValidatorApiStorage>,
first_epoch: &State<Epoch>,
identity: String,
) -> Result<Json<RewardEstimationResponse>, ErrorResponse> {
let (bond, status) = cache.mixnode_details(&identity).await;
if let Some(bond) = bond {
let epoch_reward_params = cache.epoch_reward_params().await;
let as_at = epoch_reward_params.timestamp();
let epoch_reward_params = epoch_reward_params.into_inner();
let current_epoch = first_epoch.current(OffsetDateTime::now_utc());
let uptime = storage
.get_average_mixnode_uptime_in_interval(
&identity,
current_epoch.start_unix_timestamp(),
current_epoch.end_unix_timestamp(),
)
.await
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))?;
let (estimated_total_node_reward, estimated_operator_reward, estimated_delegators_reward) =
epoch_reward_params.estimate_reward(&bond, uptime.u8(), status.is_active());
Ok(Json(RewardEstimationResponse {
estimated_total_node_reward,
estimated_operator_reward,
estimated_delegators_reward,
current_epoch_start: current_epoch.start_unix_timestamp(),
current_epoch_end: current_epoch.end_unix_timestamp(),
current_epoch_uptime: uptime,
as_at,
}))
} else {
Err(ErrorResponse::new(
"mixnode bond not found",
Status::NotFound,
))
}
}
#[get("/mixnode/<identity>/stake_saturation")]
pub(crate) async fn get_mixnode_stake_saturation(
cache: &State<ValidatorCache>,
identity: String,
) -> Result<Json<StakeSaturationResponse>, ErrorResponse> {
let (bond, _) = cache.mixnode_details(&identity).await;
if let Some(bond) = bond {
let epoch_reward_params = cache.epoch_reward_params().await;
let as_at = epoch_reward_params.timestamp();
let epoch_reward_params = epoch_reward_params.into_inner();
let saturation = bond.stake_saturation(
epoch_reward_params.circulating_supply,
epoch_reward_params.rewarded_set_size,
);
Ok(Json(StakeSaturationResponse {
saturation: saturation.to_num(),
as_at,
}))
} else {
Err(ErrorResponse::new(
"mixnode bond not found",
Status::NotFound,
))
}
}