Node family management (#1670)

* Family management messages

* Add family queries

* Add queries to client

* Layer assignment message

* Paged family queries, annotate mixnodes with family

* Add layer assignments to epoch operations

* Remove family layer peristence

* Add NotImplemented error for kick

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
This commit is contained in:
Drazen Urch
2022-12-05 09:36:06 +01:00
committed by GitHub
parent ab4e39e1b3
commit 7e3bc2d6bb
37 changed files with 1768 additions and 1871 deletions
+20 -1
View File
@@ -5,6 +5,7 @@ use crate::nymd_client::Client;
use crate::storage::ValidatorApiStorage;
use ::time::OffsetDateTime;
use anyhow::Result;
use mixnet_contract_common::families::FamilyHead;
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::reward_params::{Performance, RewardingParams};
use mixnet_contract_common::{
@@ -141,7 +142,11 @@ impl<C> ValidatorCacheRefresher<C> {
interval_reward_params: RewardingParams,
current_interval: Interval,
rewarded_set: &HashMap<MixId, RewardedSetNodeStatus>,
mix_to_family: Vec<(IdentityKey, FamilyHead)>,
) -> Vec<MixNodeBondAnnotated> {
let mix_to_family = mix_to_family
.into_iter()
.collect::<HashMap<IdentityKey, FamilyHead>>();
let mut annotated = Vec::new();
for mixnode in mixnodes {
let stake_saturation = mixnode
@@ -174,6 +179,10 @@ impl<C> ValidatorCacheRefresher<C> {
current_interval,
);
let family = mix_to_family
.get(&mixnode.bond_information.identity().to_string())
.cloned();
annotated.push(MixNodeBondAnnotated {
mixnode_details: mixnode,
stake_saturation,
@@ -181,6 +190,7 @@ impl<C> ValidatorCacheRefresher<C> {
performance,
estimated_operator_apy,
estimated_delegators_apy,
family,
});
}
annotated
@@ -226,10 +236,18 @@ impl<C> ValidatorCacheRefresher<C> {
let mixnodes = self.nymd_client.get_mixnodes().await?;
let gateways = self.nymd_client.get_gateways().await?;
let mix_to_family = self.nymd_client.get_all_family_members().await?;
let rewarded_set = self.get_rewarded_set_map().await;
let mixnodes = self
.annotate_node_with_details(mixnodes, rewarding_params, current_interval, &rewarded_set)
.annotate_node_with_details(
mixnodes,
rewarding_params,
current_interval,
&rewarded_set,
mix_to_family,
)
.await;
let (rewarded_set, active_set) =
@@ -322,6 +340,7 @@ impl ValidatorCache {
})
}
#[allow(clippy::too_many_arguments)]
async fn update_cache(
&self,
mixnodes: Vec<MixNodeBondAnnotated>,
@@ -25,6 +25,11 @@ pub enum RewardingError {
#[from]
source: std::num::TryFromIntError,
},
#[error("{source}")]
WeightedError {
#[from]
source: rand::distributions::WeightedError,
},
}
impl From<NymdError> for RewardingError {
+72 -22
View File
@@ -19,11 +19,14 @@ use crate::storage::ValidatorApiStorage;
use mixnet_contract_common::{
reward_params::Performance, CurrentIntervalResponse, ExecuteMsg, Interval, MixId,
};
use mixnet_contract_common::{Layer, LayerAssignment};
use rand::prelude::SliceRandom;
use rand::rngs::OsRng;
use std::collections::HashMap;
use std::collections::HashSet;
use std::time::Duration;
use tokio::time::sleep;
use validator_api_requests::models::MixNodeBondAnnotated;
use validator_client::nymd::SigningNymdClient;
pub(crate) mod error;
@@ -32,7 +35,6 @@ mod helpers;
use crate::epoch_operations::helpers::stake_to_f64;
use crate::node_status_api::ONE_DAY;
use error::RewardingError;
use mixnet_contract_common::mixnode::MixNodeDetails;
use task::ShutdownListener;
#[derive(Debug, Clone, Copy)]
@@ -57,6 +59,16 @@ pub struct RewardedSetUpdater {
storage: ValidatorApiStorage,
}
// Weight of a layer being chose is reciprocal to current count in layer
fn layer_weight(l: &Layer, layer_assignments: &HashMap<Layer, f32>) -> f32 {
let total = layer_assignments.values().fold(0., |acc, i| acc + i);
if total == 0. {
1.
} else {
1. - (layer_assignments.get(l).unwrap_or(&0.) / total)
}
}
impl RewardedSetUpdater {
pub(crate) async fn current_interval_details(
&self,
@@ -76,23 +88,62 @@ impl RewardedSetUpdater {
})
}
async fn determine_layers(
&self,
rewarded_set: &[MixNodeBondAnnotated],
) -> Result<(Vec<LayerAssignment>, HashMap<String, Layer>), RewardingError> {
let mut families_in_layer: HashMap<String, Layer> = HashMap::new();
let mut assignments = vec![];
let mut layer_assignments: HashMap<Layer, f32> = HashMap::new();
let mut rng = OsRng;
let layers = vec![Layer::One, Layer::Two, Layer::Three];
for mix in rewarded_set {
// Get layer already assigned to nodes family, if any
let family_layer = mix
.family
.as_ref()
.and_then(|h| families_in_layer.get(h.identity()));
// Same node families are always assigned to the same layer, otherwise layer selected by a random weighted choice
let layer = if let Some(layer) = family_layer {
layer.to_owned()
} else {
layers
.choose_weighted(&mut rng, |l| layer_weight(l, &layer_assignments))?
.to_owned()
};
assignments.push(LayerAssignment::new(mix.mix_id(), layer));
// layer accounting
let layer_entry = layer_assignments.entry(layer).or_insert(0.);
*layer_entry += 1.;
if let Some(ref family) = mix.family {
families_in_layer.insert(family.identity().to_string(), layer);
}
}
Ok((assignments, families_in_layer))
}
fn determine_rewarded_set(
&self,
mixnodes: Vec<MixNodeDetails>,
mixnodes: &[MixNodeBondAnnotated],
nodes_to_select: u32,
) -> Vec<MixId> {
) -> Result<Vec<MixNodeBondAnnotated>, RewardingError> {
if mixnodes.is_empty() {
return Vec::new();
return Ok(Vec::new());
}
let mut rng = OsRng;
// generate list of mixnodes and their relatively weight (by total stake)
let choices = mixnodes
.into_iter()
.iter()
.map(|mix| {
let total_stake = stake_to_f64(mix.total_stake());
(mix.mix_id(), total_stake)
let total_stake = stake_to_f64(mix.mixnode_details.total_stake());
(mix.to_owned(), total_stake)
})
.collect::<Vec<_>>();
@@ -101,11 +152,10 @@ impl RewardedSetUpdater {
// - we have invalid weights, i.e. less than zero or NaNs - it shouldn't happen in our case as we safely cast down from u128
// - all weights are zero - it's impossible in our case as the list of nodes is not empty and weight is proportional to stake. You must have non-zero stake in order to bond
// - 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)
choices
.choose_multiple_weighted(&mut rng, nodes_to_select as usize, |item| item.1)
.unwrap()
.map(|(mix_id, _weight)| *mix_id)
.collect()
Ok(choices
.choose_multiple_weighted(&mut rng, nodes_to_select as usize, |item| item.1)?
.map(|(mix, _weight)| mix.to_owned())
.collect())
}
async fn reward_current_rewarded_set(
@@ -186,15 +236,19 @@ impl RewardedSetUpdater {
async fn update_rewarded_set_and_advance_epoch(
&self,
all_mixnodes: Vec<MixNodeDetails>,
all_mixnodes: &[MixNodeBondAnnotated],
) -> Result<(), RewardingError> {
// we grab rewarding parameters here as they might have gotten updated when performing epoch actions
let rewarding_parameters = self.nymd_client.get_current_rewarding_parameters().await?;
let new_rewarded_set =
self.determine_rewarded_set(all_mixnodes, rewarding_parameters.rewarded_set_size);
self.determine_rewarded_set(all_mixnodes, rewarding_parameters.rewarded_set_size)?;
let (layer_assignments, _families_in_layer) =
self.determine_layers(&new_rewarded_set).await?;
self.nymd_client
.advance_current_epoch(new_rewarded_set, rewarding_parameters.active_set_size)
.advance_current_epoch(layer_assignments, rewarding_parameters.active_set_size)
.await?;
Ok(())
@@ -219,15 +273,11 @@ impl RewardedSetUpdater {
let epoch_end = interval.current_epoch_end();
let all_nodes = self.validator_cache.mixnodes().await;
if all_nodes.is_empty() {
let all_mixnodes = self.validator_cache.mixnodes_detailed().await;
if all_mixnodes.is_empty() {
log::warn!("there don't seem to be any mixnodes on the network!")
}
// get list of all mixnodes BEFORE rewarding happens as to now be biased by rewards
// that might be given to them
let all_mixnodes = self.validator_cache.mixnodes().await;
// Reward all the nodes in the still current, soon to be previous rewarded set
log::info!("Rewarding the current rewarded set...");
if let Err(err) = self.reward_current_rewarded_set(interval).await {
@@ -257,7 +307,7 @@ impl RewardedSetUpdater {
log::info!("Advancing epoch and updating the rewarded set...");
if let Err(err) = self
.update_rewarded_set_and_advance_epoch(all_mixnodes)
.update_rewarded_set_and_advance_epoch(&all_mixnodes)
.await
{
log::error!("FAILED to advance the current epoch... - {}", err);
+21 -2
View File
@@ -4,10 +4,12 @@
use crate::config::Config;
use crate::epoch_operations::MixnodeToReward;
use config::defaults::{NymNetworkDetails, DEFAULT_VALIDATOR_API_PORT};
use mixnet_contract_common::families::{Family, FamilyHead};
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::reward_params::RewardingParams;
use mixnet_contract_common::{
CurrentIntervalResponse, ExecuteMsg, GatewayBond, MixId, RewardedSetNodeStatus,
CurrentIntervalResponse, ExecuteMsg, GatewayBond, IdentityKey, LayerAssignment, MixId,
RewardedSetNodeStatus,
};
use std::sync::Arc;
use tokio::sync::RwLock;
@@ -191,6 +193,23 @@ impl<C> Client<C> {
.await
}
#[allow(dead_code)]
pub(crate) async fn get_all_node_families(&self) -> Result<Vec<Family>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
self.0.read().await.get_all_node_families().await
}
pub(crate) async fn get_all_family_members(
&self,
) -> Result<Vec<(IdentityKey, FamilyHead)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
self.0.read().await.get_all_family_members().await
}
pub(crate) async fn send_rewarding_messages(
&self,
nodes: &[MixnodeToReward],
@@ -237,7 +256,7 @@ impl<C> Client<C> {
pub(crate) async fn advance_current_epoch(
&self,
new_rewarded_set: Vec<MixId>,
new_rewarded_set: Vec<LayerAssignment>,
expected_active_set_size: u32,
) -> Result<(), ValidatorClientError>
where