diff --git a/Cargo.lock b/Cargo.lock index 84003aa52b..0f31900699 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 5537a3d5a5..0d2ed7a3c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ members = [ "mixnode", "service-providers/network-requester", "validator-api", + "validator-api/validator-api-requests", ] default-members = [ diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index e4862c387f..205f3f198a 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -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"] diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index afacd3260c..1cd25c71c1 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.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, + ) -> Result { + 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, + ) -> Result { + Ok(self + .validator_api + .get_mixnode_core_status_count(identity, since) + .await?) + } + + pub async fn get_mixnode_status( + &self, + identity: IdentityKeyRef<'_>, + ) -> Result { + Ok(self.validator_api.get_mixnode_status(identity).await?) + } + + pub async fn get_mixnode_reward_estimation( + &self, + identity: IdentityKeyRef<'_>, + ) -> Result { + Ok(self + .validator_api + .get_mixnode_reward_estimation(identity) + .await?) + } + + pub async fn get_mixnode_stake_saturation( + &self, + identity: IdentityKeyRef<'_>, + ) -> Result { + Ok(self + .validator_api + .get_mixnode_stake_saturation(identity) + .await?) + } + pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index 9ef577d97f..251501114d 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -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}; diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index 841e59b69e..0374a4885e 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -1,16 +1,24 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // 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(&self, path: PathSegments<'_>) -> Result + async fn query_validator_api( + &self, + path: PathSegments<'_>, + params: Params<'_, K, V>, + ) -> Result where for<'a> T: Deserialize<'a>, + K: AsRef, + V: AsRef, { - 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( + async fn post_validator_api( &self, path: PathSegments<'_>, + params: Params<'_, K, V>, json_body: &B, ) -> Result where B: Serialize + ?Sized, for<'a> T: Deserialize<'a>, + K: AsRef, + V: AsRef, { - 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, 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, 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, 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, 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, + ) -> Result { + 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, + ) -> Result { + 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 { + 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 { + 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 { + 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 { 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 { - 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, V: AsRef>( + 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() ); } } diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/validator_api/routes.rs index ac171b01e6..7259ed6610 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/validator_api/routes.rs @@ -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"; diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index e7a5bdf2ed..43eb2fbad4 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -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", ] diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 52c1469c9b..fff7095a05 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -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"] diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 605cc78047..a99090ba56 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -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}")] diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index c0c89b1a21..00d158b82c 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -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", } } diff --git a/nym-wallet/src-tauri/src/operations/mod.rs b/nym-wallet/src-tauri/src/operations/mod.rs index 9bfd654e73..c49e27d4d2 100644 --- a/nym-wallet/src-tauri/src/operations/mod.rs +++ b/nym-wallet/src-tauri/src/operations/mod.rs @@ -1,2 +1,3 @@ pub mod mixnet; +pub mod validator_api; pub mod vesting; diff --git a/nym-wallet/src-tauri/src/operations/validator_api/mod.rs b/nym-wallet/src-tauri/src/operations/validator_api/mod.rs new file mode 100644 index 0000000000..3529bb0b4a --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/validator_api/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod status; diff --git a/nym-wallet/src-tauri/src/operations/validator_api/status.rs b/nym-wallet/src-tauri/src/operations/validator_api/status.rs new file mode 100644 index 0000000000..cb2fd9d17a --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/validator_api/status.rs @@ -0,0 +1,69 @@ +// Copyright 2022 - Nym Technologies SA +// 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, + state: tauri::State<'_, Arc>>, +) -> Result { + 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, + state: tauri::State<'_, Arc>>, +) -> Result { + 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>>, +) -> Result { + Ok(api_client!(state).get_mixnode_status(identity).await?) +} + +#[tauri::command] +pub async fn mixnode_reward_estimation( + identity: &str, + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + api_client!(state) + .get_mixnode_reward_estimation(identity) + .await?, + ) +} + +#[tauri::command] +pub async fn mixnode_stake_saturation( + identity: &str, + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + api_client!(state) + .get_mixnode_stake_saturation(identity) + .await?, + ) +} diff --git a/nym-wallet/src/types/corenodestatusresponse.ts b/nym-wallet/src/types/corenodestatusresponse.ts new file mode 100644 index 0000000000..2a743b37a9 --- /dev/null +++ b/nym-wallet/src/types/corenodestatusresponse.ts @@ -0,0 +1,4 @@ +export interface CoreNodeStatusResponse { + identity: string; + count: number; +} \ No newline at end of file diff --git a/nym-wallet/src/types/rust/mixnodestatus.ts b/nym-wallet/src/types/rust/mixnodestatus.ts new file mode 100644 index 0000000000..75849a8ce9 --- /dev/null +++ b/nym-wallet/src/types/rust/mixnodestatus.ts @@ -0,0 +1 @@ +export type MixnodeStatus = "Active" | "Standby" | "Inactive" | "NotFound"; \ No newline at end of file diff --git a/nym-wallet/src/types/rust/mixnodestatusresponse.ts b/nym-wallet/src/types/rust/mixnodestatusresponse.ts new file mode 100644 index 0000000000..6015807b4f --- /dev/null +++ b/nym-wallet/src/types/rust/mixnodestatusresponse.ts @@ -0,0 +1,5 @@ +import { MixnodeStatus } from "./mixnodestatus"; + +export interface MixnodeStatusResponse { + status: MixnodeStatus; +} \ No newline at end of file diff --git a/nym-wallet/src/types/rust/rewardestimationresponse.ts b/nym-wallet/src/types/rust/rewardestimationresponse.ts new file mode 100644 index 0000000000..973641f97c --- /dev/null +++ b/nym-wallet/src/types/rust/rewardestimationresponse.ts @@ -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; +} \ No newline at end of file diff --git a/nym-wallet/src/types/rust/stakesaturaionresponse.ts b/nym-wallet/src/types/rust/stakesaturaionresponse.ts new file mode 100644 index 0000000000..78044fb241 --- /dev/null +++ b/nym-wallet/src/types/rust/stakesaturaionresponse.ts @@ -0,0 +1,4 @@ +export interface StakeSaturationResponse { + saturation: number; + as_at: bigint; +} \ No newline at end of file diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index 5f52d05c1d..a419e47fa1 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -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 } diff --git a/validator-api/src/cache/mod.rs b/validator-api/src/cache/mod.rs index 4f1063cc35..9596aef2b9 100644 --- a/validator-api/src/cache/mod.rs +++ b/validator-api/src/cache/mod.rs @@ -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 { nymd_client: Client, cache: ValidatorCache, diff --git a/validator-api/src/coconut/mod.rs b/validator-api/src/coconut/mod.rs index ace0c62851..cd50c7cdf8 100644 --- a/validator-api/src/coconut/mod.rs +++ b/validator-api/src/coconut/mod.rs @@ -62,7 +62,7 @@ fn blind_sign(request: InternalSignRequest, key_pair: &KeyPair) -> BlindedSignat .unwrap() } -#[post("/blind_sign", data = "")] +#[post("/blind-sign", data = "")] // Until we have serialization and deserialization traits we'll be using a crutch pub async fn post_blind_sign( blind_sign_request_body: Json, @@ -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) -> Json { Json(VerificationKeyResponse::new(key_pair.verification_key())) } diff --git a/validator-api/src/node_status_api/models.rs b/validator-api/src/node_status_api/models.rs index 605e182812..4ad57950dd 100644 --- a/validator-api/src/node_status_api/models.rs +++ b/validator-api/src/node_status_api/models.rs @@ -1,7 +1,6 @@ // Copyright 2021 - Nym Technologies SA // 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, -} diff --git a/validator-api/src/node_status_api/routes.rs b/validator-api/src/node_status_api/routes.rs index 2a0d2a6039..b4a9530566 100644 --- a/validator-api/src/node_status_api/routes.rs +++ b/validator-api/src/node_status_api/routes.rs @@ -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//report")] +#[get("/mixnode//report")] pub(crate) async fn mixnode_report( storage: &State, - pubkey: &str, + identity: &str, ) -> Result, 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//report")] +#[get("/gateway//report")] pub(crate) async fn gateway_report( storage: &State, - pubkey: &str, + identity: &str, ) -> Result, 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//history")] +#[get("/mixnode//history")] pub(crate) async fn mixnode_uptime_history( storage: &State, - pubkey: &str, + identity: &str, ) -> Result, 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//history")] +#[get("/gateway//history")] pub(crate) async fn gateway_uptime_history( storage: &State, - pubkey: &str, + identity: &str, ) -> Result, 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//core-status-count?")] +#[get("/mixnode//core-status-count?")] pub(crate) async fn mixnode_core_status_count( storage: &State, - pubkey: &str, + identity: &str, since: Option, -) -> Json { +) -> Json { 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//core-status-count?")] +#[get("/gateway//core-status-count?")] pub(crate) async fn gateway_core_status_count( storage: &State, - pubkey: &str, + identity: &str, since: Option, -) -> Json { +) -> Json { 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//reward_estimation")] +#[get("/mixnode//reward-estimation")] pub(crate) async fn get_mixnode_reward_estimation( cache: &State, storage: &State, @@ -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//stake_saturation")] +#[get("/mixnode//stake-saturation")] pub(crate) async fn get_mixnode_stake_saturation( cache: &State, identity: String, diff --git a/validator-api/validator-api-requests/Cargo.toml b/validator-api/validator-api-requests/Cargo.toml new file mode 100644 index 0000000000..946dd5703d --- /dev/null +++ b/validator-api/validator-api-requests/Cargo.toml @@ -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 } + diff --git a/validator-api/validator-api-requests/src/lib.rs b/validator-api/validator-api-requests/src/lib.rs new file mode 100644 index 0000000000..9beea87f0d --- /dev/null +++ b/validator-api/validator-api-requests/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod models; diff --git a/validator-api/validator-api-requests/src/models.rs b/validator-api/validator-api-requests/src/models.rs new file mode 100644 index 0000000000..fddd64d788 --- /dev/null +++ b/validator-api/validator-api-requests/src/models.rs @@ -0,0 +1,53 @@ +// Copyright 2022 - Nym Technologies SA +// 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, +}