// Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only use crate::nym_contract_cache::cache::data::CachedContractsInfo; use crate::support::caching::Cache; use data::ValidatorCacheData; use nym_api_requests::models::MixnodeStatus; use nym_mixnet_contract_common::{ families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeBond, MixNodeDetails, RewardingParams, }; use rocket::fairing::AdHoc; use std::{ collections::HashSet, sync::{ atomic::{AtomicBool, Ordering}, Arc, }, time::Duration, }; use tokio::sync::RwLock; use tokio::time; mod data; pub(crate) mod refresher; #[derive(Clone)] pub struct NymContractCache { pub(crate) initialised: Arc, pub(crate) inner: Arc>, } impl NymContractCache { fn new() -> Self { NymContractCache { initialised: Arc::new(AtomicBool::new(false)), inner: Arc::new(RwLock::new(ValidatorCacheData::new())), } } pub fn stage() -> AdHoc { AdHoc::on_ignite("Validator Cache Stage", |rocket| async { rocket.manage(Self::new()) }) } #[allow(clippy::too_many_arguments)] pub(crate) async fn update( &self, mixnodes: Vec, gateways: Vec, rewarded_set: Vec, active_set: Vec, rewarding_params: RewardingParams, current_interval: Interval, mix_to_family: Vec<(IdentityKey, FamilyHead)>, nym_contracts_info: CachedContractsInfo, ) { match time::timeout(Duration::from_millis(100), self.inner.write()).await { Ok(mut cache) => { cache.mixnodes.unchecked_update(mixnodes); cache.gateways.unchecked_update(gateways); cache.rewarded_set.unchecked_update(rewarded_set); cache.active_set.unchecked_update(active_set); cache .current_reward_params .unchecked_update(Some(rewarding_params)); cache .current_interval .unchecked_update(Some(current_interval)); cache.mix_to_family.unchecked_update(mix_to_family); cache.contracts_info.unchecked_update(nym_contracts_info) } Err(err) => { error!("{err}"); } } } pub async fn mixnodes_blacklist(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.mixnodes_blacklist.clone_cache(), Err(err) => { error!("{err}"); Cache::new(HashSet::new()) } } } pub async fn gateways_blacklist(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.gateways_blacklist.clone_cache(), Err(err) => { error!("{err}"); Cache::new(HashSet::new()) } } } pub async fn update_mixnodes_blacklist(&self, add: HashSet, remove: HashSet) { let blacklist = self.mixnodes_blacklist().await; let mut blacklist = blacklist.union(&add).cloned().collect::>(); let to_remove = blacklist .intersection(&remove) .cloned() .collect::>(); 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.unchecked_update(blacklist); } Err(err) => { error!("Failed to update mixnodes blacklist: {err}"); } } } pub async fn update_gateways_blacklist( &self, add: HashSet, remove: HashSet, ) { let blacklist = self.gateways_blacklist().await; let mut blacklist = blacklist .union(&add) .cloned() .collect::>(); let to_remove = blacklist .intersection(&remove) .cloned() .collect::>(); 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.unchecked_update(blacklist); } Err(err) => { error!("Failed to update gateways blacklist: {err}"); } } } pub async fn mixnodes_filtered(&self) -> Vec { let mixnodes = self.mixnodes_all().await; if mixnodes.is_empty() { return Vec::new(); } let blacklist = self.mixnodes_blacklist().await; if !blacklist.is_empty() { mixnodes .into_iter() .filter(|mix| !blacklist.contains(&mix.mix_id())) .collect() } else { mixnodes } } pub async fn mixnodes_all(&self) -> Vec { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.mixnodes.clone(), Err(err) => { error!("{err}"); Vec::new() } } } pub async fn mixnodes_filtered_basic(&self) -> Vec { self.mixnodes_filtered() .await .into_iter() .map(|bond| bond.bond_information) .collect() } pub async fn mixnodes_all_basic(&self) -> Vec { self.mixnodes_all() .await .into_iter() .map(|bond| bond.bond_information) .collect() } pub async fn gateways_filtered(&self) -> Vec { let gateways = self.gateways_all().await; if gateways.is_empty() { return Vec::new(); } let blacklist = self.gateways_blacklist().await; if !blacklist.is_empty() { gateways .into_iter() .filter(|mix| !blacklist.contains(mix.identity())) .collect() } else { gateways } } pub async fn gateways_all(&self) -> Vec { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.gateways.clone(), Err(err) => { error!("{err}"); Vec::new() } } } pub async fn rewarded_set(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.rewarded_set.clone_cache(), Err(err) => { error!("{err}"); Cache::new(Vec::new()) } } } pub async fn active_set(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.active_set.clone_cache(), Err(err) => { error!("{err}"); Cache::new(Vec::new()) } } } pub async fn mix_to_family(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.mix_to_family.clone_cache(), Err(err) => { error!("{err}"); Cache::new(Vec::new()) } } } pub(crate) async fn interval_reward_params(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.current_reward_params.clone_cache(), Err(err) => { error!("{err}"); Cache::new(None) } } } pub(crate) async fn current_interval(&self) -> Cache> { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.current_interval.clone_cache(), Err(err) => { error!("{err}"); Cache::new(None) } } } pub(crate) async fn contract_details(&self) -> Cache { match time::timeout(Duration::from_millis(100), self.inner.read()).await { Ok(cache) => cache.contracts_info.clone_cache(), Err(err) => { error!("{err}"); Cache::default() } } } pub async fn mixnode_details(&self, mix_id: MixId) -> (Option, 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, < 1000 for active set) let active_set = &self.active_set().await; if let Some(bond) = active_set.iter().find(|mix| mix.mix_id() == mix_id) { return (Some(bond.clone()), MixnodeStatus::Active); } let rewarded_set = &self.rewarded_set().await; if let Some(bond) = rewarded_set.iter().find(|mix| mix.mix_id() == mix_id) { return (Some(bond.clone()), MixnodeStatus::Standby); } 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 { (None, MixnodeStatus::NotFound) } } pub async fn mixnode_status(&self, mix_id: MixId) -> MixnodeStatus { self.mixnode_details(mix_id).await.1 } pub fn initialised(&self) -> bool { self.initialised.load(Ordering::Relaxed) } pub(crate) async fn wait_for_initial_values(&self) { let initialisation_backoff = Duration::from_secs(5); loop { if self.initialised() { break; } else { debug!("Validator cache hasn't been initialised yet - waiting for {:?} before trying again", initialisation_backoff); tokio::time::sleep(initialisation_backoff).await; } } } }