Feature/develop resync (#1844)

* Network-requester: throttle inbound connections (#1789)

* Return and handle ClientRequest::LaneQueueLenghts

* Pass lane queue lengths to inbound future

* Remove unused self reference

* Request lane queue lengths periodically for all open connections

* Add timeouts

* Rename to ConnectionCommandSender and Receiver

* Rename to client_connection_tx/rx

* Fix wasm build

* Replace bool with enum

* rust: bump required version to 1.65 in some crates that need it

* Add step to release GH actions (#1792)

* feat: add a release step to nym contracts GH action

* feat: add shrinking the size of wasm

* Possibilty to change gateway ws listener (#1779)

* add: set gatewayListener

* Update types.ts

* Update worker.ts

* Update contracts-build.yml

* real_traffic_stream: reduce frequency of status print (#1794)

* Update wallet and connect lock files (#1793)

* client-core: add warning when delay multiplier is larger than 1

* Fix decrypting stored received msg (#1786)

* Fix decrypting stored received msg

* rustfmt

* Moving binary message recovery to separate function

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Feature/use expect instead of panicking (#1797)

* Implementation of 'Debug' on 'RealMessage'

* expect with failed channel name instead of throwing empty panics

* Introduced Debug trait constraint in ProxyRunner

* Derive Debug for socks5_requests::Message

* Make connection_id optional in ClientRequest::Send (#1798)

* changelog: add missing entry for fixing message decrypt in gateway-client

* websocket-requests: fix length check before deserialize (#1799)

* Fix export dkg contract addr (#1800)

* Export dkg contract for mainnet when no config file present

* Remove redundant env files

* nym-cli: improve error reporting/handling and changed `vesting-schedule` queries to use query client instead of signing client

* Feature/gateway client protocol version (#1795)

* Introducing concept of gateway protocol version

* Remove version-based gateway filtering

* Fixed the unit test

* grammar

* Set build on latest release on schedule event

* Added nightly build workflow on second latest release

* socks5: if any task panics, signal all other tasks to shutdown (#1805)

* socks5: signal shutdown on error

* Mark as success

* Tidy

* Reduce wait to 5 sec

* Replace unwrap with expect

* Two more unwraps

* Update changelog

* client-core: less frequent status logging (#1806)

* Feature/nym connect UI updates (#1784)

* create custom titlebar

* create help page

* create generic modal component

* create separate connection time component

* link to shipyard docs

* move timer to separate component and update connection status component usage

* use separate component for copying ip and port details

* only show infomodal once after connection

* set service provider on tauri side

* Emit events when stopped

* listen and unlisten for tauri events

* connect: add trace log to get_services

* Add back CI notifications

* Update README

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com>

* Use default serde value for upgrade (#1807)

* fix ui overflow bug (#1808)

* update nym connect error text (#1809)

* set flag to false

* Fix wait_for_signal_and_error on win (#1811)

* Add socks5-client changes to nym-connect changelog

* Fix links in nym-connect changelog

* More entries in nym-connect CHANGELOG

* Fix typo in changelog

* Update CHANGELOG.md

* Experiment/client refactoring (#1814)

* experimenting with extracting more common client code

* drying up the wasm client

* allowing some dead code for the time being

* fixed formatting in nym-connect

* made socks5 client inside nym-connect immutable

* made clippy a bit happier

* hidden away target locking for recv timeout

* New transactions for increasing amount of pledged tokens

* unit tests

* Added an option to pledge extra tokens through the vesting contract

* Introduced wallet endpoints for new operations

* Using updated pledge cap in the vesting contract

* Bumping version numbers

* Changelog for v1.1.1

* Bumping final version numbers for 1.1.1

* Bumping nym-cli version, missed it last time

* socks5-client: SOCKS4a support (#1822)

* socks5-client: SOCKS4a support

* Tidy

* Fix a few errors in socks5 client and network-requester (#1823)

* Fix two unwraps in socks5 and network-requester

* Make sure client task never sends shutdown signal

* Fix panic on getting socks version

* wip

* connecting to the back and making the requests work

* display details modal

* logs removal

* Feature/pledge more (#1679)

* New transactions for increasing amount of pledged tokens

* unit tests

* Added an option to pledge extra tokens through the vesting contract

* Introduced wallet endpoints for new operations

* Using updated pledge cap in the vesting contract

* Changelog update

* nym-connect: update lock file

* avoid mix tokens pools

* amount error

* envs/mainnet: update to latest mixnet contract and nymd validator url

* validator-api: add missing shortform for --config-env-file (#1830)

* gateway-client: handle shutdown listener (#1829)

* WIP

* WIP: try another approach

* WIP

* Reworked

* Tidy

* fix

* validator-api: remove storage dependency in contract cache (#1685)

* validator-api: remove storage dependency in contract cache

* validator-client: update detailed routes

* contract_cache: forward to new endpoints for compat

* Move reward_estimate

* client: add --no-cover and update --fastmode (#1831)

* adding a oversaturaded bonding more modal

* common/task: extract out spawn_with_report_error (#1837)

* stop panic on failed buffer request

* Compilable wasm client

* Enabled hard error on lack of gateway-client protocol version

* Missing generic parameter for ClientCoreError in BackendError

* Removed unused imports

* Additional wasm feature locking

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
Co-authored-by: Fran Arbanas <arbanasfran@gmail.com>
Co-authored-by: cgi-bin/ <6095048+sven-hash@users.noreply.github.com>
Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com>
Co-authored-by: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: Raphaël Walther <raphael@nymtech.net>
Co-authored-by: Fouad <fmtabbara@hotmail.co.uk>
Co-authored-by: Gala <calero.vg@gmail.com>
Co-authored-by: Dave Hrycyszyn <futurechimp@users.noreply.github.com>
This commit is contained in:
Jędrzej Stuczyński
2022-12-05 18:27:44 +00:00
committed by GitHub
parent 758ba5593c
commit 4578cda0ae
196 changed files with 4649 additions and 2496 deletions
+31 -127
View File
@@ -2,13 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
use crate::nymd_client::Client;
use crate::storage::ValidatorApiStorage;
use ::time::OffsetDateTime;
use anyhow::Result;
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::reward_params::{Performance, RewardingParams};
use mixnet_contract_common::{
GatewayBond, IdentityKey, Interval, MixId, MixNodeBond, RewardedSetNodeStatus,
mixnode::MixNodeDetails, reward_params::RewardingParams, GatewayBond, IdentityKey, Interval,
MixId, MixNodeBond, RewardedSetNodeStatus,
};
use okapi::openapi3::OpenApi;
use rocket::fairing::AdHoc;
@@ -16,18 +14,17 @@ use rocket::Route;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
use serde::Serialize;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use task::ShutdownListener;
use tokio::sync::{watch, RwLock};
use tokio::time;
use validator_api_requests::models::{MixNodeBondAnnotated, MixnodeStatus};
use validator_api_requests::models::MixnodeStatus;
use validator_client::nymd::CosmWasmClient;
pub(crate) mod reward_estimate;
pub(crate) mod routes;
// The cache can emit notifications to listeners about the current state
@@ -42,9 +39,6 @@ pub struct ValidatorCacheRefresher<C> {
cache: ValidatorCache,
caching_interval: Duration,
// Readonly: some of the quantities cached depends on values from the storage.
storage: Option<ValidatorApiStorage>,
// Notify listeners that the cache has been updated
update_notifier: watch::Sender<CacheNotification>,
}
@@ -56,14 +50,14 @@ pub struct ValidatorCache {
}
struct ValidatorCacheInner {
mixnodes: Cache<Vec<MixNodeBondAnnotated>>,
mixnodes: Cache<Vec<MixNodeDetails>>,
gateways: Cache<Vec<GatewayBond>>,
mixnodes_blacklist: Cache<HashSet<MixId>>,
gateways_blacklist: Cache<HashSet<IdentityKey>>,
rewarded_set: Cache<Vec<MixNodeBondAnnotated>>,
active_set: Cache<Vec<MixNodeBondAnnotated>>,
rewarded_set: Cache<Vec<MixNodeDetails>>,
active_set: Cache<Vec<MixNodeDetails>>,
current_reward_params: Cache<Option<RewardingParams>>,
current_interval: Cache<Option<Interval>>,
@@ -102,90 +96,33 @@ impl<T: Clone> Cache<T> {
}
}
impl<T> Deref for Cache<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<C> ValidatorCacheRefresher<C> {
pub(crate) fn new(
nymd_client: Client<C>,
caching_interval: Duration,
cache: ValidatorCache,
storage: Option<ValidatorApiStorage>,
) -> Self {
let (tx, _) = watch::channel(CacheNotification::Start);
ValidatorCacheRefresher {
nymd_client,
cache,
caching_interval,
storage,
update_notifier: tx,
}
}
async fn get_performance(&self, mix_id: MixId, epoch: Interval) -> Option<Performance> {
self.storage
.as_ref()?
.get_average_mixnode_uptime_in_the_last_24hrs(
mix_id,
epoch.current_epoch_end_unix_timestamp(),
)
.await
.ok()
.map(Into::into)
}
pub fn subscribe(&self) -> watch::Receiver<CacheNotification> {
self.update_notifier.subscribe()
}
async fn annotate_node_with_details(
&self,
mixnodes: Vec<MixNodeDetails>,
interval_reward_params: RewardingParams,
current_interval: Interval,
rewarded_set: &HashMap<MixId, RewardedSetNodeStatus>,
) -> Vec<MixNodeBondAnnotated> {
let mut annotated = Vec::new();
for mixnode in mixnodes {
let stake_saturation = mixnode
.rewarding_details
.bond_saturation(&interval_reward_params);
let uncapped_stake_saturation = mixnode
.rewarding_details
.uncapped_bond_saturation(&interval_reward_params);
let performance = self
.get_performance(mixnode.mix_id(), current_interval)
.await
.unwrap_or_default();
let rewarded_set_status = rewarded_set.get(&mixnode.mix_id()).cloned();
let reward_estimate = reward_estimate::compute_reward_estimate(
&mixnode,
performance,
rewarded_set_status,
interval_reward_params,
current_interval,
);
let (estimated_operator_apy, estimated_delegators_apy) =
reward_estimate::compute_apy_from_reward(
&mixnode,
reward_estimate,
current_interval,
);
annotated.push(MixNodeBondAnnotated {
mixnode_details: mixnode,
stake_saturation,
uncapped_stake_saturation,
performance,
estimated_operator_apy,
estimated_delegators_apy,
});
}
annotated
}
async fn get_rewarded_set_map(&self) -> HashMap<MixId, RewardedSetNodeStatus>
where
C: CosmWasmClient + Sync + Send,
@@ -198,9 +135,9 @@ impl<C> ValidatorCacheRefresher<C> {
}
fn collect_rewarded_and_active_set_details(
all_mixnodes: &[MixNodeBondAnnotated],
all_mixnodes: &[MixNodeDetails],
rewarded_set_nodes: &HashMap<MixId, RewardedSetNodeStatus>,
) -> (Vec<MixNodeBondAnnotated>, Vec<MixNodeBondAnnotated>) {
) -> (Vec<MixNodeDetails>, Vec<MixNodeDetails>) {
let mut active_set = Vec::new();
let mut rewarded_set = Vec::new();
@@ -226,14 +163,10 @@ impl<C> ValidatorCacheRefresher<C> {
let mixnodes = self.nymd_client.get_mixnodes().await?;
let gateways = self.nymd_client.get_gateways().await?;
let rewarded_set = self.get_rewarded_set_map().await;
let mixnodes = self
.annotate_node_with_details(mixnodes, rewarding_params, current_interval, &rewarded_set)
.await;
let rewarded_set_map = self.get_rewarded_set_map().await;
let (rewarded_set, active_set) =
Self::collect_rewarded_and_active_set_details(&mixnodes, &rewarded_set);
Self::collect_rewarded_and_active_set_details(&mixnodes, &rewarded_set_map);
info!(
"Updating validator cache. There are {} mixnodes and {} gateways",
@@ -324,10 +257,10 @@ impl ValidatorCache {
async fn update_cache(
&self,
mixnodes: Vec<MixNodeBondAnnotated>,
mixnodes: Vec<MixNodeDetails>,
gateways: Vec<GatewayBond>,
rewarded_set: Vec<MixNodeBondAnnotated>,
active_set: Vec<MixNodeBondAnnotated>,
rewarded_set: Vec<MixNodeDetails>,
active_set: Vec<MixNodeDetails>,
rewarding_params: RewardingParams,
current_interval: Interval,
) {
@@ -422,7 +355,7 @@ impl ValidatorCache {
error!("Failed to update gateways blacklist");
}
pub async fn mixnodes_detailed(&self) -> Vec<MixNodeBondAnnotated> {
pub async fn mixnodes(&self) -> Vec<MixNodeDetails> {
let blacklist = self.mixnodes_blacklist().await;
let mixnodes = match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.mixnodes.clone(),
@@ -444,14 +377,6 @@ impl ValidatorCache {
}
}
pub async fn mixnodes(&self) -> Vec<MixNodeDetails> {
self.mixnodes_detailed()
.await
.into_iter()
.map(|bond| bond.mixnode_details)
.collect()
}
pub async fn mixnodes_basic(&self) -> Vec<MixNodeBond> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache
@@ -459,7 +384,7 @@ impl ValidatorCache {
.clone()
.into_inner()
.into_iter()
.map(|bond| bond.mixnode_details.bond_information)
.map(|bond| bond.bond_information)
.collect(),
Err(e) => {
error!("{}", e);
@@ -500,7 +425,7 @@ impl ValidatorCache {
}
}
pub async fn rewarded_set_detailed(&self) -> Cache<Vec<MixNodeBondAnnotated>> {
pub async fn rewarded_set(&self) -> Cache<Vec<MixNodeDetails>> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.rewarded_set.clone(),
Err(e) => {
@@ -510,16 +435,7 @@ impl ValidatorCache {
}
}
pub async fn rewarded_set(&self) -> Vec<MixNodeDetails> {
self.rewarded_set_detailed()
.await
.value
.into_iter()
.map(|bond| bond.mixnode_details)
.collect()
}
pub async fn active_set_detailed(&self) -> Cache<Vec<MixNodeBondAnnotated>> {
pub async fn active_set(&self) -> Cache<Vec<MixNodeDetails>> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.active_set.clone(),
Err(e) => {
@@ -529,15 +445,6 @@ impl ValidatorCache {
}
}
pub async fn active_set(&self) -> Vec<MixNodeDetails> {
self.active_set_detailed()
.await
.value
.into_iter()
.map(|bond| bond.mixnode_details)
.collect()
}
pub(crate) async fn interval_reward_params(&self) -> Cache<Option<RewardingParams>> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.current_reward_params.clone(),
@@ -558,24 +465,21 @@ impl ValidatorCache {
}
}
pub async fn mixnode_details(
&self,
mix_id: MixId,
) -> (Option<MixNodeBondAnnotated>, MixnodeStatus) {
pub async fn mixnode_details(&self, mix_id: MixId) -> (Option<MixNodeDetails>, MixnodeStatus) {
// it might not be the most optimal to possibly iterate the entire vector to find (or not)
// the relevant value. However, the vectors are relatively small (< 10_000 elements, < 1000 for active set)
let active_set = &self.active_set_detailed().await.value;
let active_set = &self.active_set().await.value;
if let Some(bond) = active_set.iter().find(|mix| mix.mix_id() == mix_id) {
return (Some(bond.clone()), MixnodeStatus::Active);
}
let rewarded_set = &self.rewarded_set_detailed().await.value;
let rewarded_set = &self.rewarded_set().await.value;
if let Some(bond) = rewarded_set.iter().find(|mix| mix.mix_id() == mix_id) {
return (Some(bond.clone()), MixnodeStatus::Standby);
}
let all_bonded = &self.mixnodes_detailed().await;
let all_bonded = &self.mixnodes().await;
if let Some(bond) = all_bonded.iter().find(|mix| mix.mix_id() == mix_id) {
(Some(bond.clone()), MixnodeStatus::Inactive)
} else {
+42 -15
View File
@@ -1,15 +1,21 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::contract_cache::ValidatorCache;
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::reward_params::RewardingParams;
use mixnet_contract_common::{GatewayBond, Interval, MixId};
use rocket::serde::json::Json;
use rocket::State;
use crate::{
contract_cache::ValidatorCache,
node_status_api::{
helpers::{_get_active_set_detailed, _get_mixnodes_detailed, _get_rewarded_set_detailed},
NodeStatusCache,
},
};
use mixnet_contract_common::{
mixnode::MixNodeDetails, reward_params::RewardingParams, GatewayBond, Interval, MixId,
};
use validator_api_requests::models::MixNodeBondAnnotated;
use rocket::{serde::json::Json, State};
use rocket_okapi::openapi;
use std::collections::HashSet;
use validator_api_requests::models::MixNodeBondAnnotated;
#[openapi(tag = "contract-cache")]
#[get("/mixnodes")]
@@ -17,12 +23,19 @@ pub async fn get_mixnodes(cache: &State<ValidatorCache>) -> Json<Vec<MixNodeDeta
Json(cache.mixnodes().await)
}
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
// replace this with
// ```
// pub fn get_mixnodes_detailed() -> Redirect {
// Redirect::to(uri!("/v1/status/mixnodes/detailed"))
// }
// ```
#[openapi(tag = "contract-cache")]
#[get("/mixnodes/detailed")]
pub async fn get_mixnodes_detailed(
cache: &State<ValidatorCache>,
cache: &State<NodeStatusCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
Json(cache.mixnodes_detailed().await)
Json(_get_mixnodes_detailed(cache).await)
}
#[openapi(tag = "contract-cache")]
@@ -34,29 +47,43 @@ pub async fn get_gateways(cache: &State<ValidatorCache>) -> Json<Vec<GatewayBond
#[openapi(tag = "contract-cache")]
#[get("/mixnodes/rewarded")]
pub async fn get_rewarded_set(cache: &State<ValidatorCache>) -> Json<Vec<MixNodeDetails>> {
Json(cache.rewarded_set().await)
Json(cache.rewarded_set().await.value)
}
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
// replace this with
// ```
// pub fn get_mixnodes_set_detailed() -> Redirect {
// Redirect::to(uri!("/v1/status/mixnodes/rewarded/detailed"))
// }
// ```
#[openapi(tag = "contract-cache")]
#[get("/mixnodes/rewarded/detailed")]
pub async fn get_rewarded_set_detailed(
cache: &State<ValidatorCache>,
cache: &State<NodeStatusCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
Json(cache.rewarded_set_detailed().await.value)
Json(_get_rewarded_set_detailed(cache).await)
}
#[openapi(tag = "contract-cache")]
#[get("/mixnodes/active")]
pub async fn get_active_set(cache: &State<ValidatorCache>) -> Json<Vec<MixNodeDetails>> {
Json(cache.active_set().await)
Json(cache.active_set().await.value)
}
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
// replace this with
// ```
// pub fn get_active_set_detailed() -> Redirect {
// Redirect::to(uri!("/status/mixnodes/active/detailed"))
// }
// ```
#[openapi(tag = "contract-cache")]
#[get("/mixnodes/active/detailed")]
pub async fn get_active_set_detailed(
cache: &State<ValidatorCache>,
cache: &State<NodeStatusCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
Json(cache.active_set_detailed().await.value)
Json(_get_active_set_detailed(cache).await)
}
#[openapi(tag = "contract-cache")]
+1 -1
View File
@@ -156,7 +156,7 @@ impl RewardedSetUpdater {
Err(err) => {
warn!("failed to obtain the current rewarded set - {}. falling back to the cached version", err);
self.validator_cache
.rewarded_set_detailed()
.rewarded_set()
.await
.into_inner()
.into_iter()
+4 -3
View File
@@ -121,6 +121,7 @@ fn parse_args() -> ArgMatches {
Arg::with_name(CONFIG_ENV_FILE)
.help("Path pointing to an env file that configures the validator API")
.long(CONFIG_ENV_FILE)
.short('c')
.takes_value(true)
)
.arg(
@@ -577,7 +578,6 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> {
signing_nymd_client.clone(),
config.get_caching_interval(),
validator_cache.clone(),
Some(storage.clone()),
);
let validator_cache_listener = validator_cache_refresher.subscribe();
let shutdown_listener = shutdown.subscribe();
@@ -599,7 +599,6 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> {
nymd_client,
config.get_caching_interval(),
validator_cache.clone(),
None,
);
let validator_cache_listener = validator_cache_refresher.subscribe();
let shutdown_listener = shutdown.subscribe();
@@ -611,11 +610,13 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> {
// Spawn the node status cache refresher.
// It is primarily refreshed in-sync with the validator cache, however provide a fallback
// caching interval that is twice the validator cache
let storage = rocket.state::<ValidatorApiStorage>().cloned();
let mut validator_api_cache_refresher = node_status_api::NodeStatusCacheRefresher::new(
node_status_cache,
config.get_caching_interval().saturating_mul(2),
validator_cache,
validator_cache_listener,
config.get_caching_interval().saturating_mul(2),
storage,
);
let shutdown_listener = shutdown.subscribe();
tokio::spawn(async move { validator_api_cache_refresher.run(shutdown_listener).await });
@@ -209,7 +209,7 @@ impl PacketSender {
ack_sender,
fresh_gateway_client_data.gateway_response_timeout,
Some(fresh_gateway_client_data.bandwidth_controller.clone()),
None,
task::ShutdownListener::dummy(),
);
gateway_client
+228 -98
View File
@@ -2,25 +2,33 @@
// SPDX-License-Identifier: Apache-2.0
use crate::contract_cache::{Cache, CacheNotification, ValidatorCache};
use contracts_common::truncate_decimal;
use mixnet_contract_common::{MixId, MixNodeDetails, RewardingParams};
use crate::storage::ValidatorApiStorage;
use mixnet_contract_common::reward_params::Performance;
use mixnet_contract_common::{
Interval, MixId, MixNodeDetails, RewardedSetNodeStatus, RewardingParams,
};
use rocket::fairing::AdHoc;
use serde::Serialize;
use std::collections::HashMap;
use std::{sync::Arc, time::Duration};
use tap::TapFallible;
use task::ShutdownListener;
use tokio::sync::RwLockReadGuard;
use tokio::{
sync::{watch, RwLock},
time,
};
use validator_api_requests::models::InclusionProbability;
use validator_api_requests::models::{MixNodeBondAnnotated, MixnodeStatus};
use self::inclusion_probabilities::InclusionProbabilities;
use super::reward_estimate::{compute_apy_from_reward, compute_reward_estimate};
mod inclusion_probabilities;
const CACHE_TIMOUT_MS: u64 = 100;
const MAX_SIMULATION_SAMPLES: u64 = 5000;
const MAX_SIMULATION_TIME_SEC: u64 = 15;
enum NodeStatusCacheError {
SimulationFailed,
SourceDataMissing,
}
// A node status cache suitable for caching values computed in one sweep, such as active set
@@ -32,27 +40,16 @@ pub struct NodeStatusCache {
inner: Arc<RwLock<NodeStatusCacheInner>>,
}
#[derive(Default)]
struct NodeStatusCacheInner {
mixnodes_annotated: Cache<Vec<MixNodeBondAnnotated>>,
rewarded_set_annotated: Cache<Vec<MixNodeBondAnnotated>>,
active_set_annotated: Cache<Vec<MixNodeBondAnnotated>>,
// Estimated active set inclusion probabilities from Monte Carlo simulation
inclusion_probabilities: Cache<InclusionProbabilities>,
}
#[derive(Clone, Default, Serialize, schemars::JsonSchema)]
pub(crate) struct InclusionProbabilities {
pub inclusion_probabilities: Vec<InclusionProbability>,
pub samples: u64,
pub elapsed: Duration,
pub delta_max: f64,
pub delta_l2: f64,
}
impl InclusionProbabilities {
pub fn node(&self, mix_id: MixId) -> Option<&InclusionProbability> {
self.inclusion_probabilities
.iter()
.find(|x| x.mix_id == mix_id)
}
}
impl NodeStatusCache {
fn new() -> Self {
NodeStatusCache {
@@ -66,9 +63,18 @@ impl NodeStatusCache {
})
}
async fn update_cache(&self, inclusion_probabilities: InclusionProbabilities) {
async fn update_cache(
&self,
mixnodes: Vec<MixNodeBondAnnotated>,
rewarded_set: Vec<MixNodeBondAnnotated>,
active_set: Vec<MixNodeBondAnnotated>,
inclusion_probabilities: InclusionProbabilities,
) {
match time::timeout(Duration::from_millis(CACHE_TIMOUT_MS), self.inner.write()).await {
Ok(mut cache) => {
cache.mixnodes_annotated.update(mixnodes);
cache.rewarded_set_annotated.update(rewarded_set);
cache.active_set_annotated.update(active_set);
cache
.inclusion_probabilities
.update(inclusion_probabilities);
@@ -77,45 +83,93 @@ impl NodeStatusCache {
}
}
pub(crate) async fn inclusion_probabilities(&self) -> Option<Cache<InclusionProbabilities>> {
async fn get_cache<T>(
&self,
fn_arg: impl FnOnce(RwLockReadGuard<'_, NodeStatusCacheInner>) -> Cache<T>,
) -> Option<Cache<T>> {
match time::timeout(Duration::from_millis(CACHE_TIMOUT_MS), self.inner.read()).await {
Ok(cache) => Some(cache.inclusion_probabilities.clone()),
Ok(cache) => Some(fn_arg(cache)),
Err(e) => {
error!("{e}");
None
}
}
}
pub(crate) async fn mixnodes_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> {
self.get_cache(|c| c.mixnodes_annotated.clone()).await
}
pub(crate) async fn rewarded_set_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> {
self.get_cache(|c| c.rewarded_set_annotated.clone()).await
}
pub(crate) async fn active_set_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> {
self.get_cache(|c| c.active_set_annotated.clone()).await
}
pub(crate) async fn inclusion_probabilities(&self) -> Option<Cache<InclusionProbabilities>> {
self.get_cache(|c| c.inclusion_probabilities.clone()).await
}
pub async fn mixnode_details(
&self,
mix_id: MixId,
) -> (Option<MixNodeBondAnnotated>, MixnodeStatus) {
// it might not be the most optimal to possibly iterate the entire vector to find (or not)
// the relevant value. However, the vectors are relatively small (< 10_000 elements, < 1000 for active set)
let active_set = &self.active_set_annotated().await.unwrap().into_inner();
if let Some(bond) = active_set.iter().find(|mix| mix.mix_id() == mix_id) {
return (Some(bond.clone()), MixnodeStatus::Active);
}
let rewarded_set = &self.rewarded_set_annotated().await.unwrap().into_inner();
if let Some(bond) = rewarded_set.iter().find(|mix| mix.mix_id() == mix_id) {
return (Some(bond.clone()), MixnodeStatus::Standby);
}
let all_bonded = &self.mixnodes_annotated().await.unwrap().into_inner();
if let Some(bond) = all_bonded.iter().find(|mix| mix.mix_id() == mix_id) {
(Some(bond.clone()), MixnodeStatus::Inactive)
} else {
(None, MixnodeStatus::NotFound)
}
}
}
impl NodeStatusCacheInner {
pub fn new() -> Self {
Self {
inclusion_probabilities: Default::default(),
}
Self::default()
}
}
// Long running task responsible of keeping the cache up-to-date.
pub struct NodeStatusCacheRefresher {
// Main stored data
cache: NodeStatusCache,
fallback_caching_interval: Duration,
// Sources for when refreshing data
contract_cache: ValidatorCache,
contract_cache_listener: watch::Receiver<CacheNotification>,
fallback_caching_interval: Duration,
storage: Option<ValidatorApiStorage>,
}
impl NodeStatusCacheRefresher {
pub(crate) fn new(
cache: NodeStatusCache,
fallback_caching_interval: Duration,
contract_cache: ValidatorCache,
contract_cache_listener: watch::Receiver<CacheNotification>,
fallback_caching_interval: Duration,
storage: Option<ValidatorApiStorage>,
) -> Self {
Self {
cache,
fallback_caching_interval,
contract_cache,
contract_cache_listener,
fallback_caching_interval,
storage,
}
}
@@ -176,78 +230,154 @@ impl NodeStatusCacheRefresher {
async fn refresh_cache(&self) -> Result<(), NodeStatusCacheError> {
log::info!("Updating node status cache");
let mixnode_bonds = self.contract_cache.mixnodes().await;
let params = self
.contract_cache
.interval_reward_params()
.await
.into_inner()
.ok_or(NodeStatusCacheError::SimulationFailed)?;
let inclusion_probabilities = compute_inclusion_probabilities(&mixnode_bonds, params)
.ok_or_else(|| {
error!(
"Failed to simulate selection probabilties for mixnodes, not updating cache"
);
NodeStatusCacheError::SimulationFailed
})?;
self.cache.update_cache(inclusion_probabilities).await;
// Fetch contract cache data to work with
let mixnode_details = self.contract_cache.mixnodes().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 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 probabilties 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 = self
.annotate_node_with_details(
mixnode_details,
interval_reward_params,
current_interval,
&rewarded_set_node_status,
)
.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);
self.cache
.update_cache(
mixnodes_annotated,
rewarded_set,
active_set,
inclusion_probabilities,
)
.await;
Ok(())
}
async fn get_performance_from_storage(
&self,
mix_id: MixId,
epoch: Interval,
) -> Option<Performance> {
self.storage
.as_ref()?
.get_average_mixnode_uptime_in_the_last_24hrs(
mix_id,
epoch.current_epoch_end_unix_timestamp(),
)
.await
.ok()
.map(Into::into)
}
async fn annotate_node_with_details(
&self,
mixnodes: Vec<MixNodeDetails>,
interval_reward_params: RewardingParams,
current_interval: Interval,
rewarded_set: &HashMap<MixId, RewardedSetNodeStatus>,
) -> Vec<MixNodeBondAnnotated> {
let mut annotated = Vec::new();
for mixnode in mixnodes {
let stake_saturation = mixnode
.rewarding_details
.bond_saturation(&interval_reward_params);
let uncapped_stake_saturation = mixnode
.rewarding_details
.uncapped_bond_saturation(&interval_reward_params);
// If the performance can't be obtained, because the validator-api was not started with
// the monitoring (and hence, storage), then reward estimates will be all zero
let performance = self
.get_performance_from_storage(mixnode.mix_id(), current_interval)
.await
.unwrap_or_default();
let rewarded_set_status = rewarded_set.get(&mixnode.mix_id()).copied();
let reward_estimate = compute_reward_estimate(
&mixnode,
performance,
rewarded_set_status,
interval_reward_params,
current_interval,
);
let (estimated_operator_apy, estimated_delegators_apy) =
compute_apy_from_reward(&mixnode, reward_estimate, current_interval);
annotated.push(MixNodeBondAnnotated {
mixnode_details: mixnode,
stake_saturation,
uncapped_stake_saturation,
performance,
estimated_operator_apy,
estimated_delegators_apy,
});
}
annotated
}
}
fn compute_inclusion_probabilities(
mixnodes: &[MixNodeDetails],
params: RewardingParams,
) -> Option<InclusionProbabilities> {
let active_set_size = params.active_set_size;
let standby_set_size = params.rewarded_set_size - active_set_size;
// 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 = 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,
)
.tap_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: &[MixNodeDetails]) -> (Vec<MixId>, Vec<u128>) {
mixnodes
fn to_rewarded_set_node_status(
rewarded_set: &[MixNodeDetails],
active_set: &[MixNodeDetails],
) -> HashMap<MixId, RewardedSetNodeStatus> {
let mut rewarded_set_node_status: HashMap<MixId, RewardedSetNodeStatus> = rewarded_set
.iter()
.map(|m| (m.mix_id(), truncate_decimal(m.total_stake()).u128()))
.unzip()
.map(|m| (m.mix_id(), RewardedSetNodeStatus::Standby))
.collect();
for mixnode in active_set {
*rewarded_set_node_status
.get_mut(&mixnode.mix_id())
.expect("All active nodes are rewarded nodes") = RewardedSetNodeStatus::Active;
}
rewarded_set_node_status
}
fn zip_ids_together_with_results(
ids: &[MixId],
results: &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,
fn split_into_active_and_rewarded_set(
mixnodes_annotated: &[MixNodeBondAnnotated],
rewarded_set_node_status: &HashMap<u32, RewardedSetNodeStatus>,
) -> (Vec<MixNodeBondAnnotated>, Vec<MixNodeBondAnnotated>) {
let rewarded_set: Vec<_> = mixnodes_annotated
.iter()
.filter(|mixnode| rewarded_set_node_status.get(&mixnode.mix_id()).is_some())
.cloned()
.collect();
let active_set: Vec<_> = rewarded_set
.iter()
.filter(|mixnode| {
rewarded_set_node_status
.get(&mixnode.mix_id())
.map_or(false, RewardedSetNodeStatus::is_active)
})
.collect()
.cloned()
.collect();
(rewarded_set, active_set)
}
@@ -0,0 +1,89 @@
use contracts_common::truncate_decimal;
use mixnet_contract_common::{MixId, MixNodeDetails, RewardingParams};
use serde::Serialize;
use std::time::Duration;
use tap::TapFallible;
use validator_api_requests::models::InclusionProbability;
const MAX_SIMULATION_SAMPLES: u64 = 5000;
const MAX_SIMULATION_TIME_SEC: u64 = 15;
#[derive(Clone, Default, Serialize, schemars::JsonSchema)]
pub(crate) struct InclusionProbabilities {
pub inclusion_probabilities: Vec<InclusionProbability>,
pub samples: u64,
pub elapsed: Duration,
pub delta_max: f64,
pub delta_l2: f64,
}
impl InclusionProbabilities {
pub(crate) fn compute(
mixnodes: &[MixNodeDetails],
params: RewardingParams,
) -> Option<InclusionProbabilities> {
compute_inclusion_probabilities(mixnodes, params)
}
pub(crate) fn node(&self, mix_id: MixId) -> Option<&InclusionProbability> {
self.inclusion_probabilities
.iter()
.find(|x| x.mix_id == mix_id)
}
}
fn compute_inclusion_probabilities(
mixnodes: &[MixNodeDetails],
params: RewardingParams,
) -> Option<InclusionProbabilities> {
let active_set_size = params.active_set_size;
let standby_set_size = params.rewarded_set_size - active_set_size;
// 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 = 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,
)
.tap_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: &[MixNodeDetails]) -> (Vec<MixId>, Vec<u128>) {
mixnodes
.iter()
.map(|m| (m.mix_id(), truncate_decimal(m.total_stake()).u128()))
.unzip()
}
fn zip_ids_together_with_results(
ids: &[MixId],
results: &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()
}
+65 -12
View File
@@ -1,7 +1,6 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::contract_cache::reward_estimate::compute_reward_estimate;
use crate::contract_cache::Cache;
use crate::node_status_api::models::ErrorResponse;
use crate::storage::ValidatorApiStorage;
@@ -12,11 +11,14 @@ use mixnet_contract_common::{Interval, MixId, RewardedSetNodeStatus};
use rocket::http::Status;
use rocket::State;
use validator_api_requests::models::{
ComputeRewardEstParam, InclusionProbabilityResponse, MixnodeCoreStatusResponse,
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
AllInclusionProbabilitiesResponse, ComputeRewardEstParam, InclusionProbabilityResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
use super::reward_estimate::compute_reward_estimate;
pub(crate) async fn _mixnode_report(
storage: &ValidatorApiStorage,
mix_id: MixId,
@@ -62,17 +64,18 @@ pub(crate) async fn _get_mixnode_status(
}
pub(crate) async fn _get_mixnode_reward_estimation(
cache: &State<ValidatorCache>,
cache: &State<NodeStatusCache>,
validator_cache: &State<ValidatorCache>,
mix_id: MixId,
) -> Result<RewardEstimationResponse, ErrorResponse> {
let (mixnode, status) = cache.mixnode_details(mix_id).await;
if let Some(mixnode) = mixnode {
let reward_params = cache.interval_reward_params().await;
let reward_params = validator_cache.interval_reward_params().await;
let as_at = reward_params.timestamp();
let reward_params = reward_params
.into_inner()
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
let current_interval = cache
let current_interval = validator_cache
.current_interval()
.await
.into_inner()
@@ -117,17 +120,18 @@ async fn average_mixnode_performance(
pub(crate) async fn _compute_mixnode_reward_estimation(
user_reward_param: ComputeRewardEstParam,
cache: &ValidatorCache,
cache: &NodeStatusCache,
validator_cache: &ValidatorCache,
mix_id: MixId,
) -> Result<RewardEstimationResponse, ErrorResponse> {
let (mixnode, actual_status) = cache.mixnode_details(mix_id).await;
if let Some(mut mixnode) = mixnode {
let reward_params = cache.interval_reward_params().await;
let reward_params = validator_cache.interval_reward_params().await;
let as_at = reward_params.timestamp();
let reward_params = reward_params
.into_inner()
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
let current_interval = cache
let current_interval = validator_cache
.current_interval()
.await
.into_inner()
@@ -200,14 +204,15 @@ pub(crate) async fn _compute_mixnode_reward_estimation(
}
pub(crate) async fn _get_mixnode_stake_saturation(
cache: &ValidatorCache,
cache: &NodeStatusCache,
validator_cache: &ValidatorCache,
mix_id: MixId,
) -> Result<StakeSaturationResponse, ErrorResponse> {
let (mixnode, _) = cache.mixnode_details(mix_id).await;
if let Some(mixnode) = mixnode {
// Recompute the stake saturation just so that we can confidently state that the `as_at`
// field is consistent and correct. Luckily this is very cheap.
let reward_params = cache.interval_reward_params().await;
let reward_params = validator_cache.interval_reward_params().await;
let as_at = reward_params.timestamp();
let rewarding_params = reward_params
.into_inner()
@@ -266,3 +271,51 @@ pub(crate) async fn _get_mixnode_avg_uptime(
avg_uptime: performance.round_to_integer(),
})
}
pub(crate) async fn _get_mixnode_inclusion_probabilities(
cache: &NodeStatusCache,
) -> Result<AllInclusionProbabilitiesResponse, ErrorResponse> {
if let Some(prob) = cache.inclusion_probabilities().await {
let as_at = prob.timestamp();
let prob = prob.into_inner();
Ok(AllInclusionProbabilitiesResponse {
inclusion_probabilities: prob.inclusion_probabilities,
samples: prob.samples,
elapsed: prob.elapsed,
delta_max: prob.delta_max,
delta_l2: prob.delta_l2,
as_at,
})
} else {
Err(ErrorResponse::new(
"No data available".to_string(),
Status::ServiceUnavailable,
))
}
}
pub(crate) async fn _get_mixnodes_detailed(cache: &NodeStatusCache) -> Vec<MixNodeBondAnnotated> {
cache
.mixnodes_annotated()
.await
.unwrap_or_default()
.into_inner()
}
pub(crate) async fn _get_rewarded_set_detailed(
cache: &NodeStatusCache,
) -> Vec<MixNodeBondAnnotated> {
cache
.rewarded_set_annotated()
.await
.unwrap_or_default()
.into_inner()
}
pub(crate) async fn _get_active_set_detailed(cache: &NodeStatusCache) -> Vec<MixNodeBondAnnotated> {
cache
.active_set_annotated()
.await
.unwrap_or_default()
.into_inner()
}
+7
View File
@@ -10,6 +10,7 @@ pub(crate) mod cache;
pub(crate) mod helpers;
pub(crate) mod local_guard;
pub(crate) mod models;
pub(crate) mod reward_estimate;
pub(crate) mod routes;
pub(crate) mod uptime_updater;
pub(crate) mod utils;
@@ -37,6 +38,9 @@ pub(crate) fn node_status_routes(
routes::get_mixnode_inclusion_probability,
routes::get_mixnode_avg_uptime,
routes::get_mixnode_inclusion_probabilities,
routes::get_mixnodes_detailed,
routes::get_rewarded_set_detailed,
routes::get_active_set_detailed,
]
} else {
// in the minimal variant we would not have access to endpoints relying on existence
@@ -46,6 +50,9 @@ pub(crate) fn node_status_routes(
routes::get_mixnode_stake_saturation,
routes::get_mixnode_inclusion_probability,
routes::get_mixnode_inclusion_probabilities,
routes::get_mixnodes_detailed,
routes::get_rewarded_set_detailed,
routes::get_active_set_detailed,
]
}
}
@@ -7,7 +7,7 @@ use mixnet_contract_common::reward_params::{NodeRewardParams, Performance, Rewar
use mixnet_contract_common::rewarding::RewardEstimate;
use mixnet_contract_common::{Interval, RewardedSetNodeStatus};
pub fn compute_apy(epochs_in_year: Decimal, reward: Decimal, pledge_amount: Decimal) -> Decimal {
fn compute_apy(epochs_in_year: Decimal, reward: Decimal, pledge_amount: Decimal) -> Decimal {
if pledge_amount.is_zero() {
return Decimal::zero();
}
+51 -29
View File
@@ -3,9 +3,10 @@
use super::NodeStatusCache;
use crate::node_status_api::helpers::{
_compute_mixnode_reward_estimation, _get_mixnode_avg_uptime,
_get_mixnode_inclusion_probability, _get_mixnode_reward_estimation,
_get_mixnode_stake_saturation, _get_mixnode_status, _mixnode_core_status_count,
_compute_mixnode_reward_estimation, _get_active_set_detailed, _get_mixnode_avg_uptime,
_get_mixnode_inclusion_probabilities, _get_mixnode_inclusion_probability,
_get_mixnode_reward_estimation, _get_mixnode_stake_saturation, _get_mixnode_status,
_get_mixnodes_detailed, _get_rewarded_set_detailed, _mixnode_core_status_count,
_mixnode_report, _mixnode_uptime_history,
};
use crate::node_status_api::models::ErrorResponse;
@@ -19,9 +20,9 @@ use rocket_okapi::openapi;
use validator_api_requests::models::{
AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse,
UptimeResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
#[openapi(tag = "status")]
@@ -112,10 +113,13 @@ pub(crate) async fn get_mixnode_status(
#[openapi(tag = "status")]
#[get("/mixnode/<mix_id>/reward-estimation")]
pub(crate) async fn get_mixnode_reward_estimation(
cache: &State<ValidatorCache>,
cache: &State<NodeStatusCache>,
validator_cache: &State<ValidatorCache>,
mix_id: MixId,
) -> Result<Json<RewardEstimationResponse>, ErrorResponse> {
Ok(Json(_get_mixnode_reward_estimation(cache, mix_id).await?))
Ok(Json(
_get_mixnode_reward_estimation(cache, validator_cache, mix_id).await?,
))
}
#[openapi(tag = "status")]
@@ -125,21 +129,31 @@ pub(crate) async fn get_mixnode_reward_estimation(
)]
pub(crate) async fn compute_mixnode_reward_estimation(
user_reward_param: Json<ComputeRewardEstParam>,
cache: &State<ValidatorCache>,
cache: &State<NodeStatusCache>,
validator_cache: &State<ValidatorCache>,
mix_id: MixId,
) -> Result<Json<RewardEstimationResponse>, ErrorResponse> {
Ok(Json(
_compute_mixnode_reward_estimation(user_reward_param.into_inner(), cache, mix_id).await?,
_compute_mixnode_reward_estimation(
user_reward_param.into_inner(),
cache,
validator_cache,
mix_id,
)
.await?,
))
}
#[openapi(tag = "status")]
#[get("/mixnode/<mix_id>/stake-saturation")]
pub(crate) async fn get_mixnode_stake_saturation(
cache: &State<ValidatorCache>,
cache: &State<NodeStatusCache>,
validator_cache: &State<ValidatorCache>,
mix_id: MixId,
) -> Result<Json<StakeSaturationResponse>, ErrorResponse> {
Ok(Json(_get_mixnode_stake_saturation(cache, mix_id).await?))
Ok(Json(
_get_mixnode_stake_saturation(cache, validator_cache, mix_id).await?,
))
}
#[openapi(tag = "status")]
@@ -168,21 +182,29 @@ pub(crate) async fn get_mixnode_avg_uptime(
pub(crate) async fn get_mixnode_inclusion_probabilities(
cache: &State<NodeStatusCache>,
) -> Result<Json<AllInclusionProbabilitiesResponse>, ErrorResponse> {
if let Some(prob) = cache.inclusion_probabilities().await {
let as_at = prob.timestamp();
let prob = prob.into_inner();
Ok(Json(AllInclusionProbabilitiesResponse {
inclusion_probabilities: prob.inclusion_probabilities,
samples: prob.samples,
elapsed: prob.elapsed,
delta_max: prob.delta_max,
delta_l2: prob.delta_l2,
as_at,
}))
} else {
Err(ErrorResponse::new(
"No data available".to_string(),
Status::ServiceUnavailable,
))
}
Ok(Json(_get_mixnode_inclusion_probabilities(cache).await?))
}
#[openapi(tag = "status")]
#[get("/mixnodes/detailed")]
pub async fn get_mixnodes_detailed(
cache: &State<NodeStatusCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
Json(_get_mixnodes_detailed(cache).await)
}
#[openapi(tag = "status")]
#[get("/mixnodes/rewarded/detailed")]
pub async fn get_rewarded_set_detailed(
cache: &State<NodeStatusCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
Json(_get_rewarded_set_detailed(cache).await)
}
#[openapi(tag = "status")]
#[get("/mixnodes/active/detailed")]
pub async fn get_active_set_detailed(
cache: &State<NodeStatusCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
Json(_get_active_set_detailed(cache).await)
}