Files
nym/explorer-api/src/mix_nodes/models.rs
T
Jędrzej Stuczyński 529e8d49ee chore: apply 1.84 linter suggestions (#5330)
* chore: apply 1.84 linter suggestions

* updated wasm dependencies to fix the macro issue

* second batch of clippy fixes
2025-01-10 13:00:18 +00:00

236 lines
8.4 KiB
Rust

// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::location::MixnodeLocationCache;
use crate::helpers::best_effort_small_dec_to_f64;
use crate::location::LocationCacheItem;
use crate::mix_nodes::CACHE_ENTRY_TTL;
use nym_explorer_api_requests::{Location, MixnodeStatus, PrettyDetailedMixNodeBond};
use nym_mixnet_contract_common::rewarding::helpers::truncate_reward;
use nym_mixnet_contract_common::{MixNodeBond, NodeId};
use nym_validator_client::models::MixNodeBondAnnotated;
use serde::Serialize;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use tokio::sync::{RwLock, RwLockReadGuard};
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub(crate) struct MixNodeActiveSetSummary {
pub active: usize,
pub standby: usize,
pub inactive: usize,
}
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub(crate) struct MixNodeSummary {
pub count: usize,
pub activeset: MixNodeActiveSetSummary,
}
#[derive(Clone, Debug)]
pub(crate) struct MixNodesResult {
pub(crate) valid_until: SystemTime,
pub(crate) all_mixnodes: HashMap<NodeId, MixNodeBondAnnotated>,
active_mixnodes: HashSet<NodeId>,
rewarded_mixnodes: HashSet<NodeId>,
}
impl MixNodesResult {
fn new() -> Self {
MixNodesResult {
valid_until: SystemTime::now() - Duration::from_secs(60), // in the past
all_mixnodes: HashMap::new(),
active_mixnodes: HashSet::new(),
rewarded_mixnodes: HashSet::new(),
}
}
fn determine_node_status(&self, mix_id: NodeId) -> MixnodeStatus {
if self.active_mixnodes.contains(&mix_id) {
MixnodeStatus::Active
} else if self.rewarded_mixnodes.contains(&mix_id) {
MixnodeStatus::Standby
} else {
MixnodeStatus::Inactive
}
}
fn is_valid(&self) -> bool {
self.valid_until >= SystemTime::now()
}
fn get_mixnode(&self, mix_id: NodeId) -> Option<MixNodeBondAnnotated> {
if self.is_valid() {
self.all_mixnodes.get(&mix_id).cloned()
} else {
None
}
}
fn get_mixnodes(&self) -> Option<HashMap<NodeId, MixNodeBondAnnotated>> {
if self.is_valid() {
Some(self.all_mixnodes.clone())
} else {
None
}
}
}
#[derive(Clone)]
pub(crate) struct ThreadsafeMixNodesCache {
mixnodes: Arc<RwLock<MixNodesResult>>,
legacy_mixnode_bonds: Arc<RwLock<MixNodesResult>>,
locations: Arc<RwLock<MixnodeLocationCache>>,
}
impl ThreadsafeMixNodesCache {
pub(crate) fn new() -> Self {
ThreadsafeMixNodesCache {
mixnodes: Arc::new(RwLock::new(MixNodesResult::new())),
legacy_mixnode_bonds: Arc::new(RwLock::new(MixNodesResult::new())),
locations: Arc::new(RwLock::new(MixnodeLocationCache::new())),
}
}
pub(crate) fn new_with_location_cache(locations: MixnodeLocationCache) -> Self {
ThreadsafeMixNodesCache {
mixnodes: Arc::new(RwLock::new(MixNodesResult::new())),
legacy_mixnode_bonds: Arc::new(RwLock::new(MixNodesResult::new())),
locations: Arc::new(RwLock::new(locations)),
}
}
pub(crate) async fn is_location_valid(&self, mix_id: NodeId) -> bool {
self.locations
.read()
.await
.get(&mix_id)
.is_some_and(|cache_item| cache_item.valid_until > SystemTime::now())
}
pub(crate) async fn get_locations(&self) -> MixnodeLocationCache {
self.locations.read().await.clone()
}
pub(crate) async fn set_location(&self, mix_id: NodeId, location: Option<Location>) {
// cache the location for this mix node so that it can be used when the mix node list is refreshed
self.locations
.write()
.await
.insert(mix_id, LocationCacheItem::new_from_location(location));
}
pub(crate) async fn get_mixnode(&self, mix_id: NodeId) -> Option<MixNodeBondAnnotated> {
self.mixnodes.read().await.get_mixnode(mix_id)
}
pub(crate) async fn get_mixnodes(&self) -> Option<HashMap<NodeId, MixNodeBondAnnotated>> {
self.mixnodes.read().await.get_mixnodes()
}
fn create_detailed_mixnode(
&self,
mix_id: NodeId,
mixnodes_guard: &RwLockReadGuard<'_, MixNodesResult>,
location: Option<&LocationCacheItem>,
node: &MixNodeBondAnnotated,
) -> PrettyDetailedMixNodeBond {
let rewarding_info = &node.mixnode_details.rewarding_details;
let denom = &rewarding_info.cost_params.interval_operating_cost.denom;
PrettyDetailedMixNodeBond {
mix_id,
location: location.and_then(|l| l.location.clone()),
status: mixnodes_guard.determine_node_status(mix_id),
pledge_amount: truncate_reward(rewarding_info.operator, denom),
total_delegation: truncate_reward(rewarding_info.delegates, denom),
owner: node.mixnode_details.bond_information.owner.clone(),
layer: node.mixnode_details.bond_information.layer,
mix_node: node.mixnode_details.bond_information.mix_node.clone(),
avg_uptime: node.performance.round_to_integer(),
node_performance: node.node_performance.clone(),
stake_saturation: best_effort_small_dec_to_f64(node.stake_saturation) as f32,
uncapped_saturation: best_effort_small_dec_to_f64(node.uncapped_stake_saturation)
as f32,
estimated_operator_apy: best_effort_small_dec_to_f64(node.estimated_operator_apy),
estimated_delegators_apy: best_effort_small_dec_to_f64(node.estimated_delegators_apy),
operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(),
profit_margin_percent: rewarding_info.cost_params.profit_margin_percent,
family_id: None,
blacklisted: node.blacklisted,
}
}
pub(crate) async fn get_detailed_mixnode(
&self,
mix_id: NodeId,
) -> Option<PrettyDetailedMixNodeBond> {
let mixnodes_guard = self.mixnodes.read().await;
let location_guard = self.locations.read().await;
let bond = mixnodes_guard.get_mixnode(mix_id);
let location = location_guard.get(&mix_id);
bond.map(|bond| self.create_detailed_mixnode(mix_id, &mixnodes_guard, location, &bond))
}
pub(crate) async fn get_detailed_mixnodes(&self) -> Vec<PrettyDetailedMixNodeBond> {
let mixnodes_guard = self.mixnodes.read().await;
let location_guard = self.locations.read().await;
mixnodes_guard
.all_mixnodes
.values()
.map(|bond| {
let location = location_guard.get(&bond.mix_id());
self.create_detailed_mixnode(bond.mix_id(), &mixnodes_guard, location, bond)
})
.collect()
}
pub(crate) async fn get_legacy_detailed_mixnodes(&self) -> Vec<PrettyDetailedMixNodeBond> {
let legacy_mixnodes = self.legacy_mixnode_bonds.read().await;
let location_guard = self.locations.read().await;
legacy_mixnodes
.all_mixnodes
.values()
.map(|bond| {
let location = location_guard.get(&bond.mix_id());
self.create_detailed_mixnode(bond.mix_id(), &legacy_mixnodes, location, bond)
})
.collect()
}
pub(crate) async fn update_cache(
&self,
all_bonds: Vec<MixNodeBondAnnotated>,
rewarded_nodes: HashSet<NodeId>,
active_nodes: HashSet<NodeId>,
legacy_mixnode_bonds: Vec<MixNodeBond>,
) {
let mut guard = self.mixnodes.write().await;
let mut guard_legacy_mixnodes = self.legacy_mixnode_bonds.write().await;
let legacy_mixnode_bond_ids: Vec<&NodeId> = legacy_mixnode_bonds
.iter()
.map(|bond| &bond.mix_id)
.collect();
guard.all_mixnodes = all_bonds
.into_iter()
.map(|bond| (bond.mix_id(), bond))
.collect();
guard.rewarded_mixnodes = rewarded_nodes;
guard.active_mixnodes = active_nodes;
guard.valid_until = SystemTime::now() + CACHE_ENTRY_TTL;
guard_legacy_mixnodes.all_mixnodes = guard
.all_mixnodes
.clone()
.into_iter()
.filter(|(node_id, _bond)| legacy_mixnode_bond_ids.iter().any(|i| **i == *node_id))
.collect();
}
}