Feature/validator api client endpoints (#1024)

* Moved mixnode status route to node status api module

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

* Stake saturation endpoint

* kebab-cased coconut routes

* Created separate crate for validator API models

* Additional routes in validator API client

* Introduced support for new queries in the wallet

* Typescript type derivation

* Fixed up date in license notice
This commit is contained in:
Jędrzej Stuczyński
2022-01-11 16:37:07 +00:00
committed by GitHub
parent 835d4f46f6
commit e2e06df4e6
27 changed files with 499 additions and 106 deletions
Generated
+10
View File
@@ -3884,6 +3884,7 @@ dependencies = [
"tokio",
"topology",
"url",
"validator-api-requests",
"validator-client",
"vergen",
"version-checker",
@@ -7318,6 +7319,14 @@ dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "validator-api-requests"
version = "0.1.0"
dependencies = [
"serde",
"ts-rs",
]
[[package]]
name = "validator-client"
version = "0.1.0"
@@ -7342,6 +7351,7 @@ dependencies = [
"thiserror",
"ts-rs",
"url",
"validator-api-requests",
"vesting-contract",
]
+1
View File
@@ -52,6 +52,7 @@ members = [
"mixnode",
"service-providers/network-requester",
"validator-api",
"validator-api/validator-api-requests",
]
default-members = [
@@ -20,6 +20,7 @@ url = { version = "2.2", features = ["serde"] }
coconut-interface = { path = "../../coconut-interface" }
network-defaults = { path = "../../network-defaults" }
validator-api-requests = { path = "../../../validator-api/validator-api-requests" }
# required for nymd-client
# at some point it might be possible to make it wasm-compatible
@@ -37,3 +38,4 @@ ts-rs = {version = "5.1", optional = true}
[features]
nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
typescript-types = ["ts-rs", "validator-api-requests/ts-rs"]
@@ -14,11 +14,15 @@ use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, Verifica
use mixnet_contract::{
Delegation, MixnetContractVersion, MixnodeRewardingStatusResponse, RewardingIntervalResponse,
};
use mixnet_contract::{GatewayBond, MixNodeBond};
use mixnet_contract::{GatewayBond, IdentityKeyRef, MixNodeBond};
#[cfg(feature = "nymd-client")]
use std::str::FromStr;
use url::Url;
use validator_api_requests::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
};
#[cfg(feature = "nymd-client")]
pub struct Config {
@@ -434,6 +438,55 @@ impl ApiClient {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_gateway_core_status_count(identity, since)
.await?)
}
pub async fn get_mixnode_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_core_status_count(identity, since)
.await?)
}
pub async fn get_mixnode_status(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<MixnodeStatusResponse, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_status(identity).await?)
}
pub async fn get_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<RewardEstimationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_reward_estimation(identity)
.await?)
}
pub async fn get_mixnode_stake_saturation(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<StakeSaturationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_stake_saturation(identity)
.await?)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -9,6 +9,7 @@ pub mod validator_api;
pub use crate::client::ApiClient;
pub use crate::error::ValidatorClientError;
pub use validator_api_requests::*;
#[cfg(feature = "nymd-client")]
pub use client::{Client, Config};
@@ -1,16 +1,24 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::validator_api::error::ValidatorAPIError;
use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract::{GatewayBond, MixNodeBond};
use mixnet_contract::{GatewayBond, IdentityKeyRef, MixNodeBond};
use serde::{Deserialize, Serialize};
use url::Url;
use validator_api_requests::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
};
pub mod error;
pub(crate) mod routes;
type PathSegments<'a> = &'a [&'a str];
type Params<'a, K, V> = &'a [(K, V)];
const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
pub struct Client {
url: Url,
@@ -30,24 +38,33 @@ impl Client {
self.url = new_url
}
async fn query_validator_api<T>(&self, path: PathSegments<'_>) -> Result<T, ValidatorAPIError>
async fn query_validator_api<T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, ValidatorAPIError>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let url = create_api_url(&self.url, path);
let url = create_api_url(&self.url, path, params);
Ok(self.reqwest_client.get(url).send().await?.json().await?)
}
async fn post_validator_api<B, T>(
async fn post_validator_api<B, T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, ValidatorAPIError>
where
B: Serialize + ?Sized,
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let url = create_api_url(&self.url, path);
let url = create_api_url(&self.url, path, params);
Ok(self
.reqwest_client
.post(url)
@@ -59,23 +76,142 @@ impl Client {
}
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES])
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
.await
}
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS])
self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE])
.await
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
NO_PARAMS,
)
.await
}
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED])
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
NO_PARAMS,
)
.await
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::GATEWAY,
identity,
CORE_STATUS_COUNT,
],
&[(SINCE_ARG, since.to_string())],
)
.await
} else {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::GATEWAY,
identity,
],
NO_PARAMS,
)
.await
}
}
pub async fn get_mixnode_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
CORE_STATUS_COUNT,
],
&[(SINCE_ARG, since.to_string())],
)
.await
} else {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
],
NO_PARAMS,
)
.await
}
}
pub async fn get_mixnode_status(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<MixnodeStatusResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::STATUS,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::REWARD_ESTIMATION,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_stake_saturation(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<StakeSaturationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::STAKE_SATURATION,
],
NO_PARAMS,
)
.await
}
pub async fn blind_sign(
@@ -84,6 +220,7 @@ impl Client {
) -> Result<BlindedSignatureResponse, ValidatorAPIError> {
self.post_validator_api(
&[routes::API_VERSION, routes::COCONUT_BLIND_SIGN],
NO_PARAMS,
request_body,
)
.await
@@ -92,13 +229,20 @@ impl Client {
pub async fn get_coconut_verification_key(
&self,
) -> Result<VerificationKeyResponse, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::COCONUT_VERIFICATION_KEY])
.await
self.query_validator_api(
&[routes::API_VERSION, routes::COCONUT_VERIFICATION_KEY],
NO_PARAMS,
)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
fn create_api_url(base: &Url, segments: PathSegments<'_>) -> Url {
fn create_api_url<K: AsRef<str>, V: AsRef<str>>(
base: &Url,
segments: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Url {
let mut url = base.clone();
let mut path_segments = url
.path_segments_mut()
@@ -113,6 +257,10 @@ fn create_api_url(base: &Url, segments: PathSegments<'_>) -> Url {
// and can be dropped
drop(path_segments);
if !params.is_empty() {
url.query_pairs_mut().extend_pairs(params);
}
url
}
@@ -127,51 +275,66 @@ mod tests {
// works with 1 segment
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["foo"]).as_str()
create_api_url(&base_url, &["foo"], NO_PARAMS).as_str()
);
// works with 2 segments
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "bar"]).as_str()
create_api_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
);
// works with leading slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["/foo"]).as_str()
create_api_url(&base_url, &["/foo"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["/foo", "bar"]).as_str()
create_api_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "/bar"]).as_str()
create_api_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
);
// works with trailing slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["foo/"]).as_str()
create_api_url(&base_url, &["foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo/", "bar"]).as_str()
create_api_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "bar/"]).as_str()
create_api_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
);
// works with both leading and trailing slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["/foo/"]).as_str()
create_api_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["/foo/", "/bar/"]).as_str()
create_api_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
);
// adds params
assert_eq!(
"http://foomp.com/foo/bar?foomp=baz",
create_api_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
create_api_url(
&base_url,
&["/foo/", "/bar/"],
&[("arg1", "val1"), ("arg2", "val2")]
)
.as_str()
);
}
}
@@ -10,5 +10,16 @@ pub const GATEWAYS: &str = "gateways";
pub const ACTIVE: &str = "active";
pub const REWARDED: &str = "rewarded";
pub const COCONUT_BLIND_SIGN: &str = "blind_sign";
pub const COCONUT_VERIFICATION_KEY: &str = "verification_key";
pub const COCONUT_BLIND_SIGN: &str = "blind-sign";
pub const COCONUT_VERIFICATION_KEY: &str = "verification-key";
pub const STATUS_ROUTES: &str = "status";
pub const MIXNODE: &str = "mixnode";
pub const GATEWAY: &str = "gateway";
pub const CORE_STATUS_COUNT: &str = "core-status-count";
pub const SINCE_ARG: &str = "since";
pub const STATUS: &str = "status";
pub const REWARD_ESTIMATION: &str = "reward-estimation";
pub const STAKE_SATURATION: &str = "stake-saturation";
+9
View File
@@ -5261,6 +5261,14 @@ dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "validator-api-requests"
version = "0.1.0"
dependencies = [
"serde",
"ts-rs",
]
[[package]]
name = "validator-client"
version = "0.1.0"
@@ -5285,6 +5293,7 @@ dependencies = [
"thiserror",
"ts-rs",
"url",
"validator-api-requests",
"vesting-contract",
]
+1 -1
View File
@@ -49,7 +49,7 @@ features = ["ts-rs"]
[dev-dependencies.validator-client]
path = "../../common/client-libs/validator-client"
features = ["ts-rs"]
features = ["typescript-types"]
[features]
default = ["custom-protocol"]
+6
View File
@@ -1,6 +1,7 @@
use serde::{Serialize, Serializer};
use thiserror::Error;
use validator_client::nymd::error::NymdError;
use validator_client::validator_api::error::ValidatorAPIError;
#[derive(Error, Debug)]
pub enum BackendError {
@@ -29,6 +30,11 @@ pub enum BackendError {
#[from]
source: eyre::Report,
},
#[error("{source}")]
ValidatorApiError {
#[from]
source: ValidatorAPIError,
},
#[error("Client has not been initialized yet, connect with mnemonic to initialize")]
ClientNotInitialized,
#[error("No balance available for address {0}")]
+12 -1
View File
@@ -19,6 +19,7 @@ mod utils;
use crate::menu::AddDefaultSubmenus;
use crate::operations::mixnet;
use crate::operations::validator_api;
use crate::operations::vesting;
use crate::state::State;
@@ -63,6 +64,11 @@ fn main() {
vesting::queries::original_vesting,
vesting::queries::delegated_free,
vesting::queries::delegated_vesting,
validator_api::status::mixnode_core_node_status,
validator_api::status::gateway_core_node_status,
validator_api::status::mixnode_status,
validator_api::status::mixnode_reward_estimation,
validator_api::status::mixnode_stake_saturation,
])
.menu(Menu::new().add_default_app_submenu_if_macos())
.run(tauri::generate_context!())
@@ -82,6 +88,11 @@ mod test {
crate::coin::Denom => "../src/types/rust/denom.ts",
crate::utils::DelegationResult => "../src/types/rust/delegationresult.ts",
crate::mixnet::account::Account => "../src/types/rust/account.ts",
crate::mixnet::admin::TauriContractStateParams => "../src/types/rust/stateparams.ts"
crate::mixnet::admin::TauriContractStateParams => "../src/types/rust/stateparams.ts",
validator_client::models::CoreNodeStatusResponse => "../src/types/corenodestatusresponse.ts",
validator_client::models::MixnodeStatus => "../src/types/rust/mixnodestatus.ts",
validator_client::models::MixnodeStatusResponse => "../src/types/rust/mixnodestatusresponse.ts",
validator_client::models::RewardEstimationResponse => "../src/types/rust/rewardestimationresponse.ts",
validator_client::models::StakeSaturationResponse => "../src/types/rust/stakesaturaionresponse.ts",
}
}
@@ -1,2 +1,3 @@
pub mod mixnet;
pub mod validator_api;
pub mod vesting;
@@ -0,0 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod status;
@@ -0,0 +1,69 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::api_client;
use crate::error::BackendError;
use crate::state::State;
use std::sync::Arc;
use tokio::sync::RwLock;
use validator_client::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse,
};
#[tauri::command]
pub async fn mixnode_core_node_status(
identity: &str,
since: Option<i64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<CoreNodeStatusResponse, BackendError> {
Ok(
api_client!(state)
.get_mixnode_core_status_count(identity, since)
.await?,
)
}
#[tauri::command]
pub async fn gateway_core_node_status(
identity: &str,
since: Option<i64>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<CoreNodeStatusResponse, BackendError> {
Ok(
api_client!(state)
.get_gateway_core_status_count(identity, since)
.await?,
)
}
#[tauri::command]
pub async fn mixnode_status(
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<MixnodeStatusResponse, BackendError> {
Ok(api_client!(state).get_mixnode_status(identity).await?)
}
#[tauri::command]
pub async fn mixnode_reward_estimation(
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<RewardEstimationResponse, BackendError> {
Ok(
api_client!(state)
.get_mixnode_reward_estimation(identity)
.await?,
)
}
#[tauri::command]
pub async fn mixnode_stake_saturation(
identity: &str,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<StakeSaturationResponse, BackendError> {
Ok(
api_client!(state)
.get_mixnode_stake_saturation(identity)
.await?,
)
}
@@ -0,0 +1,4 @@
export interface CoreNodeStatusResponse {
identity: string;
count: number;
}
@@ -0,0 +1 @@
export type MixnodeStatus = "Active" | "Standby" | "Inactive" | "NotFound";
@@ -0,0 +1,5 @@
import { MixnodeStatus } from "./mixnodestatus";
export interface MixnodeStatusResponse {
status: MixnodeStatus;
}
@@ -0,0 +1,9 @@
export interface RewardEstimationResponse {
estimated_total_node_reward: bigint;
estimated_operator_reward: bigint;
estimated_delegators_reward: bigint;
current_epoch_start: bigint;
current_epoch_end: bigint;
current_epoch_uptime: number;
as_at: bigint;
}
@@ -0,0 +1,4 @@
export interface StakeSaturationResponse {
saturation: number;
as_at: bigint;
}
+2 -1
View File
@@ -39,7 +39,7 @@ time = { version = "0.3", features = ["serde-human-readable", "parsing"]}
anyhow = "1"
getset = "0.1.1"
rocket_sync_db_pools = {version = "0.1.0-rc.1", default-features = false}
rocket_sync_db_pools = { version = "0.1.0-rc.1", default-features = false }
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]}
@@ -50,6 +50,7 @@ gateway-client = { path="../common/client-libs/gateway-client" }
mixnet-contract = { path="../common/mixnet-contract" }
nymsphinx = { path="../common/nymsphinx" }
topology = { path="../common/topology" }
validator-api-requests = { path = "validator-api-requests" }
validator-client = { path="../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path="../common/version-checker" }
coconut-interface = { path = "../common/coconut-interface", optional = true }
+1 -15
View File
@@ -20,26 +20,12 @@ use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tokio::time;
use validator_api_requests::models::MixnodeStatus;
use validator_client::nymd::hash::SHA256_HASH_SIZE;
use validator_client::nymd::CosmWasmClient;
pub(crate) mod routes;
#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum MixnodeStatus {
Active, // in both the active set and the rewarded set
Standby, // only in the rewarded set
Inactive, // in neither the rewarded set nor the active set, but is bonded
NotFound, // doesn't even exist in the bonded set
}
impl MixnodeStatus {
pub fn is_active(&self) -> bool {
*self == MixnodeStatus::Active
}
}
pub struct ValidatorCacheRefresher<C> {
nymd_client: Client<C>,
cache: ValidatorCache,
+2 -2
View File
@@ -62,7 +62,7 @@ fn blind_sign(request: InternalSignRequest, key_pair: &KeyPair) -> BlindedSignat
.unwrap()
}
#[post("/blind_sign", data = "<blind_sign_request_body>")]
#[post("/blind-sign", data = "<blind_sign_request_body>")]
// Until we have serialization and deserialization traits we'll be using a crutch
pub async fn post_blind_sign(
blind_sign_request_body: Json<BlindSignRequestBody>,
@@ -79,7 +79,7 @@ pub async fn post_blind_sign(
Json(BlindedSignatureResponse::new(blinded_signature))
}
#[get("/verification_key")]
#[get("/verification-key")]
pub async fn get_verification_key(key_pair: &State<KeyPair>) -> Json<VerificationKeyResponse> {
Json(VerificationKeyResponse::new(key_pair.verification_key()))
}
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::cache::MixnodeStatus;
use crate::node_status_api::utils::NodeUptimes;
use crate::storage::models::NodeStatus;
use rocket::http::{ContentType, Status};
@@ -272,32 +271,3 @@ impl Display for ValidatorApiStorageError {
}
}
}
#[derive(Serialize)]
pub struct CoreNodeStatus {
pub(crate) identity: String,
pub(crate) count: i32,
}
#[derive(Serialize)]
pub(crate) struct MixnodeStatusResponse {
pub(crate) status: MixnodeStatus,
}
#[derive(Serialize)]
pub(crate) struct RewardEstimationResponse {
pub(crate) estimated_total_node_reward: u128,
pub(crate) estimated_operator_reward: u128,
pub(crate) estimated_delegators_reward: u128,
pub(crate) current_epoch_start: i64,
pub(crate) current_epoch_end: i64,
pub(crate) current_epoch_uptime: Uptime,
pub(crate) as_at: i64,
}
#[derive(Serialize)]
pub(crate) struct StakeSaturationResponse {
pub(crate) saturation: f32,
pub(crate) as_at: i64,
}
+33 -29
View File
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::node_status_api::models::{
CoreNodeStatus, ErrorResponse, GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport,
MixnodeStatusResponse, MixnodeUptimeHistory, RewardEstimationResponse, StakeSaturationResponse,
ErrorResponse, GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport,
MixnodeUptimeHistory,
};
use crate::storage::ValidatorApiStorage;
use crate::{Epoch, ValidatorCache};
@@ -11,85 +11,89 @@ use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
use time::OffsetDateTime;
use validator_api_requests::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
};
#[get("/mixnode/<pubkey>/report")]
#[get("/mixnode/<identity>/report")]
pub(crate) async fn mixnode_report(
storage: &State<ValidatorApiStorage>,
pubkey: &str,
identity: &str,
) -> Result<Json<MixnodeStatusReport>, ErrorResponse> {
storage
.construct_mixnode_report(pubkey)
.construct_mixnode_report(identity)
.await
.map(Json)
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
}
#[get("/gateway/<pubkey>/report")]
#[get("/gateway/<identity>/report")]
pub(crate) async fn gateway_report(
storage: &State<ValidatorApiStorage>,
pubkey: &str,
identity: &str,
) -> Result<Json<GatewayStatusReport>, ErrorResponse> {
storage
.construct_gateway_report(pubkey)
.construct_gateway_report(identity)
.await
.map(Json)
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
}
#[get("/mixnode/<pubkey>/history")]
#[get("/mixnode/<identity>/history")]
pub(crate) async fn mixnode_uptime_history(
storage: &State<ValidatorApiStorage>,
pubkey: &str,
identity: &str,
) -> Result<Json<MixnodeUptimeHistory>, ErrorResponse> {
storage
.get_mixnode_uptime_history(pubkey)
.get_mixnode_uptime_history(identity)
.await
.map(Json)
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
}
#[get("/gateway/<pubkey>/history")]
#[get("/gateway/<identity>/history")]
pub(crate) async fn gateway_uptime_history(
storage: &State<ValidatorApiStorage>,
pubkey: &str,
identity: &str,
) -> Result<Json<GatewayUptimeHistory>, ErrorResponse> {
storage
.get_gateway_uptime_history(pubkey)
.get_gateway_uptime_history(identity)
.await
.map(Json)
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
}
#[get("/mixnode/<pubkey>/core-status-count?<since>")]
#[get("/mixnode/<identity>/core-status-count?<since>")]
pub(crate) async fn mixnode_core_status_count(
storage: &State<ValidatorApiStorage>,
pubkey: &str,
identity: &str,
since: Option<i64>,
) -> Json<CoreNodeStatus> {
) -> Json<CoreNodeStatusResponse> {
let count = storage
.get_core_mixnode_status_count(pubkey, since)
.get_core_mixnode_status_count(identity, since)
.await
.unwrap_or_default();
Json(CoreNodeStatus {
identity: pubkey.to_string(),
Json(CoreNodeStatusResponse {
identity: identity.to_string(),
count,
})
}
#[get("/gateway/<pubkey>/core-status-count?<since>")]
#[get("/gateway/<identity>/core-status-count?<since>")]
pub(crate) async fn gateway_core_status_count(
storage: &State<ValidatorApiStorage>,
pubkey: &str,
identity: &str,
since: Option<i64>,
) -> Json<CoreNodeStatus> {
) -> Json<CoreNodeStatusResponse> {
let count = storage
.get_core_gateway_status_count(pubkey, since)
.get_core_gateway_status_count(identity, since)
.await
.unwrap_or_default();
Json(CoreNodeStatus {
identity: pubkey.to_string(),
Json(CoreNodeStatusResponse {
identity: identity.to_string(),
count,
})
}
@@ -104,7 +108,7 @@ pub(crate) async fn get_mixnode_status(
})
}
#[get("/mixnode/<identity>/reward_estimation")]
#[get("/mixnode/<identity>/reward-estimation")]
pub(crate) async fn get_mixnode_reward_estimation(
cache: &State<ValidatorCache>,
storage: &State<ValidatorApiStorage>,
@@ -136,7 +140,7 @@ pub(crate) async fn get_mixnode_reward_estimation(
estimated_delegators_reward,
current_epoch_start: current_epoch.start_unix_timestamp(),
current_epoch_end: current_epoch.end_unix_timestamp(),
current_epoch_uptime: uptime,
current_epoch_uptime: uptime.u8(),
as_at,
}))
} else {
@@ -147,7 +151,7 @@ pub(crate) async fn get_mixnode_reward_estimation(
}
}
#[get("/mixnode/<identity>/stake_saturation")]
#[get("/mixnode/<identity>/stake-saturation")]
pub(crate) async fn get_mixnode_stake_saturation(
cache: &State<ValidatorCache>,
identity: String,
@@ -0,0 +1,11 @@
[package]
name = "validator-api-requests"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = "1.0"
ts-rs = { version = "5.1", optional = true }
@@ -0,0 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod models;
@@ -0,0 +1,53 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[serde(rename_all = "snake_case")]
pub enum MixnodeStatus {
Active, // in both the active set and the rewarded set
Standby, // only in the rewarded set
Inactive, // in neither the rewarded set nor the active set, but is bonded
NotFound, // doesn't even exist in the bonded set
}
impl MixnodeStatus {
pub fn is_active(&self) -> bool {
*self == MixnodeStatus::Active
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
pub struct CoreNodeStatusResponse {
pub identity: String,
pub count: i32,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
pub struct MixnodeStatusResponse {
pub status: MixnodeStatus,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
pub struct RewardEstimationResponse {
pub estimated_total_node_reward: u128,
pub estimated_operator_reward: u128,
pub estimated_delegators_reward: u128,
pub current_epoch_start: i64,
pub current_epoch_end: i64,
pub current_epoch_uptime: u8,
pub as_at: i64,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
pub struct StakeSaturationResponse {
pub saturation: f32,
pub as_at: i64,
}