use combination of stake and performance for rewarded set selection

This commit is contained in:
Jędrzej Stuczyński
2023-03-28 11:25:43 +01:00
parent de2406a2c7
commit e6957e7a99
4 changed files with 97 additions and 35 deletions
+36 -1
View File
@@ -1,9 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::epoch_operations::RewardedSetUpdater;
use cosmwasm_std::{Decimal, Fraction}; use cosmwasm_std::{Decimal, Fraction};
use nym_mixnet_contract_common::reward_params::Performance; 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)] #[derive(Debug, Clone, Copy)]
pub(crate) struct MixnodeWithPerformance { 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<MixnodeWithPerformance> {
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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+2 -2
View File
@@ -17,9 +17,9 @@ use crate::nym_contract_cache::cache::NymContractCache;
use crate::support::nyxd::Client; use crate::support::nyxd::Client;
use crate::support::storage::NymApiStorage; use crate::support::storage::NymApiStorage;
use error::RewardingError; use error::RewardingError;
pub(crate) use helpers::MixnodeWithPerformance;
use nym_mixnet_contract_common::{CurrentIntervalResponse, Interval}; use nym_mixnet_contract_common::{CurrentIntervalResponse, Interval};
use nym_task::{TaskClient, TaskManager}; use nym_task::{TaskClient, TaskManager};
pub(crate) use rewarding::MixnodeWithPerformance;
use std::collections::HashSet; use std::collections::HashSet;
use std::time::Duration; use std::time::Duration;
use tokio::time::sleep; 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 // note: those operations don't really have to be atomic, so it's fine to send them
// as separate transactions // as separate transactions
self.reconcile_epoch_events().await?; 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?; .await?;
log::info!("Purging old node statuses from the storage..."); log::info!("Purging old node statuses from the storage...");
@@ -4,17 +4,36 @@
use crate::epoch_operations::error::RewardingError; use crate::epoch_operations::error::RewardingError;
use crate::epoch_operations::helpers::stake_to_f64; use crate::epoch_operations::helpers::stake_to_f64;
use crate::RewardedSetUpdater; use crate::RewardedSetUpdater;
use cosmwasm_std::Decimal;
use nym_mixnet_contract_common::families::FamilyHead; 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::prelude::SliceRandom;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use std::collections::HashMap; 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 { impl RewardedSetUpdater {
// Needs to run for active and reserve sets separatley, as it does not preserve order // Needs to run for active and reserve sets separatley, as it does not preserve order
async fn determine_layers( async fn determine_layers(
&self, &self,
set: &[MixNodeDetails], set: &[MixnodeWithStakeAndPerformance],
) -> Result<Vec<LayerAssignment>, RewardingError> { ) -> Result<Vec<LayerAssignment>, RewardingError> {
let mut assignments = Vec::with_capacity(set.len()); let mut assignments = Vec::with_capacity(set.len());
let target_layer_count = set.len() / 3; let target_layer_count = set.len() / 3;
@@ -30,11 +49,11 @@ impl RewardedSetUpdater {
let mut families = HashMap::new(); let mut families = HashMap::new();
for node in set.iter() { 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<u32> = families.entry(fh.identity()).or_default(); let family: &mut Vec<u32> = families.entry(fh.identity()).or_default();
family.push(node.mix_id()) family.push(node.mix_id)
} else { } else {
regular_nodes.push(node.mix_id()) regular_nodes.push(node.mix_id)
} }
} }
@@ -82,9 +101,9 @@ impl RewardedSetUpdater {
fn determine_rewarded_set( fn determine_rewarded_set(
&self, &self,
mixnodes: &[MixNodeDetails], mixnodes: Vec<MixnodeWithStakeAndPerformance>,
nodes_to_select: u32, nodes_to_select: u32,
) -> Result<Vec<MixNodeDetails>, RewardingError> { ) -> Result<Vec<MixnodeWithStakeAndPerformance>, RewardingError> {
if mixnodes.is_empty() { if mixnodes.is_empty() {
return Ok(Vec::new()); return Ok(Vec::new());
} }
@@ -93,10 +112,10 @@ impl RewardedSetUpdater {
// generate list of mixnodes and their relatively weight (by total stake) // generate list of mixnodes and their relatively weight (by total stake)
let choices = mixnodes let choices = mixnodes
.iter() .into_iter()
.map(|mix| { .map(|mix| {
let total_stake = stake_to_f64(mix.total_stake()); let weight = mix.to_selection_weight();
(mix.to_owned(), total_stake) (mix, weight)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@@ -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) // - 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 Ok(choices
.choose_multiple_weighted(&mut rng, nodes_to_select as usize, |item| item.1)? .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()) .collect())
} }
async fn attach_performance(
&self,
interval: Interval,
mixnodes: &[MixNodeDetails],
) -> Vec<MixnodeWithStakeAndPerformance> {
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( pub(super) async fn update_rewarded_set_and_advance_epoch(
&self, &self,
current_interval: Interval,
all_mixnodes: &[MixNodeDetails], all_mixnodes: &[MixNodeDetails],
) -> Result<(), RewardingError> { ) -> Result<(), RewardingError> {
let epoch_status = self.nyxd_client.get_current_epoch_status().await?; let epoch_status = self.nyxd_client.get_current_epoch_status().await?;
match epoch_status.state { match epoch_status.state {
EpochState::AdvancingEpoch => { EpochState::AdvancingEpoch => {
log::info!("Advancing epoch and updating the rewarded set..."); 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 if let Err(err) = self
._update_rewarded_set_and_advance_epoch(all_mixnodes) ._update_rewarded_set_and_advance_epoch(nodes_with_performance)
.await .await
{ {
log::error!("FAILED to advance the current epoch... - {err}"); log::error!("FAILED to advance the current epoch... - {err}");
@@ -143,7 +187,7 @@ impl RewardedSetUpdater {
async fn _update_rewarded_set_and_advance_epoch( async fn _update_rewarded_set_and_advance_epoch(
&self, &self,
all_mixnodes: &[MixNodeDetails], all_mixnodes: Vec<MixnodeWithStakeAndPerformance>,
) -> Result<(), RewardingError> { ) -> Result<(), RewardingError> {
// we grab rewarding parameters here as they might have gotten updated when performing epoch actions // 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?; let rewarding_parameters = self.nyxd_client.get_current_rewarding_parameters().await?;
+2 -19
View File
@@ -4,8 +4,7 @@
use crate::epoch_operations::error::RewardingError; use crate::epoch_operations::error::RewardingError;
use crate::epoch_operations::helpers::MixnodeWithPerformance; use crate::epoch_operations::helpers::MixnodeWithPerformance;
use crate::RewardedSetUpdater; use crate::RewardedSetUpdater;
use nym_mixnet_contract_common::reward_params::Performance; use nym_mixnet_contract_common::{EpochState, Interval, MixId};
use nym_mixnet_contract_common::{EpochState, ExecuteMsg, Interval, MixId};
impl RewardedSetUpdater { impl RewardedSetUpdater {
pub(super) async fn reward_current_rewarded_set( pub(super) async fn reward_current_rewarded_set(
@@ -85,22 +84,6 @@ impl RewardedSetUpdater {
} }
}; };
let mut eligible_nodes = Vec::with_capacity(rewarded_set.len()); self.load_nodes_performance(&interval, &rewarded_set).await
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
} }
} }