use combination of stake and performance for rewarded set selection
This commit is contained in:
@@ -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::*;
|
||||||
|
|||||||
@@ -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?;
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user