diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index ee6dd47be9..1aaa514b87 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -749,6 +749,12 @@ where Ok(self.nym_api.get_mixnodes_detailed().await?) } + pub async fn get_cached_mixnodes_detailed_unfiltered( + &self, + ) -> Result, ValidatorClientError> { + Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?) + } + pub async fn get_cached_rewarded_mixnodes( &self, ) -> Result, ValidatorClientError> { diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index fa453d3c83..10bea35417 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -144,6 +144,21 @@ impl Client { .await } + pub async fn get_mixnodes_detailed_unfiltered( + &self, + ) -> Result, NymAPIError> { + self.query_nym_api( + &[ + routes::API_VERSION, + routes::STATUS, + routes::MIXNODES, + routes::DETAILED_UNFILTERED, + ], + NO_PARAMS, + ) + .await + } + pub async fn get_gateways(&self) -> Result, NymAPIError> { self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS) .await diff --git a/common/client-libs/validator-client/src/nym_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs index 1c715ff1b8..8b6bdeca2f 100644 --- a/common/client-libs/validator-client/src/nym_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -8,6 +8,7 @@ pub const MIXNODES: &str = "mixnodes"; pub const GATEWAYS: &str = "gateways"; pub const DETAILED: &str = "detailed"; +pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered"; pub const ACTIVE: &str = "active"; pub const REWARDED: &str = "rewarded"; pub const COCONUT_ROUTES: &str = "coconut"; diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index 6cbb295b6b..1a8d34dc7a 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -42,6 +42,7 @@ pub(crate) struct PrettyDetailedMixNodeBond { pub operating_cost: Coin, pub profit_margin_percent: Percent, pub family_id: Option, + pub blacklisted: bool, } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs index 0cbc7b0167..0fc351ed89 100644 --- a/explorer-api/src/mix_nodes/models.rs +++ b/explorer-api/src/mix_nodes/models.rs @@ -162,6 +162,7 @@ impl ThreadsafeMixNodesCache { operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(), profit_margin_percent: rewarding_info.cost_params.profit_margin_percent, family_id, + blacklisted: node.blacklisted, } } diff --git a/explorer-api/src/tasks.rs b/explorer-api/src/tasks.rs index 0602a2af69..2019d74dce 100644 --- a/explorer-api/src/tasks.rs +++ b/explorer-api/src/tasks.rs @@ -43,7 +43,7 @@ impl ExplorerApiTasks { async fn retrieve_all_mixnodes(&self) -> Vec { info!("About to retrieve all mixnode bonds..."); - self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed) + self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed_unfiltered) .await } diff --git a/explorer/src/components/MixNodes/Economics/Table.tsx b/explorer/src/components/MixNodes/Economics/Table.tsx index 74bfa30251..e6630334db 100644 --- a/explorer/src/components/MixNodes/Economics/Table.tsx +++ b/explorer/src/components/MixNodes/Economics/Table.tsx @@ -53,7 +53,6 @@ export const DelegatorsInfoTable: FCWithChildren { const { field } = columnsData[index]; const value: EconomicsRowsType = (eachRow as any)[field]; - console.log(value); return ( { const { mixNode, mixNodeRow, description, stats, status, uptimeStory, uniqDelegations } = useMixnodeContext(); - console.log(mixNodeRow); + const isMobile = useIsMobile(); return ( @@ -73,6 +74,11 @@ const PageMixnodeDetailWithState: FCWithChildren = () => { {mixNodeRow && description?.data && ( <MixNodeDetailSection mixNodeRow={mixNodeRow} mixnodeDescription={description.data} /> )} + {mixNodeRow?.blacklisted && ( + <Typography textAlign={isMobile ? 'left' : 'right'} fontSize="smaller" sx={{ color: 'error.main' }}> + This node is having a poor performance + </Typography> + )} </Grid> </Grid> <Grid container> diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts index 6f1fe6a891..d81e3a3d8a 100644 --- a/explorer/src/typeDefs/explorer-api.ts +++ b/explorer/src/typeDefs/explorer-api.ts @@ -89,6 +89,7 @@ export interface MixNodeResponseItem { uncapped_saturation: number; operating_cost: Amount; profit_margin_percent: string; + blacklisted: boolean; } export type MixNodeResponse = MixNodeResponseItem[]; diff --git a/nym-api/nym-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs index daebc0b0ac..319d49edd1 100644 --- a/nym-api/nym-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -111,6 +111,7 @@ pub struct MixNodeBondAnnotated { pub estimated_operator_apy: Decimal, pub estimated_delegators_apy: Decimal, pub family: Option<FamilyHead>, + pub blacklisted: bool, } impl MixNodeBondAnnotated { @@ -137,6 +138,7 @@ pub struct GatewayBondAnnotated { // NOTE: the performance field is deprecated in favour of node_performance pub performance: Performance, pub node_performance: NodePerformance, + pub blacklisted: bool, } impl GatewayBondAnnotated { diff --git a/nym-api/src/epoch_operations/mod.rs b/nym-api/src/epoch_operations/mod.rs index c151482bcb..4b25a8ae53 100644 --- a/nym-api/src/epoch_operations/mod.rs +++ b/nym-api/src/epoch_operations/mod.rs @@ -102,7 +102,7 @@ impl RewardedSetUpdater { let epoch_end = interval.current_epoch_end(); - let all_mixnodes = self.nym_contract_cache.mixnodes().await; + let all_mixnodes = self.nym_contract_cache.mixnodes_filtered().await; if all_mixnodes.is_empty() { // that's a bit weird, but log::warn!("there don't seem to be any mixnodes on the network!") diff --git a/nym-api/src/node_status_api/cache/mod.rs b/nym-api/src/node_status_api/cache/mod.rs index d996fbb69b..ba74ab561c 100644 --- a/nym-api/src/node_status_api/cache/mod.rs +++ b/nym-api/src/node_status_api/cache/mod.rs @@ -89,10 +89,15 @@ impl NodeStatusCache { } } - pub(crate) async fn mixnodes_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> { + pub(crate) async fn mixnodes_annotated_full(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> { self.get(|c| c.mixnodes_annotated.clone()).await } + pub(crate) async fn mixnodes_annotated_filtered(&self) -> Option<Vec<MixNodeBondAnnotated>> { + let full = self.mixnodes_annotated_full().await?; + Some(full.value.into_iter().filter(|m| !m.blacklisted).collect()) + } + pub(crate) async fn rewarded_set_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> { self.get(|c| c.rewarded_set_annotated.clone()).await } @@ -101,10 +106,15 @@ impl NodeStatusCache { self.get(|c| c.active_set_annotated.clone()).await } - pub(crate) async fn gateways_annotated(&self) -> Option<Cache<Vec<GatewayBondAnnotated>>> { + pub(crate) async fn gateways_annotated_full(&self) -> Option<Cache<Vec<GatewayBondAnnotated>>> { self.get(|c| c.gateways_annotated.clone()).await } + pub(crate) async fn gateways_annotated_filtered(&self) -> Option<Vec<GatewayBondAnnotated>> { + let full = self.gateways_annotated_full().await?; + Some(full.value.into_iter().filter(|m| !m.blacklisted).collect()) + } + pub(crate) async fn inclusion_probabilities(&self) -> Option<Cache<InclusionProbabilities>> { self.get(|c| c.inclusion_probabilities.clone()).await } @@ -126,7 +136,7 @@ impl NodeStatusCache { return (Some(bond.clone()), MixnodeStatus::Standby); } - let all_bonded = &self.mixnodes_annotated().await.unwrap().into_inner(); + let all_bonded = &self.mixnodes_annotated_filtered().await.unwrap(); if let Some(bond) = all_bonded.iter().find(|mix| mix.mix_id() == mix_id) { (Some(bond.clone()), MixnodeStatus::Inactive) } else { diff --git a/nym-api/src/node_status_api/cache/node_sets.rs b/nym-api/src/node_status_api/cache/node_sets.rs index 2d856bf9e7..a80c84d2d1 100644 --- a/nym-api/src/node_status_api/cache/node_sets.rs +++ b/nym-api/src/node_status_api/cache/node_sets.rs @@ -6,7 +6,7 @@ use nym_mixnet_contract_common::{reward_params::Performance, Interval, MixId}; use nym_mixnet_contract_common::{ GatewayBond, IdentityKey, MixNodeDetails, RewardedSetNodeStatus, RewardingParams, }; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; pub(super) fn to_rewarded_set_node_status( rewarded_set: &[MixNodeDetails], @@ -84,6 +84,7 @@ pub(super) async fn annotate_nodes_with_details( current_interval: Interval, rewarded_set: &HashMap<MixId, RewardedSetNodeStatus>, mix_to_family: Vec<(IdentityKey, FamilyHead)>, + blacklist: &HashSet<MixId>, ) -> Vec<MixNodeBondAnnotated> { let mix_to_family = mix_to_family .into_iter() @@ -135,6 +136,7 @@ pub(super) async fn annotate_nodes_with_details( .cloned(); annotated.push(MixNodeBondAnnotated { + blacklisted: blacklist.contains(&mixnode.mix_id()), mixnode_details: mixnode, stake_saturation, uncapped_stake_saturation, @@ -152,6 +154,7 @@ pub(crate) async fn annotate_gateways_with_details( storage: &Option<NymApiStorage>, gateway_bonds: Vec<GatewayBond>, current_interval: Interval, + blacklist: &HashSet<IdentityKey>, ) -> Vec<GatewayBondAnnotated> { let mut annotated = Vec::new(); for gateway_bond in gateway_bonds { @@ -175,6 +178,7 @@ pub(crate) async fn annotate_gateways_with_details( .unwrap_or_default(); annotated.push(GatewayBondAnnotated { + blacklisted: blacklist.contains(&gateway_bond.gateway.identity_key), gateway_bond, performance, node_performance, diff --git a/nym-api/src/node_status_api/cache/refresher.rs b/nym-api/src/node_status_api/cache/refresher.rs index 146e75ebfb..7e84ea2ee8 100644 --- a/nym-api/src/node_status_api/cache/refresher.rs +++ b/nym-api/src/node_status_api/cache/refresher.rs @@ -109,13 +109,17 @@ impl NodeStatusCacheRefresher { log::info!("Updating node status cache"); // Fetch contract cache data to work with - let mixnode_details = self.contract_cache.mixnodes().await; + let mixnode_details = self.contract_cache.mixnodes_all().await; let interval_reward_params = self.contract_cache.interval_reward_params().await; let current_interval = self.contract_cache.current_interval().await; let rewarded_set = self.contract_cache.rewarded_set().await; let active_set = self.contract_cache.active_set().await; let mix_to_family = self.contract_cache.mix_to_family().await; - let gateway_bonds = self.contract_cache.gateways().await; + let gateway_bonds = self.contract_cache.gateways_all().await; + + // get blacklists + let mixnodes_blacklist = self.contract_cache.mixnodes_blacklist().await; + let gateways_blacklist = self.contract_cache.gateways_blacklist().await; let interval_reward_params = interval_reward_params.ok_or(NodeStatusCacheError::SourceDataMissing)?; @@ -140,6 +144,7 @@ impl NodeStatusCacheRefresher { current_interval, &rewarded_set_node_status, mix_to_family.to_vec(), + &mixnodes_blacklist, ) .await; @@ -147,8 +152,13 @@ impl NodeStatusCacheRefresher { let (rewarded_set, active_set) = split_into_active_and_rewarded_set(&mixnodes_annotated, &rewarded_set_node_status); - let gateways_annotated = - annotate_gateways_with_details(&self.storage, gateway_bonds, current_interval).await; + let gateways_annotated = annotate_gateways_with_details( + &self.storage, + gateway_bonds, + current_interval, + &gateways_blacklist, + ) + .await; // Update the cache self.cache diff --git a/nym-api/src/node_status_api/helpers.rs b/nym-api/src/node_status_api/helpers.rs index d131c6088e..6a67910810 100644 --- a/nym-api/src/node_status_api/helpers.rs +++ b/nym-api/src/node_status_api/helpers.rs @@ -24,17 +24,19 @@ async fn get_gateway_bond_annotated( cache: &NodeStatusCache, identity: &str, ) -> Result<GatewayBondAnnotated, ErrorResponse> { - let gateways = cache.gateways_annotated().await.ok_or(ErrorResponse::new( - "no data available", - Status::ServiceUnavailable, - ))?; + let gateways = cache + .gateways_annotated_filtered() + .await + .ok_or(ErrorResponse::new( + "no data available", + Status::ServiceUnavailable, + ))?; gateways - .into_inner() .into_iter() .find(|gateway| gateway.identity() == identity) .ok_or(ErrorResponse::new( - "mixnode bond not found", + "gateway bond not found", Status::NotFound, )) } @@ -43,13 +45,15 @@ async fn get_mixnode_bond_annotated( cache: &NodeStatusCache, mix_id: MixId, ) -> Result<MixNodeBondAnnotated, ErrorResponse> { - let mixnodes = cache.mixnodes_annotated().await.ok_or(ErrorResponse::new( - "no data available", - Status::ServiceUnavailable, - ))?; + let mixnodes = cache + .mixnodes_annotated_filtered() + .await + .ok_or(ErrorResponse::new( + "no data available", + Status::ServiceUnavailable, + ))?; mixnodes - .into_inner() .into_iter() .find(|mixnode| mixnode.mix_id() == mix_id) .ok_or(ErrorResponse::new( @@ -374,7 +378,16 @@ pub(crate) async fn _get_mixnode_inclusion_probabilities( pub(crate) async fn _get_mixnodes_detailed(cache: &NodeStatusCache) -> Vec<MixNodeBondAnnotated> { cache - .mixnodes_annotated() + .mixnodes_annotated_filtered() + .await + .unwrap_or_default() +} + +pub(crate) async fn _get_mixnodes_detailed_unfiltered( + cache: &NodeStatusCache, +) -> Vec<MixNodeBondAnnotated> { + cache + .mixnodes_annotated_full() .await .unwrap_or_default() .into_inner() @@ -400,7 +413,16 @@ pub(crate) async fn _get_active_set_detailed(cache: &NodeStatusCache) -> Vec<Mix pub(crate) async fn _get_gateways_detailed(cache: &NodeStatusCache) -> Vec<GatewayBondAnnotated> { cache - .gateways_annotated() + .gateways_annotated_filtered() + .await + .unwrap_or_default() +} + +pub(crate) async fn _get_gateways_detailed_unfiltered( + cache: &NodeStatusCache, +) -> Vec<GatewayBondAnnotated> { + cache + .gateways_annotated_full() .await .unwrap_or_default() .into_inner() diff --git a/nym-api/src/node_status_api/mod.rs b/nym-api/src/node_status_api/mod.rs index 1b05827e97..6906e8aab3 100644 --- a/nym-api/src/node_status_api/mod.rs +++ b/nym-api/src/node_status_api/mod.rs @@ -48,9 +48,11 @@ pub(crate) fn node_status_routes( routes::get_gateway_avg_uptime, routes::get_mixnode_inclusion_probabilities, routes::get_mixnodes_detailed, + routes::get_mixnodes_detailed_unfiltered, routes::get_rewarded_set_detailed, routes::get_active_set_detailed, routes::get_gateways_detailed, + routes::get_gateways_detailed_unfiltered, ] } else { // in the minimal variant we would not have access to endpoints relying on existence diff --git a/nym-api/src/node_status_api/routes.rs b/nym-api/src/node_status_api/routes.rs index 9d1f9d7dc0..b014abef06 100644 --- a/nym-api/src/node_status_api/routes.rs +++ b/nym-api/src/node_status_api/routes.rs @@ -6,11 +6,11 @@ use super::NodeStatusCache; use crate::node_status_api::helpers::{ _compute_mixnode_reward_estimation, _gateway_core_status_count, _gateway_report, _gateway_uptime_history, _get_active_set_detailed, _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, - _get_rewarded_set_detailed, _mixnode_core_status_count, _mixnode_report, - _mixnode_uptime_history, + _get_gateways_detailed_unfiltered, _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, _get_mixnodes_detailed_unfiltered, _get_rewarded_set_detailed, + _mixnode_core_status_count, _mixnode_report, _mixnode_uptime_history, }; use crate::node_status_api::models::ErrorResponse; use crate::storage::NymApiStorage; @@ -188,6 +188,14 @@ pub async fn get_mixnodes_detailed( Json(_get_mixnodes_detailed(cache).await) } +#[openapi(tag = "status")] +#[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")] #[get("/mixnodes/rewarded/detailed")] pub async fn get_rewarded_set_detailed( @@ -211,3 +219,11 @@ pub async fn get_gateways_detailed( ) -> Json<Vec<GatewayBondAnnotated>> { Json(_get_gateways_detailed(cache).await) } + +#[openapi(tag = "status")] +#[get("/gateways/detailed-unfiltered")] +pub async fn get_gateways_detailed_unfiltered( + cache: &State<NodeStatusCache>, +) -> Json<Vec<GatewayBondAnnotated>> { + Json(_get_gateways_detailed_unfiltered(cache).await) +} diff --git a/nym-api/src/nym_contract_cache/cache/mod.rs b/nym-api/src/nym_contract_cache/cache/mod.rs index 1ee787af17..cd885f7a5d 100644 --- a/nym-api/src/nym_contract_cache/cache/mod.rs +++ b/nym-api/src/nym_contract_cache/cache/mod.rs @@ -67,50 +67,48 @@ impl NymContractCache { } } - pub async fn mixnodes_blacklist(&self) -> Option<Cache<HashSet<MixId>>> { + pub async fn mixnodes_blacklist(&self) -> Cache<HashSet<MixId>> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { - Ok(cache) => Some(cache.mixnodes_blacklist.clone()), + Ok(cache) => cache.mixnodes_blacklist.clone(), Err(err) => { error!("{err}"); - None + Cache::new(HashSet::new()) } } } - pub async fn gateways_blacklist(&self) -> Option<Cache<HashSet<IdentityKey>>> { + pub async fn gateways_blacklist(&self) -> Cache<HashSet<IdentityKey>> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { - Ok(cache) => Some(cache.gateways_blacklist.clone()), + Ok(cache) => cache.gateways_blacklist.clone(), Err(err) => { error!("{err}"); - None + Cache::new(HashSet::new()) } } } pub async fn update_mixnodes_blacklist(&self, add: HashSet<MixId>, remove: HashSet<MixId>) { let blacklist = self.mixnodes_blacklist().await; - if let Some(blacklist) = blacklist { - let mut blacklist = blacklist - .value - .union(&add) - .cloned() - .collect::<HashSet<MixId>>(); - let to_remove = blacklist - .intersection(&remove) - .cloned() - .collect::<HashSet<MixId>>(); - for key in to_remove { - blacklist.remove(&key); + let mut blacklist = blacklist + .value + .union(&add) + .cloned() + .collect::<HashSet<MixId>>(); + let to_remove = blacklist + .intersection(&remove) + .cloned() + .collect::<HashSet<MixId>>(); + for key in to_remove { + blacklist.remove(&key); + } + match time::timeout(Duration::from_millis(100), self.inner.write()).await { + Ok(mut cache) => { + cache.mixnodes_blacklist.update(blacklist); } - match time::timeout(Duration::from_millis(100), self.inner.write()).await { - Ok(mut cache) => { - cache.mixnodes_blacklist.update(blacklist); - return; - } - Err(err) => error!("{err}"), + Err(err) => { + error!("Failed to update mixnodes blacklist: {err}"); } } - error!("Failed to update mixnodes blacklist"); } pub async fn update_gateways_blacklist( @@ -119,49 +117,52 @@ impl NymContractCache { remove: HashSet<IdentityKey>, ) { let blacklist = self.gateways_blacklist().await; - if let Some(blacklist) = blacklist { - let mut blacklist = blacklist - .value - .union(&add) - .cloned() - .collect::<HashSet<IdentityKey>>(); - let to_remove = blacklist - .intersection(&remove) - .cloned() - .collect::<HashSet<IdentityKey>>(); - for key in to_remove { - blacklist.remove(&key); + let mut blacklist = blacklist + .value + .union(&add) + .cloned() + .collect::<HashSet<IdentityKey>>(); + let to_remove = blacklist + .intersection(&remove) + .cloned() + .collect::<HashSet<IdentityKey>>(); + for key in to_remove { + blacklist.remove(&key); + } + match time::timeout(Duration::from_millis(100), self.inner.write()).await { + Ok(mut cache) => { + cache.gateways_blacklist.update(blacklist); } - match time::timeout(Duration::from_millis(100), self.inner.write()).await { - Ok(mut cache) => { - cache.gateways_blacklist.update(blacklist); - return; - } - Err(err) => error!("{err}"), + Err(err) => { + error!("Failed to update gateways blacklist: {err}"); } } - error!("Failed to update gateways blacklist"); } - pub async fn mixnodes(&self) -> Vec<MixNodeDetails> { + pub async fn mixnodes_filtered(&self) -> Vec<MixNodeDetails> { + let mixnodes = self.mixnodes_all().await; + if mixnodes.is_empty() { + return Vec::new(); + } let blacklist = self.mixnodes_blacklist().await; - let mixnodes = match time::timeout(Duration::from_millis(100), self.inner.read()).await { - Ok(cache) => cache.mixnodes.clone(), - Err(err) => { - error!("{err}"); - return Vec::new(); - } - }; - if let Some(blacklist) = blacklist { + if !blacklist.is_empty() { mixnodes - .value - .iter() + .into_iter() .filter(|mix| !blacklist.value.contains(&mix.mix_id())) - .cloned() .collect() } else { - mixnodes.value + mixnodes + } + } + + pub async fn mixnodes_all(&self) -> Vec<MixNodeDetails> { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.mixnodes.clone().value, + Err(err) => { + error!("{err}"); + Vec::new() + } } } @@ -181,25 +182,21 @@ impl NymContractCache { } } - pub async fn gateways(&self) -> Vec<GatewayBond> { - let blacklist = self.gateways_blacklist().await; - let gateways = match time::timeout(Duration::from_millis(100), self.inner.read()).await { - Ok(cache) => cache.gateways.clone(), - Err(err) => { - error!("{err}"); - return Vec::new(); - } - }; + pub async fn gateways_filtered(&self) -> Vec<GatewayBond> { + let gateways = self.gateways_all().await; + if gateways.is_empty() { + return Vec::new(); + } - if let Some(blacklist) = blacklist { + let blacklist = self.gateways_blacklist().await; + + if !blacklist.is_empty() { gateways - .value - .iter() + .into_iter() .filter(|mix| !blacklist.value.contains(mix.identity())) - .cloned() .collect() } else { - gateways.value + gateways } } @@ -277,7 +274,7 @@ impl NymContractCache { return (Some(bond.clone()), MixnodeStatus::Standby); } - let all_bonded = &self.mixnodes().await; + let all_bonded = &self.mixnodes_filtered().await; if let Some(bond) = all_bonded.iter().find(|mix| mix.mix_id() == mix_id) { (Some(bond.clone()), MixnodeStatus::Inactive) } else { diff --git a/nym-api/src/nym_contract_cache/routes.rs b/nym-api/src/nym_contract_cache/routes.rs index d649fc46a4..7b33269c60 100644 --- a/nym-api/src/nym_contract_cache/routes.rs +++ b/nym-api/src/nym_contract_cache/routes.rs @@ -20,7 +20,7 @@ use std::collections::HashSet; #[openapi(tag = "contract-cache")] #[get("/mixnodes")] pub async fn get_mixnodes(cache: &State<NymContractCache>) -> Json<Vec<MixNodeDetails>> { - Json(cache.mixnodes().await) + Json(cache.mixnodes_filtered().await) } // DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated, @@ -41,7 +41,7 @@ pub async fn get_mixnodes_detailed( #[openapi(tag = "contract-cache")] #[get("/gateways")] pub async fn get_gateways(cache: &State<NymContractCache>) -> Json<Vec<GatewayBond>> { - Json(cache.gateways().await) + Json(cache.gateways_filtered().await) } #[openapi(tag = "contract-cache")] @@ -91,7 +91,12 @@ pub async fn get_active_set_detailed( pub async fn get_blacklisted_mixnodes( cache: &State<NymContractCache>, ) -> Json<Option<HashSet<MixId>>> { - Json(cache.mixnodes_blacklist().await.map(|c| c.value)) + let blacklist = cache.mixnodes_blacklist().await.value; + if blacklist.is_empty() { + Json(None) + } else { + Json(Some(blacklist)) + } } #[openapi(tag = "contract-cache")] @@ -99,7 +104,12 @@ pub async fn get_blacklisted_mixnodes( pub async fn get_blacklisted_gateways( cache: &State<NymContractCache>, ) -> Json<Option<HashSet<String>>> { - Json(cache.gateways_blacklist().await.map(|c| c.value)) + let blacklist = cache.gateways_blacklist().await.value; + if blacklist.is_empty() { + Json(None) + } else { + Json(Some(blacklist)) + } } #[openapi(tag = "contract-cache")]