From 279b00d83394caf7a37800fa8f8ec500ea9b19a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 23 Sep 2024 17:51:32 +0100 Subject: [PATCH] exposing node role in nym node annotation --- .../validator-client/src/nym_api/mod.rs | 12 +- .../contract_traits/mixnet_query_client.rs | 5 +- .../mixnet-contract/src/types.rs | 56 -------- nym-api/nym-api-requests/src/models.rs | 2 + nym-api/src/epoch_operations/rewarding.rs | 1 + .../src/network_monitor/monitor/preparer.rs | 6 +- .../src/node_status_api/cache/node_sets.rs | 11 +- .../src/node_status_api/cache/refresher.rs | 1 + nym-api/src/nym_contract_cache/cache/data.rs | 123 +++++++++++++++++- nym-api/src/nym_contract_cache/cache/mod.rs | 6 +- nym-wallet/src-tauri/src/main.rs | 4 + .../src-tauri/src/operations/mixnet/bond.rs | 38 +++++- .../src/operations/nym_api/status.rs | 24 +++- 13 files changed, 211 insertions(+), 78 deletions(-) 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 7723ab89a8..2ecadd7641 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -11,7 +11,9 @@ use nym_api_requests::ecash::models::{ }; use nym_api_requests::ecash::VerificationKeyResponse; use nym_api_requests::legacy::LegacyGatewayBondWithId; -use nym_api_requests::models::{LegacyDescribedMixNode, NodePerformanceResponse}; +use nym_api_requests::models::{ + AnnotationResponse, LegacyDescribedMixNode, NodePerformanceResponse, +}; pub use nym_api_requests::{ ecash::{ models::{ @@ -435,6 +437,14 @@ pub trait NymApiClientExt: ApiClient { .await } + async fn get_node_annotation( + &self, + node_id: NodeId, + ) -> Result { + self.get_json_from(format!("/v1/nym-nodes/annotation/{node_id}")) + .await + } + async fn get_mixnode_avg_uptime(&self, mix_id: NodeId) -> Result { self.get_json( &[ diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs index a217f91663..ba90467f32 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs @@ -7,14 +7,13 @@ use crate::nyxd::error::NyxdError; use crate::nyxd::CosmWasmClient; use async_trait::async_trait; use cosmrs::AccountId; -use nym_api_requests::models::StakeSaturationResponse; use nym_contracts_common::signing::Nonce; use nym_mixnet_contract_common::gateway::{PreassignedGatewayIdsResponse, PreassignedId}; use nym_mixnet_contract_common::nym_node::{ EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeOwnershipResponse, NodeRewardingDetailsResponse, PagedNymNodeBondsResponse, PagedNymNodeDetailsResponse, - PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse, UnbondedNodeResponse, - UnbondedNymNode, + PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse, StakeSaturationResponse, + UnbondedNodeResponse, UnbondedNymNode, }; use nym_mixnet_contract_common::reward_params::WorkFactor; use nym_mixnet_contract_common::{ diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index f695bb22f7..ad71782c44 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -64,62 +64,6 @@ impl RewardedSet { pub fn rewarded_set_size(&self) -> usize { self.active_set_size() + self.standby.len() } - - pub fn try_get_mix_layer(&self, node_id: &NodeId) -> Option { - if self.layer1.contains(node_id) { - Some(1) - } else if self.layer2.contains(node_id) { - Some(2) - } else if self.layer3.contains(node_id) { - Some(3) - } else { - None - } - } - - pub fn is_entry(&self, node_id: &NodeId) -> bool { - self.entry_gateways.contains(node_id) - } - - pub fn is_exit(&self, node_id: &NodeId) -> bool { - self.exit_gateways.contains(node_id) - } - - pub fn is_active_mixnode(&self, node_id: &NodeId) -> bool { - self.layer1.contains(node_id) - || self.layer2.contains(node_id) - || self.layer3.contains(node_id) - } - - pub fn gateways(&self) -> Vec { - let mut gateways = Vec::with_capacity(self.entry_gateways.len() + self.exit_gateways.len()); - for entry in &self.entry_gateways { - gateways.push(*entry) - } - for exit in &self.exit_gateways { - gateways.push(*exit) - } - gateways - } - - pub fn active_mixnodes(&self) -> Vec { - let mut mixnodes = - Vec::with_capacity(self.layer1.len() + self.layer2.len() + self.layer3.len()); - for mix in &self.layer1 { - mixnodes.push(*mix) - } - for mix in &self.layer2 { - mixnodes.push(*mix) - } - for mix in &self.layer3 { - mixnodes.push(*mix) - } - mixnodes - } - - pub fn is_standby(&self, node_id: &NodeId) -> bool { - self.standby.contains(node_id) - } } #[cw_serde] diff --git a/nym-api/nym-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs index 072a2aa549..196b351eeb 100644 --- a/nym-api/nym-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -8,6 +8,7 @@ use crate::legacy::{ use crate::nym_nodes::{BasicEntryInformation, NodeRole, SkimmedNode}; use crate::pagination::PaginatedResponse; use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; +use nym_mixnet_contract_common::nym_node::Role; use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams}; use nym_mixnet_contract_common::rewarding::RewardEstimate; use nym_mixnet_contract_common::{IdentityKey, Interval, MixNode, NodeId, Percent}; @@ -125,6 +126,7 @@ pub struct NodePerformance { #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct NodeAnnotation { pub last_24h_performance: Performance, + pub current_role: Option, } #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] diff --git a/nym-api/src/epoch_operations/rewarding.rs b/nym-api/src/epoch_operations/rewarding.rs index 624ec520b4..af4d29db4a 100644 --- a/nym-api/src/epoch_operations/rewarding.rs +++ b/nym-api/src/epoch_operations/rewarding.rs @@ -84,6 +84,7 @@ impl EpochAdvancer { .rewarded_set_owned() .await .into_inner() + .into() } }; diff --git a/nym-api/src/network_monitor/monitor/preparer.rs b/nym-api/src/network_monitor/monitor/preparer.rs index 0f139b05d0..3567563e63 100644 --- a/nym-api/src/network_monitor/monitor/preparer.rs +++ b/nym-api/src/network_monitor/monitor/preparer.rs @@ -4,7 +4,7 @@ use crate::network_monitor::monitor::sender::GatewayPackets; use crate::network_monitor::test_route::TestRoute; use crate::node_describe_cache::{DescribedNodes, NodeDescriptionTopologyExt}; -use crate::nym_contract_cache::cache::NymContractCache; +use crate::nym_contract_cache::cache::{CachedRewardedSet, NymContractCache}; use crate::support::caching::cache::SharedCache; use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer}; use nym_api_requests::models::NymNodeDescription; @@ -275,7 +275,7 @@ impl PacketPreparer { &self, rng: &mut R, blacklist: &mut HashSet, - rewarded_set: &RewardedSet, + rewarded_set: &CachedRewardedSet, legacy_mixnodes: Vec, mixing_nym_nodes: impl Iterator + 'a, ) -> HashMap> { @@ -477,7 +477,7 @@ impl PacketPreparer { fn nym_node_to_legacy_mix( &self, rng: &mut R, - rewarded_set: &RewardedSet, + rewarded_set: &CachedRewardedSet, mixing_nym_node: &NymNodeDescription, ) -> Option { let maybe_explicit_layer = rewarded_set 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 b60f4d34a6..d532308c68 100644 --- a/nym-api/src/node_status_api/cache/node_sets.rs +++ b/nym-api/src/node_status_api/cache/node_sets.rs @@ -3,12 +3,13 @@ use crate::node_status_api::helpers::RewardedSetStatus; use crate::node_status_api::reward_estimate::{compute_apy_from_reward, compute_reward_estimate}; +use crate::nym_contract_cache::cache::CachedRewardedSet; use crate::support::storage::NymApiStorage; use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer}; use nym_api_requests::models::{ GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation, NodePerformance, }; -use nym_mixnet_contract_common::{reward_params::Performance, Interval, NodeId, RewardedSet}; +use nym_mixnet_contract_common::{reward_params::Performance, Interval, NodeId}; use nym_mixnet_contract_common::{NymNodeDetails, RewardingParams}; use nym_topology::NetworkAddress; use std::collections::{HashMap, HashSet}; @@ -46,7 +47,7 @@ pub(super) async fn get_gateway_performance_from_storage( } // TODO: this might have to be moved to a different file if other places also rely on this functionality -fn get_rewarded_set_status(rewarded_set: &RewardedSet, node_id: NodeId) -> RewardedSetStatus { +fn get_rewarded_set_status(rewarded_set: &CachedRewardedSet, node_id: NodeId) -> RewardedSetStatus { if rewarded_set.is_standby(&node_id) { RewardedSetStatus::Standby } else if rewarded_set.is_active_mixnode(&node_id) { @@ -61,7 +62,7 @@ pub(super) async fn annotate_legacy_mixnodes_nodes_with_details( mixnodes: Vec, interval_reward_params: RewardingParams, current_interval: Interval, - rewarded_set: &RewardedSet, + rewarded_set: &CachedRewardedSet, blacklist: &HashSet, ) -> HashMap { let mut annotated = HashMap::new(); @@ -188,6 +189,7 @@ pub(crate) async fn produce_node_annotations( legacy_mixnodes: &[LegacyMixNodeDetailsWithLayer], legacy_gateways: &[LegacyGatewayBondWithId], nym_nodes: &[NymNodeDetails], + rewarded_set: &CachedRewardedSet, current_interval: Interval, ) -> HashMap { let mut annotations = HashMap::new(); @@ -207,6 +209,7 @@ pub(crate) async fn produce_node_annotations( legacy_mix.mix_id(), NodeAnnotation { last_24h_performance: perf, + current_role: rewarded_set.role(legacy_mix.mix_id()), }, ); } @@ -226,6 +229,7 @@ pub(crate) async fn produce_node_annotations( legacy_gateway.node_id, NodeAnnotation { last_24h_performance: perf, + current_role: rewarded_set.role(legacy_gateway.node_id), }, ); } @@ -245,6 +249,7 @@ pub(crate) async fn produce_node_annotations( nym_node.node_id(), NodeAnnotation { last_24h_performance: perf, + current_role: rewarded_set.role(nym_node.node_id()), }, ); } diff --git a/nym-api/src/node_status_api/cache/refresher.rs b/nym-api/src/node_status_api/cache/refresher.rs index aa664885a9..900051f911 100644 --- a/nym-api/src/node_status_api/cache/refresher.rs +++ b/nym-api/src/node_status_api/cache/refresher.rs @@ -149,6 +149,7 @@ impl NodeStatusCacheRefresher { &mixnode_details, &gateway_bonds, &nym_nodes, + &rewarded_set, current_interval, ) .await; diff --git a/nym-api/src/nym_contract_cache/cache/data.rs b/nym-api/src/nym_contract_cache/cache/data.rs index 2ced76b8fe..622286be24 100644 --- a/nym-api/src/nym_contract_cache/cache/data.rs +++ b/nym-api/src/nym_contract_cache/cache/data.rs @@ -4,15 +4,136 @@ use crate::support::caching::Cache; use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer}; use nym_contracts_common::ContractBuildInformation; +use nym_mixnet_contract_common::nym_node::Role; use nym_mixnet_contract_common::{Interval, NodeId, NymNodeDetails, RewardedSet, RewardingParams}; use nym_validator_client::nyxd::AccountId; use std::collections::{HashMap, HashSet}; +#[derive(Default, Clone)] +pub(crate) struct CachedRewardedSet { + pub(crate) entry_gateways: HashSet, + + pub(crate) exit_gateways: HashSet, + + pub(crate) layer1: HashSet, + + pub(crate) layer2: HashSet, + + pub(crate) layer3: HashSet, + + pub(crate) standby: HashSet, +} + +impl From for CachedRewardedSet { + fn from(value: RewardedSet) -> Self { + CachedRewardedSet { + entry_gateways: value.entry_gateways.into_iter().collect(), + exit_gateways: value.exit_gateways.into_iter().collect(), + layer1: value.layer1.into_iter().collect(), + layer2: value.layer2.into_iter().collect(), + layer3: value.layer3.into_iter().collect(), + standby: value.standby.into_iter().collect(), + } + } +} + +impl From for RewardedSet { + fn from(value: CachedRewardedSet) -> Self { + RewardedSet { + entry_gateways: value.entry_gateways.into_iter().collect(), + exit_gateways: value.exit_gateways.into_iter().collect(), + layer1: value.layer1.into_iter().collect(), + layer2: value.layer2.into_iter().collect(), + layer3: value.layer3.into_iter().collect(), + standby: value.standby.into_iter().collect(), + } + } +} + +impl CachedRewardedSet { + pub(crate) fn role(&self, node_id: NodeId) -> Option { + if self.entry_gateways.contains(&node_id) { + Some(Role::EntryGateway) + } else if self.exit_gateways.contains(&node_id) { + Some(Role::ExitGateway) + } else if self.layer1.contains(&node_id) { + Some(Role::Layer1) + } else if self.layer2.contains(&node_id) { + Some(Role::Layer2) + } else if self.layer3.contains(&node_id) { + Some(Role::Layer3) + } else if self.standby.contains(&node_id) { + Some(Role::Standby) + } else { + None + } + } + + pub fn active_set_size(&self) -> usize { + self.entry_gateways.len() + + self.exit_gateways.len() + + self.layer1.len() + + self.layer2.len() + + self.layer3.len() + } + + pub fn rewarded_set_size(&self) -> usize { + self.active_set_size() + self.standby.len() + } + + pub fn try_get_mix_layer(&self, node_id: &NodeId) -> Option { + if self.layer1.contains(node_id) { + Some(1) + } else if self.layer2.contains(node_id) { + Some(2) + } else if self.layer3.contains(node_id) { + Some(3) + } else { + None + } + } + + pub fn is_standby(&self, node_id: &NodeId) -> bool { + self.standby.contains(node_id) + } + + pub fn is_entry(&self, node_id: &NodeId) -> bool { + self.entry_gateways.contains(node_id) + } + + pub fn is_exit(&self, node_id: &NodeId) -> bool { + self.exit_gateways.contains(node_id) + } + + pub fn is_active_mixnode(&self, node_id: &NodeId) -> bool { + self.layer1.contains(node_id) + || self.layer2.contains(node_id) + || self.layer3.contains(node_id) + } + + pub(crate) fn gateways(&self) -> HashSet { + let mut gateways = + HashSet::with_capacity(self.entry_gateways.len() + self.exit_gateways.len()); + gateways.extend(&self.entry_gateways); + gateways.extend(&self.exit_gateways); + gateways + } + + pub(crate) fn active_mixnodes(&self) -> HashSet { + let mut mixnodes = + HashSet::with_capacity(self.layer1.len() + self.layer2.len() + self.layer3.len()); + mixnodes.extend(&self.layer1); + mixnodes.extend(&self.layer2); + mixnodes.extend(&self.layer3); + mixnodes + } +} + pub(crate) struct ValidatorCacheData { pub(crate) legacy_mixnodes: Cache>, pub(crate) legacy_gateways: Cache>, pub(crate) nym_nodes: Cache>, - pub(crate) rewarded_set: Cache, + pub(crate) rewarded_set: Cache, // this purposely does not deal with nym-nodes as they don't have a concept of a blacklist. // instead clients are meant to be filtering out them themselves based on the provided scores. diff --git a/nym-api/src/nym_contract_cache/cache/mod.rs b/nym-api/src/nym_contract_cache/cache/mod.rs index 8f7e26caa5..1f40768a9e 100644 --- a/nym-api/src/nym_contract_cache/cache/mod.rs +++ b/nym-api/src/nym_contract_cache/cache/mod.rs @@ -24,6 +24,8 @@ use tracing::{debug, error}; mod data; pub(crate) mod refresher; +pub(crate) use self::data::CachedRewardedSet; + const CACHE_TIMEOUT_MS: u64 = 100; #[derive(Clone)] @@ -259,11 +261,11 @@ impl NymContractCache { todo!() } - pub async fn rewarded_set(&self) -> Option>> { + pub async fn rewarded_set(&self) -> Option>> { self.get(|cache| &cache.rewarded_set).await } - pub async fn rewarded_set_owned(&self) -> Cache { + pub async fn rewarded_set_owned(&self) -> Cache { self.get_owned(|cache| cache.rewarded_set.clone_cache()) .await .unwrap_or_default() diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index a570bfb00b..acaaa6d995 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -81,6 +81,8 @@ fn main() { mixnet::bond::migrate_legacy_gateway, mixnet::bond::update_nymnode_config, mixnet::bond::get_nymnode_performance, + mixnet::bond::get_nymnode_uptime, + mixnet::bond::get_nymnode_stake_saturation, mixnet::delegate::delegate_to_mixnode, mixnet::delegate::get_pending_delegator_rewards, mixnet::delegate::get_pending_delegation_events, @@ -123,6 +125,8 @@ fn main() { nym_api::status::mixnode_stake_saturation, nym_api::status::mixnode_status, nym_api::status::gateway_report, + nym_api::status::get_nymnode_role, + nym_api::status::get_nymnode_annotation, vesting::rewards::vesting_claim_delegator_reward, vesting::rewards::vesting_claim_operator_reward, vesting::bond::vesting_bond_gateway, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs index ba3d03b6ba..28075562ec 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -11,7 +11,7 @@ use crate::{nyxd_client, Gateway, MixNode}; use log::info; use nym_contracts_common::signing::MessageSignature; use nym_mixnet_contract_common::gateway::GatewayConfigUpdate; -use nym_mixnet_contract_common::nym_node::NodeConfigUpdate; +use nym_mixnet_contract_common::nym_node::{NodeConfigUpdate, StakeSaturationResponse}; use nym_mixnet_contract_common::{MixNodeConfigUpdate, NodeId, NymNode}; use nym_node_requests::api::client::NymNodeApiClientExt; use nym_node_requests::api::v1::node::models::NodeDescription; @@ -682,13 +682,9 @@ pub async fn update_nymnode_config( #[tauri::command] pub async fn get_nymnode_performance( + node_id: NodeId, state: tauri::State<'_, WalletState>, ) -> Result, BackendError> { - let Some(details) = nym_node_bond_details(state.clone()).await? else { - return Ok(None); - }; - let node_id = details.bond_information.node_id; - log::trace!(" >>> Get node performance: node_id = {node_id}"); let guard = state.read().await; let res = guard @@ -700,3 +696,33 @@ pub async fn get_nymnode_performance( Ok(res.performance) } + +#[tauri::command] +pub async fn get_nymnode_uptime( + node_id: NodeId, + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Get legacy nymnode uptime"); + + let performance = get_nymnode_performance(node_id, state).await?; + + // convert value in range 0.0 - 1.0 into 0-100 + Ok(performance + .map(|p| (p * 100.).floor() as u8) + .unwrap_or_default()) +} + +#[tauri::command] +pub async fn get_nymnode_stake_saturation( + node_id: NodeId, + state: tauri::State<'_, WalletState>, +) -> Result { + log::trace!(" >>> Get node stake saturation: node_id = {node_id}"); + + let res = nyxd_client!(state) + .get_node_stake_saturation(node_id) + .await?; + log::trace!(" <<< {res:?}"); + + Ok(res) +} diff --git a/nym-wallet/src-tauri/src/operations/nym_api/status.rs b/nym-wallet/src-tauri/src/operations/nym_api/status.rs index 916dac0cea..1fa8dc70c4 100644 --- a/nym-wallet/src-tauri/src/operations/nym_api/status.rs +++ b/nym-wallet/src-tauri/src/operations/nym_api/status.rs @@ -4,14 +4,15 @@ use crate::api_client; use crate::error::BackendError; use crate::state::WalletState; +use nym_mixnet_contract_common::nym_node::Role; use nym_mixnet_contract_common::{ reward_params::Performance, Coin, IdentityKeyRef, NodeId, Percent, }; use nym_validator_client::client::NymApiClientExt; use nym_validator_client::models::{ - ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse, - InclusionProbabilityResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse, - RewardEstimationResponse, StakeSaturationResponse, + AnnotationResponse, ComputeRewardEstParam, GatewayCoreStatusResponse, + GatewayStatusReportResponse, InclusionProbabilityResponse, MixnodeCoreStatusResponse, + MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, }; #[tauri::command] @@ -104,3 +105,20 @@ pub async fn mixnode_inclusion_probability( .get_mixnode_inclusion_probability(mix_id) .await?) } + +#[tauri::command] +pub async fn get_nymnode_role( + node_id: NodeId, + state: tauri::State<'_, WalletState>, +) -> Result, BackendError> { + let annotation = get_nymnode_annotation(node_id, state).await?; + Ok(annotation.annotation.and_then(|n| n.current_role)) +} + +#[tauri::command] +pub async fn get_nymnode_annotation( + node_id: NodeId, + state: tauri::State<'_, WalletState>, +) -> Result { + Ok(api_client!(state).get_node_annotation(node_id).await?) +}