From 7f87d42f9a2e1b549e314a42826fb8a265793b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 17 May 2024 11:01:16 +0100 Subject: [PATCH] nym-api: make report/avg_uptime endpoints ignore blacklist --- nym-api/src/node_status_api/cache/data.rs | 7 ++- nym-api/src/node_status_api/cache/mod.rs | 60 +++++++++++++++---- .../src/node_status_api/cache/node_sets.rs | 54 +++++++++-------- nym-api/src/node_status_api/helpers.rs | 33 ++-------- nym-api/src/support/caching/cache.rs | 12 ++++ 5 files changed, 101 insertions(+), 65 deletions(-) diff --git a/nym-api/src/node_status_api/cache/data.rs b/nym-api/src/node_status_api/cache/data.rs index 2b828420f6..b2e6512417 100644 --- a/nym-api/src/node_status_api/cache/data.rs +++ b/nym-api/src/node_status_api/cache/data.rs @@ -2,6 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated}; +use nym_contracts_common::IdentityKey; +use nym_mixnet_contract_common::MixId; +use std::collections::HashMap; use crate::support::caching::Cache; @@ -9,11 +12,11 @@ use super::inclusion_probabilities::InclusionProbabilities; #[derive(Default)] pub(crate) struct NodeStatusCacheData { - pub(crate) mixnodes_annotated: Cache>, + pub(crate) mixnodes_annotated: Cache>, pub(crate) rewarded_set_annotated: Cache>, pub(crate) active_set_annotated: Cache>, - pub(crate) gateways_annotated: Cache>, + pub(crate) gateways_annotated: Cache>, // Estimated active set inclusion probabilities from Monte Carlo simulation pub(crate) inclusion_probabilities: Cache, diff --git a/nym-api/src/node_status_api/cache/mod.rs b/nym-api/src/node_status_api/cache/mod.rs index b563afb6a0..9261129e67 100644 --- a/nym-api/src/node_status_api/cache/mod.rs +++ b/nym-api/src/node_status_api/cache/mod.rs @@ -1,13 +1,14 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::support::caching::Cache; - use self::data::NodeStatusCacheData; use self::inclusion_probabilities::InclusionProbabilities; +use crate::support::caching::Cache; use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, MixnodeStatus}; +use nym_contracts_common::{IdentityKey, IdentityKeyRef}; use nym_mixnet_contract_common::MixId; use rocket::fairing::AdHoc; +use std::collections::HashMap; use std::{sync::Arc, time::Duration}; use thiserror::Error; use tokio::sync::RwLockReadGuard; @@ -55,10 +56,10 @@ impl NodeStatusCache { /// Updates the cache with the latest data. async fn update( &self, - mixnodes: Vec, + mixnodes: HashMap, rewarded_set: Vec, active_set: Vec, - gateways: Vec, + gateways: HashMap, inclusion_probabilities: InclusionProbabilities, ) { match time::timeout(Duration::from_millis(CACHE_TIMEOUT_MS), self.inner.write()).await { @@ -76,7 +77,7 @@ impl NodeStatusCache { } /// Returns a copy of the current cache data. - async fn get( + async fn get_owned( &self, fn_arg: impl FnOnce(RwLockReadGuard<'_, NodeStatusCacheData>) -> Cache, ) -> Option> { @@ -89,8 +90,24 @@ impl NodeStatusCache { } } - pub(crate) async fn mixnodes_annotated_full(&self) -> Option>> { - self.get(|c| c.mixnodes_annotated.clone_cache()).await + async fn get<'a, T: 'a>( + &'a self, + fn_arg: impl FnOnce(&NodeStatusCacheData) -> &Cache, + ) -> Option>> { + match time::timeout(Duration::from_millis(CACHE_TIMEOUT_MS), self.inner.read()).await { + Ok(cache) => Some(RwLockReadGuard::map(cache, |item| fn_arg(item))), + Err(e) => { + error!("{e}"); + None + } + } + } + + pub(crate) async fn mixnodes_annotated_full(&self) -> Option> { + let mixnodes = self.get(|c| &c.mixnodes_annotated).await?; + + // just clone everything and return the vec to work with the existing code + Some(mixnodes.values().cloned().collect()) } pub(crate) async fn mixnodes_annotated_filtered(&self) -> Option> { @@ -98,16 +115,26 @@ impl NodeStatusCache { Some(full.iter().filter(|m| !m.blacklisted).cloned().collect()) } + pub(crate) async fn mixnode_annotated(&self, mix_id: MixId) -> Option { + let mixnodes = self.get(|c| &c.mixnodes_annotated).await?; + mixnodes.get(&mix_id).cloned() + } + pub(crate) async fn rewarded_set_annotated(&self) -> Option>> { - self.get(|c| c.rewarded_set_annotated.clone_cache()).await + self.get_owned(|c| c.rewarded_set_annotated.clone_cache()) + .await } pub(crate) async fn active_set_annotated(&self) -> Option>> { - self.get(|c| c.active_set_annotated.clone_cache()).await + self.get_owned(|c| c.active_set_annotated.clone_cache()) + .await } - pub(crate) async fn gateways_annotated_full(&self) -> Option>> { - self.get(|c| c.gateways_annotated.clone_cache()).await + pub(crate) async fn gateways_annotated_full(&self) -> Option> { + let gateways = self.get(|c| &c.gateways_annotated).await?; + + // just clone everything and return the vec to work with the existing code + Some(gateways.values().cloned().collect()) } pub(crate) async fn gateways_annotated_filtered(&self) -> Option> { @@ -115,8 +142,17 @@ impl NodeStatusCache { Some(full.iter().filter(|m| !m.blacklisted).cloned().collect()) } + pub(crate) async fn gateway_annotated( + &self, + gateway_id: IdentityKeyRef<'_>, + ) -> Option { + let gateways = self.get(|c| &c.gateways_annotated).await?; + gateways.get(gateway_id).cloned() + } + pub(crate) async fn inclusion_probabilities(&self) -> Option> { - self.get(|c| c.inclusion_probabilities.clone_cache()).await + self.get_owned(|c| c.inclusion_probabilities.clone_cache()) + .await } pub async fn mixnode_details( 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 872b532d2a..62b39807b8 100644 --- a/nym-api/src/node_status_api/cache/node_sets.rs +++ b/nym-api/src/node_status_api/cache/node_sets.rs @@ -28,11 +28,11 @@ pub(super) fn to_rewarded_set_node_status( } pub(super) fn split_into_active_and_rewarded_set( - mixnodes_annotated: &[MixNodeBondAnnotated], + mixnodes_annotated: &HashMap, rewarded_set_node_status: &HashMap, ) -> (Vec, Vec) { let rewarded_set: Vec<_> = mixnodes_annotated - .iter() + .values() .filter(|mixnode| rewarded_set_node_status.get(&mixnode.mix_id()).is_some()) .cloned() .collect(); @@ -88,12 +88,12 @@ pub(super) async fn annotate_nodes_with_details( rewarded_set: &HashMap, mix_to_family: Vec<(IdentityKey, FamilyHead)>, blacklist: &HashSet, -) -> Vec { +) -> HashMap { let mix_to_family = mix_to_family .into_iter() .collect::>(); - let mut annotated = Vec::new(); + let mut annotated = HashMap::new(); for mixnode in mixnodes { let stake_saturation = mixnode .rewarding_details @@ -138,17 +138,20 @@ pub(super) async fn annotate_nodes_with_details( .get(mixnode.bond_information.identity()) .cloned(); - annotated.push(MixNodeBondAnnotated { - blacklisted: blacklist.contains(&mixnode.mix_id()), - mixnode_details: mixnode, - stake_saturation, - uncapped_stake_saturation, - performance, - node_performance, - estimated_operator_apy, - estimated_delegators_apy, - family, - }); + annotated.insert( + mixnode.mix_id(), + MixNodeBondAnnotated { + blacklisted: blacklist.contains(&mixnode.mix_id()), + mixnode_details: mixnode, + stake_saturation, + uncapped_stake_saturation, + performance, + node_performance, + estimated_operator_apy, + estimated_delegators_apy, + family, + }, + ); } annotated } @@ -158,8 +161,8 @@ pub(crate) async fn annotate_gateways_with_details( gateway_bonds: Vec, current_interval: Interval, blacklist: &HashSet, -) -> Vec { - let mut annotated = Vec::new(); +) -> HashMap { + let mut annotated = HashMap::new(); for gateway_bond in gateway_bonds { let performance = get_gateway_performance_from_storage( storage, @@ -180,13 +183,16 @@ pub(crate) async fn annotate_gateways_with_details( } .unwrap_or_default(); - annotated.push(GatewayBondAnnotated { - blacklisted: blacklist.contains(&gateway_bond.gateway.identity_key), - gateway_bond, - self_described: None, - performance, - node_performance, - }); + annotated.insert( + gateway_bond.identity().to_string(), + GatewayBondAnnotated { + blacklisted: blacklist.contains(&gateway_bond.gateway.identity_key), + gateway_bond, + self_described: None, + performance, + node_performance, + }, + ); } annotated } diff --git a/nym-api/src/node_status_api/helpers.rs b/nym-api/src/node_status_api/helpers.rs index 894391253d..f5ab4beca1 100644 --- a/nym-api/src/node_status_api/helpers.rs +++ b/nym-api/src/node_status_api/helpers.rs @@ -24,14 +24,9 @@ async fn get_gateway_bond_annotated( cache: &NodeStatusCache, identity: &str, ) -> Result { - let gateways = cache - .gateways_annotated_filtered() + cache + .gateway_annotated(identity) .await - .ok_or_else(|| ErrorResponse::new("no data available", Status::ServiceUnavailable))?; - - gateways - .into_iter() - .find(|gateway| gateway.identity() == identity) .ok_or(ErrorResponse::new( "gateway bond not found", Status::NotFound, @@ -42,17 +37,9 @@ async fn get_mixnode_bond_annotated( cache: &NodeStatusCache, mix_id: MixId, ) -> Result { - let mixnodes = cache - .mixnodes_annotated_filtered() + cache + .mixnode_annotated(mix_id) .await - .ok_or(ErrorResponse::new( - "no data available", - Status::ServiceUnavailable, - ))?; - - mixnodes - .into_iter() - .find(|mixnode| mixnode.mix_id() == mix_id) .ok_or(ErrorResponse::new( "mixnode bond not found", Status::NotFound, @@ -383,11 +370,7 @@ pub(crate) async fn _get_mixnodes_detailed(cache: &NodeStatusCache) -> Vec Vec { - cache - .mixnodes_annotated_full() - .await - .unwrap_or_default() - .into_inner() + cache.mixnodes_annotated_full().await.unwrap_or_default() } pub(crate) async fn _get_rewarded_set_detailed( @@ -418,9 +401,5 @@ pub(crate) async fn _get_gateways_detailed(cache: &NodeStatusCache) -> Vec Vec { - cache - .gateways_annotated_full() - .await - .unwrap_or_default() - .into_inner() + cache.gateways_annotated_full().await.unwrap_or_default() } diff --git a/nym-api/src/support/caching/cache.rs b/nym-api/src/support/caching/cache.rs index fc33124f12..d12c9801e3 100644 --- a/nym-api/src/support/caching/cache.rs +++ b/nym-api/src/support/caching/cache.rs @@ -94,6 +94,18 @@ impl Cache { } } + // I know, it's dead code for now, but I feel it could be useful code in the future + #[allow(dead_code)] + pub(crate) fn map(this: Self, f: F) -> Cache + where + F: FnOnce(T) -> U, + { + Cache { + value: f(this.value), + as_at: this.as_at, + } + } + // ugh. I hate to expose it, but it'd have broken pre-existing code pub(crate) fn clone_cache(&self) -> Self where