3b97844310
* additional unfiltered endpoints for nym-api * add poor performance UI * display appropriate UI when node is blacklisted * update explorer api with blacklisted nodes * add new unfiltered endpoint add new unfiltered endpoint * show blacklisted detail even when node description is unavailable remove console.log --------- Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
176 lines
6.4 KiB
Rust
176 lines
6.4 KiB
Rust
use super::NodeStatusCache;
|
|
use crate::{
|
|
node_status_api::cache::{
|
|
inclusion_probabilities::InclusionProbabilities,
|
|
node_sets::{
|
|
annotate_gateways_with_details, annotate_nodes_with_details,
|
|
split_into_active_and_rewarded_set, to_rewarded_set_node_status,
|
|
},
|
|
NodeStatusCacheError,
|
|
},
|
|
nym_contract_cache::cache::NymContractCache,
|
|
storage::NymApiStorage,
|
|
support::caching::CacheNotification,
|
|
};
|
|
use nym_task::TaskClient;
|
|
use std::time::Duration;
|
|
use tokio::sync::watch;
|
|
use tokio::time;
|
|
|
|
// Long running task responsible for keeping the node status cache up-to-date.
|
|
pub struct NodeStatusCacheRefresher {
|
|
// Main stored data
|
|
cache: NodeStatusCache,
|
|
fallback_caching_interval: Duration,
|
|
|
|
// Sources for when refreshing data
|
|
contract_cache: NymContractCache,
|
|
contract_cache_listener: watch::Receiver<CacheNotification>,
|
|
storage: Option<NymApiStorage>,
|
|
}
|
|
|
|
impl NodeStatusCacheRefresher {
|
|
pub(crate) fn new(
|
|
cache: NodeStatusCache,
|
|
fallback_caching_interval: Duration,
|
|
contract_cache: NymContractCache,
|
|
contract_cache_listener: watch::Receiver<CacheNotification>,
|
|
storage: Option<NymApiStorage>,
|
|
) -> Self {
|
|
Self {
|
|
cache,
|
|
fallback_caching_interval,
|
|
contract_cache,
|
|
contract_cache_listener,
|
|
storage,
|
|
}
|
|
}
|
|
|
|
/// Runs the node status cache refresher task.
|
|
pub async fn run(&mut self, mut shutdown: TaskClient) {
|
|
let mut fallback_interval = time::interval(self.fallback_caching_interval);
|
|
while !shutdown.is_shutdown() {
|
|
tokio::select! {
|
|
biased;
|
|
_ = shutdown.recv() => {
|
|
log::trace!("NodeStatusCacheRefresher: Received shutdown");
|
|
}
|
|
// Update node status cache when the contract cache / validator cache is updated
|
|
Ok(_) = self.contract_cache_listener.changed() => {
|
|
tokio::select! {
|
|
_ = self.update_on_notify(&mut fallback_interval) => (),
|
|
_ = shutdown.recv() => {
|
|
log::trace!("NodeStatusCacheRefresher: Received shutdown");
|
|
}
|
|
}
|
|
}
|
|
// ... however, if we don't receive any notifications we fall back to periodic
|
|
// refreshes
|
|
_ = fallback_interval.tick() => {
|
|
tokio::select! {
|
|
_ = self.update_on_timer() => (),
|
|
_ = shutdown.recv() => {
|
|
log::trace!("NodeStatusCacheRefresher: Received shutdown");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
log::info!("NodeStatusCacheRefresher: Exiting");
|
|
}
|
|
|
|
/// Updates the node status cache when the contract cache / validator cache is updated
|
|
async fn update_on_notify(&self, fallback_interval: &mut time::Interval) {
|
|
log::debug!(
|
|
"Validator cache event detected: {:?}",
|
|
&*self.contract_cache_listener.borrow(),
|
|
);
|
|
let _ = self.refresh().await;
|
|
fallback_interval.reset();
|
|
}
|
|
|
|
/// Updates the node status cache when the fallback interval is reached
|
|
async fn update_on_timer(&self) {
|
|
log::debug!("Timed trigger for the node status cache");
|
|
let have_contract_cache_data =
|
|
*self.contract_cache_listener.borrow() != CacheNotification::Start;
|
|
|
|
if have_contract_cache_data {
|
|
let _ = self.refresh().await;
|
|
} else {
|
|
log::trace!(
|
|
"Skipping updating node status cache, is the contract cache not yet available?"
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Refreshes the node status cache by fetching the latest data from the contract cache
|
|
async fn refresh(&self) -> Result<(), NodeStatusCacheError> {
|
|
log::info!("Updating node status cache");
|
|
|
|
// Fetch contract cache data to work with
|
|
let mixnode_details = self.contract_cache.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().await;
|
|
let active_set = self.contract_cache.active_set().await;
|
|
let mix_to_family = self.contract_cache.mix_to_family().await;
|
|
let gateway_bonds = self.contract_cache.gateways_all().await;
|
|
|
|
// 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)?;
|
|
|
|
// Compute inclusion probabilities
|
|
let 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
|
|
})?;
|
|
|
|
// Create annotated data
|
|
let rewarded_set_node_status = to_rewarded_set_node_status(&rewarded_set, &active_set);
|
|
let mixnodes_annotated = annotate_nodes_with_details(
|
|
&self.storage,
|
|
mixnode_details,
|
|
interval_reward_params,
|
|
current_interval,
|
|
&rewarded_set_node_status,
|
|
mix_to_family.to_vec(),
|
|
&mixnodes_blacklist,
|
|
)
|
|
.await;
|
|
|
|
// Create the annotated rewarded and active sets
|
|
let (rewarded_set, active_set) =
|
|
split_into_active_and_rewarded_set(&mixnodes_annotated, &rewarded_set_node_status);
|
|
|
|
let gateways_annotated = annotate_gateways_with_details(
|
|
&self.storage,
|
|
gateway_bonds,
|
|
current_interval,
|
|
&gateways_blacklist,
|
|
)
|
|
.await;
|
|
|
|
// Update the cache
|
|
self.cache
|
|
.update(
|
|
mixnodes_annotated,
|
|
rewarded_set,
|
|
active_set,
|
|
gateways_annotated,
|
|
inclusion_probabilities,
|
|
)
|
|
.await;
|
|
Ok(())
|
|
}
|
|
}
|