feat: key rotation (#5777)
* wip * wip: wrap node's sphinx key with a manager * wip: choosing correct key for packet processing * further propagation of key rotation information * attaching key rotation information to reply surbs * added basic key rotation information to mixnet contract * wip: introducing cached queries for key rotation info from nym api * unified nym-api contract cache refreshing * finish packet decoding * multi api client + retrieving rotation id * rotating sphinx key files * logic for migrating config file * wip: putting new sphinx keys to self described endpoints * processing loop of KeyRotationController * fixed sphinx key loading * rotating bloomfilters * wired up KeyRotationController * flushing bloomfilters to disk and loading * most of nym-node changes * post rebase fixes * fixes due to backwards compatible hostkeys * split http state.rs file * dont use deprecated fields * fixed backwards compatible deserialisation of host information * split up node describe cache * added a dedicated CacheRefresher listener to perform full refresh outside the set interval * controlling announced sphinx keys within nym-api * retrieving rotation id when pulling topology * split nym-nodes http handlers * v2 nym-api endpoints to retrieve nodes with additional metadata information * bug fixes... * additional bugfixes and guards against stuck epoch * testnet manager: set first nym-api as the rewarder * fixed host information deserialisation * fixed panic during first key rotation * post rebase fixes * clippy * more guards against stuck epochs * added helper method to reset node's sphinx key * instantiate mixnet contract with custom key rotation validity * additional bugfixes and debugging nym-api deadlock * passing shutdown to nym apis client * remove dead test * post rebasing fixes * missing MixnetQueryClient variants * remove usage of deprecated methods in sdk example * fix: incorrect method signature * post rebasing fixes * attempt to retrieve key rotation id before doing any config migration work * ignore tests relying on networking behaviour * allow networking failures in certain tests
This commit is contained in:
committed by
GitHub
parent
adbe0392ca
commit
d8c84cc4d6
+18
-70
@@ -5,14 +5,9 @@
|
||||
|
||||
use nym_api_requests::legacy::LegacyMixNodeDetailsWithLayer;
|
||||
use nym_api_requests::models::InclusionProbability;
|
||||
use nym_contracts_common::truncate_decimal;
|
||||
use nym_mixnet_contract_common::{NodeId, RewardingParams};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use tracing::error;
|
||||
|
||||
const MAX_SIMULATION_SAMPLES: u64 = 5000;
|
||||
const MAX_SIMULATION_TIME_SEC: u64 = 15;
|
||||
|
||||
#[deprecated]
|
||||
#[derive(Clone, Default, Serialize, schemars::JsonSchema)]
|
||||
@@ -25,11 +20,24 @@ pub(crate) struct InclusionProbabilities {
|
||||
}
|
||||
|
||||
impl InclusionProbabilities {
|
||||
pub(crate) fn compute(
|
||||
pub(crate) fn legacy_zero(
|
||||
mixnodes: &[LegacyMixNodeDetailsWithLayer],
|
||||
params: RewardingParams,
|
||||
) -> Option<InclusionProbabilities> {
|
||||
compute_inclusion_probabilities(mixnodes, params)
|
||||
) -> InclusionProbabilities {
|
||||
// (all legacy mixnodes have 0% chance of being selected)
|
||||
InclusionProbabilities {
|
||||
inclusion_probabilities: mixnodes
|
||||
.iter()
|
||||
.map(|m| InclusionProbability {
|
||||
mix_id: m.mix_id(),
|
||||
in_active: 0.0,
|
||||
in_reserve: 0.0,
|
||||
})
|
||||
.collect(),
|
||||
samples: 0,
|
||||
elapsed: Default::default(),
|
||||
delta_max: 0.0,
|
||||
delta_l2: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn node(&self, mix_id: NodeId) -> Option<&InclusionProbability> {
|
||||
@@ -38,63 +46,3 @@ impl InclusionProbabilities {
|
||||
.find(|x| x.mix_id == mix_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
fn compute_inclusion_probabilities(
|
||||
mixnodes: &[LegacyMixNodeDetailsWithLayer],
|
||||
params: RewardingParams,
|
||||
) -> Option<InclusionProbabilities> {
|
||||
let active_set_size = params.active_set_size();
|
||||
let standby_set_size = params.rewarded_set.standby;
|
||||
|
||||
// Unzip list of total bonds into ids and bonds.
|
||||
// We need to go through this zip/unzip procedure to make sure we have matching identities
|
||||
// for the input to the simulator, which assumes the identity is the position in the vec
|
||||
let (ids, mixnode_total_bonds) = unzip_into_mixnode_ids_and_total_bonds(mixnodes);
|
||||
|
||||
// Compute inclusion probabilitites and keep track of how long time it took.
|
||||
let mut rng = rand::thread_rng();
|
||||
let results = nym_inclusion_probability::simulate_selection_probability_mixnodes(
|
||||
&mixnode_total_bonds,
|
||||
active_set_size as usize,
|
||||
standby_set_size as usize,
|
||||
MAX_SIMULATION_SAMPLES,
|
||||
Duration::from_secs(MAX_SIMULATION_TIME_SEC),
|
||||
&mut rng,
|
||||
)
|
||||
.inspect_err(|err| error!("{err}"))
|
||||
.ok()?;
|
||||
|
||||
Some(InclusionProbabilities {
|
||||
inclusion_probabilities: zip_ids_together_with_results(&ids, &results),
|
||||
samples: results.samples,
|
||||
elapsed: results.time,
|
||||
delta_max: results.delta_max,
|
||||
delta_l2: results.delta_l2,
|
||||
})
|
||||
}
|
||||
|
||||
fn unzip_into_mixnode_ids_and_total_bonds(
|
||||
mixnodes: &[LegacyMixNodeDetailsWithLayer],
|
||||
) -> (Vec<NodeId>, Vec<u128>) {
|
||||
mixnodes
|
||||
.iter()
|
||||
.map(|m| (m.mix_id(), truncate_decimal(m.total_stake()).u128()))
|
||||
.unzip()
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
fn zip_ids_together_with_results(
|
||||
ids: &[NodeId],
|
||||
results: &nym_inclusion_probability::SelectionProbability,
|
||||
) -> Vec<InclusionProbability> {
|
||||
ids.iter()
|
||||
.zip(results.active_set_probability.iter())
|
||||
.zip(results.reserve_set_probability.iter())
|
||||
.map(|((&mix_id, a), r)| InclusionProbability {
|
||||
mix_id,
|
||||
in_active: *a,
|
||||
in_reserve: *r,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
+7
-3
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use self::data::NodeStatusCacheData;
|
||||
use crate::support::caching::cache::UninitialisedCache;
|
||||
use crate::support::caching::Cache;
|
||||
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation};
|
||||
use nym_contracts_common::IdentityKey;
|
||||
@@ -22,9 +23,6 @@ pub mod refresher;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum NodeStatusCacheError {
|
||||
#[error("failed to simulate selection probabilities for mixnodes, not updating cache")]
|
||||
SimulationFailed,
|
||||
|
||||
#[error("the current interval information is not available at the moment")]
|
||||
SourceDataMissing,
|
||||
|
||||
@@ -32,6 +30,12 @@ enum NodeStatusCacheError {
|
||||
UnavailableDescribedCache,
|
||||
}
|
||||
|
||||
impl From<UninitialisedCache> for NodeStatusCacheError {
|
||||
fn from(_: UninitialisedCache) -> Self {
|
||||
NodeStatusCacheError::SourceDataMissing
|
||||
}
|
||||
}
|
||||
|
||||
/// A node status cache suitable for caching values computed in one sweep, such as active set
|
||||
/// inclusion probabilities that are computed for all mixnodes at the same time.
|
||||
///
|
||||
|
||||
+27
-12
@@ -1,7 +1,7 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_describe_cache::cache::DescribedNodes;
|
||||
use crate::node_status_api::helpers::RewardedSetStatus;
|
||||
use crate::node_status_api::models::Uptime;
|
||||
use crate::node_status_api::reward_estimate::{compute_apy_from_reward, compute_reward_estimate};
|
||||
@@ -9,7 +9,6 @@ use crate::nym_contract_cache::cache::data::ConfigScoreData;
|
||||
use crate::support::legacy_helpers::legacy_host_to_ips_and_hostname;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer};
|
||||
use nym_api_requests::models::DescribedNodeType::{LegacyGateway, LegacyMixnode, NymNode};
|
||||
use nym_api_requests::models::{
|
||||
ConfigScore, DescribedNodeType, DetailedNodePerformance, GatewayBondAnnotated,
|
||||
MixNodeBondAnnotated, NodeAnnotation, NodePerformance, NymNodeDescription, RoutingScore,
|
||||
@@ -18,7 +17,7 @@ use nym_contracts_common::NaiveFloat;
|
||||
use nym_mixnet_contract_common::{Interval, NodeId, VersionScoreFormulaParams};
|
||||
use nym_mixnet_contract_common::{NymNodeDetails, RewardingParams};
|
||||
use nym_topology::CachedEpochRewardedSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use tracing::trace;
|
||||
|
||||
pub(super) async fn get_mixnode_reliability_from_storage(
|
||||
@@ -166,7 +165,6 @@ pub(super) async fn annotate_legacy_mixnodes_nodes_with_details(
|
||||
interval_reward_params: RewardingParams,
|
||||
current_interval: Interval,
|
||||
rewarded_set: &CachedEpochRewardedSet,
|
||||
blacklist: &HashSet<NodeId>,
|
||||
) -> HashMap<NodeId, MixNodeBondAnnotated> {
|
||||
let mut annotated = HashMap::new();
|
||||
for mixnode in mixnodes {
|
||||
@@ -216,7 +214,8 @@ pub(super) async fn annotate_legacy_mixnodes_nodes_with_details(
|
||||
annotated.insert(
|
||||
mixnode.mix_id(),
|
||||
MixNodeBondAnnotated {
|
||||
blacklisted: blacklist.contains(&mixnode.mix_id()),
|
||||
// all legacy nodes are always blacklisted
|
||||
blacklisted: true,
|
||||
mixnode_details: mixnode,
|
||||
stake_saturation,
|
||||
uncapped_stake_saturation,
|
||||
@@ -236,7 +235,6 @@ pub(crate) async fn annotate_legacy_gateways_with_details(
|
||||
storage: &NymApiStorage,
|
||||
gateway_bonds: Vec<LegacyGatewayBondWithId>,
|
||||
current_interval: Interval,
|
||||
blacklist: &HashSet<NodeId>,
|
||||
) -> HashMap<NodeId, GatewayBondAnnotated> {
|
||||
let mut annotated = HashMap::new();
|
||||
for gateway_bond in gateway_bonds {
|
||||
@@ -263,7 +261,8 @@ pub(crate) async fn annotate_legacy_gateways_with_details(
|
||||
annotated.insert(
|
||||
gateway_bond.node_id,
|
||||
GatewayBondAnnotated {
|
||||
blacklisted: blacklist.contains(&gateway_bond.node_id),
|
||||
// all legacy nodes are always blacklisted
|
||||
blacklisted: true,
|
||||
gateway_bond,
|
||||
self_described: None,
|
||||
performance,
|
||||
@@ -291,8 +290,13 @@ pub(crate) async fn produce_node_annotations(
|
||||
for legacy_mix in legacy_mixnodes {
|
||||
let node_id = legacy_mix.mix_id();
|
||||
|
||||
let routing_score =
|
||||
get_routing_score(storage, node_id, LegacyMixnode, current_interval).await;
|
||||
let routing_score = get_routing_score(
|
||||
storage,
|
||||
node_id,
|
||||
DescribedNodeType::LegacyMixnode,
|
||||
current_interval,
|
||||
)
|
||||
.await;
|
||||
let config_score =
|
||||
calculate_config_score(config_score_data, described_nodes.get_node(&node_id));
|
||||
|
||||
@@ -317,8 +321,13 @@ pub(crate) async fn produce_node_annotations(
|
||||
|
||||
for legacy_gateway in legacy_gateways {
|
||||
let node_id = legacy_gateway.node_id;
|
||||
let routing_score =
|
||||
get_routing_score(storage, node_id, LegacyGateway, current_interval).await;
|
||||
let routing_score = get_routing_score(
|
||||
storage,
|
||||
node_id,
|
||||
DescribedNodeType::LegacyGateway,
|
||||
current_interval,
|
||||
)
|
||||
.await;
|
||||
let config_score =
|
||||
calculate_config_score(config_score_data, described_nodes.get_node(&node_id));
|
||||
|
||||
@@ -343,7 +352,13 @@ pub(crate) async fn produce_node_annotations(
|
||||
|
||||
for nym_node in nym_nodes {
|
||||
let node_id = nym_node.node_id();
|
||||
let routing_score = get_routing_score(storage, node_id, NymNode, current_interval).await;
|
||||
let routing_score = get_routing_score(
|
||||
storage,
|
||||
node_id,
|
||||
DescribedNodeType::NymNode,
|
||||
current_interval,
|
||||
)
|
||||
.await;
|
||||
let config_score =
|
||||
calculate_config_score(config_score_data, described_nodes.get_node(&node_id));
|
||||
|
||||
|
||||
+10
-33
@@ -2,14 +2,12 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::NodeStatusCache;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_describe_cache::cache::DescribedNodes;
|
||||
use crate::node_status_api::cache::node_sets::produce_node_annotations;
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::{
|
||||
node_status_api::cache::{inclusion_probabilities, NodeStatusCacheError},
|
||||
nym_contract_cache::cache::NymContractCache,
|
||||
storage::NymApiStorage,
|
||||
support::caching::CacheNotification,
|
||||
node_status_api::cache::NodeStatusCacheError, nym_contract_cache::cache::NymContractCache,
|
||||
storage::NymApiStorage, support::caching::CacheNotification,
|
||||
};
|
||||
use ::time::OffsetDateTime;
|
||||
use nym_task::TaskClient;
|
||||
@@ -17,7 +15,7 @@ use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::watch;
|
||||
use tokio::time;
|
||||
use tracing::{error, info, trace, warn};
|
||||
use tracing::{info, trace, warn};
|
||||
|
||||
// Long running task responsible for keeping the node status cache up-to-date.
|
||||
pub struct NodeStatusCacheRefresher {
|
||||
@@ -139,35 +137,16 @@ impl NodeStatusCacheRefresher {
|
||||
|
||||
// Fetch contract cache data to work with
|
||||
let mixnode_details = self.contract_cache.legacy_mixnodes_all().await;
|
||||
let interval_reward_params = self.contract_cache.interval_reward_params().await;
|
||||
let current_interval = self.contract_cache.current_interval().await;
|
||||
let rewarded_set = self.contract_cache.rewarded_set_owned().await;
|
||||
let interval_reward_params = self.contract_cache.interval_reward_params().await?;
|
||||
let current_interval = self.contract_cache.current_interval().await?;
|
||||
let rewarded_set = self.contract_cache.rewarded_set_owned().await?;
|
||||
let gateway_bonds = self.contract_cache.legacy_gateways_all().await;
|
||||
let nym_nodes = self.contract_cache.nym_nodes().await;
|
||||
let config_score_data = self
|
||||
.contract_cache
|
||||
.config_score_data_owned()
|
||||
.await
|
||||
.into_inner()
|
||||
.ok_or(NodeStatusCacheError::SourceDataMissing)?;
|
||||
|
||||
// get blacklists
|
||||
let mixnodes_blacklist = self.contract_cache.mixnodes_blacklist().await;
|
||||
let gateways_blacklist = self.contract_cache.gateways_blacklist().await;
|
||||
|
||||
let interval_reward_params =
|
||||
interval_reward_params.ok_or(NodeStatusCacheError::SourceDataMissing)?;
|
||||
let current_interval = current_interval.ok_or(NodeStatusCacheError::SourceDataMissing)?;
|
||||
let config_score_data = self.contract_cache.maybe_config_score_data().await?;
|
||||
|
||||
// Compute inclusion probabilities
|
||||
let inclusion_probabilities = inclusion_probabilities::InclusionProbabilities::compute(
|
||||
&mixnode_details,
|
||||
interval_reward_params,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
error!("Failed to simulate selection probabilities for mixnodes, not updating cache");
|
||||
NodeStatusCacheError::SimulationFailed
|
||||
})?;
|
||||
// (all legacy mixnodes have 0% chance of being selected)
|
||||
let inclusion_probabilities = crate::node_status_api::cache::inclusion_probabilities::InclusionProbabilities::legacy_zero(&mixnode_details);
|
||||
|
||||
let Ok(described) = self.described_cache.get().await else {
|
||||
return Err(NodeStatusCacheError::UnavailableDescribedCache);
|
||||
@@ -198,7 +177,6 @@ impl NodeStatusCacheRefresher {
|
||||
interval_reward_params,
|
||||
current_interval,
|
||||
&rewarded_set,
|
||||
&mixnodes_blacklist,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -207,7 +185,6 @@ impl NodeStatusCacheRefresher {
|
||||
&self.storage,
|
||||
gateway_bonds,
|
||||
current_interval,
|
||||
&gateways_blacklist,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user