diff --git a/nym-api/src/epoch_operations/helpers.rs b/nym-api/src/epoch_operations/helpers.rs index 5f01a2474a..2f82df8248 100644 --- a/nym-api/src/epoch_operations/helpers.rs +++ b/nym-api/src/epoch_operations/helpers.rs @@ -1,9 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::epoch_operations::RewardedSetUpdater; use cosmwasm_std::{Decimal, Fraction}; use nym_mixnet_contract_common::reward_params::Performance; -use nym_mixnet_contract_common::{ExecuteMsg, MixId}; +use nym_mixnet_contract_common::{ExecuteMsg, Interval, MixId}; #[derive(Debug, Clone, Copy)] pub(crate) struct MixnodeWithPerformance { @@ -35,6 +36,40 @@ pub(super) fn stake_to_f64(stake: Decimal) -> f64 { } } +impl RewardedSetUpdater { + pub(crate) async fn load_performance( + &self, + interval: &Interval, + mix_id: MixId, + ) -> MixnodeWithPerformance { + let uptime = self + .storage + .get_average_mixnode_uptime_in_the_last_24hrs( + mix_id, + interval.current_epoch_end_unix_timestamp(), + ) + .await + .unwrap_or_default(); + + MixnodeWithPerformance { + mix_id, + performance: uptime.into(), + } + } + + pub(crate) async fn load_nodes_performance( + &self, + interval: &Interval, + nodes: &[MixId], + ) -> Vec { + let mut with_performance = Vec::with_capacity(nodes.len()); + for mix_id in nodes { + with_performance.push(self.load_performance(interval, *mix_id).await) + } + with_performance + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/nym-api/src/epoch_operations/mod.rs b/nym-api/src/epoch_operations/mod.rs index 5f598bcfe8..d1d58998cc 100644 --- a/nym-api/src/epoch_operations/mod.rs +++ b/nym-api/src/epoch_operations/mod.rs @@ -17,9 +17,9 @@ use crate::nym_contract_cache::cache::NymContractCache; use crate::support::nyxd::Client; use crate::support::storage::NymApiStorage; use error::RewardingError; +pub(crate) use helpers::MixnodeWithPerformance; use nym_mixnet_contract_common::{CurrentIntervalResponse, Interval}; use nym_task::{TaskClient, TaskManager}; -pub(crate) use rewarding::MixnodeWithPerformance; use std::collections::HashSet; use std::time::Duration; use tokio::time::sleep; @@ -133,7 +133,7 @@ impl RewardedSetUpdater { // note: those operations don't really have to be atomic, so it's fine to send them // as separate transactions self.reconcile_epoch_events().await?; - self.update_rewarded_set_and_advance_epoch(&all_mixnodes) + self.update_rewarded_set_and_advance_epoch(interval, &all_mixnodes) .await?; log::info!("Purging old node statuses from the storage..."); diff --git a/nym-api/src/epoch_operations/rewarded_set_assignment.rs b/nym-api/src/epoch_operations/rewarded_set_assignment.rs index adb7508867..12bc33eaae 100644 --- a/nym-api/src/epoch_operations/rewarded_set_assignment.rs +++ b/nym-api/src/epoch_operations/rewarded_set_assignment.rs @@ -4,17 +4,36 @@ use crate::epoch_operations::error::RewardingError; use crate::epoch_operations::helpers::stake_to_f64; use crate::RewardedSetUpdater; +use cosmwasm_std::Decimal; use nym_mixnet_contract_common::families::FamilyHead; -use nym_mixnet_contract_common::{EpochState, IdentityKey, Layer, LayerAssignment, MixNodeDetails}; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::{ + EpochState, IdentityKey, Interval, Layer, LayerAssignment, MixId, MixNodeDetails, +}; use rand::prelude::SliceRandom; use rand::rngs::OsRng; use std::collections::HashMap; +#[derive(Debug, Clone)] +struct MixnodeWithStakeAndPerformance { + mix_id: MixId, + identity: IdentityKey, + total_stake: Decimal, + performance: Performance, +} + +impl MixnodeWithStakeAndPerformance { + fn to_selection_weight(&self) -> f64 { + let scaled_stake = self.total_stake * self.performance; + stake_to_f64(scaled_stake) + } +} + impl RewardedSetUpdater { // Needs to run for active and reserve sets separatley, as it does not preserve order async fn determine_layers( &self, - set: &[MixNodeDetails], + set: &[MixnodeWithStakeAndPerformance], ) -> Result, RewardingError> { let mut assignments = Vec::with_capacity(set.len()); let target_layer_count = set.len() / 3; @@ -30,11 +49,11 @@ impl RewardedSetUpdater { let mut families = HashMap::new(); for node in set.iter() { - if let Some(fh) = mix_to_family.get(node.bond_information.identity()) { + if let Some(fh) = mix_to_family.get(&node.identity) { let family: &mut Vec = families.entry(fh.identity()).or_default(); - family.push(node.mix_id()) + family.push(node.mix_id) } else { - regular_nodes.push(node.mix_id()) + regular_nodes.push(node.mix_id) } } @@ -82,9 +101,9 @@ impl RewardedSetUpdater { fn determine_rewarded_set( &self, - mixnodes: &[MixNodeDetails], + mixnodes: Vec, nodes_to_select: u32, - ) -> Result, RewardingError> { + ) -> Result, RewardingError> { if mixnodes.is_empty() { return Ok(Vec::new()); } @@ -93,10 +112,10 @@ impl RewardedSetUpdater { // generate list of mixnodes and their relatively weight (by total stake) let choices = mixnodes - .iter() + .into_iter() .map(|mix| { - let total_stake = stake_to_f64(mix.total_stake()); - (mix.to_owned(), total_stake) + let weight = mix.to_selection_weight(); + (mix, weight) }) .collect::>(); @@ -107,20 +126,45 @@ impl RewardedSetUpdater { // - we have more than u32::MAX values (which is incredibly unrealistic to have 4B mixnodes bonded... literally every other person on the planet would need one) Ok(choices .choose_multiple_weighted(&mut rng, nodes_to_select as usize, |item| item.1)? - .map(|(mix, _weight)| mix.to_owned()) + .map(|(mix, _weight)| mix.clone()) .collect()) } + async fn attach_performance( + &self, + interval: Interval, + mixnodes: &[MixNodeDetails], + ) -> Vec { + let mut with_performance = Vec::with_capacity(mixnodes.len()); + for mix in mixnodes { + with_performance.push(MixnodeWithStakeAndPerformance { + mix_id: mix.mix_id(), + identity: mix.bond_information.identity().to_owned(), + total_stake: mix.total_stake(), + performance: self + .load_performance(&interval, mix.mix_id()) + .await + .performance, + }) + } + with_performance + } + pub(super) async fn update_rewarded_set_and_advance_epoch( &self, + current_interval: Interval, all_mixnodes: &[MixNodeDetails], ) -> Result<(), RewardingError> { let epoch_status = self.nyxd_client.get_current_epoch_status().await?; match epoch_status.state { EpochState::AdvancingEpoch => { log::info!("Advancing epoch and updating the rewarded set..."); + let nodes_with_performance = self + .attach_performance(current_interval, all_mixnodes) + .await; + if let Err(err) = self - ._update_rewarded_set_and_advance_epoch(all_mixnodes) + ._update_rewarded_set_and_advance_epoch(nodes_with_performance) .await { log::error!("FAILED to advance the current epoch... - {err}"); @@ -143,7 +187,7 @@ impl RewardedSetUpdater { async fn _update_rewarded_set_and_advance_epoch( &self, - all_mixnodes: &[MixNodeDetails], + all_mixnodes: Vec, ) -> Result<(), RewardingError> { // we grab rewarding parameters here as they might have gotten updated when performing epoch actions let rewarding_parameters = self.nyxd_client.get_current_rewarding_parameters().await?; diff --git a/nym-api/src/epoch_operations/rewarding.rs b/nym-api/src/epoch_operations/rewarding.rs index f4f506ca00..d050f7efd6 100644 --- a/nym-api/src/epoch_operations/rewarding.rs +++ b/nym-api/src/epoch_operations/rewarding.rs @@ -4,8 +4,7 @@ use crate::epoch_operations::error::RewardingError; use crate::epoch_operations::helpers::MixnodeWithPerformance; use crate::RewardedSetUpdater; -use nym_mixnet_contract_common::reward_params::Performance; -use nym_mixnet_contract_common::{EpochState, ExecuteMsg, Interval, MixId}; +use nym_mixnet_contract_common::{EpochState, Interval, MixId}; impl RewardedSetUpdater { pub(super) async fn reward_current_rewarded_set( @@ -85,22 +84,6 @@ impl RewardedSetUpdater { } }; - let mut eligible_nodes = Vec::with_capacity(rewarded_set.len()); - for mix_id in rewarded_set { - let uptime = self - .storage - .get_average_mixnode_uptime_in_the_last_24hrs( - mix_id, - interval.current_epoch_end_unix_timestamp(), - ) - .await - .unwrap_or_default(); - eligible_nodes.push(MixnodeWithPerformance { - mix_id, - performance: uptime.into(), - }) - } - - eligible_nodes + self.load_nodes_performance(&interval, &rewarded_set).await } }