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>
// 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<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)]
mod tests {
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::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...");
@@ -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<Vec<LayerAssignment>, 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<u32> = 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<MixnodeWithStakeAndPerformance>,
nodes_to_select: u32,
) -> Result<Vec<MixNodeDetails>, RewardingError> {
) -> Result<Vec<MixnodeWithStakeAndPerformance>, 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::<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)
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<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(
&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<MixnodeWithStakeAndPerformance>,
) -> 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?;
+2 -19
View File
@@ -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
}
}