Add axum server to nym-api (#4803)
* Migrate nym-api HTTP server from rocket to axum (#4698) Migrate endpoints to Axum * Squashed after PR review Initial WIP - bootstrap axum server with same data as rocket - start axum server alongside rocket - add routes for circulating-supply, contract-cache, network - write simple bash validation that migrated APIs return 200 - mark rocket parts of code as deprecated - start more complicated routes: WIP Init storage always Add coconut routes Add api-status routes Expand tests WIP Migrate unstable APIs with query params Update bash tests Add node-status routes Redirect / to /swagger Update API tests Implement graceful shutdown rustfmt Fix clippy * Add ecash routes after rebase * PR feedback - add CORS layer - move logger to common crate - remove global log filters for nym-api and axum * Serve OpenAPI for all endpoints (#4761) * Playing around with swagger * Generate OpenAPI for /status routes * Phase out static_routes as strings - also nest routers in a clearer way * Generate OpenAPI for /network routes * Generate OpenAPI for /api-status routes * Generate OpenAPI for "nym nodes" routes * Fix some network-monitor routes * Generate OpenAPI for /ecash routes * Add utoipa feature to /common mods * Add OpenAPI for unstable routes * Fix MixNodeDetails field in models * Introduce axum feature flag (#4775) * Add Axum bind_address to config * Introduce axum feature flag * Add comment to template.rs * Add Github action to build wtih `axum` feature * Refactor server start & shutdown (#4777) * Clippy: don't forget axum feature * Refactor router so it's safer * Implement graceful shutdown * Nicer pattern matching * Better Result syntax
This commit is contained in:
+2
-1
@@ -41,12 +41,13 @@ pub struct NodeStatusCache {
|
||||
|
||||
impl NodeStatusCache {
|
||||
/// Creates a new cache with no data.
|
||||
fn new() -> NodeStatusCache {
|
||||
pub(crate) fn new() -> NodeStatusCache {
|
||||
NodeStatusCache {
|
||||
inner: Arc::new(RwLock::new(NodeStatusCacheData::new())),
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated(note = "TODO rocket: obsolete because it's used for Rocket")]
|
||||
pub fn stage() -> AdHoc {
|
||||
AdHoc::on_ignite("Node Status Cache", |rocket| async {
|
||||
rocket.manage(Self::new())
|
||||
|
||||
+16
-26
@@ -52,12 +52,11 @@ pub(super) fn split_into_active_and_rewarded_set(
|
||||
}
|
||||
|
||||
pub(super) async fn get_mixnode_performance_from_storage(
|
||||
storage: &Option<NymApiStorage>,
|
||||
storage: &NymApiStorage,
|
||||
mix_id: MixId,
|
||||
epoch: Interval,
|
||||
) -> Option<Performance> {
|
||||
storage
|
||||
.as_ref()?
|
||||
.get_average_mixnode_uptime_in_the_last_24hrs(
|
||||
mix_id,
|
||||
epoch.current_epoch_end_unix_timestamp(),
|
||||
@@ -68,12 +67,11 @@ pub(super) async fn get_mixnode_performance_from_storage(
|
||||
}
|
||||
|
||||
pub(super) async fn get_gateway_performance_from_storage(
|
||||
storage: &Option<NymApiStorage>,
|
||||
storage: &NymApiStorage,
|
||||
gateway_id: &str,
|
||||
epoch: Interval,
|
||||
) -> Option<Performance> {
|
||||
storage
|
||||
.as_ref()?
|
||||
.get_average_gateway_uptime_in_the_last_24hrs(
|
||||
gateway_id,
|
||||
epoch.current_epoch_end_unix_timestamp(),
|
||||
@@ -84,7 +82,7 @@ pub(super) async fn get_gateway_performance_from_storage(
|
||||
}
|
||||
|
||||
pub(super) async fn annotate_nodes_with_details(
|
||||
storage: &Option<NymApiStorage>,
|
||||
storage: &NymApiStorage,
|
||||
mixnodes: Vec<MixNodeDetails>,
|
||||
interval_reward_params: RewardingParams,
|
||||
current_interval: Interval,
|
||||
@@ -123,16 +121,12 @@ pub(super) async fn annotate_nodes_with_details(
|
||||
current_interval,
|
||||
);
|
||||
|
||||
let node_performance = if let Some(storage) = storage {
|
||||
storage
|
||||
.construct_mixnode_report(mixnode.mix_id())
|
||||
.await
|
||||
.map(NodePerformance::from)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.unwrap_or_default();
|
||||
let node_performance = storage
|
||||
.construct_mixnode_report(mixnode.mix_id())
|
||||
.await
|
||||
.map(NodePerformance::from)
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
|
||||
// safety: this conversion is infallible
|
||||
let ip_addresses =
|
||||
@@ -177,7 +171,7 @@ pub(super) async fn annotate_nodes_with_details(
|
||||
}
|
||||
|
||||
pub(crate) async fn annotate_gateways_with_details(
|
||||
storage: &Option<NymApiStorage>,
|
||||
storage: &NymApiStorage,
|
||||
gateway_bonds: Vec<GatewayBond>,
|
||||
current_interval: Interval,
|
||||
blacklist: &HashSet<IdentityKey>,
|
||||
@@ -192,16 +186,12 @@ pub(crate) async fn annotate_gateways_with_details(
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let node_performance = if let Some(storage) = storage {
|
||||
storage
|
||||
.construct_gateway_report(gateway_bond.identity())
|
||||
.await
|
||||
.map(NodePerformance::from)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.unwrap_or_default();
|
||||
let node_performance = storage
|
||||
.construct_gateway_report(gateway_bond.identity())
|
||||
.await
|
||||
.map(NodePerformance::from)
|
||||
.ok()
|
||||
.unwrap_or_default();
|
||||
|
||||
// safety: this conversion is infallible
|
||||
let ip_addresses = match NetworkAddress::from_str(&gateway_bond.gateway.host).unwrap() {
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@ pub struct NodeStatusCacheRefresher {
|
||||
// Sources for when refreshing data
|
||||
contract_cache: NymContractCache,
|
||||
contract_cache_listener: watch::Receiver<CacheNotification>,
|
||||
storage: Option<NymApiStorage>,
|
||||
storage: NymApiStorage,
|
||||
}
|
||||
|
||||
impl NodeStatusCacheRefresher {
|
||||
@@ -38,7 +38,7 @@ impl NodeStatusCacheRefresher {
|
||||
fallback_caching_interval: Duration,
|
||||
contract_cache: NymContractCache,
|
||||
contract_cache_listener: watch::Receiver<CacheNotification>,
|
||||
storage: Option<NymApiStorage>,
|
||||
storage: NymApiStorage,
|
||||
) -> Self {
|
||||
Self {
|
||||
cache,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::v2::AxumAppState;
|
||||
use axum::Router;
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
pub(crate) mod network_monitor;
|
||||
pub(crate) mod unstable;
|
||||
pub(crate) mod without_monitor;
|
||||
|
||||
pub(crate) fn node_status_routes(network_monitor: bool) -> Router<AxumAppState> {
|
||||
// in the minimal variant we would not have access to endpoints relying on existence
|
||||
// of the network monitor and the associated storage
|
||||
let without_network_monitor = without_monitor::mandatory_routes();
|
||||
|
||||
if network_monitor {
|
||||
let with_network_monitor = network_monitor::network_monitor_routes();
|
||||
|
||||
with_network_monitor.merge(without_network_monitor)
|
||||
} else {
|
||||
without_network_monitor
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in = Path)]
|
||||
struct MixIdParam {
|
||||
mix_id: MixId,
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node_status_api::handlers::MixIdParam;
|
||||
use crate::node_status_api::helpers::{
|
||||
_compute_mixnode_reward_estimation, _gateway_core_status_count, _gateway_report,
|
||||
_gateway_uptime_history, _get_gateway_avg_uptime, _get_gateways_detailed,
|
||||
_get_gateways_detailed_unfiltered, _get_mixnode_avg_uptime, _get_mixnode_reward_estimation,
|
||||
_get_mixnodes_detailed_unfiltered, _mixnode_core_status_count, _mixnode_report,
|
||||
_mixnode_uptime_history,
|
||||
};
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::v2::AxumAppState;
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::Json;
|
||||
use axum::Router;
|
||||
use nym_api_requests::models::{
|
||||
ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, GatewayUptimeResponse,
|
||||
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
|
||||
MixnodeUptimeHistoryResponse, RewardEstimationResponse, UptimeResponse,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
use super::unstable;
|
||||
|
||||
pub(super) fn network_monitor_routes() -> Router<AxumAppState> {
|
||||
Router::new()
|
||||
.nest(
|
||||
"/gateway/:identity",
|
||||
Router::new()
|
||||
.route("/report", axum::routing::get(gateway_report))
|
||||
.route("/history", axum::routing::get(gateway_uptime_history))
|
||||
.route(
|
||||
"/core-status-count",
|
||||
axum::routing::get(gateway_core_status_count),
|
||||
)
|
||||
.route("/avg_uptime", axum::routing::get(get_gateway_avg_uptime)),
|
||||
)
|
||||
.nest(
|
||||
"/mixnode/:mix_id",
|
||||
Router::new()
|
||||
.route("/report", axum::routing::get(mixnode_report))
|
||||
.route("/history", axum::routing::get(mixnode_uptime_history))
|
||||
.route(
|
||||
"/core-status-count",
|
||||
axum::routing::get(mixnode_core_status_count),
|
||||
)
|
||||
.route(
|
||||
"/reward-estimation",
|
||||
axum::routing::get(get_mixnode_reward_estimation),
|
||||
)
|
||||
.route(
|
||||
"/compute-reward-estimation",
|
||||
axum::routing::post(compute_mixnode_reward_estimation),
|
||||
)
|
||||
.route("/avg_uptime", axum::routing::get(get_mixnode_avg_uptime)),
|
||||
)
|
||||
.nest(
|
||||
"/mixnodes",
|
||||
Router::new()
|
||||
.route(
|
||||
"/detailed-unfiltered",
|
||||
axum::routing::get(get_mixnodes_detailed_unfiltered),
|
||||
)
|
||||
.route(
|
||||
"/unstable/:mix_id/test-results",
|
||||
axum::routing::get(unstable::mixnode_test_results),
|
||||
),
|
||||
)
|
||||
.nest(
|
||||
"/gateways",
|
||||
Router::new()
|
||||
.route("/detailed", axum::routing::get(get_gateways_detailed))
|
||||
.route(
|
||||
"/detailed-unfiltered",
|
||||
axum::routing::get(get_gateways_detailed_unfiltered),
|
||||
)
|
||||
.route(
|
||||
"/unstable/:gateway_identity/test-results",
|
||||
axum::routing::get(unstable::gateway_test_results),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
path = "/v1/status/gateway/{identity}/report",
|
||||
responses(
|
||||
(status = 200, body = GatewayStatusReportResponse)
|
||||
)
|
||||
)]
|
||||
async fn gateway_report(
|
||||
Path(identity): Path<String>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<GatewayStatusReportResponse>> {
|
||||
Ok(Json(
|
||||
_gateway_report(state.node_status_cache(), &identity).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
path = "/v1/status/gateway/{identity}/history",
|
||||
responses(
|
||||
(status = 200, body = GatewayUptimeHistoryResponse)
|
||||
)
|
||||
)]
|
||||
async fn gateway_uptime_history(
|
||||
Path(identity): Path<String>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<GatewayUptimeHistoryResponse>> {
|
||||
Ok(Json(
|
||||
_gateway_uptime_history(state.storage(), &identity).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in = Query)]
|
||||
struct SinceQueryParams {
|
||||
since: Option<i64>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
params(
|
||||
SinceQueryParams
|
||||
),
|
||||
path = "/v1/status/gateway/{identity}/core-status-count",
|
||||
responses(
|
||||
(status = 200, body = GatewayCoreStatusResponse)
|
||||
)
|
||||
)]
|
||||
async fn gateway_core_status_count(
|
||||
Path(identity): Path<String>,
|
||||
Query(SinceQueryParams { since }): Query<SinceQueryParams>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<GatewayCoreStatusResponse>> {
|
||||
Ok(Json(
|
||||
_gateway_core_status_count(state.storage(), &identity, since).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
path = "/v1/status/gateway/{identity}/avg_uptime",
|
||||
responses(
|
||||
(status = 200, body = GatewayUptimeResponse)
|
||||
)
|
||||
)]
|
||||
async fn get_gateway_avg_uptime(
|
||||
Path(identity): Path<String>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<GatewayUptimeResponse>> {
|
||||
Ok(Json(
|
||||
_get_gateway_avg_uptime(state.node_status_cache(), &identity).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/report",
|
||||
responses(
|
||||
(status = 200, body = MixnodeStatusReportResponse)
|
||||
)
|
||||
)]
|
||||
async fn mixnode_report(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<MixnodeStatusReportResponse>> {
|
||||
Ok(Json(
|
||||
_mixnode_report(state.node_status_cache(), mix_id).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/history",
|
||||
responses(
|
||||
(status = 200, body = MixnodeUptimeHistoryResponse)
|
||||
)
|
||||
)]
|
||||
async fn mixnode_uptime_history(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<MixnodeUptimeHistoryResponse>> {
|
||||
Ok(Json(
|
||||
_mixnode_uptime_history(state.storage(), mix_id).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam, SinceQueryParams
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/core-status-count",
|
||||
responses(
|
||||
(status = 200, body = MixnodeCoreStatusResponse)
|
||||
)
|
||||
)]
|
||||
async fn mixnode_core_status_count(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
Query(SinceQueryParams { since }): Query<SinceQueryParams>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<MixnodeCoreStatusResponse>> {
|
||||
Ok(Json(
|
||||
_mixnode_core_status_count(state.storage(), mix_id, since).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/reward-estimation",
|
||||
responses(
|
||||
(status = 200, body = RewardEstimationResponse)
|
||||
)
|
||||
)]
|
||||
async fn get_mixnode_reward_estimation(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<RewardEstimationResponse>> {
|
||||
Ok(Json(
|
||||
_get_mixnode_reward_estimation(
|
||||
state.node_status_cache(),
|
||||
state.nym_contract_cache(),
|
||||
mix_id,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
post,
|
||||
params(
|
||||
ComputeRewardEstParam, MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/compute-reward-estimation",
|
||||
request_body = ComputeRewardEstParam,
|
||||
responses(
|
||||
(status = 200, body = RewardEstimationResponse)
|
||||
)
|
||||
)]
|
||||
async fn compute_mixnode_reward_estimation(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
State(state): State<AxumAppState>,
|
||||
Json(user_reward_param): Json<ComputeRewardEstParam>,
|
||||
) -> AxumResult<Json<RewardEstimationResponse>> {
|
||||
Ok(Json(
|
||||
_compute_mixnode_reward_estimation(
|
||||
&user_reward_param,
|
||||
state.node_status_cache(),
|
||||
state.nym_contract_cache(),
|
||||
mix_id,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/avg_uptime",
|
||||
responses(
|
||||
(status = 200, body = UptimeResponse)
|
||||
)
|
||||
)]
|
||||
async fn get_mixnode_avg_uptime(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<UptimeResponse>> {
|
||||
Ok(Json(
|
||||
_get_mixnode_avg_uptime(state.node_status_cache(), mix_id).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
path = "/v1/status/mixnodes/detailed-unfiltered",
|
||||
responses(
|
||||
(status = 200, body = MixNodeBondAnnotated)
|
||||
)
|
||||
)]
|
||||
pub async fn get_mixnodes_detailed_unfiltered(
|
||||
State(state): State<AxumAppState>,
|
||||
) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
Json(_get_mixnodes_detailed_unfiltered(state.node_status_cache()).await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
path = "/v1/status/gateways/detailed",
|
||||
responses(
|
||||
(status = 200, body = GatewayBondAnnotated)
|
||||
)
|
||||
)]
|
||||
pub async fn get_gateways_detailed(
|
||||
State(state): State<AxumAppState>,
|
||||
) -> Json<Vec<GatewayBondAnnotated>> {
|
||||
Json(_get_gateways_detailed(state.node_status_cache()).await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network-monitor-status",
|
||||
get,
|
||||
path = "/v1/status/gateways/detailed-unfiltered",
|
||||
responses(
|
||||
(status = 200, body = GatewayBondAnnotated)
|
||||
)
|
||||
)]
|
||||
pub async fn get_gateways_detailed_unfiltered(
|
||||
State(state): State<AxumAppState>,
|
||||
) -> Json<Vec<GatewayBondAnnotated>> {
|
||||
Json(_get_gateways_detailed_unfiltered(state.node_status_cache()).await)
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::support::http::helpers::PaginationRequest;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use crate::v2::AxumAppState;
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::Json;
|
||||
use nym_api_requests::models::{
|
||||
GatewayTestResultResponse, MixnodeTestResultResponse, PartialTestResult, TestNode, TestRoute,
|
||||
};
|
||||
use nym_api_requests::pagination::Pagination;
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub type DbId = i64;
|
||||
|
||||
// a simply in-memory cache of node details
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NodeInfoCache {
|
||||
inner: Arc<RwLock<NodeInfoCacheInner>>,
|
||||
}
|
||||
|
||||
impl NodeInfoCache {
|
||||
async fn get_mix_node_details(&self, db_id: DbId, storage: &NymApiStorage) -> TestNode {
|
||||
{
|
||||
let read_guard = self.inner.read().await;
|
||||
if let Some(cached) = read_guard.mixnodes.get(&db_id) {
|
||||
trace!("cache hit for mixnode {db_id}");
|
||||
return cached.clone();
|
||||
}
|
||||
}
|
||||
trace!("cache miss for mixnode {db_id}");
|
||||
|
||||
let mut write_guard = self.inner.write().await;
|
||||
// double-check the cache in case somebody already updated it while we were waiting for the lock
|
||||
if let Some(cached) = write_guard.mixnodes.get(&db_id) {
|
||||
return cached.clone();
|
||||
}
|
||||
|
||||
let details = match storage.get_mixnode_details_by_db_id(db_id).await {
|
||||
Ok(Some(details)) => details.into(),
|
||||
Ok(None) => {
|
||||
error!("somebody has been messing with the database! details for mixnode with database id {db_id} have been removed!");
|
||||
TestNode::default()
|
||||
}
|
||||
Err(err) => {
|
||||
// don't insert into the cache in case another request is successful
|
||||
error!("failed to retrieve details for mixnode {db_id}: {err}");
|
||||
return TestNode::default();
|
||||
}
|
||||
};
|
||||
|
||||
write_guard.mixnodes.insert(db_id, details.clone());
|
||||
details
|
||||
}
|
||||
|
||||
async fn get_gateway_details(&self, db_id: DbId, storage: &NymApiStorage) -> TestNode {
|
||||
{
|
||||
let read_guard = self.inner.read().await;
|
||||
if let Some(cached) = read_guard.gateways.get(&db_id) {
|
||||
trace!("cache hit for gateway {db_id}");
|
||||
return cached.clone();
|
||||
}
|
||||
}
|
||||
trace!("cache miss for gateway {db_id}");
|
||||
|
||||
let mut write_guard = self.inner.write().await;
|
||||
// double-check the cache in case somebody already updated it while we were waiting for the lock
|
||||
if let Some(cached) = write_guard.gateways.get(&db_id) {
|
||||
return cached.clone();
|
||||
}
|
||||
|
||||
let details = match storage.get_gateway_details_by_db_id(db_id).await {
|
||||
Ok(Some(details)) => details.into(),
|
||||
Ok(None) => {
|
||||
error!("somebody has been messing with the database! details for gateway with database id {db_id} have been removed!");
|
||||
TestNode::default()
|
||||
}
|
||||
Err(err) => {
|
||||
// don't insert into the cache in case another request is successful
|
||||
error!("failed to retrieve details for gateway {db_id}: {err}");
|
||||
return TestNode::default();
|
||||
}
|
||||
};
|
||||
|
||||
write_guard.gateways.insert(db_id, details.clone());
|
||||
details
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct NodeInfoCacheInner {
|
||||
mixnodes: HashMap<DbId, TestNode>,
|
||||
gateways: HashMap<DbId, TestNode>,
|
||||
}
|
||||
|
||||
const MAX_TEST_RESULTS_PAGE_SIZE: u32 = 100;
|
||||
const DEFAULT_TEST_RESULTS_PAGE_SIZE: u32 = 50;
|
||||
|
||||
async fn _mixnode_test_results(
|
||||
mix_id: MixId,
|
||||
page: u32,
|
||||
per_page: u32,
|
||||
info_cache: &NodeInfoCache,
|
||||
storage: &NymApiStorage,
|
||||
) -> anyhow::Result<MixnodeTestResultResponse> {
|
||||
// convert to db offset
|
||||
// we're paging from page 0 like civilised people,
|
||||
// so we have to skip (page * per_page) results
|
||||
let offset = page * per_page;
|
||||
let limit = per_page;
|
||||
|
||||
let raw_results = storage
|
||||
.get_mixnode_detailed_statuses(mix_id, limit, offset)
|
||||
.await?;
|
||||
let total = match raw_results.first() {
|
||||
None => 0,
|
||||
Some(r) => storage.get_mixnode_detailed_statuses_count(r.db_id).await?,
|
||||
};
|
||||
|
||||
let mut partial_results = Vec::new();
|
||||
for result in raw_results {
|
||||
let gateway = info_cache
|
||||
.get_gateway_details(result.gateway_id, storage)
|
||||
.await;
|
||||
let layer1 = info_cache
|
||||
.get_mix_node_details(result.layer1_mix_id, storage)
|
||||
.await;
|
||||
let layer2 = info_cache
|
||||
.get_mix_node_details(result.layer2_mix_id, storage)
|
||||
.await;
|
||||
let layer3 = info_cache
|
||||
.get_mix_node_details(result.layer3_mix_id, storage)
|
||||
.await;
|
||||
|
||||
partial_results.push(PartialTestResult {
|
||||
monitor_run_id: result.monitor_run_id,
|
||||
timestamp: result.timestamp,
|
||||
overall_reliability_for_all_routes_in_monitor_run: result.reliability,
|
||||
test_routes: TestRoute {
|
||||
gateway,
|
||||
layer1,
|
||||
layer2,
|
||||
layer3,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Ok(MixnodeTestResultResponse {
|
||||
pagination: Pagination {
|
||||
total,
|
||||
page,
|
||||
size: partial_results.len(),
|
||||
},
|
||||
data: partial_results,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn mixnode_test_results(
|
||||
Path(mix_id): Path<MixId>,
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<MixnodeTestResultResponse>> {
|
||||
let page = pagination.page.unwrap_or_default();
|
||||
let per_page = min(
|
||||
pagination
|
||||
.per_page
|
||||
.unwrap_or(DEFAULT_TEST_RESULTS_PAGE_SIZE),
|
||||
MAX_TEST_RESULTS_PAGE_SIZE,
|
||||
);
|
||||
|
||||
match _mixnode_test_results(
|
||||
mix_id,
|
||||
page,
|
||||
per_page,
|
||||
state.node_info_cache(),
|
||||
state.storage(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Err(err) => Err(AxumErrorResponse::internal_msg(format!(
|
||||
"failed to retrieve mixnode test results for node {mix_id}: {err}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn _gateway_test_results(
|
||||
gateway_identity: &str,
|
||||
page: u32,
|
||||
per_page: u32,
|
||||
info_cache: &NodeInfoCache,
|
||||
storage: &NymApiStorage,
|
||||
) -> anyhow::Result<GatewayTestResultResponse> {
|
||||
// convert to db offset
|
||||
// we're paging from page 0 like civilised people,
|
||||
// so we have to skip (page * per_page) results
|
||||
let offset = page * per_page;
|
||||
let limit = per_page;
|
||||
|
||||
let raw_results = storage
|
||||
.get_gateway_detailed_statuses(gateway_identity, limit, offset)
|
||||
.await?;
|
||||
let total = match raw_results.first() {
|
||||
None => 0,
|
||||
Some(r) => storage.get_gateway_detailed_statuses_count(r.db_id).await?,
|
||||
};
|
||||
|
||||
let mut partial_results = Vec::new();
|
||||
for result in raw_results {
|
||||
let gateway = info_cache
|
||||
.get_gateway_details(result.gateway_id, storage)
|
||||
.await;
|
||||
let layer1 = info_cache
|
||||
.get_mix_node_details(result.layer1_mix_id, storage)
|
||||
.await;
|
||||
let layer2 = info_cache
|
||||
.get_mix_node_details(result.layer2_mix_id, storage)
|
||||
.await;
|
||||
let layer3 = info_cache
|
||||
.get_mix_node_details(result.layer3_mix_id, storage)
|
||||
.await;
|
||||
|
||||
partial_results.push(PartialTestResult {
|
||||
monitor_run_id: result.monitor_run_id,
|
||||
timestamp: result.timestamp,
|
||||
overall_reliability_for_all_routes_in_monitor_run: result.reliability,
|
||||
test_routes: TestRoute {
|
||||
gateway,
|
||||
layer1,
|
||||
layer2,
|
||||
layer3,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Ok(GatewayTestResultResponse {
|
||||
pagination: Pagination {
|
||||
total,
|
||||
page,
|
||||
size: partial_results.len(),
|
||||
},
|
||||
data: partial_results,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn gateway_test_results(
|
||||
Path(gateway_identity): Path<String>,
|
||||
Query(pagination): Query<PaginationRequest>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<GatewayTestResultResponse>> {
|
||||
let page = pagination.page.unwrap_or_default();
|
||||
let per_page = min(
|
||||
pagination
|
||||
.per_page
|
||||
.unwrap_or(DEFAULT_TEST_RESULTS_PAGE_SIZE),
|
||||
MAX_TEST_RESULTS_PAGE_SIZE,
|
||||
);
|
||||
|
||||
match _gateway_test_results(
|
||||
&gateway_identity,
|
||||
page,
|
||||
per_page,
|
||||
state.node_info_cache(),
|
||||
state.storage(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Err(err) => Err(AxumErrorResponse::internal_msg(format!(
|
||||
"failed to retrieve mixnode test results for gateway {gateway_identity}: {err}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node_status_api::handlers::MixIdParam;
|
||||
use crate::node_status_api::helpers::{
|
||||
_get_active_set_detailed, _get_mixnode_inclusion_probabilities,
|
||||
_get_mixnode_inclusion_probability, _get_mixnode_stake_saturation, _get_mixnode_status,
|
||||
_get_mixnodes_detailed, _get_rewarded_set_detailed,
|
||||
};
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::v2::AxumAppState;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::Json;
|
||||
use axum::Router;
|
||||
use nym_api_requests::models::{
|
||||
AllInclusionProbabilitiesResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
|
||||
MixnodeStatusResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
|
||||
pub(super) fn mandatory_routes() -> Router<AxumAppState> {
|
||||
Router::new()
|
||||
.nest(
|
||||
"/mixnode/:mix_id",
|
||||
Router::new()
|
||||
.route("/status", axum::routing::get(get_mixnode_status))
|
||||
.route(
|
||||
"/stake-saturation",
|
||||
axum::routing::get(get_mixnode_stake_saturation),
|
||||
)
|
||||
.route(
|
||||
"/inclusion-probability",
|
||||
axum::routing::get(get_mixnode_inclusion_probability),
|
||||
),
|
||||
)
|
||||
.merge(
|
||||
Router::new().nest(
|
||||
"/mixnodes",
|
||||
Router::new()
|
||||
.route(
|
||||
"/inclusion-probability",
|
||||
axum::routing::get(get_mixnode_inclusion_probabilities),
|
||||
)
|
||||
.route("/detailed", axum::routing::get(get_mixnodes_detailed))
|
||||
.route(
|
||||
"/rewarded/detailed",
|
||||
axum::routing::get(get_rewarded_set_detailed),
|
||||
)
|
||||
.route(
|
||||
"/active/detailed",
|
||||
axum::routing::get(get_active_set_detailed),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/status",
|
||||
responses(
|
||||
(status = 200, body = MixnodeStatusResponse)
|
||||
)
|
||||
)]
|
||||
async fn get_mixnode_status(
|
||||
Path(MixIdParam { mix_id }): Path<MixIdParam>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> Json<MixnodeStatusResponse> {
|
||||
Json(_get_mixnode_status(state.nym_contract_cache(), mix_id).await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/stake-saturation",
|
||||
responses(
|
||||
(status = 200, body = StakeSaturationResponse)
|
||||
)
|
||||
)]
|
||||
async fn get_mixnode_stake_saturation(
|
||||
Path(mix_id): Path<MixId>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<StakeSaturationResponse>> {
|
||||
Ok(Json(
|
||||
_get_mixnode_stake_saturation(
|
||||
state.node_status_cache(),
|
||||
state.nym_contract_cache(),
|
||||
mix_id,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
get,
|
||||
params(
|
||||
MixIdParam
|
||||
),
|
||||
path = "/v1/status/mixnode/{mix_id}/inclusion-probability",
|
||||
responses(
|
||||
(status = 200, body = InclusionProbabilityResponse)
|
||||
)
|
||||
)]
|
||||
async fn get_mixnode_inclusion_probability(
|
||||
Path(mix_id): Path<MixId>,
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<InclusionProbabilityResponse>> {
|
||||
Ok(Json(
|
||||
_get_mixnode_inclusion_probability(state.node_status_cache(), mix_id).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
get,
|
||||
path = "/v1/status/mixnodes/inclusion-probability",
|
||||
responses(
|
||||
(status = 200, body = AllInclusionProbabilitiesResponse)
|
||||
)
|
||||
)]
|
||||
async fn get_mixnode_inclusion_probabilities(
|
||||
State(state): State<AxumAppState>,
|
||||
) -> AxumResult<Json<AllInclusionProbabilitiesResponse>> {
|
||||
Ok(Json(
|
||||
_get_mixnode_inclusion_probabilities(state.node_status_cache()).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
get,
|
||||
path = "/v1/status/mixnodes/detailed",
|
||||
responses(
|
||||
(status = 200, body = MixNodeBondAnnotated)
|
||||
)
|
||||
)]
|
||||
pub async fn get_mixnodes_detailed(
|
||||
State(state): State<AxumAppState>,
|
||||
) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
Json(_get_mixnodes_detailed(state.node_status_cache()).await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
get,
|
||||
path = "/v1/status/mixnodes/rewarded/detailed",
|
||||
responses(
|
||||
(status = 200, body = MixNodeBondAnnotated)
|
||||
)
|
||||
)]
|
||||
pub async fn get_rewarded_set_detailed(
|
||||
State(state): State<AxumAppState>,
|
||||
) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
Json(_get_rewarded_set_detailed(state.node_status_cache()).await)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "status",
|
||||
get,
|
||||
path = "/v1/status/mixnodes/active/detailed",
|
||||
responses(
|
||||
(status = 200, body = MixNodeBondAnnotated)
|
||||
)
|
||||
)]
|
||||
pub async fn get_active_set_detailed(
|
||||
State(state): State<AxumAppState>,
|
||||
) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
Json(_get_active_set_detailed(state.node_status_cache()).await)
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use super::reward_estimate::compute_reward_estimate;
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::storage::NymApiStorage;
|
||||
use crate::support::caching::Cache;
|
||||
use crate::{NodeStatusCache, NymContractCache};
|
||||
@@ -15,41 +16,31 @@ use nym_api_requests::models::{
|
||||
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> {
|
||||
) -> AxumResult<GatewayBondAnnotated> {
|
||||
cache
|
||||
.gateway_annotated(identity)
|
||||
.await
|
||||
.ok_or(ErrorResponse::new(
|
||||
"gateway bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
.ok_or(AxumErrorResponse::not_found("gateway bond not found"))
|
||||
}
|
||||
|
||||
async fn get_mixnode_bond_annotated(
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixNodeBondAnnotated, ErrorResponse> {
|
||||
) -> AxumResult<MixNodeBondAnnotated> {
|
||||
cache
|
||||
.mixnode_annotated(mix_id)
|
||||
.await
|
||||
.ok_or(ErrorResponse::new(
|
||||
"mixnode bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
.ok_or(AxumErrorResponse::not_found("mixnode bond not found"))
|
||||
}
|
||||
|
||||
pub(crate) async fn _gateway_report(
|
||||
cache: &NodeStatusCache,
|
||||
identity: &str,
|
||||
) -> Result<GatewayStatusReportResponse, ErrorResponse> {
|
||||
) -> AxumResult<GatewayStatusReportResponse> {
|
||||
let gateway = get_gateway_bond_annotated(cache, identity).await?;
|
||||
|
||||
Ok(GatewayStatusReportResponse {
|
||||
@@ -64,23 +55,23 @@ pub(crate) async fn _gateway_report(
|
||||
pub(crate) async fn _gateway_uptime_history(
|
||||
storage: &NymApiStorage,
|
||||
identity: &str,
|
||||
) -> Result<GatewayUptimeHistoryResponse, ErrorResponse> {
|
||||
) -> AxumResult<GatewayUptimeHistoryResponse> {
|
||||
storage
|
||||
.get_gateway_uptime_history(identity)
|
||||
.await
|
||||
.map(GatewayUptimeHistoryResponse::from)
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
|
||||
.map_err(AxumErrorResponse::not_found)
|
||||
}
|
||||
|
||||
pub(crate) async fn _gateway_core_status_count(
|
||||
storage: &State<NymApiStorage>,
|
||||
storage: &NymApiStorage,
|
||||
identity: &str,
|
||||
since: Option<i64>,
|
||||
) -> Result<GatewayCoreStatusResponse, ErrorResponse> {
|
||||
) -> AxumResult<GatewayCoreStatusResponse> {
|
||||
let count = storage
|
||||
.get_core_gateway_status_count(identity, since)
|
||||
.await
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))?;
|
||||
.map_err(AxumErrorResponse::not_found)?;
|
||||
|
||||
Ok(GatewayCoreStatusResponse {
|
||||
identity: identity.to_string(),
|
||||
@@ -91,7 +82,7 @@ pub(crate) async fn _gateway_core_status_count(
|
||||
pub(crate) async fn _mixnode_report(
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeStatusReportResponse, ErrorResponse> {
|
||||
) -> AxumResult<MixnodeStatusReportResponse> {
|
||||
let mixnode = get_mixnode_bond_annotated(cache, mix_id).await?;
|
||||
|
||||
Ok(MixnodeStatusReportResponse {
|
||||
@@ -107,23 +98,23 @@ pub(crate) async fn _mixnode_report(
|
||||
pub(crate) async fn _mixnode_uptime_history(
|
||||
storage: &NymApiStorage,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeUptimeHistoryResponse, ErrorResponse> {
|
||||
) -> AxumResult<MixnodeUptimeHistoryResponse> {
|
||||
storage
|
||||
.get_mixnode_uptime_history(mix_id)
|
||||
.await
|
||||
.map(MixnodeUptimeHistoryResponse::from)
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
|
||||
.map_err(AxumErrorResponse::not_found)
|
||||
}
|
||||
|
||||
pub(crate) async fn _mixnode_core_status_count(
|
||||
storage: &State<NymApiStorage>,
|
||||
storage: &NymApiStorage,
|
||||
mix_id: MixId,
|
||||
since: Option<i64>,
|
||||
) -> Result<MixnodeCoreStatusResponse, ErrorResponse> {
|
||||
) -> AxumResult<MixnodeCoreStatusResponse> {
|
||||
let count = storage
|
||||
.get_core_mixnode_status_count(mix_id, since)
|
||||
.await
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))?;
|
||||
.map_err(AxumErrorResponse::not_found)?;
|
||||
|
||||
Ok(MixnodeCoreStatusResponse { mix_id, count })
|
||||
}
|
||||
@@ -138,22 +129,22 @@ pub(crate) async fn _get_mixnode_status(
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_mixnode_reward_estimation(
|
||||
cache: &State<NodeStatusCache>,
|
||||
validator_cache: &State<NymContractCache>,
|
||||
cache: &NodeStatusCache,
|
||||
validator_cache: &NymContractCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<RewardEstimationResponse, ErrorResponse> {
|
||||
) -> AxumResult<RewardEstimationResponse> {
|
||||
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))?;
|
||||
.ok_or_else(AxumErrorResponse::internal)?;
|
||||
let current_interval = validator_cache
|
||||
.current_interval()
|
||||
.await
|
||||
.into_inner()
|
||||
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
|
||||
.ok_or_else(AxumErrorResponse::internal)?;
|
||||
|
||||
let reward_estimation = compute_reward_estimate(
|
||||
&mixnode.mixnode_details,
|
||||
@@ -170,31 +161,28 @@ pub(crate) async fn _get_mixnode_reward_estimation(
|
||||
as_at: as_at.unix_timestamp(),
|
||||
})
|
||||
} else {
|
||||
Err(ErrorResponse::new(
|
||||
"mixnode bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
Err(AxumErrorResponse::not_found("mixnode bond not found"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn _compute_mixnode_reward_estimation(
|
||||
user_reward_param: ComputeRewardEstParam,
|
||||
user_reward_param: &ComputeRewardEstParam,
|
||||
cache: &NodeStatusCache,
|
||||
validator_cache: &NymContractCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<RewardEstimationResponse, ErrorResponse> {
|
||||
) -> AxumResult<RewardEstimationResponse> {
|
||||
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))?;
|
||||
.ok_or_else(AxumErrorResponse::internal)?;
|
||||
let current_interval = validator_cache
|
||||
.current_interval()
|
||||
.await
|
||||
.into_inner()
|
||||
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
|
||||
.ok_or_else(AxumErrorResponse::internal)?;
|
||||
|
||||
// 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);
|
||||
@@ -222,21 +210,20 @@ pub(crate) async fn _compute_mixnode_reward_estimation(
|
||||
.profit_margin_percent = profit_margin_percent;
|
||||
}
|
||||
|
||||
if let Some(interval_operating_cost) = user_reward_param.interval_operating_cost {
|
||||
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;
|
||||
.interval_operating_cost = interval_operating_cost.clone();
|
||||
}
|
||||
|
||||
if mixnode.mixnode_details.rewarding_details.operator
|
||||
+ mixnode.mixnode_details.rewarding_details.delegates
|
||||
> reward_params.interval.staking_supply
|
||||
{
|
||||
return Err(ErrorResponse::new(
|
||||
return Err(AxumErrorResponse::unprocessable_entity(
|
||||
"Pledge plus delegation too large",
|
||||
Status::UnprocessableEntity,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -255,10 +242,7 @@ pub(crate) async fn _compute_mixnode_reward_estimation(
|
||||
as_at: as_at.unix_timestamp(),
|
||||
})
|
||||
} else {
|
||||
Err(ErrorResponse::new(
|
||||
"mixnode bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
Err(AxumErrorResponse::not_found("mixnode bond not found"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +250,7 @@ pub(crate) async fn _get_mixnode_stake_saturation(
|
||||
cache: &NodeStatusCache,
|
||||
validator_cache: &NymContractCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<StakeSaturationResponse, ErrorResponse> {
|
||||
) -> AxumResult<StakeSaturationResponse> {
|
||||
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`
|
||||
@@ -275,7 +259,7 @@ pub(crate) async fn _get_mixnode_stake_saturation(
|
||||
let as_at = reward_params.timestamp();
|
||||
let rewarding_params = reward_params
|
||||
.into_inner()
|
||||
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
|
||||
.ok_or_else(AxumErrorResponse::internal)?;
|
||||
|
||||
Ok(StakeSaturationResponse {
|
||||
saturation: mixnode
|
||||
@@ -289,17 +273,14 @@ pub(crate) async fn _get_mixnode_stake_saturation(
|
||||
as_at: as_at.unix_timestamp(),
|
||||
})
|
||||
} else {
|
||||
Err(ErrorResponse::new(
|
||||
"mixnode bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
Err(AxumErrorResponse::not_found("mixnode bond not found"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_mixnode_inclusion_probability(
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<InclusionProbabilityResponse, ErrorResponse> {
|
||||
) -> AxumResult<InclusionProbabilityResponse> {
|
||||
cache
|
||||
.inclusion_probabilities()
|
||||
.await
|
||||
@@ -309,13 +290,13 @@ pub(crate) async fn _get_mixnode_inclusion_probability(
|
||||
in_active: p.in_active.into(),
|
||||
in_reserve: p.in_reserve.into(),
|
||||
})
|
||||
.ok_or_else(|| ErrorResponse::new("mixnode bond not found", Status::NotFound))
|
||||
.ok_or_else(|| AxumErrorResponse::not_found("mixnode bond not found"))
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_mixnode_avg_uptime(
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<UptimeResponse, ErrorResponse> {
|
||||
) -> AxumResult<UptimeResponse> {
|
||||
let mixnode = get_mixnode_bond_annotated(cache, mix_id).await?;
|
||||
|
||||
Ok(UptimeResponse {
|
||||
@@ -328,7 +309,7 @@ pub(crate) async fn _get_mixnode_avg_uptime(
|
||||
pub(crate) async fn _get_gateway_avg_uptime(
|
||||
cache: &NodeStatusCache,
|
||||
identity: &str,
|
||||
) -> Result<GatewayUptimeResponse, ErrorResponse> {
|
||||
) -> AxumResult<GatewayUptimeResponse> {
|
||||
let gateway = get_gateway_bond_annotated(cache, identity).await?;
|
||||
|
||||
Ok(GatewayUptimeResponse {
|
||||
@@ -340,7 +321,7 @@ pub(crate) async fn _get_gateway_avg_uptime(
|
||||
|
||||
pub(crate) async fn _get_mixnode_inclusion_probabilities(
|
||||
cache: &NodeStatusCache,
|
||||
) -> Result<AllInclusionProbabilitiesResponse, ErrorResponse> {
|
||||
) -> AxumResult<AllInclusionProbabilitiesResponse> {
|
||||
if let Some(prob) = cache.inclusion_probabilities().await {
|
||||
let as_at = prob.timestamp();
|
||||
let prob = prob.into_inner();
|
||||
@@ -353,10 +334,7 @@ pub(crate) async fn _get_mixnode_inclusion_probabilities(
|
||||
as_at: as_at.unix_timestamp(),
|
||||
})
|
||||
} else {
|
||||
Err(ErrorResponse::new(
|
||||
"No data available",
|
||||
Status::ServiceUnavailable,
|
||||
))
|
||||
Err(AxumErrorResponse::service_unavailable())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node_status_api::models::RocketErrorResponse;
|
||||
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, RocketErrorResponse> {
|
||||
cache
|
||||
.gateway_annotated(identity)
|
||||
.await
|
||||
.ok_or(RocketErrorResponse::new(
|
||||
"gateway bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_mixnode_bond_annotated(
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixNodeBondAnnotated, RocketErrorResponse> {
|
||||
cache
|
||||
.mixnode_annotated(mix_id)
|
||||
.await
|
||||
.ok_or(RocketErrorResponse::new(
|
||||
"mixnode bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn _gateway_report(
|
||||
cache: &NodeStatusCache,
|
||||
identity: &str,
|
||||
) -> Result<GatewayStatusReportResponse, RocketErrorResponse> {
|
||||
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, RocketErrorResponse> {
|
||||
storage
|
||||
.get_gateway_uptime_history(identity)
|
||||
.await
|
||||
.map(GatewayUptimeHistoryResponse::from)
|
||||
.map_err(|err| RocketErrorResponse::new(err.to_string(), Status::NotFound))
|
||||
}
|
||||
|
||||
pub(crate) async fn _gateway_core_status_count(
|
||||
storage: &State<NymApiStorage>,
|
||||
identity: &str,
|
||||
since: Option<i64>,
|
||||
) -> Result<GatewayCoreStatusResponse, RocketErrorResponse> {
|
||||
let count = storage
|
||||
.get_core_gateway_status_count(identity, since)
|
||||
.await
|
||||
.map_err(|err| RocketErrorResponse::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, RocketErrorResponse> {
|
||||
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, RocketErrorResponse> {
|
||||
storage
|
||||
.get_mixnode_uptime_history(mix_id)
|
||||
.await
|
||||
.map(MixnodeUptimeHistoryResponse::from)
|
||||
.map_err(|err| RocketErrorResponse::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, RocketErrorResponse> {
|
||||
let count = storage
|
||||
.get_core_mixnode_status_count(mix_id, since)
|
||||
.await
|
||||
.map_err(|err| RocketErrorResponse::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, RocketErrorResponse> {
|
||||
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(|| RocketErrorResponse::new("server error", Status::InternalServerError))?;
|
||||
let current_interval = validator_cache
|
||||
.current_interval()
|
||||
.await
|
||||
.into_inner()
|
||||
.ok_or_else(|| RocketErrorResponse::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: as_at.unix_timestamp(),
|
||||
})
|
||||
} else {
|
||||
Err(RocketErrorResponse::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, RocketErrorResponse> {
|
||||
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(|| RocketErrorResponse::new("server error", Status::InternalServerError))?;
|
||||
let current_interval = validator_cache
|
||||
.current_interval()
|
||||
.await
|
||||
.into_inner()
|
||||
.ok_or_else(|| RocketErrorResponse::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(RocketErrorResponse::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: as_at.unix_timestamp(),
|
||||
})
|
||||
} else {
|
||||
Err(RocketErrorResponse::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, RocketErrorResponse> {
|
||||
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(|| RocketErrorResponse::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: as_at.unix_timestamp(),
|
||||
})
|
||||
} else {
|
||||
Err(RocketErrorResponse::new(
|
||||
"mixnode bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_mixnode_inclusion_probability(
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<InclusionProbabilityResponse, RocketErrorResponse> {
|
||||
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(|| RocketErrorResponse::new("mixnode bond not found", Status::NotFound))
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_mixnode_avg_uptime(
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<UptimeResponse, RocketErrorResponse> {
|
||||
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, RocketErrorResponse> {
|
||||
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, RocketErrorResponse> {
|
||||
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: as_at.unix_timestamp(),
|
||||
})
|
||||
} else {
|
||||
Err(RocketErrorResponse::new(
|
||||
"No data available",
|
||||
Status::ServiceUnavailable,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_mixnodes_detailed(cache: &NodeStatusCache) -> Vec<MixNodeBondAnnotated> {
|
||||
cache
|
||||
.mixnodes_annotated_filtered()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_mixnodes_detailed_unfiltered(
|
||||
cache: &NodeStatusCache,
|
||||
) -> Vec<MixNodeBondAnnotated> {
|
||||
cache.mixnodes_annotated_full().await.unwrap_or_default()
|
||||
}
|
||||
|
||||
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_filtered()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_gateways_detailed_unfiltered(
|
||||
cache: &NodeStatusCache,
|
||||
) -> Vec<GatewayBondAnnotated> {
|
||||
cache.gateways_annotated_full().await.unwrap_or_default()
|
||||
}
|
||||
@@ -15,10 +15,14 @@ use rocket_okapi::{openapi_get_routes_spec, settings::OpenApiSettings};
|
||||
use std::time::Duration;
|
||||
|
||||
pub(crate) mod cache;
|
||||
#[cfg(feature = "axum")]
|
||||
pub(crate) mod handlers;
|
||||
#[cfg(feature = "axum")]
|
||||
pub(crate) mod helpers;
|
||||
pub(crate) mod helpers_deprecated;
|
||||
pub(crate) mod models;
|
||||
pub(crate) mod reward_estimate;
|
||||
pub(crate) mod routes;
|
||||
pub(crate) mod routes_deprecated;
|
||||
pub(crate) mod uptime_updater;
|
||||
pub(crate) mod utils;
|
||||
|
||||
@@ -32,42 +36,42 @@ pub(crate) fn node_status_routes(
|
||||
) -> (Vec<Route>, OpenApi) {
|
||||
if enabled {
|
||||
openapi_get_routes_spec![
|
||||
settings: routes::gateway_report,
|
||||
routes::gateway_uptime_history,
|
||||
routes::gateway_core_status_count,
|
||||
routes::mixnode_report,
|
||||
routes::mixnode_uptime_history,
|
||||
routes::mixnode_core_status_count,
|
||||
routes::get_mixnode_status,
|
||||
routes::get_mixnode_reward_estimation,
|
||||
routes::compute_mixnode_reward_estimation,
|
||||
routes::get_mixnode_stake_saturation,
|
||||
routes::get_mixnode_inclusion_probability,
|
||||
routes::get_mixnode_avg_uptime,
|
||||
routes::get_gateway_avg_uptime,
|
||||
routes::get_mixnode_inclusion_probabilities,
|
||||
routes::get_mixnodes_detailed,
|
||||
routes::get_mixnodes_detailed_unfiltered,
|
||||
routes::get_rewarded_set_detailed,
|
||||
routes::get_active_set_detailed,
|
||||
routes::get_gateways_detailed,
|
||||
routes::get_gateways_detailed_unfiltered,
|
||||
routes::unstable::mixnode_test_results,
|
||||
routes::unstable::gateway_test_results,
|
||||
routes::submit_gateway_monitoring_results,
|
||||
routes::submit_node_monitoring_results,
|
||||
settings: routes_deprecated::gateway_report,
|
||||
routes_deprecated::gateway_uptime_history,
|
||||
routes_deprecated::gateway_core_status_count,
|
||||
routes_deprecated::mixnode_report,
|
||||
routes_deprecated::mixnode_uptime_history,
|
||||
routes_deprecated::mixnode_core_status_count,
|
||||
routes_deprecated::get_mixnode_status,
|
||||
routes_deprecated::get_mixnode_reward_estimation,
|
||||
routes_deprecated::compute_mixnode_reward_estimation,
|
||||
routes_deprecated::get_mixnode_stake_saturation,
|
||||
routes_deprecated::get_mixnode_inclusion_probability,
|
||||
routes_deprecated::get_mixnode_avg_uptime,
|
||||
routes_deprecated::get_gateway_avg_uptime,
|
||||
routes_deprecated::get_mixnode_inclusion_probabilities,
|
||||
routes_deprecated::get_mixnodes_detailed,
|
||||
routes_deprecated::get_mixnodes_detailed_unfiltered,
|
||||
routes_deprecated::get_rewarded_set_detailed,
|
||||
routes_deprecated::get_active_set_detailed,
|
||||
routes_deprecated::get_gateways_detailed,
|
||||
routes_deprecated::get_gateways_detailed_unfiltered,
|
||||
routes_deprecated::unstable::mixnode_test_results,
|
||||
routes_deprecated::unstable::gateway_test_results,
|
||||
routes_deprecated::submit_gateway_monitoring_results,
|
||||
routes_deprecated::submit_node_monitoring_results,
|
||||
]
|
||||
} else {
|
||||
// in the minimal variant we would not have access to endpoints relying on existence
|
||||
// of the network monitor and the associated storage
|
||||
openapi_get_routes_spec![
|
||||
settings: routes::get_mixnode_status,
|
||||
routes::get_mixnode_stake_saturation,
|
||||
routes::get_mixnode_inclusion_probability,
|
||||
routes::get_mixnode_inclusion_probabilities,
|
||||
routes::get_mixnodes_detailed,
|
||||
routes::get_rewarded_set_detailed,
|
||||
routes::get_active_set_detailed,
|
||||
settings: routes_deprecated::get_mixnode_status,
|
||||
routes_deprecated::get_mixnode_stake_saturation,
|
||||
routes_deprecated::get_mixnode_inclusion_probability,
|
||||
routes_deprecated::get_mixnode_inclusion_probabilities,
|
||||
routes_deprecated::get_mixnodes_detailed,
|
||||
routes_deprecated::get_rewarded_set_detailed,
|
||||
routes_deprecated::get_active_set_detailed,
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -80,7 +84,7 @@ pub(crate) fn start_cache_refresh(
|
||||
config: &config::NodeStatusAPI,
|
||||
nym_contract_cache_state: &NymContractCache,
|
||||
node_status_cache_state: &NodeStatusCache,
|
||||
storage: Option<&storage::NymApiStorage>,
|
||||
storage: storage::NymApiStorage,
|
||||
nym_contract_cache_listener: tokio::sync::watch::Receiver<support::caching::CacheNotification>,
|
||||
shutdown: &TaskManager,
|
||||
) {
|
||||
@@ -89,7 +93,7 @@ pub(crate) fn start_cache_refresh(
|
||||
config.debug.caching_interval,
|
||||
nym_contract_cache_state.to_owned(),
|
||||
nym_contract_cache_listener,
|
||||
storage.cloned(),
|
||||
storage,
|
||||
);
|
||||
let shutdown_listener = shutdown.subscribe();
|
||||
tokio::spawn(async move { nym_api_cache_refresher.run(shutdown_listener).await });
|
||||
|
||||
@@ -330,21 +330,22 @@ impl From<HistoricalUptime> for HistoricalUptimeResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ErrorResponse {
|
||||
#[deprecated(note = "TODO rocket remove once Rocket is phased out")]
|
||||
pub(crate) struct RocketErrorResponse {
|
||||
error_message: RequestError,
|
||||
status: Status,
|
||||
}
|
||||
|
||||
impl ErrorResponse {
|
||||
impl RocketErrorResponse {
|
||||
pub(crate) fn new(error_message: impl Into<String>, status: Status) -> Self {
|
||||
ErrorResponse {
|
||||
RocketErrorResponse {
|
||||
error_message: RequestError::new(error_message),
|
||||
status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 'o: 'r> Responder<'r, 'o> for ErrorResponse {
|
||||
impl<'r, 'o: 'r> Responder<'r, 'o> for RocketErrorResponse {
|
||||
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
|
||||
// piggyback on the existing implementation
|
||||
// also prefer json over plain for ease of use in frontend
|
||||
@@ -355,7 +356,7 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for ErrorResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for ErrorResponse {
|
||||
impl JsonSchema for RocketErrorResponse {
|
||||
fn schema_name() -> String {
|
||||
"ErrorResponse".to_owned()
|
||||
}
|
||||
@@ -384,7 +385,7 @@ impl JsonSchema for ErrorResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenApiResponderInner for ErrorResponse {
|
||||
impl OpenApiResponderInner for RocketErrorResponse {
|
||||
fn responses(_gen: &mut OpenApiGenerator) -> rocket_okapi::Result<Responses> {
|
||||
let mut responses = Responses::default();
|
||||
ensure_status_code_exists(&mut responses, 404);
|
||||
@@ -392,6 +393,103 @@ impl OpenApiResponderInner for ErrorResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
pub(crate) use axum_error::{AxumErrorResponse, AxumResult};
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
/// TODO rocket: extract types from this module when axum becomes the only server in Nym API
|
||||
mod axum_error {
|
||||
pub use super::*;
|
||||
use crate::ecash::error::{EcashError, RedemptionError};
|
||||
use std::fmt::Display;
|
||||
|
||||
// TODO rocket remove smurf name after eliminating `rocket`
|
||||
pub(crate) type AxumResult<T> = Result<T, AxumErrorResponse>;
|
||||
pub(crate) struct AxumErrorResponse {
|
||||
message: RequestError,
|
||||
status: axum::http::StatusCode,
|
||||
}
|
||||
|
||||
impl AxumErrorResponse {
|
||||
pub(crate) fn internal_msg(msg: impl Display) -> Self {
|
||||
Self {
|
||||
message: RequestError::new(msg.to_string()),
|
||||
status: axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn internal() -> Self {
|
||||
Self {
|
||||
message: RequestError::new("Internal server error"),
|
||||
status: axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn not_implemented() -> Self {
|
||||
Self {
|
||||
message: RequestError::empty(),
|
||||
status: axum::http::StatusCode::NOT_IMPLEMENTED,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn not_found(msg: impl Display) -> Self {
|
||||
Self {
|
||||
message: RequestError::new(msg.to_string()),
|
||||
status: axum::http::StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn service_unavailable() -> Self {
|
||||
Self {
|
||||
message: RequestError::empty(),
|
||||
status: axum::http::StatusCode::SERVICE_UNAVAILABLE,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unprocessable_entity(msg: impl Display) -> Self {
|
||||
Self {
|
||||
message: RequestError::new(msg.to_string()),
|
||||
status: axum::http::StatusCode::UNPROCESSABLE_ENTITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl axum::response::IntoResponse for AxumErrorResponse {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
(self.status, self.message.message().to_string()).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NymApiStorageError> for AxumErrorResponse {
|
||||
fn from(value: NymApiStorageError) -> Self {
|
||||
error!("{value}");
|
||||
Self {
|
||||
message: RequestError::empty(),
|
||||
status: axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EcashError> for AxumErrorResponse {
|
||||
fn from(value: EcashError) -> Self {
|
||||
Self {
|
||||
message: RequestError::new(value.to_string()),
|
||||
status: axum::http::StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
impl From<RedemptionError> for AxumErrorResponse {
|
||||
fn from(value: RedemptionError) -> Self {
|
||||
Self {
|
||||
message: RequestError::new(value.to_string()),
|
||||
status: axum::http::StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum NymApiStorageError {
|
||||
#[error("could not find status report associated with mixnode {mix_id}")]
|
||||
|
||||
+31
-31
@@ -15,9 +15,9 @@ use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
|
||||
use super::helpers::_get_gateways_detailed;
|
||||
use super::helpers_deprecated::_get_gateways_detailed;
|
||||
use super::NodeStatusCache;
|
||||
use crate::node_status_api::helpers::{
|
||||
use crate::node_status_api::helpers_deprecated::{
|
||||
_compute_mixnode_reward_estimation, _gateway_core_status_count, _gateway_report,
|
||||
_gateway_uptime_history, _get_active_set_detailed, _get_gateway_avg_uptime,
|
||||
_get_gateways_detailed_unfiltered, _get_mixnode_avg_uptime,
|
||||
@@ -26,7 +26,7 @@ use crate::node_status_api::helpers::{
|
||||
_get_mixnodes_detailed, _get_mixnodes_detailed_unfiltered, _get_rewarded_set_detailed,
|
||||
_mixnode_core_status_count, _mixnode_report, _mixnode_uptime_history,
|
||||
};
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use crate::node_status_api::models::RocketErrorResponse;
|
||||
use crate::storage::NymApiStorage;
|
||||
use crate::NymContractCache;
|
||||
|
||||
@@ -35,23 +35,23 @@ use crate::NymContractCache;
|
||||
pub(crate) async fn submit_gateway_monitoring_results(
|
||||
message: Json<MonitorMessage>,
|
||||
storage: &State<NymApiStorage>,
|
||||
) -> Result<(), ErrorResponse> {
|
||||
) -> Result<(), RocketErrorResponse> {
|
||||
if !message.from_allowed() {
|
||||
return Err(ErrorResponse::new(
|
||||
return Err(RocketErrorResponse::new(
|
||||
"Monitor not registered to submit results".to_string(),
|
||||
rocket::http::Status::Forbidden,
|
||||
));
|
||||
}
|
||||
|
||||
if !message.timely() {
|
||||
return Err(ErrorResponse::new(
|
||||
return Err(RocketErrorResponse::new(
|
||||
"Message is too old".to_string(),
|
||||
rocket::http::Status::BadRequest,
|
||||
));
|
||||
}
|
||||
|
||||
if !message.verify() {
|
||||
return Err(ErrorResponse::new(
|
||||
return Err(RocketErrorResponse::new(
|
||||
"Invalid signature".to_string(),
|
||||
rocket::http::Status::BadRequest,
|
||||
));
|
||||
@@ -65,7 +65,7 @@ pub(crate) async fn submit_gateway_monitoring_results(
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit gateway monitoring results: {}", err);
|
||||
Err(ErrorResponse::new(
|
||||
Err(RocketErrorResponse::new(
|
||||
"failed to submit gateway monitoring results".to_string(),
|
||||
rocket::http::Status::InternalServerError,
|
||||
))
|
||||
@@ -78,23 +78,23 @@ pub(crate) async fn submit_gateway_monitoring_results(
|
||||
pub(crate) async fn submit_node_monitoring_results(
|
||||
message: Json<MonitorMessage>,
|
||||
storage: &State<NymApiStorage>,
|
||||
) -> Result<(), ErrorResponse> {
|
||||
) -> Result<(), RocketErrorResponse> {
|
||||
if !message.from_allowed() {
|
||||
return Err(ErrorResponse::new(
|
||||
return Err(RocketErrorResponse::new(
|
||||
"Monitor not registered to submit results".to_string(),
|
||||
rocket::http::Status::Forbidden,
|
||||
));
|
||||
}
|
||||
|
||||
if !message.timely() {
|
||||
return Err(ErrorResponse::new(
|
||||
return Err(RocketErrorResponse::new(
|
||||
"Message is too old".to_string(),
|
||||
rocket::http::Status::BadRequest,
|
||||
));
|
||||
}
|
||||
|
||||
if !message.verify() {
|
||||
return Err(ErrorResponse::new(
|
||||
return Err(RocketErrorResponse::new(
|
||||
"Invalid signature".to_string(),
|
||||
rocket::http::Status::BadRequest,
|
||||
));
|
||||
@@ -108,7 +108,7 @@ pub(crate) async fn submit_node_monitoring_results(
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!("failed to submit node monitoring results: {}", err);
|
||||
Err(ErrorResponse::new(
|
||||
Err(RocketErrorResponse::new(
|
||||
"failed to submit node monitoring results".to_string(),
|
||||
rocket::http::Status::InternalServerError,
|
||||
))
|
||||
@@ -121,7 +121,7 @@ pub(crate) async fn submit_node_monitoring_results(
|
||||
pub(crate) async fn gateway_report(
|
||||
cache: &State<NodeStatusCache>,
|
||||
identity: &str,
|
||||
) -> Result<Json<GatewayStatusReportResponse>, ErrorResponse> {
|
||||
) -> Result<Json<GatewayStatusReportResponse>, RocketErrorResponse> {
|
||||
Ok(Json(_gateway_report(cache, identity).await?))
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ pub(crate) async fn gateway_report(
|
||||
pub(crate) async fn gateway_uptime_history(
|
||||
storage: &State<NymApiStorage>,
|
||||
identity: &str,
|
||||
) -> Result<Json<GatewayUptimeHistoryResponse>, ErrorResponse> {
|
||||
) -> Result<Json<GatewayUptimeHistoryResponse>, RocketErrorResponse> {
|
||||
Ok(Json(_gateway_uptime_history(storage, identity).await?))
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ pub(crate) async fn gateway_core_status_count(
|
||||
storage: &State<NymApiStorage>,
|
||||
identity: &str,
|
||||
since: Option<i64>,
|
||||
) -> Result<Json<GatewayCoreStatusResponse>, ErrorResponse> {
|
||||
) -> Result<Json<GatewayCoreStatusResponse>, RocketErrorResponse> {
|
||||
Ok(Json(
|
||||
_gateway_core_status_count(storage, identity, since).await?,
|
||||
))
|
||||
@@ -151,7 +151,7 @@ pub(crate) async fn gateway_core_status_count(
|
||||
pub(crate) async fn mixnode_report(
|
||||
cache: &State<NodeStatusCache>,
|
||||
mix_id: MixId,
|
||||
) -> Result<Json<MixnodeStatusReportResponse>, ErrorResponse> {
|
||||
) -> Result<Json<MixnodeStatusReportResponse>, RocketErrorResponse> {
|
||||
Ok(Json(_mixnode_report(cache, mix_id).await?))
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ pub(crate) async fn mixnode_report(
|
||||
pub(crate) async fn mixnode_uptime_history(
|
||||
storage: &State<NymApiStorage>,
|
||||
mix_id: MixId,
|
||||
) -> Result<Json<MixnodeUptimeHistoryResponse>, ErrorResponse> {
|
||||
) -> Result<Json<MixnodeUptimeHistoryResponse>, RocketErrorResponse> {
|
||||
Ok(Json(_mixnode_uptime_history(storage, mix_id).await?))
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ pub(crate) async fn mixnode_core_status_count(
|
||||
storage: &State<NymApiStorage>,
|
||||
mix_id: MixId,
|
||||
since: Option<i64>,
|
||||
) -> Result<Json<MixnodeCoreStatusResponse>, ErrorResponse> {
|
||||
) -> Result<Json<MixnodeCoreStatusResponse>, RocketErrorResponse> {
|
||||
Ok(Json(
|
||||
_mixnode_core_status_count(storage, mix_id, since).await?,
|
||||
))
|
||||
@@ -191,7 +191,7 @@ pub(crate) async fn get_mixnode_reward_estimation(
|
||||
cache: &State<NodeStatusCache>,
|
||||
validator_cache: &State<NymContractCache>,
|
||||
mix_id: MixId,
|
||||
) -> Result<Json<RewardEstimationResponse>, ErrorResponse> {
|
||||
) -> Result<Json<RewardEstimationResponse>, RocketErrorResponse> {
|
||||
Ok(Json(
|
||||
_get_mixnode_reward_estimation(cache, validator_cache, mix_id).await?,
|
||||
))
|
||||
@@ -207,7 +207,7 @@ pub(crate) async fn compute_mixnode_reward_estimation(
|
||||
cache: &State<NodeStatusCache>,
|
||||
validator_cache: &State<NymContractCache>,
|
||||
mix_id: MixId,
|
||||
) -> Result<Json<RewardEstimationResponse>, ErrorResponse> {
|
||||
) -> Result<Json<RewardEstimationResponse>, RocketErrorResponse> {
|
||||
Ok(Json(
|
||||
_compute_mixnode_reward_estimation(
|
||||
user_reward_param.into_inner(),
|
||||
@@ -225,7 +225,7 @@ pub(crate) async fn get_mixnode_stake_saturation(
|
||||
cache: &State<NodeStatusCache>,
|
||||
validator_cache: &State<NymContractCache>,
|
||||
mix_id: MixId,
|
||||
) -> Result<Json<StakeSaturationResponse>, ErrorResponse> {
|
||||
) -> Result<Json<StakeSaturationResponse>, RocketErrorResponse> {
|
||||
Ok(Json(
|
||||
_get_mixnode_stake_saturation(cache, validator_cache, mix_id).await?,
|
||||
))
|
||||
@@ -236,7 +236,7 @@ pub(crate) async fn get_mixnode_stake_saturation(
|
||||
pub(crate) async fn get_mixnode_inclusion_probability(
|
||||
cache: &State<NodeStatusCache>,
|
||||
mix_id: MixId,
|
||||
) -> Result<Json<InclusionProbabilityResponse>, ErrorResponse> {
|
||||
) -> Result<Json<InclusionProbabilityResponse>, RocketErrorResponse> {
|
||||
Ok(Json(
|
||||
_get_mixnode_inclusion_probability(cache, mix_id).await?,
|
||||
))
|
||||
@@ -247,7 +247,7 @@ pub(crate) async fn get_mixnode_inclusion_probability(
|
||||
pub(crate) async fn get_mixnode_avg_uptime(
|
||||
cache: &State<NodeStatusCache>,
|
||||
mix_id: MixId,
|
||||
) -> Result<Json<UptimeResponse>, ErrorResponse> {
|
||||
) -> Result<Json<UptimeResponse>, RocketErrorResponse> {
|
||||
Ok(Json(_get_mixnode_avg_uptime(cache, mix_id).await?))
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ pub(crate) async fn get_mixnode_avg_uptime(
|
||||
pub(crate) async fn get_gateway_avg_uptime(
|
||||
cache: &State<NodeStatusCache>,
|
||||
identity: &str,
|
||||
) -> Result<Json<GatewayUptimeResponse>, ErrorResponse> {
|
||||
) -> Result<Json<GatewayUptimeResponse>, RocketErrorResponse> {
|
||||
Ok(Json(_get_gateway_avg_uptime(cache, identity).await?))
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ pub(crate) async fn get_gateway_avg_uptime(
|
||||
#[get("/mixnodes/inclusion_probability")]
|
||||
pub(crate) async fn get_mixnode_inclusion_probabilities(
|
||||
cache: &State<NodeStatusCache>,
|
||||
) -> Result<Json<AllInclusionProbabilitiesResponse>, ErrorResponse> {
|
||||
) -> Result<Json<AllInclusionProbabilitiesResponse>, RocketErrorResponse> {
|
||||
Ok(Json(_get_mixnode_inclusion_probabilities(cache).await?))
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ pub async fn get_gateways_detailed_unfiltered(
|
||||
}
|
||||
|
||||
pub mod unstable {
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use crate::node_status_api::models::RocketErrorResponse;
|
||||
use crate::support::http::helpers::PaginationRequest;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use nym_api_requests::models::{
|
||||
@@ -486,7 +486,7 @@ pub mod unstable {
|
||||
pagination: PaginationRequest,
|
||||
info_cache: &State<NodeInfoCache>,
|
||||
storage: &State<NymApiStorage>,
|
||||
) -> Result<Json<MixnodeTestResultResponse>, ErrorResponse> {
|
||||
) -> Result<Json<MixnodeTestResultResponse>, RocketErrorResponse> {
|
||||
let page = pagination.page.unwrap_or_default();
|
||||
let per_page = min(
|
||||
pagination
|
||||
@@ -497,7 +497,7 @@ pub mod unstable {
|
||||
|
||||
match _mixnode_test_results(mix_id, page, per_page, info_cache, storage).await {
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Err(err) => Err(ErrorResponse::new(
|
||||
Err(err) => Err(RocketErrorResponse::new(
|
||||
format!("failed to retrieve mixnode test results for node {mix_id}: {err}"),
|
||||
Status::InternalServerError,
|
||||
)),
|
||||
@@ -570,7 +570,7 @@ pub mod unstable {
|
||||
pagination: PaginationRequest,
|
||||
info_cache: &State<NodeInfoCache>,
|
||||
storage: &State<NymApiStorage>,
|
||||
) -> Result<Json<GatewayTestResultResponse>, ErrorResponse> {
|
||||
) -> Result<Json<GatewayTestResultResponse>, RocketErrorResponse> {
|
||||
let page = pagination.page.unwrap_or_default();
|
||||
let per_page = min(
|
||||
pagination
|
||||
@@ -581,7 +581,7 @@ pub mod unstable {
|
||||
|
||||
match _gateway_test_results(gateway_identity, page, per_page, info_cache, storage).await {
|
||||
Ok(res) => Ok(Json(res)),
|
||||
Err(err) => Err(ErrorResponse::new(
|
||||
Err(err) => Err(RocketErrorResponse::new(
|
||||
format!(
|
||||
"failed to retrieve mixnode test results for gateway {gateway_identity}: {err}"
|
||||
),
|
||||
@@ -118,8 +118,8 @@ impl HistoricalUptimeUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(storage: &NymApiStorage, shutdown: &TaskManager) {
|
||||
let uptime_updater = HistoricalUptimeUpdater::new(storage.to_owned());
|
||||
pub(crate) fn start(storage: NymApiStorage, shutdown: &TaskManager) {
|
||||
let uptime_updater = HistoricalUptimeUpdater::new(storage);
|
||||
let shutdown_listener = shutdown.subscribe();
|
||||
tokio::spawn(async move { uptime_updater.run(shutdown_listener).await });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user