diff --git a/validator-api/src/cache/mod.rs b/validator-api/src/cache/mod.rs index e35a84c41b..b13dd53ae2 100644 --- a/validator-api/src/cache/mod.rs +++ b/validator-api/src/cache/mod.rs @@ -4,7 +4,9 @@ use crate::nymd_client::Client; use anyhow::Result; use config::defaults::VALIDATOR_API_VERSION; -use mixnet_contract::{ContractStateParams, GatewayBond, MixNodeBond, RewardingIntervalResponse}; +use mixnet_contract::{ + ContractStateParams, GatewayBond, IdentityKey, MixNodeBond, RewardingIntervalResponse, +}; use rand::prelude::SliceRandom; use rand_chacha::rand_core::SeedableRng; use rand_chacha::ChaCha20Rng; @@ -20,6 +22,15 @@ use validator_client::nymd::CosmWasmClient; pub(crate) mod routes; +#[derive(Clone, Copy, Debug, Serialize)] +#[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 +} + pub struct ValidatorCacheRefresher { nymd_client: Client, cache: ValidatorCache, @@ -157,6 +168,7 @@ impl ValidatorCache { routes::get_gateways, routes::get_active_mixnodes, routes::get_rewarded_mixnodes, + routes::get_mixnode_status, ], ) }) @@ -305,6 +317,47 @@ impl ValidatorCache { } } + pub async fn mixnode_status(&self, identity: IdentityKey) -> MixnodeStatus { + // it might not be the most optimal to possibly iterate the entire vector to find (or not) + // the relevant value. However, the vectors are relatively small (< 10_000 elements) and + // the implementation for active/rewarded sets might change soon so there's no point in premature optimisation + // with HashSets + let rewarded_mixnodes = &self.inner.rewarded_mixnodes.read().await.value; + let active_set_size = self + .inner + .current_mixnode_active_set_size + .load(Ordering::SeqCst) as usize; + + // see if node is in the top active_set_size of rewarded nodes, i.e. it's active + if rewarded_mixnodes + .iter() + .take(active_set_size) + .any(|mix| mix.mix_node.identity_key == identity) + { + MixnodeStatus::Active + // see if it's in the bottom part of the rewarded set, i.e. it's in standby + } else if rewarded_mixnodes + .iter() + .skip(active_set_size) + .any(|mix| mix.mix_node.identity_key == identity) + { + MixnodeStatus::Standby + // if it's not in the rewarded set see if its bonded at all + } else if self + .inner + .mixnodes + .read() + .await + .value + .iter() + .any(|mix| mix.mix_node.identity_key == identity) + { + MixnodeStatus::Inactive + } else { + MixnodeStatus::NotFound + } + } + pub fn initialised(&self) -> bool { self.inner.initialised.load(Ordering::Relaxed) } diff --git a/validator-api/src/cache/routes.rs b/validator-api/src/cache/routes.rs index cf59be469e..41ebc3c2d9 100644 --- a/validator-api/src/cache/routes.rs +++ b/validator-api/src/cache/routes.rs @@ -1,10 +1,11 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::cache::ValidatorCache; +use crate::cache::{MixnodeStatus, ValidatorCache}; use mixnet_contract::{GatewayBond, MixNodeBond}; use rocket::serde::json::Json; use rocket::State; +use serde::Serialize; #[get("/mixnodes")] pub(crate) async fn get_mixnodes(cache: &State) -> Json> { @@ -25,3 +26,18 @@ pub(crate) async fn get_rewarded_mixnodes(cache: &State) -> Json pub(crate) async fn get_active_mixnodes(cache: &State) -> Json> { Json(cache.active_mixnodes().await.value) } + +#[derive(Serialize)] +pub(crate) struct MixnodeStatusResponse { + status: MixnodeStatus, +} + +#[get("/mixnode//status")] +pub(crate) async fn get_mixnode_status( + cache: &State, + identity: String, +) -> Json { + Json(MixnodeStatusResponse { + status: cache.mixnode_status(identity).await, + }) +} diff --git a/validator-api/src/config/mod.rs b/validator-api/src/config/mod.rs index c5513710ee..094a0f820e 100644 --- a/validator-api/src/config/mod.rs +++ b/validator-api/src/config/mod.rs @@ -36,7 +36,7 @@ const DEFAULT_MINIMUM_TEST_ROUTES: usize = 1; const DEFAULT_ROUTE_TEST_PACKETS: usize = 1000; const DEFAULT_PER_NODE_TEST_PACKETS: usize = 3; -const DEFAULT_CACHE_INTERVAL: Duration = Duration::from_secs(5); +const DEFAULT_CACHE_INTERVAL: Duration = Duration::from_secs(30); const DEFAULT_MONITOR_THRESHOLD: u8 = 60; #[derive(Debug, Default, Deserialize, PartialEq, Serialize)]