chore: remove unused rocket code

This commit is contained in:
Jędrzej Stuczyński
2024-10-14 10:27:59 +01:00
parent 8113948f48
commit 845b5df14c
25 changed files with 341 additions and 2477 deletions
-10
View File
@@ -7,16 +7,6 @@ use nym_task::TaskManager;
pub(crate) mod cache;
pub(crate) mod handlers;
// pub(crate) mod routes;
// /// Merges the routes with http information and returns it to Rocket for serving
// pub(crate) fn circulating_supply_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
// openapi_get_routes_spec![
// settings: routes::get_full_circulating_supply,
// routes::get_total_supply,
// routes::get_circulating_supply
// ]
// }
/// Spawn the circulating supply cache refresher.
pub(crate) fn start_cache_refresh(
@@ -1,77 +0,0 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
use crate::node_status_api::models::RocketErrorResponse;
use nym_api_requests::models::CirculatingSupplyResponse;
use nym_validator_client::nyxd::Coin;
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
use rocket_okapi::openapi;
// TODO: this is not the best place to put it, it should be more centralised,
// but for a quick fix, that's good enough for now...
// (for proper solution we should be managing `NymNetworkDetails` via rocket and grabbing display exponent
// value from the mix denom here.
const UNYM_RATIO: f64 = 1000000.;
fn unym_coin_to_float_unym(coin: Coin) -> f64 {
// our total supply can't exceed 1B so an overflow here is impossible
// (if it happened, then we SHOULD crash)
coin.amount as f64 / UNYM_RATIO
}
#[openapi(tag = "circulating-supply")]
#[get("/circulating-supply")]
pub(crate) async fn get_full_circulating_supply(
cache: &State<CirculatingSupplyCache>,
) -> Result<Json<CirculatingSupplyResponse>, RocketErrorResponse> {
match cache.get_circulating_supply().await {
Some(value) => Ok(Json(value)),
None => Err(RocketErrorResponse::new(
"unavailable",
Status::InternalServerError,
)),
}
}
#[openapi(tag = "circulating-supply")]
#[get("/circulating-supply/total-supply-value")]
pub(crate) async fn get_total_supply(
cache: &State<CirculatingSupplyCache>,
) -> Result<Json<f64>, RocketErrorResponse> {
let full_circulating_supply = match cache.get_circulating_supply().await {
Some(res) => res,
None => {
return Err(RocketErrorResponse::new(
"unavailable",
Status::InternalServerError,
))
}
};
Ok(Json(unym_coin_to_float_unym(
full_circulating_supply.total_supply.into(),
)))
}
#[openapi(tag = "circulating-supply")]
#[get("/circulating-supply/circulating-supply-value")]
pub(crate) async fn get_circulating_supply(
cache: &State<CirculatingSupplyCache>,
) -> Result<Json<f64>, RocketErrorResponse> {
let full_circulating_supply = match cache.get_circulating_supply().await {
Some(res) => res,
None => {
return Err(RocketErrorResponse::new(
"unavailable",
Status::InternalServerError,
))
}
};
Ok(Json(unym_coin_to_float_unym(
full_circulating_supply.circulating_supply.into(),
)))
}
+84 -22
View File
@@ -1,28 +1,66 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::error::{EcashError, Result};
use crate::ecash::api_routes::helpers::EpochIdParam;
use crate::ecash::error::EcashError;
use crate::ecash::state::EcashState;
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::extract::Path;
use axum::{Json, Router};
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
};
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_ecash_time::{cred_exp_date, EcashTime};
use nym_validator_client::nym_api::rfc_3339_date;
use rocket::serde::json::Json;
use rocket::State as RocketState;
use rocket_okapi::openapi;
use serde::Deserialize;
use std::sync::Arc;
use time::Date;
use tracing::trace;
use utoipa::IntoParams;
// routes with globally aggregated keys, signatures, etc.
/// routes with globally aggregated keys, signatures, etc.
pub(crate) fn aggregation_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
Router::new()
.route(
"/master-verification-key:epoch_id",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch_id| master_verification_key(epoch_id, ecash_state)
}),
)
.route(
"/aggregated-expiration-date-signatures:expiration_date",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|expiration_date| expiration_date_signatures(expiration_date, ecash_state)
}),
)
.route(
"/aggregated-coin-indices-signatures:epoch_id",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch_id| coin_indices_signatures(epoch_id, ecash_state)
}),
)
}
#[openapi(tag = "Ecash Global Data")]
#[get("/master-verification-key?<epoch_id>")]
pub async fn master_verification_key(
epoch_id: Option<u64>,
state: &RocketState<EcashState>,
) -> Result<Json<VerificationKeyResponse>> {
#[utoipa::path(
tag = "Ecash Global Data",
get,
params(
EpochIdParam
),
path = "/v1/ecash/master-verification-key/{epoch_id}",
responses(
(status = 200, body = VerificationKeyResponse)
)
)]
async fn master_verification_key(
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<VerificationKeyResponse>> {
trace!("aggregated_verification_key request");
// see if we're not in the middle of new dkg
@@ -33,12 +71,27 @@ pub async fn master_verification_key(
Ok(Json(VerificationKeyResponse::new(key.clone())))
}
#[openapi(tag = "Ecash Global Data")]
#[get("/aggregated-expiration-date-signatures?<expiration_date>")]
pub async fn expiration_date_signatures(
#[derive(Deserialize, IntoParams)]
#[into_params(parameter_in = Path)]
struct ExpirationDateParam {
expiration_date: Option<String>,
state: &RocketState<EcashState>,
) -> Result<Json<AggregatedExpirationDateSignatureResponse>> {
}
#[utoipa::path(
tag = "Ecash Global Data",
get,
params(
ExpirationDateParam
),
path = "/v1/ecash/aggregated-expiration-date-signatures/{epoch_id}",
responses(
(status = 200, body = AggregatedExpirationDateSignatureResponse)
)
)]
async fn expiration_date_signatures(
Path(ExpirationDateParam { expiration_date }): Path<ExpirationDateParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<AggregatedExpirationDateSignatureResponse>> {
trace!("aggregated_expiration_date_signatures request");
let expiration_date = match expiration_date {
@@ -61,12 +114,21 @@ pub async fn expiration_date_signatures(
}))
}
#[openapi(tag = "Ecash Global Data")]
#[get("/aggregated-coin-indices-signatures?<epoch_id>")]
pub async fn coin_indices_signatures(
epoch_id: Option<u64>,
state: &RocketState<EcashState>,
) -> Result<Json<AggregatedCoinIndicesSignatureResponse>> {
#[utoipa::path(
tag = "Ecash Global Data",
get,
params(
EpochIdParam
),
path = "/v1/ecash/aggregated-coin-indices-signatures/{epoch_id}",
responses(
(status = 200, body = AggregatedCoinIndicesSignatureResponse)
)
)]
async fn coin_indices_signatures(
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<AggregatedCoinIndicesSignatureResponse>> {
trace!("aggregated_coin_indices_signatures request");
// see if we're not in the middle of new dkg
@@ -1,143 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::api_routes::helpers::EpochIdParam;
use crate::ecash::error::EcashError;
use crate::ecash::state::EcashState;
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::extract::Path;
use axum::{Json, Router};
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
};
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_ecash_time::{cred_exp_date, EcashTime};
use nym_validator_client::nym_api::rfc_3339_date;
use serde::Deserialize;
use std::sync::Arc;
use time::Date;
use tracing::trace;
use utoipa::IntoParams;
/// routes with globally aggregated keys, signatures, etc.
pub(crate) fn aggregation_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
Router::new()
.route(
"/master-verification-key:epoch_id",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch_id| master_verification_key(epoch_id, ecash_state)
}),
)
.route(
"/aggregated-expiration-date-signatures:expiration_date",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|expiration_date| expiration_date_signatures(expiration_date, ecash_state)
}),
)
.route(
"/aggregated-coin-indices-signatures:epoch_id",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch_id| coin_indices_signatures(epoch_id, ecash_state)
}),
)
}
#[utoipa::path(
tag = "Ecash Global Data",
get,
params(
EpochIdParam
),
path = "/v1/ecash/master-verification-key/{epoch_id}",
responses(
(status = 200, body = VerificationKeyResponse)
)
)]
async fn master_verification_key(
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<VerificationKeyResponse>> {
trace!("aggregated_verification_key request");
// see if we're not in the middle of new dkg
state.ensure_dkg_not_in_progress().await?;
let key = state.master_verification_key(epoch_id).await?;
Ok(Json(VerificationKeyResponse::new(key.clone())))
}
#[derive(Deserialize, IntoParams)]
#[into_params(parameter_in = Path)]
struct ExpirationDateParam {
expiration_date: Option<String>,
}
#[utoipa::path(
tag = "Ecash Global Data",
get,
params(
ExpirationDateParam
),
path = "/v1/ecash/aggregated-expiration-date-signatures/{epoch_id}",
responses(
(status = 200, body = AggregatedExpirationDateSignatureResponse)
)
)]
async fn expiration_date_signatures(
Path(ExpirationDateParam { expiration_date }): Path<ExpirationDateParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<AggregatedExpirationDateSignatureResponse>> {
trace!("aggregated_expiration_date_signatures request");
let expiration_date = match expiration_date {
None => cred_exp_date().ecash_date(),
Some(raw) => Date::parse(&raw, &rfc_3339_date())
.map_err(|_| EcashError::MalformedExpirationDate { raw })?,
};
// see if we're not in the middle of new dkg
state.ensure_dkg_not_in_progress().await?;
let expiration_date_signatures = state
.master_expiration_date_signatures(expiration_date)
.await?;
Ok(Json(AggregatedExpirationDateSignatureResponse {
epoch_id: expiration_date_signatures.epoch_id,
expiration_date,
signatures: expiration_date_signatures.signatures.clone(),
}))
}
#[utoipa::path(
tag = "Ecash Global Data",
get,
params(
EpochIdParam
),
path = "/v1/ecash/aggregated-coin-indices-signatures/{epoch_id}",
responses(
(status = 200, body = AggregatedCoinIndicesSignatureResponse)
)
)]
async fn coin_indices_signatures(
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<AggregatedCoinIndicesSignatureResponse>> {
trace!("aggregated_coin_indices_signatures request");
// see if we're not in the middle of new dkg
state.ensure_dkg_not_in_progress().await?;
let coin_indices_signatures = state.master_coin_index_signatures(epoch_id).await?;
Ok(Json(AggregatedCoinIndicesSignatureResponse {
epoch_id: coin_indices_signatures.epoch_id,
signatures: coin_indices_signatures.signatures.clone(),
}))
}
+4 -4
View File
@@ -1,10 +1,10 @@
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::api_routes::aggregation_axum::aggregation_routes;
use crate::ecash::api_routes::issued_axum::issued_routes;
use crate::ecash::api_routes::partial_signing_axum::partial_signing_routes;
use crate::ecash::api_routes::spending_axum::spending_routes;
use crate::ecash::api_routes::aggregation::aggregation_routes;
use crate::ecash::api_routes::issued::issued_routes;
use crate::ecash::api_routes::partial_signing::partial_signing_routes;
use crate::ecash::api_routes::spending::spending_routes;
use crate::ecash::state::EcashState;
use crate::support::http::state::AppState;
use axum::Router;
+90 -25
View File
@@ -5,21 +5,64 @@ use crate::ecash::api_routes::helpers::build_credentials_response;
use crate::ecash::error::EcashError;
use crate::ecash::state::EcashState;
use crate::ecash::storage::EcashStorageExt;
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::extract::Path;
use axum::{Json, Router};
use nym_api_requests::ecash::models::{
EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse,
};
use nym_api_requests::ecash::CredentialsRequestBody;
use nym_coconut_dkg_common::types::EpochId;
use rocket::serde::json::Json;
use rocket::State as RocketState;
use rocket_okapi::openapi;
use serde::Deserialize;
use std::sync::Arc;
use utoipa::IntoParams;
#[openapi(tag = "Ecash")]
#[get("/epoch-credentials/<epoch>")]
pub async fn epoch_credentials(
epoch: EpochId,
state: &RocketState<EcashState>,
) -> crate::ecash::error::Result<Json<EpochCredentialsResponse>> {
pub(crate) fn issued_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
Router::new()
.route(
"/epoch-credentials/:epoch",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch| epoch_credentials(epoch, ecash_state)
}),
)
.route(
"/issued-credential/:id",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|id| issued_credential(id, ecash_state)
}),
)
.route(
"/issued-credentials",
axum::routing::post({
let ecash_state = Arc::clone(&ecash_state);
|body| issued_credentials(body, ecash_state)
}),
)
}
#[derive(Deserialize, IntoParams)]
#[into_params(parameter_in = Path)]
struct EpochParam {
epoch: u64,
}
#[utoipa::path(
tag = "Ecash",
get,
params(
EpochParam
),
path = "/v1/ecash/epoch-credentials/{epoch}",
responses(
(status = 200, body = EpochCredentialsResponse)
)
)]
async fn epoch_credentials(
Path(EpochParam { epoch }): Path<EpochParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<EpochCredentialsResponse>> {
let issued = state.aux.storage.get_epoch_credentials(epoch).await?;
let response = if let Some(issued) = issued {
@@ -35,12 +78,27 @@ pub async fn epoch_credentials(
Ok(Json(response))
}
#[openapi(tag = "Ecash")]
#[get("/issued-credential/<id>")]
pub async fn issued_credential(
#[derive(Deserialize, IntoParams)]
#[into_params(parameter_in = Path)]
struct IdParam {
id: i64,
state: &RocketState<EcashState>,
) -> crate::ecash::error::Result<Json<IssuedCredentialResponse>> {
}
#[utoipa::path(
tag = "Ecash",
get,
params(
IdParam
),
path = "/v1/ecash/issued-credential/{id}",
responses(
(status = 200, body = IssuedCredentialResponse)
)
)]
async fn issued_credential(
Path(IdParam { id }): Path<IdParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<IssuedCredentialResponse>> {
let issued = state.aux.storage.get_issued_credential(id).await?;
let credential = if let Some(issued) = issued {
@@ -52,16 +110,21 @@ pub async fn issued_credential(
Ok(Json(IssuedCredentialResponse { credential }))
}
#[openapi(tag = "Ecash")]
#[post("/issued-credentials", data = "<params>")]
pub async fn issued_credentials(
params: Json<CredentialsRequestBody>,
state: &RocketState<EcashState>,
) -> crate::ecash::error::Result<Json<IssuedCredentialsResponse>> {
let params = params.into_inner();
#[utoipa::path(
tag = "Ecash",
post,
request_body = CredentialsRequestBody,
path = "/v1/ecash/issued-credentials",
responses(
(status = 200, body = IssuedCredentialsResponse)
)
)]
async fn issued_credentials(
Json(params): Json<CredentialsRequestBody>,
state: Arc<EcashState>,
) -> AxumResult<Json<IssuedCredentialsResponse>> {
if params.pagination.is_some() && !params.credential_ids.is_empty() {
return Err(EcashError::InvalidQueryArguments);
return Err(EcashError::InvalidQueryArguments.into());
}
let credentials = if let Some(pagination) = params.pagination {
@@ -78,5 +141,7 @@ pub async fn issued_credentials(
.await?
};
build_credentials_response(credentials).map(Json)
build_credentials_response(credentials)
.map(Json)
.map_err(From::from)
}
-147
View File
@@ -1,147 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::api_routes::helpers::build_credentials_response;
use crate::ecash::error::EcashError;
use crate::ecash::state::EcashState;
use crate::ecash::storage::EcashStorageExt;
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::extract::Path;
use axum::{Json, Router};
use nym_api_requests::ecash::models::{
EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse,
};
use nym_api_requests::ecash::CredentialsRequestBody;
use serde::Deserialize;
use std::sync::Arc;
use utoipa::IntoParams;
pub(crate) fn issued_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
Router::new()
.route(
"/epoch-credentials/:epoch",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch| epoch_credentials(epoch, ecash_state)
}),
)
.route(
"/issued-credential/:id",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|id| issued_credential(id, ecash_state)
}),
)
.route(
"/issued-credentials",
axum::routing::post({
let ecash_state = Arc::clone(&ecash_state);
|body| issued_credentials(body, ecash_state)
}),
)
}
#[derive(Deserialize, IntoParams)]
#[into_params(parameter_in = Path)]
struct EpochParam {
epoch: u64,
}
#[utoipa::path(
tag = "Ecash",
get,
params(
EpochParam
),
path = "/v1/ecash/epoch-credentials/{epoch}",
responses(
(status = 200, body = EpochCredentialsResponse)
)
)]
async fn epoch_credentials(
Path(EpochParam { epoch }): Path<EpochParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<EpochCredentialsResponse>> {
let issued = state.aux.storage.get_epoch_credentials(epoch).await?;
let response = if let Some(issued) = issued {
issued.into()
} else {
EpochCredentialsResponse {
epoch_id: epoch,
first_epoch_credential_id: None,
total_issued: 0,
}
};
Ok(Json(response))
}
#[derive(Deserialize, IntoParams)]
#[into_params(parameter_in = Path)]
struct IdParam {
id: i64,
}
#[utoipa::path(
tag = "Ecash",
get,
params(
IdParam
),
path = "/v1/ecash/issued-credential/{id}",
responses(
(status = 200, body = IssuedCredentialResponse)
)
)]
async fn issued_credential(
Path(IdParam { id }): Path<IdParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<IssuedCredentialResponse>> {
let issued = state.aux.storage.get_issued_credential(id).await?;
let credential = if let Some(issued) = issued {
Some(issued.try_into()?)
} else {
None
};
Ok(Json(IssuedCredentialResponse { credential }))
}
#[utoipa::path(
tag = "Ecash",
post,
request_body = CredentialsRequestBody,
path = "/v1/ecash/issued-credentials",
responses(
(status = 200, body = IssuedCredentialsResponse)
)
)]
async fn issued_credentials(
Json(params): Json<CredentialsRequestBody>,
state: Arc<EcashState>,
) -> AxumResult<Json<IssuedCredentialsResponse>> {
if params.pagination.is_some() && !params.credential_ids.is_empty() {
return Err(EcashError::InvalidQueryArguments.into());
}
let credentials = if let Some(pagination) = params.pagination {
state
.aux
.storage
.get_issued_credentials_paged(pagination)
.await?
} else {
state
.aux
.storage
.get_issued_credentials(params.credential_ids)
.await?
};
build_credentials_response(credentials)
.map(Json)
.map_err(From::from)
}
+4 -4
View File
@@ -7,8 +7,8 @@ mod helpers;
// pub(crate) mod partial_signing;
// pub(crate) mod spending;
pub(crate) mod aggregation_axum;
pub(crate) mod aggregation;
pub(crate) mod handlers;
pub(crate) mod issued_axum;
pub(crate) mod partial_signing_axum;
pub(crate) mod spending_axum;
pub(crate) mod issued;
pub(crate) mod partial_signing;
pub(crate) mod spending;
+84 -23
View File
@@ -1,27 +1,65 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::api_routes::helpers::EpochIdParam;
use crate::ecash::error::EcashError;
use crate::ecash::helpers::blind_sign;
use crate::ecash::state::EcashState;
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::extract::Path;
use axum::{Json, Router};
use nym_api_requests::ecash::{
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
PartialExpirationDateSignatureResponse,
};
use nym_ecash_time::{cred_exp_date, EcashTime};
use nym_validator_client::nym_api::rfc_3339_date;
use rocket::serde::json::Json;
use rocket::State as RocketState;
use rocket_okapi::openapi;
use serde::Deserialize;
use std::ops::Deref;
use std::sync::Arc;
use time::Date;
use tracing::{debug, trace};
use utoipa::IntoParams;
#[openapi(tag = "Ecash")]
#[post("/blind-sign", data = "<blind_sign_request_body>")]
pub async fn post_blind_sign(
blind_sign_request_body: Json<BlindSignRequestBody>,
state: &RocketState<EcashState>,
) -> crate::ecash::error::Result<Json<BlindedSignatureResponse>> {
pub(crate) fn partial_signing_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
Router::new()
.route(
"/blind-sign",
axum::routing::post({
let ecash_state = Arc::clone(&ecash_state);
|body| post_blind_sign(body, ecash_state)
}),
)
.route(
"/partial-expiration-date-signatures:expiration_date",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|expiration_date| partial_expiration_date_signatures(expiration_date, ecash_state)
}),
)
.route(
"/partial-coin-indices-signatures:epoch_id",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch_id| partial_coin_indices_signatures(epoch_id, ecash_state)
}),
)
}
#[utoipa::path(
tag = "Ecash",
post,
request_body = BlindSignRequestBody,
path = "/v1/ecash/blind-sign",
responses(
(status = 200, body = BlindedSignatureResponse)
)
)]
async fn post_blind_sign(
Json(blind_sign_request_body): Json<BlindSignRequestBody>,
state: Arc<EcashState>,
) -> AxumResult<Json<BlindedSignatureResponse>> {
debug!("Received blind sign request");
trace!("body: {:?}", blind_sign_request_body);
@@ -31,7 +69,7 @@ pub async fn post_blind_sign(
// basic check of expiration date validity
if blind_sign_request_body.expiration_date > cred_exp_date().ecash_date() {
return Err(EcashError::ExpirationDateTooLate);
return Err(EcashError::ExpirationDateTooLate.into());
}
// see if we're not in the middle of new dkg
@@ -62,24 +100,38 @@ pub async fn post_blind_sign(
// produce the partial signature
debug!("producing the partial credential");
let blinded_signature = blind_sign(blind_sign_request_body.deref(), signing_key.deref())?;
let blinded_signature = blind_sign(&blind_sign_request_body, signing_key.deref())?;
// store the information locally
debug!("storing the issued credential in the database");
state
.store_issued_credential(blind_sign_request_body.into_inner(), &blinded_signature)
.store_issued_credential(blind_sign_request_body, &blinded_signature)
.await?;
// finally return the credential to the client
Ok(Json(BlindedSignatureResponse { blinded_signature }))
}
#[openapi(tag = "Ecash")]
#[get("/partial-expiration-date-signatures?<expiration_date>")]
pub async fn partial_expiration_date_signatures(
#[derive(Deserialize, IntoParams)]
struct ExpirationDateParam {
expiration_date: Option<String>,
state: &RocketState<EcashState>,
) -> crate::ecash::error::Result<Json<PartialExpirationDateSignatureResponse>> {
}
#[utoipa::path(
tag = "Ecash",
get,
params(
ExpirationDateParam
),
path = "/v1/ecash/partial-expiration-date-signatures/{expiration_date}",
responses(
(status = 200, body = PartialExpirationDateSignatureResponse)
)
)]
async fn partial_expiration_date_signatures(
Path(ExpirationDateParam { expiration_date }): Path<ExpirationDateParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<PartialExpirationDateSignatureResponse>> {
let expiration_date = match expiration_date {
None => cred_exp_date().ecash_date(),
Some(raw) => Date::parse(&raw, &rfc_3339_date())
@@ -100,12 +152,21 @@ pub async fn partial_expiration_date_signatures(
}))
}
#[openapi(tag = "Ecash")]
#[get("/partial-coin-indices-signatures?<epoch_id>")]
pub async fn partial_coin_indices_signatures(
epoch_id: Option<u64>,
state: &RocketState<EcashState>,
) -> crate::ecash::error::Result<Json<PartialCoinIndicesSignatureResponse>> {
#[utoipa::path(
tag = "Ecash",
get,
params(
EpochIdParam
),
path = "/v1/ecash/partial-coin-indices-signatures/{epoch_id}",
responses(
(status = 200, body = PartialExpirationDateSignatureResponse)
)
)]
async fn partial_coin_indices_signatures(
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<PartialCoinIndicesSignatureResponse>> {
// see if we're not in the middle of new dkg
state.ensure_dkg_not_in_progress().await?;
@@ -1,179 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::api_routes::helpers::EpochIdParam;
use crate::ecash::error::EcashError;
use crate::ecash::helpers::blind_sign;
use crate::ecash::state::EcashState;
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::extract::Path;
use axum::{Json, Router};
use nym_api_requests::ecash::{
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
PartialExpirationDateSignatureResponse,
};
use nym_ecash_time::{cred_exp_date, EcashTime};
use nym_validator_client::nym_api::rfc_3339_date;
use serde::Deserialize;
use std::ops::Deref;
use std::sync::Arc;
use time::Date;
use tracing::{debug, trace};
use utoipa::IntoParams;
pub(crate) fn partial_signing_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
Router::new()
.route(
"/blind-sign",
axum::routing::post({
let ecash_state = Arc::clone(&ecash_state);
|body| post_blind_sign(body, ecash_state)
}),
)
.route(
"/partial-expiration-date-signatures:expiration_date",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|expiration_date| partial_expiration_date_signatures(expiration_date, ecash_state)
}),
)
.route(
"/partial-coin-indices-signatures:epoch_id",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|epoch_id| partial_coin_indices_signatures(epoch_id, ecash_state)
}),
)
}
#[utoipa::path(
tag = "Ecash",
post,
request_body = BlindSignRequestBody,
path = "/v1/ecash/blind-sign",
responses(
(status = 200, body = BlindedSignatureResponse)
)
)]
async fn post_blind_sign(
Json(blind_sign_request_body): Json<BlindSignRequestBody>,
state: Arc<EcashState>,
) -> AxumResult<Json<BlindedSignatureResponse>> {
debug!("Received blind sign request");
trace!("body: {:?}", blind_sign_request_body);
// check if we have the signing key available
debug!("checking if we actually have ecash keys derived...");
let signing_key = state.ecash_signing_key().await?;
// basic check of expiration date validity
if blind_sign_request_body.expiration_date > cred_exp_date().ecash_date() {
return Err(EcashError::ExpirationDateTooLate.into());
}
// see if we're not in the middle of new dkg
state.ensure_dkg_not_in_progress().await?;
// check if we already issued a credential for this deposit
let deposit_id = blind_sign_request_body.deposit_id;
debug!(
"checking if we have already issued credential for this deposit (deposit_id: {deposit_id})",
);
if let Some(blinded_signature) = state.already_issued(deposit_id).await? {
return Ok(Json(BlindedSignatureResponse { blinded_signature }));
}
//check if account was blacklisted
let pub_key_bs58 = blind_sign_request_body.ecash_pubkey.to_base58_string();
state.aux.ensure_not_blacklisted(&pub_key_bs58).await?;
// get the deposit details of the claimed id
debug!("getting deposit details from the chain");
let deposit = state.get_deposit(deposit_id).await?;
// check validity of the request
debug!("fully validating received request");
state
.validate_request(&blind_sign_request_body, deposit)
.await?;
// produce the partial signature
debug!("producing the partial credential");
let blinded_signature = blind_sign(&blind_sign_request_body, signing_key.deref())?;
// store the information locally
debug!("storing the issued credential in the database");
state
.store_issued_credential(blind_sign_request_body, &blinded_signature)
.await?;
// finally return the credential to the client
Ok(Json(BlindedSignatureResponse { blinded_signature }))
}
#[derive(Deserialize, IntoParams)]
struct ExpirationDateParam {
expiration_date: Option<String>,
}
#[utoipa::path(
tag = "Ecash",
get,
params(
ExpirationDateParam
),
path = "/v1/ecash/partial-expiration-date-signatures/{expiration_date}",
responses(
(status = 200, body = PartialExpirationDateSignatureResponse)
)
)]
async fn partial_expiration_date_signatures(
Path(ExpirationDateParam { expiration_date }): Path<ExpirationDateParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<PartialExpirationDateSignatureResponse>> {
let expiration_date = match expiration_date {
None => cred_exp_date().ecash_date(),
Some(raw) => Date::parse(&raw, &rfc_3339_date())
.map_err(|_| EcashError::MalformedExpirationDate { raw })?,
};
// see if we're not in the middle of new dkg
state.ensure_dkg_not_in_progress().await?;
let expiration_date_signatures = state
.partial_expiration_date_signatures(expiration_date)
.await?;
Ok(Json(PartialExpirationDateSignatureResponse {
epoch_id: expiration_date_signatures.epoch_id,
expiration_date,
signatures: expiration_date_signatures.signatures.clone(),
}))
}
#[utoipa::path(
tag = "Ecash",
get,
params(
EpochIdParam
),
path = "/v1/ecash/partial-coin-indices-signatures/{epoch_id}",
responses(
(status = 200, body = PartialExpirationDateSignatureResponse)
)
)]
async fn partial_coin_indices_signatures(
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
state: Arc<EcashState>,
) -> AxumResult<Json<PartialCoinIndicesSignatureResponse>> {
// see if we're not in the middle of new dkg
state.ensure_dkg_not_in_progress().await?;
let coin_indices_signatures = state.partial_coin_index_signatures(epoch_id).await?;
Ok(Json(PartialCoinIndicesSignatureResponse {
epoch_id: coin_indices_signatures.epoch_id,
signatures: coin_indices_signatures.signatures.clone(),
}))
}
+75 -31
View File
@@ -3,6 +3,9 @@
use crate::ecash::error::EcashError;
use crate::ecash::state::EcashState;
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::{Json, Router};
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
use nym_api_requests::ecash::models::{
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationRejection,
@@ -10,31 +13,62 @@ use nym_api_requests::ecash::models::{
};
use nym_compact_ecash::identify::IdentifyResult;
use nym_ecash_time::EcashTime;
use rocket::serde::json::Json;
use rocket::State as RocketState;
use rocket_okapi::openapi;
use std::collections::HashSet;
use std::ops::Deref;
use std::sync::Arc;
use time::macros::time;
use time::{OffsetDateTime, Time};
use tracing::{error, warn};
pub(crate) fn spending_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
Router::new()
.route(
"/verify-ecash-ticket",
axum::routing::post({
let ecash_state = Arc::clone(&ecash_state);
|body| verify_ticket(body, ecash_state)
}),
)
.route(
"/batch-redeem-ecash-tickets",
axum::routing::post({
let ecash_state = Arc::clone(&ecash_state);
|body| batch_redeem_tickets(body, ecash_state)
}),
)
.route(
"/double-spending-filter-v1",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|| double_spending_filter_v1(ecash_state)
}),
)
}
const ONE_AM: Time = time!(1:00);
fn reject_ticket(
reason: EcashTicketVerificationRejection,
) -> crate::ecash::error::Result<Json<EcashTicketVerificationResponse>> {
) -> AxumResult<Json<EcashTicketVerificationResponse>> {
Ok(Json(EcashTicketVerificationResponse::reject(reason)))
}
// TODO: optimise it; for now it's just dummy split of the original `verify_offline_credential`
// introduce bloomfilter checks without touching storage first, etc.
#[openapi(tag = "Ecash")]
#[post("/verify-ecash-ticket", data = "<verify_ticket_body>")]
pub async fn verify_ticket(
#[utoipa::path(
tag = "Ecash",
post,
request_body = VerifyEcashTicketBody,
path = "/v1/ecash/verify-ecash-ticket",
responses(
(status = 200, body = EcashTicketVerificationResponse)
)
)]
async fn verify_ticket(
// TODO in the future: make it send binary data rather than json
verify_ticket_body: Json<VerifyEcashTicketBody>,
state: &RocketState<EcashState>,
) -> crate::ecash::error::Result<Json<EcashTicketVerificationResponse>> {
Json(verify_ticket_body): Json<VerifyEcashTicketBody>,
state: Arc<EcashState>,
) -> AxumResult<Json<EcashTicketVerificationResponse>> {
let credential_data = &verify_ticket_body.credential;
let gateway_cosmos_addr = &verify_ticket_body.gateway_cosmos_addr;
@@ -117,26 +151,30 @@ pub async fn verify_ticket(
}
// // for particular SN returns what gateway has submitted it and whether it has been verified correctly
// pub async fn credential_status() -> ! {
// async fn credential_status() -> ! {
// todo!()
// }
#[openapi(tag = "Ecash")]
#[post(
"/batch-redeem-ecash-tickets",
data = "<batch_redeem_credentials_body>"
#[utoipa::path(
tag = "Ecash",
post,
request_body = BatchRedeemTicketsBody,
path = "/v1/ecash/batch-redeem-ecash-tickets",
responses(
(status = 200, body = EcashBatchTicketRedemptionResponse)
)
)]
pub async fn batch_redeem_tickets(
async fn batch_redeem_tickets(
// TODO in the future: make it send binary data rather than json
batch_redeem_credentials_body: Json<BatchRedeemTicketsBody>,
state: &RocketState<EcashState>,
) -> crate::ecash::error::Result<Json<EcashBatchTicketRedemptionResponse>> {
Json(batch_redeem_credentials_body): Json<BatchRedeemTicketsBody>,
state: Arc<EcashState>,
) -> AxumResult<Json<EcashBatchTicketRedemptionResponse>> {
// 1. see if that gateway has even submitted any tickets
let Some(provider_info) = state
.get_ticket_provider(batch_redeem_credentials_body.gateway_cosmos_addr.as_ref())
.await?
else {
return Err(EcashError::NotTicketsProvided);
return Err(EcashError::NotTicketsProvided.into());
};
// 2. check if the gateway is not trying to spam the redemption requests
@@ -149,13 +187,14 @@ pub async fn batch_redeem_tickets(
return Err(EcashError::TooFrequentRedemption {
last_redemption,
next_allowed,
});
}
.into());
}
}
// 3. verify the request digest
if !batch_redeem_credentials_body.verify_digest() {
return Err(EcashError::MismatchedRequestDigest);
return Err(EcashError::MismatchedRequestDigest.into());
}
// 4. verify the associated on-chain proposal (whether it's made by correct sender, has valid messages, etc.)
@@ -164,9 +203,7 @@ pub async fn batch_redeem_tickets(
.await?;
let proposal_id = batch_redeem_credentials_body.proposal_id;
let received = batch_redeem_credentials_body
.into_inner()
.included_serial_numbers;
let received = batch_redeem_credentials_body.included_serial_numbers;
// 5. check if **every** serial number included in the request has been verified by us
// if we have more than requested, tough luck, they're going to lose them
@@ -177,7 +214,8 @@ pub async fn batch_redeem_tickets(
if !verified_tickets.contains(sn.deref()) {
return Err(EcashError::TicketNotVerified {
serial_number_bs58: bs58::encode(sn).into_string(),
});
}
.into());
}
}
@@ -190,11 +228,17 @@ pub async fn batch_redeem_tickets(
// explicitly mark it as v1 in the URL because the response type WILL change;
// it will probably be compressed bincode or something
#[openapi(tag = "Ecash")]
#[get("/double-spending-filter-v1")]
pub async fn double_spending_filter_v1(
state: &RocketState<EcashState>,
) -> crate::ecash::error::Result<Json<SpentCredentialsResponse>> {
#[utoipa::path(
tag = "Ecash",
get,
path = "/v1/ecash/double-spending-filter-v1",
responses(
(status = 200, body = SpentCredentialsResponse)
)
)]
async fn double_spending_filter_v1(
state: Arc<EcashState>,
) -> AxumResult<Json<SpentCredentialsResponse>> {
let spent_credentials_export = state.get_bloomfilter_bytes().await;
Ok(Json(SpentCredentialsResponse::new(
spent_credentials_export,
@@ -1,246 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::ecash::error::EcashError;
use crate::ecash::state::EcashState;
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::{Json, Router};
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
use nym_api_requests::ecash::models::{
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationRejection,
EcashTicketVerificationResponse, SpentCredentialsResponse, VerifyEcashTicketBody,
};
use nym_compact_ecash::identify::IdentifyResult;
use nym_ecash_time::EcashTime;
use std::collections::HashSet;
use std::ops::Deref;
use std::sync::Arc;
use time::macros::time;
use time::{OffsetDateTime, Time};
use tracing::{error, warn};
pub(crate) fn spending_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
Router::new()
.route(
"/verify-ecash-ticket",
axum::routing::post({
let ecash_state = Arc::clone(&ecash_state);
|body| verify_ticket(body, ecash_state)
}),
)
.route(
"/batch-redeem-ecash-tickets",
axum::routing::post({
let ecash_state = Arc::clone(&ecash_state);
|body| batch_redeem_tickets(body, ecash_state)
}),
)
.route(
"/double-spending-filter-v1",
axum::routing::get({
let ecash_state = Arc::clone(&ecash_state);
|| double_spending_filter_v1(ecash_state)
}),
)
}
const ONE_AM: Time = time!(1:00);
fn reject_ticket(
reason: EcashTicketVerificationRejection,
) -> AxumResult<Json<EcashTicketVerificationResponse>> {
Ok(Json(EcashTicketVerificationResponse::reject(reason)))
}
// TODO: optimise it; for now it's just dummy split of the original `verify_offline_credential`
// introduce bloomfilter checks without touching storage first, etc.
#[utoipa::path(
tag = "Ecash",
post,
request_body = VerifyEcashTicketBody,
path = "/v1/ecash/verify-ecash-ticket",
responses(
(status = 200, body = EcashTicketVerificationResponse)
)
)]
async fn verify_ticket(
// TODO in the future: make it send binary data rather than json
Json(verify_ticket_body): Json<VerifyEcashTicketBody>,
state: Arc<EcashState>,
) -> AxumResult<Json<EcashTicketVerificationResponse>> {
let credential_data = &verify_ticket_body.credential;
let gateway_cosmos_addr = &verify_ticket_body.gateway_cosmos_addr;
// easy check: is there only a single payment attached?
if credential_data.payment.spend_value != 1 {
return reject_ticket(EcashTicketVerificationRejection::MultipleTickets);
}
let sn = &credential_data.encoded_serial_number();
let spend_date = credential_data.spend_date;
let epoch_id = credential_data.epoch_id;
let now = OffsetDateTime::now_utc();
let today_ecash = now.ecash_date();
#[allow(clippy::unwrap_used)]
let yesterday_ecash = today_ecash.previous_day().unwrap();
// only accept yesterday date if we're near the day transition, i.e. before 1AM UTC
if spend_date != today_ecash && now.time() > ONE_AM && spend_date != yesterday_ecash {
return reject_ticket(EcashTicketVerificationRejection::InvalidSpentDate {
today: today_ecash,
yesterday: yesterday_ecash,
received: spend_date,
});
}
// check the bloomfilter for obvious double-spending so that we wouldn't need to waste time on crypto verification
// TODO: when blacklisting is implemented, this should get removed
if state.check_bloomfilter(sn).await {
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket);
}
// actual double spend detection with storage
if let Some(previous_payment) = state.get_ticket_data_by_serial_number(sn).await? {
match nym_compact_ecash::identify::identify(
&credential_data.payment,
&previous_payment.payment,
credential_data.pay_info,
previous_payment.pay_info,
) {
IdentifyResult::NotADuplicatePayment => {} //SW NOTE This should never happen, quick message?
IdentifyResult::DuplicatePayInfo(_) => {
warn!("Identical payInfo");
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket);
}
IdentifyResult::DoubleSpendingPublicKeys(pub_key) => {
//Actual double spending
warn!(
"Double spending attempt for key {}",
pub_key.to_base58_string()
);
error!("UNIMPLEMENTED: blacklisting the double spend key");
return reject_ticket(EcashTicketVerificationRejection::DoubleSpend);
}
}
}
let verification_key = state.master_verification_key(Some(epoch_id)).await?;
// perform actual crypto verification
if credential_data.verify(&verification_key).is_err() {
return reject_ticket(EcashTicketVerificationRejection::InvalidTicket);
}
// finally get EXCLUSIVE lock on the bloomfilter, check if for the final time and insert the SN
let was_present = state
.update_bloomfilter(sn, spend_date, today_ecash)
.await?;
if was_present {
return reject_ticket(EcashTicketVerificationRejection::ReplayedTicket);
}
//store credential
state
.store_verified_ticket(credential_data, gateway_cosmos_addr)
.await?;
Ok(Json(EcashTicketVerificationResponse { verified: Ok(()) }))
}
// // for particular SN returns what gateway has submitted it and whether it has been verified correctly
// async fn credential_status() -> ! {
// todo!()
// }
#[utoipa::path(
tag = "Ecash",
post,
request_body = BatchRedeemTicketsBody,
path = "/v1/ecash/batch-redeem-ecash-tickets",
responses(
(status = 200, body = EcashBatchTicketRedemptionResponse)
)
)]
async fn batch_redeem_tickets(
// TODO in the future: make it send binary data rather than json
Json(batch_redeem_credentials_body): Json<BatchRedeemTicketsBody>,
state: Arc<EcashState>,
) -> AxumResult<Json<EcashBatchTicketRedemptionResponse>> {
// 1. see if that gateway has even submitted any tickets
let Some(provider_info) = state
.get_ticket_provider(batch_redeem_credentials_body.gateway_cosmos_addr.as_ref())
.await?
else {
return Err(EcashError::NotTicketsProvided.into());
};
// 2. check if the gateway is not trying to spam the redemption requests
// (we have to protect our poor chain)
if let Some(last_redemption) = provider_info.last_batch_verification {
let now = OffsetDateTime::now_utc();
let next_allowed = last_redemption + MIN_BATCH_REDEMPTION_DELAY;
if next_allowed > now {
return Err(EcashError::TooFrequentRedemption {
last_redemption,
next_allowed,
}
.into());
}
}
// 3. verify the request digest
if !batch_redeem_credentials_body.verify_digest() {
return Err(EcashError::MismatchedRequestDigest.into());
}
// 4. verify the associated on-chain proposal (whether it's made by correct sender, has valid messages, etc.)
state
.validate_redemption_proposal(&batch_redeem_credentials_body)
.await?;
let proposal_id = batch_redeem_credentials_body.proposal_id;
let received = batch_redeem_credentials_body.included_serial_numbers;
// 5. check if **every** serial number included in the request has been verified by us
// if we have more than requested, tough luck, they're going to lose them
let verified = state.get_redeemable_tickets(provider_info).await?;
let verified_tickets: HashSet<_> = verified.iter().map(|sn| sn.deref()).collect();
for sn in &received {
if !verified_tickets.contains(sn.deref()) {
return Err(EcashError::TicketNotVerified {
serial_number_bs58: bs58::encode(sn).into_string(),
}
.into());
}
}
// TODO: offload it to separate task with work queue and batching (of tx messages) to vote for multiple proposals in the same tx
state.accept_proposal(proposal_id).await?;
Ok(Json(EcashBatchTicketRedemptionResponse {
proposal_accepted: true,
}))
}
// explicitly mark it as v1 in the URL because the response type WILL change;
// it will probably be compressed bincode or something
#[utoipa::path(
tag = "Ecash",
get,
path = "/v1/ecash/double-spending-filter-v1",
responses(
(status = 200, body = SpentCredentialsResponse)
)
)]
async fn double_spending_filter_v1(
state: Arc<EcashState>,
) -> AxumResult<Json<SpentCredentialsResponse>> {
let spent_credentials_export = state.get_bloomfilter_bytes().await;
Ok(Json(SpentCredentialsResponse::new(
spent_credentials_export,
)))
}
-24
View File
@@ -16,27 +16,3 @@ pub(crate) mod tests;
// equivalent of 100nym
pub(crate) const MINIMUM_BALANCE: u128 = 100_000000;
// pub(crate) fn routes_open_api(settings: &OpenApiSettings, enabled: bool) -> (Vec<Route>, OpenApi) {
// if enabled {
// openapi_get_routes_spec![
// settings:
// api_routes::partial_signing::post_blind_sign,
// api_routes::partial_signing::partial_expiration_date_signatures,
// api_routes::partial_signing::partial_coin_indices_signatures,
// api_routes::spending::verify_ticket,
// api_routes::spending::batch_redeem_tickets,
// api_routes::spending::double_spending_filter_v1,
// api_routes::issued::epoch_credentials,
// api_routes::issued::issued_credential,
// api_routes::issued::issued_credentials,
// api_routes::aggregation::master_verification_key,
// api_routes::aggregation::coin_indices_signatures,
// api_routes::aggregation::expiration_date_signatures
// ]
// } else {
// openapi_get_routes_spec![
// settings:
// ]
// }
// }
-9
View File
@@ -3,12 +3,3 @@
pub(crate) mod handlers;
pub(crate) mod models;
// mod routes;
//
// pub(crate) fn network_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
// openapi_get_routes_spec![
// settings: routes::network_details,
// routes::nym_contracts,
// routes::nym_contracts_detailed
// ]
// }
-61
View File
@@ -1,61 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::network::models::{ContractInformation, NetworkDetails};
use crate::nym_contract_cache::cache::NymContractCache;
use nym_contracts_common::ContractBuildInformation;
use rocket::serde::json::Json;
use rocket::State;
use rocket_okapi::openapi;
use std::collections::HashMap;
use std::ops::Deref;
#[openapi(tag = "network")]
#[get("/details")]
pub(crate) fn network_details(details: &State<NetworkDetails>) -> Json<NetworkDetails> {
Json(details.deref().clone())
}
// I agree, it feels weird to be pulling contract cache here, but I feel like it makes
// more sense to return this information here rather than in the generic cache route
#[openapi(tag = "network")]
#[get("/nym-contracts")]
pub(crate) async fn nym_contracts(
cache: &State<NymContractCache>,
) -> Json<HashMap<String, ContractInformation<cw2::ContractVersion>>> {
let info = cache.contract_details().await;
Json(
info.iter()
.map(|(contract, info)| {
(
contract.to_owned(),
ContractInformation {
address: info.address.as_ref().map(|a| a.to_string()),
details: info.base.clone(),
},
)
})
.collect(),
)
}
#[openapi(tag = "network")]
#[get("/nym-contracts-detailed")]
pub(crate) async fn nym_contracts_detailed(
cache: &State<NymContractCache>,
) -> Json<HashMap<String, ContractInformation<ContractBuildInformation>>> {
let info = cache.contract_details().await;
Json(
info.iter()
.map(|(contract, info)| {
(
contract.to_owned(),
ContractInformation {
address: info.address.as_ref().map(|a| a.to_string()),
details: info.detailed.clone(),
},
)
})
.collect(),
)
}
-47
View File
@@ -16,7 +16,6 @@ pub(crate) mod handlers;
pub(crate) mod helpers;
pub(crate) mod models;
pub(crate) mod reward_estimate;
// pub(crate) mod routes_deprecated;
pub(crate) mod uptime_updater;
pub(crate) mod utils;
@@ -24,52 +23,6 @@ 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 node_status_routes(
// settings: &OpenApiSettings,
// enabled: bool,
// ) -> (Vec<Route>, OpenApi) {
// if enabled {
// openapi_get_routes_spec![
// 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_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,
// ]
// }
// }
/// Spawn the node status cache refresher.
///
/// It is primarily refreshed in-sync with the nym contract cache, however provide a fallback
@@ -1,615 +0,0 @@
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use super::NodeStatusCache;
use crate::node_status_api::helpers_deprecated::{
_compute_mixnode_reward_estimation, _gateway_core_status_count, _gateway_report,
_gateway_uptime_history, _get_gateway_avg_uptime, _get_mixnode_avg_uptime,
_get_mixnode_inclusion_probabilities, _get_mixnode_inclusion_probability,
_get_mixnode_reward_estimation, _get_mixnode_stake_saturation, _get_mixnode_status,
_get_mixnodes_detailed_unfiltered, _mixnode_core_status_count, _mixnode_report,
_mixnode_uptime_history,
};
use crate::node_status_api::models::RocketErrorResponse;
use crate::storage::NymApiStorage;
use crate::NymContractCache;
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::NodeId;
use nym_types::monitoring::MonitorMessage;
use rocket::serde::json::Json;
use rocket::State;
use rocket_okapi::openapi;
#[openapi(tag = "status", deprecated = true)]
#[post("/submit-gateway-monitoring-results", data = "<message>")]
pub(crate) async fn submit_gateway_monitoring_results(
message: Json<MonitorMessage>,
storage: &State<NymApiStorage>,
) -> Result<(), RocketErrorResponse> {
todo!("rebasing");
// if !message.from_allowed() {
// return Err(RocketErrorResponse::new(
// "Monitor not registered to submit results".to_string(),
// rocket::http::Status::Forbidden,
// ));
// }
//
// if !message.timely() {
// return Err(RocketErrorResponse::new(
// "Message is too old".to_string(),
// rocket::http::Status::BadRequest,
// ));
// }
//
// if !message.verify() {
// return Err(RocketErrorResponse::new(
// "Invalid signature".to_string(),
// rocket::http::Status::BadRequest,
// ));
// }
//
// match storage
// .manager
// .submit_gateway_statuses_v2(message.results())
// .await
// {
// Ok(_) => Ok(()),
// Err(err) => {
// error!("failed to submit gateway monitoring results: {}", err);
// Err(RocketErrorResponse::new(
// "failed to submit gateway monitoring results".to_string(),
// rocket::http::Status::InternalServerError,
// ))
// }
// }
}
#[openapi(tag = "status")]
#[post("/submit-node-monitoring-results", data = "<message>")]
pub(crate) async fn submit_node_monitoring_results(
message: Json<MonitorMessage>,
storage: &State<NymApiStorage>,
) -> Result<(), RocketErrorResponse> {
todo!("rebasing");
// if !message.from_allowed() {
// return Err(RocketErrorResponse::new(
// "Monitor not registered to submit results".to_string(),
// rocket::http::Status::Forbidden,
// ));
// }
//
// if !message.timely() {
// return Err(RocketErrorResponse::new(
// "Message is too old".to_string(),
// rocket::http::Status::BadRequest,
// ));
// }
//
// if !message.verify() {
// return Err(RocketErrorResponse::new(
// "Invalid signature".to_string(),
// rocket::http::Status::BadRequest,
// ));
// }
//
// match storage
// .manager
// .submit_mixnode_statuses_v2(message.results())
// .await
// {
// Ok(_) => Ok(()),
// Err(err) => {
// error!("failed to submit node monitoring results: {}", err);
// Err(RocketErrorResponse::new(
// "failed to submit node monitoring results".to_string(),
// rocket::http::Status::InternalServerError,
// ))
// }
// }
}
#[openapi(tag = "status", deprecated = true)]
#[get("/gateway/<identity>/report")]
pub(crate) async fn gateway_report(
cache: &State<NodeStatusCache>,
identity: &str,
) -> Result<Json<GatewayStatusReportResponse>, RocketErrorResponse> {
todo!("rebasing");
// Ok(Json(_gateway_report(cache, identity).await?))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/gateway/<identity>/history")]
pub(crate) async fn gateway_uptime_history(
storage: &State<NymApiStorage>,
nym_contract_cache: &State<NymContractCache>,
identity: &str,
) -> Result<Json<GatewayUptimeHistoryResponse>, RocketErrorResponse> {
todo!("rebasing");
// Ok(Json(
// _gateway_uptime_history(storage, nym_contract_cache, identity).await?,
// ))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/gateway/<identity>/core-status-count?<since>")]
pub(crate) async fn gateway_core_status_count(
storage: &State<NymApiStorage>,
identity: &str,
since: Option<i64>,
) -> Result<Json<GatewayCoreStatusResponse>, RocketErrorResponse> {
Ok(Json(
_gateway_core_status_count(storage, identity, since).await?,
))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnode/<mix_id>/report")]
pub(crate) async fn mixnode_report(
cache: &State<NodeStatusCache>,
mix_id: NodeId,
) -> Result<Json<MixnodeStatusReportResponse>, RocketErrorResponse> {
Ok(Json(_mixnode_report(cache, mix_id).await?))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnode/<mix_id>/history")]
pub(crate) async fn mixnode_uptime_history(
storage: &State<NymApiStorage>,
nym_contract_cache: &State<NymContractCache>,
mix_id: NodeId,
) -> Result<Json<MixnodeUptimeHistoryResponse>, RocketErrorResponse> {
todo!("rebasing");
// Ok(Json(
// _mixnode_uptime_history(storage, nym_contract_cache, mix_id).await?,
// ))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnode/<mix_id>/core-status-count?<since>")]
pub(crate) async fn mixnode_core_status_count(
storage: &State<NymApiStorage>,
mix_id: NodeId,
since: Option<i64>,
) -> Result<Json<MixnodeCoreStatusResponse>, RocketErrorResponse> {
Ok(Json(
_mixnode_core_status_count(storage, mix_id, since).await?,
))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnode/<mix_id>/status")]
pub(crate) async fn get_mixnode_status(
cache: &State<NymContractCache>,
mix_id: NodeId,
) -> Json<MixnodeStatusResponse> {
Json(_get_mixnode_status(cache, mix_id).await)
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnode/<mix_id>/reward-estimation")]
pub(crate) async fn get_mixnode_reward_estimation(
cache: &State<NodeStatusCache>,
validator_cache: &State<NymContractCache>,
mix_id: NodeId,
) -> Result<Json<RewardEstimationResponse>, RocketErrorResponse> {
Ok(Json(
_get_mixnode_reward_estimation(cache, validator_cache, mix_id).await?,
))
}
#[openapi(tag = "status", deprecated = true)]
#[post(
"/mixnode/<mix_id>/compute-reward-estimation",
data = "<user_reward_param>"
)]
pub(crate) async fn compute_mixnode_reward_estimation(
user_reward_param: Json<ComputeRewardEstParam>,
cache: &State<NodeStatusCache>,
validator_cache: &State<NymContractCache>,
mix_id: NodeId,
) -> Result<Json<RewardEstimationResponse>, RocketErrorResponse> {
Ok(Json(
_compute_mixnode_reward_estimation(
user_reward_param.into_inner(),
cache,
validator_cache,
mix_id,
)
.await?,
))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnode/<mix_id>/stake-saturation")]
pub(crate) async fn get_mixnode_stake_saturation(
cache: &State<NodeStatusCache>,
validator_cache: &State<NymContractCache>,
mix_id: NodeId,
) -> Result<Json<StakeSaturationResponse>, RocketErrorResponse> {
Ok(Json(
_get_mixnode_stake_saturation(cache, validator_cache, mix_id).await?,
))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnode/<mix_id>/inclusion-probability")]
pub(crate) async fn get_mixnode_inclusion_probability(
cache: &State<NodeStatusCache>,
mix_id: NodeId,
) -> Result<Json<InclusionProbabilityResponse>, RocketErrorResponse> {
Ok(Json(
_get_mixnode_inclusion_probability(cache, mix_id).await?,
))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnode/<mix_id>/avg_uptime")]
pub(crate) async fn get_mixnode_avg_uptime(
cache: &State<NodeStatusCache>,
mix_id: NodeId,
) -> Result<Json<UptimeResponse>, RocketErrorResponse> {
Ok(Json(_get_mixnode_avg_uptime(cache, mix_id).await?))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/gateway/<identity>/avg_uptime")]
pub(crate) async fn get_gateway_avg_uptime(
cache: &State<NodeStatusCache>,
identity: &str,
) -> Result<Json<GatewayUptimeResponse>, RocketErrorResponse> {
Ok(Json(_get_gateway_avg_uptime(cache, identity).await?))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnodes/inclusion_probability")]
pub(crate) async fn get_mixnode_inclusion_probabilities(
cache: &State<NodeStatusCache>,
) -> Result<Json<AllInclusionProbabilitiesResponse>, RocketErrorResponse> {
Ok(Json(_get_mixnode_inclusion_probabilities(cache).await?))
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnodes/detailed")]
pub async fn get_mixnodes_detailed(
cache: &State<NodeStatusCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
todo!("rebasing");
// Json(_get_legacy_mixnodes_detailed(cache).await)
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnodes/detailed-unfiltered")]
pub async fn get_mixnodes_detailed_unfiltered(
cache: &State<NodeStatusCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
Json(_get_mixnodes_detailed_unfiltered(cache).await)
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnodes/rewarded/detailed")]
pub async fn get_rewarded_set_detailed(
status_cache: &State<NodeStatusCache>,
contract_cache: &State<NymContractCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
todo!("rebasing");
// Json(_get_rewarded_set_legacy_mixnodes_detailed(status_cache, contract_cache).await)
}
#[openapi(tag = "status", deprecated = true)]
#[get("/mixnodes/active/detailed")]
pub async fn get_active_set_detailed(
status_cache: &State<NodeStatusCache>,
contract_cache: &State<NymContractCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
todo!("rebasing");
// Json(_get_active_set_legacy_mixnodes_detailed(status_cache, contract_cache).await)
}
#[openapi(tag = "status", deprecated = true)]
#[get("/gateways/detailed")]
pub async fn get_gateways_detailed(
cache: &State<NodeStatusCache>,
) -> Json<Vec<GatewayBondAnnotated>> {
todo!("rebasing");
// Json(_get_legacy_gateways_detailed(cache).await)
}
#[openapi(tag = "status", deprecated = true)]
#[get("/gateways/detailed-unfiltered")]
pub async fn get_gateways_detailed_unfiltered(
cache: &State<NodeStatusCache>,
) -> Json<Vec<GatewayBondAnnotated>> {
todo!("rebasing");
// Json(_get_legacy_gateways_detailed_unfiltered(cache).await)
}
pub mod unstable {
use crate::node_status_api::models::RocketErrorResponse;
use crate::support::http::helpers::PaginationRequest;
use crate::support::storage::NymApiStorage;
use nym_api_requests::models::{
GatewayTestResultResponse, MixnodeTestResultResponse, PartialTestResult, TestNode,
TestRoute,
};
use nym_api_requests::pagination::Pagination;
use nym_mixnet_contract_common::NodeId;
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
use rocket_okapi::openapi;
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, 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, 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: NodeId,
page: u32,
per_page: u32,
info_cache: &State<NodeInfoCache>,
storage: &State<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,
})
}
#[openapi(tag = "UNSTABLE - DO **NOT** USE")]
#[get("/mixnodes/unstable/<mix_id>/test-results?<pagination..>")]
pub async fn mixnode_test_results(
mix_id: NodeId,
pagination: PaginationRequest,
info_cache: &State<NodeInfoCache>,
storage: &State<NymApiStorage>,
) -> Result<Json<MixnodeTestResultResponse>, RocketErrorResponse> {
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, info_cache, storage).await {
Ok(res) => Ok(Json(res)),
Err(err) => Err(RocketErrorResponse::new(
format!("failed to retrieve mixnode test results for node {mix_id}: {err}"),
Status::InternalServerError,
)),
}
}
async fn _gateway_test_results(
gateway_identity: &str,
page: u32,
per_page: u32,
info_cache: &State<NodeInfoCache>,
storage: &State<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,
})
}
#[openapi(tag = "UNSTABLE - DO **NOT** USE")]
#[get("/gateways/unstable/<gateway_identity>/test-results?<pagination..>")]
pub async fn gateway_test_results(
gateway_identity: &str,
pagination: PaginationRequest,
info_cache: &State<NodeInfoCache>,
storage: &State<NymApiStorage>,
) -> Result<Json<GatewayTestResultResponse>, RocketErrorResponse> {
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, info_cache, storage).await {
Ok(res) => Ok(Json(res)),
Err(err) => Err(RocketErrorResponse::new(
format!(
"failed to retrieve mixnode test results for gateway {gateway_identity}: {err}"
),
Status::InternalServerError,
)),
}
}
}
@@ -1,2 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
-19
View File
@@ -9,25 +9,6 @@ use self::cache::refresher::NymContractCacheRefresher;
pub(crate) mod cache;
pub(crate) mod handlers;
pub(crate) mod legacy_helpers;
// pub(crate) mod routes;
//
// pub(crate) fn nym_contract_cache_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
// openapi_get_routes_spec![
// settings: routes::get_mixnodes,
// routes::get_mixnodes_detailed,
// routes::get_gateways,
// routes::get_active_set,
// routes::get_active_set_detailed,
// routes::get_rewarded_set,
// routes::get_rewarded_set_detailed,
// routes::get_blacklisted_mixnodes,
// routes::get_blacklisted_gateways,
// routes::get_blacklisted_gateways_v2,
// routes::get_interval_reward_params,
// routes::get_current_epoch,
// ]
// }
pub(crate) fn start_refresher(
config: &config::NodeStatusAPI,
-158
View File
@@ -1,158 +0,0 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::{node_status_api::NodeStatusCache, nym_contract_cache::cache::NymContractCache};
use nym_api_requests::models::MixNodeBondAnnotated;
use nym_mixnet_contract_common::{reward_params::RewardingParams, GatewayBond, Interval, NodeId};
use nym_api_requests::legacy::LegacyMixNodeDetailsWithLayer;
use rocket::{serde::json::Json, State};
use rocket_okapi::openapi;
use std::collections::HashSet;
#[openapi(tag = "contract-cache", deprecated = true)]
#[get("/mixnodes")]
pub async fn get_mixnodes(
cache: &State<NymContractCache>,
) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> {
Json(cache.legacy_mixnodes_filtered().await)
}
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
// replace this with
// ```
// pub fn get_mixnodes_detailed() -> Redirect {
// Redirect::to(uri!("/v1/status/mixnodes/detailed"))
// }
// ```
#[openapi(tag = "contract-cache", deprecated = true)]
#[get("/mixnodes/detailed")]
pub async fn get_mixnodes_detailed(
cache: &State<NodeStatusCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
todo!("rebasing")
// Json(_get_legacy_mixnodes_detailed(cache).await)
}
#[openapi(tag = "contract-cache", deprecated = true)]
#[get("/gateways")]
pub async fn get_gateways(cache: &State<NymContractCache>) -> Json<Vec<GatewayBond>> {
Json(
cache
.legacy_gateways_filtered()
.await
.into_iter()
.map(Into::into)
.collect(),
)
}
#[openapi(tag = "contract-cache", deprecated = true)]
#[get("/mixnodes/rewarded")]
pub async fn get_rewarded_set(
cache: &State<NymContractCache>,
) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> {
Json(cache.legacy_v1_rewarded_set_mixnodes().await.clone())
}
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
// replace this with
// ```
// pub fn get_mixnodes_set_detailed() -> Redirect {
// Redirect::to(uri!("/v1/status/mixnodes/rewarded/detailed"))
// }
// ```
#[openapi(tag = "contract-cache", deprecated = true)]
#[get("/mixnodes/rewarded/detailed")]
pub async fn get_rewarded_set_detailed(
status_cache: &State<NodeStatusCache>,
contract_cache: &State<NymContractCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
todo!("rebasing")
// Json(_get_rewarded_set_legacy_mixnodes_detailed(status_cache, contract_cache).await)
}
#[openapi(tag = "contract-cache", deprecated = true)]
#[get("/mixnodes/active")]
pub async fn get_active_set(
cache: &State<NymContractCache>,
) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> {
Json(cache.legacy_v1_active_set_mixnodes().await.clone())
}
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
// replace this with
// ```
// pub fn get_active_set_detailed() -> Redirect {
// Redirect::to(uri!("/status/mixnodes/active/detailed"))
// }
// ```
#[openapi(tag = "contract-cache", deprecated = true)]
#[get("/mixnodes/active/detailed")]
pub async fn get_active_set_detailed(
status_cache: &State<NodeStatusCache>,
contract_cache: &State<NymContractCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
todo!("rebasing")
// Json(_get_active_set_legacy_mixnodes_detailed(status_cache, contract_cache).await)
}
#[openapi(tag = "contract-cache", deprecated = true)]
#[get("/mixnodes/blacklisted")]
pub async fn get_blacklisted_mixnodes(
cache: &State<NymContractCache>,
) -> Json<Option<HashSet<NodeId>>> {
let blacklist = cache.mixnodes_blacklist().await.clone();
if blacklist.is_empty() {
Json(None)
} else {
Json(Some(blacklist))
}
}
#[openapi(tag = "contract-cache", deprecated = true)]
#[get("/gateways/blacklisted")]
pub async fn get_blacklisted_gateways(
cache: &State<NymContractCache>,
) -> Json<Option<HashSet<String>>> {
let blacklist = cache.gateways_blacklist().await.clone();
if blacklist.is_empty() {
Json(None)
} else {
let gateways = cache.legacy_gateways_all().await;
Json(Some(
gateways
.into_iter()
.filter(|g| blacklist.contains(&g.node_id))
.map(|g| g.gateway.identity_key.clone())
.collect(),
))
}
}
#[openapi(tag = "contract-cache", deprecated = true)]
#[get("/gateways/blacklisted_v2")]
pub async fn get_blacklisted_gateways_v2(
cache: &State<NymContractCache>,
) -> Json<Option<HashSet<NodeId>>> {
let blacklist = cache.gateways_blacklist().await.clone();
if blacklist.is_empty() {
Json(None)
} else {
Json(Some(blacklist))
}
}
#[openapi(tag = "contract-cache")]
#[get("/epoch/reward_params")]
pub async fn get_interval_reward_params(
cache: &State<NymContractCache>,
) -> Json<Option<RewardingParams>> {
Json(*cache.interval_reward_params().await)
}
#[openapi(tag = "contract-cache")]
#[get("/epoch/current")]
pub async fn get_current_epoch(cache: &State<NymContractCache>) -> Json<Option<Interval>> {
Json(*cache.current_interval().await)
}
-31
View File
@@ -2,34 +2,3 @@
// SPDX-License-Identifier: GPL-3.0-only
pub(crate) mod handlers;
// pub(crate) mod routes;
// mod unstable_routes;
// /// Merges the routes with http information and returns it to Rocket for serving
// pub(crate) fn nym_node_routes_deprecated(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
// openapi_get_routes_spec![
// settings:
// routes::get_gateways_described,
// routes::get_mixnodes_described,
// ]
// }
//
// pub(crate) fn nym_node_routes_next(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
// openapi_get_routes_spec![
// settings:
// unstable_routes::nodes_basic,
// unstable_routes::nodes_expanded,
// unstable_routes::nodes_detailed,
// unstable_routes::gateways_basic,
// unstable_routes::gateways_expanded,
// unstable_routes::gateways_detailed,
// unstable_routes::mixnodes_basic,
// unstable_routes::mixnodes_expanded,
// unstable_routes::mixnodes_detailed,
// routes::all_described_nodes,
// routes::node_description,
// routes::node_annotation_by_identity,
// routes::node_annotation
// ]
// }
-135
View File
@@ -1,135 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_describe_cache::DescribedNodes;
use crate::node_status_api::NodeStatusCache;
use crate::nym_contract_cache::cache::NymContractCache;
use crate::support::caching::cache::SharedCache;
use nym_api_requests::models::{
AnnotationResponse, LegacyDescribedGateway, LegacyDescribedMixNode, NymNodeDescription,
};
use nym_mixnet_contract_common::NodeId;
use rocket::serde::json::Json;
use rocket::State;
use rocket_okapi::openapi;
use std::ops::Deref;
#[openapi(tag = "Nym Nodes")]
#[get("/all/described")]
pub async fn all_described_nodes(
describe_cache: &State<SharedCache<DescribedNodes>>,
) -> Json<Vec<NymNodeDescription>> {
let Ok(self_descriptions) = describe_cache.get().await else {
return Json(Vec::new());
};
Json(self_descriptions.all_nodes().cloned().collect())
}
#[openapi(tag = "Nym Nodes")]
#[get("/all/<node_id>/described")]
pub async fn node_description(
node_id: NodeId,
describe_cache: &State<SharedCache<DescribedNodes>>,
) -> Json<Option<NymNodeDescription>> {
let Ok(self_descriptions) = describe_cache.get().await else {
return Json(None);
};
Json(self_descriptions.get_node(&node_id).cloned())
}
#[openapi(tag = "Nym Nodes")]
#[get("/annotation-by-identity/<identity>")]
pub async fn node_annotation_by_identity(
identity: String,
status_cache: &State<NodeStatusCache>,
) -> Json<AnnotationResponse> {
let Some(node_id) = status_cache.map_identity_to_node_id(&identity).await else {
return Json(Default::default());
};
node_annotation(node_id, status_cache).await
}
#[openapi(tag = "Nym Nodes")]
#[get("/annotation/<node_id>")]
pub async fn node_annotation(
node_id: NodeId,
status_cache: &State<NodeStatusCache>,
) -> Json<AnnotationResponse> {
let Some(annotation) = status_cache.node_annotations().await else {
return Json(Default::default());
};
Json(AnnotationResponse {
node_id: Some(node_id),
annotation: annotation.get(&node_id).cloned(),
})
}
/// This only returns descriptions of **legacy** gateways
#[openapi(tag = "Nym Nodes", deprecated = true)]
#[get("/gateways/described")]
pub async fn get_gateways_described(
contract_cache: &State<NymContractCache>,
describe_cache: &State<SharedCache<DescribedNodes>>,
) -> Json<Vec<LegacyDescribedGateway>> {
let gateways = contract_cache.legacy_gateways_filtered().await;
if gateways.is_empty() {
return Json(Vec::new());
}
// if the self describe cache is unavailable, well, don't attach describe data and only return legacy gateways
let Ok(self_descriptions) = describe_cache.get().await else {
return Json(gateways.into_iter().map(Into::into).collect());
};
Json(
gateways
.into_iter()
.map(|bond| LegacyDescribedGateway {
self_described: self_descriptions
.deref()
.get_description(&bond.node_id)
.cloned(),
bond,
})
.collect(),
)
}
/// This only returns descriptions of **legacy** mixnodes
#[openapi(tag = "Nym Nodes", deprecated = true)]
#[get("/mixnodes/described")]
pub async fn get_mixnodes_described(
contract_cache: &State<NymContractCache>,
describe_cache: &State<SharedCache<DescribedNodes>>,
) -> Json<Vec<LegacyDescribedMixNode>> {
let mixnodes = contract_cache
.legacy_mixnodes_filtered()
.await
.into_iter()
.map(|m| m.bond_information)
.collect::<Vec<_>>();
if mixnodes.is_empty() {
return Json(Vec::new());
}
// if the self describe cache is unavailable, well, don't attach describe data
let Ok(self_descriptions) = describe_cache.get().await else {
return Json(mixnodes.into_iter().map(Into::into).collect());
};
Json(
mixnodes
.into_iter()
.map(|bond| LegacyDescribedMixNode {
self_described: self_descriptions
.deref()
.get_description(&bond.mix_id)
.cloned(),
bond,
})
.collect(),
)
}
-403
View File
@@ -1,403 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_describe_cache::DescribedNodes;
use crate::node_status_api::models::RocketErrorResponse;
use crate::node_status_api::NodeStatusCache;
use crate::nym_contract_cache::cache::NymContractCache;
use crate::support::caching::cache::SharedCache;
use nym_api_requests::nym_nodes::{
CachedNodesResponse, FullFatNode, NodeRole, NodeRoleQueryParam, SemiSkimmedNode, SkimmedNode,
};
use nym_bin_common::version_checker;
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
use rocket_okapi::openapi;
use std::collections::HashSet;
/*
routes:
// all routes/nodes are split into three tiers:
// /skimmed => [used by clients] returns the very basic information for routing purposes
// /semi-skimmed => [used by other nodes/VPN] returns more additional information such noise keys
// /full-fat => [used by explorers, et al.] returns almost everything there is about the nodes
// there's also additional split based on the role:
?role => filters based on the specific role (mixnode/gateway/(in the future: entry/exit))
/mixnodes/<tier> => only returns mixnode role data
/gateway/<tier> => only returns (entry) gateway role data
*/
#[openapi(tag = "Unstable Nym Nodes")]
#[get("/skimmed?<role>&<semver_compatibility>")]
pub async fn nodes_basic(
status_cache: &State<NodeStatusCache>,
contract_cache: &State<NymContractCache>,
describe_cache: &State<SharedCache<DescribedNodes>>,
role: Option<NodeRoleQueryParam>,
semver_compatibility: Option<String>,
) -> Result<Json<CachedNodesResponse<SkimmedNode>>, RocketErrorResponse> {
if let Some(role) = role {
match role {
NodeRoleQueryParam::ActiveMixnode => {
return mixnodes_basic(
status_cache,
contract_cache,
describe_cache,
semver_compatibility,
)
.await
}
NodeRoleQueryParam::EntryGateway => {
return gateways_basic(
status_cache,
contract_cache,
describe_cache,
semver_compatibility,
)
.await
}
_ => {}
}
}
Err(RocketErrorResponse::new(
"unimplemented",
Status::NotImplemented,
))
}
#[openapi(tag = "Unstable Nym Nodes")]
#[get("/semi-skimmed?<role>&<semver_compatibility>")]
pub async fn nodes_expanded(
cache: &State<NodeStatusCache>,
role: Option<NodeRoleQueryParam>,
semver_compatibility: Option<String>,
) -> Result<Json<CachedNodesResponse<SemiSkimmedNode>>, RocketErrorResponse> {
if let Some(role) = role {
match role {
NodeRoleQueryParam::ActiveMixnode => {
return mixnodes_expanded(cache, semver_compatibility).await
}
NodeRoleQueryParam::EntryGateway => {
return gateways_expanded(cache, semver_compatibility).await
}
_ => {}
}
}
Err(RocketErrorResponse::new(
"unimplemented",
Status::NotImplemented,
))
}
#[openapi(tag = "Unstable Nym Nodes")]
#[get("/full-fat?<role>&<semver_compatibility>")]
pub async fn nodes_detailed(
cache: &State<NodeStatusCache>,
role: Option<NodeRoleQueryParam>,
semver_compatibility: Option<String>,
) -> Result<Json<CachedNodesResponse<FullFatNode>>, RocketErrorResponse> {
if let Some(role) = role {
match role {
NodeRoleQueryParam::ActiveMixnode => {
return mixnodes_detailed(cache, semver_compatibility).await
}
NodeRoleQueryParam::EntryGateway => {
return gateways_detailed(cache, semver_compatibility).await
}
_ => {}
}
}
Err(RocketErrorResponse::new(
"unimplemented",
Status::NotImplemented,
))
}
#[openapi(tag = "Unstable Nym Nodes")]
#[get("/gateways/skimmed?<semver_compatibility>")]
pub async fn gateways_basic(
status_cache: &State<NodeStatusCache>,
contract_cache: &State<NymContractCache>,
describe_cache: &State<SharedCache<DescribedNodes>>,
semver_compatibility: Option<String>,
) -> Result<Json<CachedNodesResponse<SkimmedNode>>, RocketErrorResponse> {
// 1. get the rewarded set
let rewarded_set = contract_cache
.rewarded_set()
.await
.ok_or_else(RocketErrorResponse::internal_server_error)?;
// determine which gateways are active, i.e. which gateways the clients should be using for connecting and routing the traffic
let active_gateways = rewarded_set.gateways().into_iter().collect::<HashSet<_>>();
// 2. grab all annotations so that we could attach scores to the [nym] nodes
let annotations = status_cache
.node_annotations()
.await
.ok_or_else(RocketErrorResponse::internal_server_error)?;
// 3. grab all legacy gateways
// due to legacy endpoints we already have fully annotated data on them
let annotated_legacy_gateways = status_cache
.annotated_legacy_gateways()
.await
.ok_or_else(RocketErrorResponse::internal_server_error)?;
// 4. grab all relevant described nym-nodes
let describe_cache = describe_cache.get().await?;
let gateway_capable_nym_nodes = describe_cache.gateway_capable_nym_nodes();
// 5. only return nodes that are present in the active set
let mut active_skimmed_gateways = Vec::new();
for (node_id, legacy) in annotated_legacy_gateways.iter() {
if !active_gateways.contains(node_id) {
continue;
}
if let Some(semver_compat) = semver_compatibility.as_ref() {
let version = legacy.version();
if !version_checker::is_minor_version_compatible(version, semver_compat) {
continue;
}
}
// if we have self-described info, prefer it over contract data
if let Some(described) = describe_cache.get_node(node_id) {
active_skimmed_gateways.push(
described.to_skimmed_node(NodeRole::EntryGateway, legacy.node_performance.last_24h),
)
} else {
active_skimmed_gateways.push(legacy.into());
}
}
for nym_node in gateway_capable_nym_nodes {
// if this node is not an active gateway, ignore it
if !active_gateways.contains(&nym_node.node_id) {
continue;
}
// if we have wrong version, ignore
if let Some(semver_compat) = semver_compatibility.as_ref() {
let version = nym_node.version();
if !version_checker::is_minor_version_compatible(version, semver_compat) {
continue;
}
}
// NOTE: if we determined our node IS an active gateway, it MUST be EITHER entry or exit
let role = if rewarded_set.is_exit(&nym_node.node_id) {
NodeRole::ExitGateway
} else {
NodeRole::EntryGateway
};
// honestly, not sure under what exact circumstances this value could be missing,
// but in that case just use 0 performance
let annotation = annotations
.get(&nym_node.node_id)
.copied()
.unwrap_or_default();
active_skimmed_gateways
.push(nym_node.to_skimmed_node(role, annotation.last_24h_performance));
}
// min of all caches
let refreshed_at = [
rewarded_set.timestamp(),
annotations.timestamp(),
describe_cache.timestamp(),
annotated_legacy_gateways.timestamp(),
]
.into_iter()
.min()
.unwrap()
.into();
Ok(Json(CachedNodesResponse {
refreshed_at,
nodes: active_skimmed_gateways,
}))
}
#[openapi(tag = "Unstable Nym Nodes")]
#[get("/gateways/semi-skimmed?<semver_compatibility>")]
pub async fn gateways_expanded(
cache: &State<NodeStatusCache>,
semver_compatibility: Option<String>,
) -> Result<Json<CachedNodesResponse<SemiSkimmedNode>>, RocketErrorResponse> {
let _ = cache;
let _ = semver_compatibility;
Err(RocketErrorResponse::new(
"unimplemented",
Status::NotImplemented,
))
}
#[openapi(tag = "Unstable Nym Nodes")]
#[get("/gateways/full-fat?<semver_compatibility>")]
pub async fn gateways_detailed(
cache: &State<NodeStatusCache>,
semver_compatibility: Option<String>,
) -> Result<Json<CachedNodesResponse<FullFatNode>>, RocketErrorResponse> {
let _ = cache;
let _ = semver_compatibility;
Err(RocketErrorResponse::new(
"unimplemented",
Status::NotImplemented,
))
}
#[openapi(tag = "Unstable Nym Nodes")]
#[get("/mixnodes/skimmed?<semver_compatibility>")]
pub async fn mixnodes_basic(
status_cache: &State<NodeStatusCache>,
contract_cache: &State<NymContractCache>,
describe_cache: &State<SharedCache<DescribedNodes>>,
semver_compatibility: Option<String>,
) -> Result<Json<CachedNodesResponse<SkimmedNode>>, RocketErrorResponse> {
// 1. get the rewarded set
let rewarded_set = contract_cache
.rewarded_set()
.await
.ok_or_else(RocketErrorResponse::internal_server_error)?;
// determine which mixnodes are active, i.e. which mixnodes the clients should be using for routing the traffic
let active_mixnodes = rewarded_set
.active_mixnodes()
.into_iter()
.collect::<HashSet<_>>();
// 2. grab all annotations so that we could attach scores to the [nym] nodes
let annotations = status_cache
.node_annotations()
.await
.ok_or_else(RocketErrorResponse::internal_server_error)?;
// 3. grab all legacy mixnodes
// due to legacy endpoints we already have fully annotated data on them
let annotated_legacy_mixnodes = status_cache
.annotated_legacy_mixnodes()
.await
.ok_or_else(RocketErrorResponse::internal_server_error)?;
// 4. grab all relevant described nym-nodes
let describe_cache = describe_cache.get().await?;
let mixing_nym_nodes = describe_cache.mixing_nym_nodes();
// TODO: in the future, only use the self-described cache and simply reject mixnodes that did not expose it
// 5. only return nodes that are present in the active set
let mut active_skimmed_mixnodes = Vec::new();
for (mix_id, legacy) in annotated_legacy_mixnodes.iter() {
if !active_mixnodes.contains(mix_id) {
continue;
}
if let Some(semver_compat) = semver_compatibility.as_ref() {
let version = legacy.version();
if !version_checker::is_minor_version_compatible(version, semver_compat) {
continue;
}
}
// if we have self-described info, prefer it over contract data
if let Some(described) = describe_cache.get_node(mix_id) {
active_skimmed_mixnodes.push(described.to_skimmed_node(
NodeRole::Mixnode {
layer: legacy.mixnode_details.bond_information.layer.into(),
},
legacy.node_performance.last_24h,
))
} else {
active_skimmed_mixnodes.push(legacy.into());
}
}
for nym_node in mixing_nym_nodes {
// if this node is not an active mixnode, ignore it
if !active_mixnodes.contains(&nym_node.node_id) {
continue;
}
// if we have wrong version, ignore
if let Some(semver_compat) = semver_compatibility.as_ref() {
let version = nym_node.version();
if !version_checker::is_minor_version_compatible(version, semver_compat) {
continue;
}
}
// SAFETY: if we determined our node IS active, it MUST have a layer
// no other thread could have updated the rewarded set as we're still holding the [read] lock on the data
#[allow(clippy::unwrap_used)]
let layer = rewarded_set.try_get_mix_layer(&nym_node.node_id).unwrap();
// honestly, not sure under what exact circumstances this value could be missing,
// but in that case just use 0 performance
let annotation = annotations
.get(&nym_node.node_id)
.copied()
.unwrap_or_default();
active_skimmed_mixnodes.push(
nym_node.to_skimmed_node(NodeRole::Mixnode { layer }, annotation.last_24h_performance),
);
}
// min of all caches
let refreshed_at = [
rewarded_set.timestamp(),
annotations.timestamp(),
describe_cache.timestamp(),
annotated_legacy_mixnodes.timestamp(),
]
.into_iter()
.min()
.unwrap()
.into();
Ok(Json(CachedNodesResponse {
refreshed_at,
nodes: active_skimmed_mixnodes,
}))
}
#[openapi(tag = "Unstable Nym Nodes")]
#[get("/mixnodes/semi-skimmed?<semver_compatibility>")]
pub async fn mixnodes_expanded(
cache: &State<NodeStatusCache>,
semver_compatibility: Option<String>,
) -> Result<Json<CachedNodesResponse<SemiSkimmedNode>>, RocketErrorResponse> {
let _ = cache;
let _ = semver_compatibility;
Err(RocketErrorResponse::new(
"unimplemented",
Status::NotImplemented,
))
}
#[openapi(tag = "Unstable Nym Nodes")]
#[get("/mixnodes/full-fat?<semver_compatibility>")]
pub async fn mixnodes_detailed(
cache: &State<NodeStatusCache>,
semver_compatibility: Option<String>,
) -> Result<Json<CachedNodesResponse<FullFatNode>>, RocketErrorResponse> {
let _ = cache;
let _ = semver_compatibility;
Err(RocketErrorResponse::new(
"unimplemented",
Status::NotImplemented,
))
}
-10
View File
@@ -8,7 +8,6 @@ use nym_bin_common::build_information::BinaryBuildInformation;
use tokio::time::Instant;
pub(crate) mod handlers;
// pub(crate) mod routes;
pub(crate) struct ApiStatusState {
startup_time: Instant,
@@ -40,12 +39,3 @@ impl ApiStatusState {
self.signer_information = Some(signer_information)
}
}
// pub(crate) fn api_status_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
// openapi_get_routes_spec![
// settings:
// routes::health,
// routes::build_information,
// routes::signer_information
// ]
// }
-52
View File
@@ -1,52 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_status_api::models::RocketErrorResponse;
use crate::status::ApiStatusState;
use nym_api_requests::models::{ApiHealthResponse, SignerInformationResponse};
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use nym_compact_ecash::Base58;
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
use rocket_okapi::openapi;
#[openapi(tag = "Api Status")]
#[get("/health")]
pub(crate) async fn health(state: &State<ApiStatusState>) -> Json<ApiHealthResponse> {
let uptime = state.startup_time.elapsed();
let health = ApiHealthResponse::new_healthy(uptime);
Json(health)
}
#[openapi(tag = "Api Status")]
#[get("/build-information")]
pub(crate) async fn build_information(
state: &State<ApiStatusState>,
) -> Json<BinaryBuildInformationOwned> {
Json(state.build_information.to_owned())
}
#[openapi(tag = "Api Status")]
#[get("/signer-information")]
pub(crate) async fn signer_information(
state: &State<ApiStatusState>,
) -> Result<Json<SignerInformationResponse>, RocketErrorResponse> {
let signer_state = state.signer_information.as_ref().ok_or_else(|| {
RocketErrorResponse::new(
"this api does not expose zk-nym signing functionalities",
Status::InternalServerError,
)
})?;
Ok(Json(SignerInformationResponse {
cosmos_address: signer_state.cosmos_address.clone(),
identity: signer_state.identity.clone(),
announce_address: signer_state.announce_address.clone(),
verification_key: signer_state
.ecash_keypair
.verification_key()
.await
.map(|maybe_vk| maybe_vk.to_bs58()),
}))
}