Skip to content

Commit

Permalink
explorer-api: add apy values to mix_nodes endpoint (nymtech#1401)
Browse files Browse the repository at this point in the history
* explorer-api: add apy fields to PrettyDetailedMixNodeBond

* explorer-api: clippy warnings

* explorer-api: use uptime from mixnodes endpoint

* changelog: add note

* rustfmt
  • Loading branch information
octol authored Jun 27, 2022
1 parent 70b0178 commit 1de8b2a
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 107 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
- explorer-api: learned how to sum the delegations by owner in a new endpoint.
- explorer-api: add apy values to `mix_nodes` endpoint
- gateway: Added gateway coconut verifications and validator-api communication for double spending protection ([#1261])
- network-explorer-ui: Upgrade to React Router 6
- rewarding: replace circulating supply with staking supply in reward calculations ([#1324])
Expand Down
8 changes: 4 additions & 4 deletions explorer-api/src/mix_node/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub(crate) async fn get_description(
Some(bond) => {
match get_mix_node_description(
&bond.mix_node().host,
&bond.mix_node().http_api_port,
bond.mix_node().http_api_port,
)
.await
{
Expand Down Expand Up @@ -129,7 +129,7 @@ pub(crate) async fn get_stats(
trace!("No valid cache value for {}", pubkey);
match state.inner.get_mix_node(pubkey).await {
Some(bond) => {
match get_mix_node_stats(&bond.mix_node().host, &bond.mix_node().http_api_port)
match get_mix_node_stats(&bond.mix_node().host, bond.mix_node().http_api_port)
.await
{
Ok(response) => {
Expand Down Expand Up @@ -188,14 +188,14 @@ pub(crate) async fn get_economic_dynamics_stats(
}
}

async fn get_mix_node_description(host: &str, port: &u16) -> Result<NodeDescription, ReqwestError> {
async fn get_mix_node_description(host: &str, port: u16) -> Result<NodeDescription, ReqwestError> {
reqwest::get(format!("http://{}:{}/description", host, port))
.await?
.json::<NodeDescription>()
.await
}

async fn get_mix_node_stats(host: &str, port: &u16) -> Result<NodeStats, ReqwestError> {
async fn get_mix_node_stats(host: &str, port: u16) -> Result<NodeStats, ReqwestError> {
reqwest::get(format!("http://{}:{}/stats", host, port))
.await?
.json::<NodeStats>()
Expand Down
4 changes: 3 additions & 1 deletion explorer-api/src/mix_node/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ pub(crate) struct PrettyDetailedMixNodeBond {
pub owner: Addr,
pub layer: Layer,
pub mix_node: MixNode,
pub avg_uptime: Option<u8>,
pub stake_saturation: f32,
pub avg_uptime: u8,
pub estimated_operator_apy: f64,
pub estimated_delegators_apy: f64,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
Expand Down
16 changes: 8 additions & 8 deletions explorer-api/src/mix_nodes/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub(crate) async fn list_active_set(
) -> Json<Vec<PrettyDetailedMixNodeBond>> {
Json(get_mixnodes_by_status(
state.inner.mixnodes.get_detailed_mixnodes().await,
MixnodeStatus::Active,
&MixnodeStatus::Active,
))
}

Expand All @@ -43,7 +43,7 @@ pub(crate) async fn list_inactive_set(
) -> Json<Vec<PrettyDetailedMixNodeBond>> {
Json(get_mixnodes_by_status(
state.inner.mixnodes.get_detailed_mixnodes().await,
MixnodeStatus::Inactive,
&MixnodeStatus::Inactive,
))
}

Expand All @@ -54,7 +54,7 @@ pub(crate) async fn list_standby_set(
) -> Json<Vec<PrettyDetailedMixNodeBond>> {
Json(get_mixnodes_by_status(
state.inner.mixnodes.get_detailed_mixnodes().await,
MixnodeStatus::Standby,
&MixnodeStatus::Standby,
))
}

Expand All @@ -66,9 +66,9 @@ pub(crate) async fn summary(state: &State<ExplorerApiStateContext>) -> Json<MixN

pub(crate) async fn get_mixnode_summary(state: &State<ExplorerApiStateContext>) -> MixNodeSummary {
let mixnodes = state.inner.mixnodes.get_detailed_mixnodes().await;
let active = get_mixnodes_by_status(mixnodes.clone(), MixnodeStatus::Active).len();
let standby = get_mixnodes_by_status(mixnodes.clone(), MixnodeStatus::Standby).len();
let inactive = get_mixnodes_by_status(mixnodes.clone(), MixnodeStatus::Inactive).len();
let active = get_mixnodes_by_status(mixnodes.clone(), &MixnodeStatus::Active).len();
let standby = get_mixnodes_by_status(mixnodes.clone(), &MixnodeStatus::Standby).len();
let inactive = get_mixnodes_by_status(mixnodes.clone(), &MixnodeStatus::Inactive).len();
MixNodeSummary {
count: mixnodes.len(),
activeset: MixNodeActiveSetSummary {
Expand All @@ -81,10 +81,10 @@ pub(crate) async fn get_mixnode_summary(state: &State<ExplorerApiStateContext>)

fn get_mixnodes_by_status(
all_mixnodes: Vec<PrettyDetailedMixNodeBond>,
status: MixnodeStatus,
status: &MixnodeStatus,
) -> Vec<PrettyDetailedMixNodeBond> {
all_mixnodes
.into_iter()
.filter(|mixnode| mixnode.status == status)
.filter(|mixnode| &mixnode.status == status)
.collect()
}
38 changes: 10 additions & 28 deletions explorer-api/src/mix_nodes/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ use std::time::{Duration, SystemTime};
use serde::Serialize;
use tokio::sync::RwLock;

use validator_client::models::{MixNodeBondAnnotated, UptimeResponse};
use validator_client::models::MixNodeBondAnnotated;

use crate::cache::Cache;
use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond};
use crate::mix_nodes::location::{Location, LocationCache, LocationCacheItem};
use crate::mix_nodes::CACHE_ENTRY_TTL;
Expand Down Expand Up @@ -77,32 +76,24 @@ impl MixNodesResult {
}
}

#[derive(Clone, Debug)]
pub(crate) struct MixNodeHealth {
avg_uptime: u8,
}

#[derive(Clone)]
pub(crate) struct ThreadsafeMixNodesCache {
mixnodes: Arc<RwLock<MixNodesResult>>,
locations: Arc<RwLock<LocationCache>>,
mixnode_health: Arc<RwLock<Cache<MixNodeHealth>>>,
}

impl ThreadsafeMixNodesCache {
pub(crate) fn new() -> Self {
ThreadsafeMixNodesCache {
mixnodes: Arc::new(RwLock::new(MixNodesResult::new())),
locations: Arc::new(RwLock::new(LocationCache::new())),
mixnode_health: Arc::new(RwLock::new(Cache::new())),
}
}

pub(crate) fn new_with_location_cache(locations: LocationCache) -> Self {
ThreadsafeMixNodesCache {
mixnodes: Arc::new(RwLock::new(MixNodesResult::new())),
locations: Arc::new(RwLock::new(locations)),
mixnode_health: Arc::new(RwLock::new(Cache::new())),
}
}

Expand All @@ -111,8 +102,9 @@ impl ThreadsafeMixNodesCache {
.read()
.await
.get(identity_key)
.map(|cache_item| cache_item.valid_until > SystemTime::now())
.unwrap_or(false)
.map_or(false, |cache_item| {
cache_item.valid_until > SystemTime::now()
})
}

pub(crate) async fn get_locations(&self) -> LocationCache {
Expand Down Expand Up @@ -141,11 +133,9 @@ impl ThreadsafeMixNodesCache {
) -> Option<PrettyDetailedMixNodeBond> {
let mixnodes_guard = self.mixnodes.read().await;
let location_guard = self.locations.read().await;
let mixnode_health_guard = self.mixnode_health.read().await;

let bond = mixnodes_guard.get_mixnode(identity_key);
let location = location_guard.get(identity_key);
let health = mixnode_health_guard.get(identity_key);

match bond {
Some(bond) => Some(PrettyDetailedMixNodeBond {
Expand All @@ -156,8 +146,10 @@ impl ThreadsafeMixNodesCache {
owner: bond.mixnode_bond.owner,
layer: bond.mixnode_bond.layer,
mix_node: bond.mixnode_bond.mix_node,
avg_uptime: health.map(|m| m.avg_uptime),
avg_uptime: bond.uptime,
stake_saturation: bond.stake_saturation,
estimated_operator_apy: bond.estimated_operator_apy,
estimated_delegators_apy: bond.estimated_delegators_apy,
}),
None => None,
}
Expand All @@ -166,15 +158,13 @@ impl ThreadsafeMixNodesCache {
pub(crate) async fn get_detailed_mixnodes(&self) -> Vec<PrettyDetailedMixNodeBond> {
let mixnodes_guard = self.mixnodes.read().await;
let location_guard = self.locations.read().await;
let mixnode_health_guard = self.mixnode_health.read().await;

mixnodes_guard
.all_mixnodes
.values()
.map(|bond| {
let location = location_guard.get(&bond.mix_node().identity_key);
let copy = bond.mixnode_bond.clone();
let health = mixnode_health_guard.get(&bond.mix_node().identity_key);
PrettyDetailedMixNodeBond {
location: location.and_then(|l| l.location.clone()),
status: mixnodes_guard.determine_node_status(&bond.mix_node().identity_key),
Expand All @@ -183,8 +173,10 @@ impl ThreadsafeMixNodesCache {
owner: copy.owner,
layer: copy.layer,
mix_node: copy.mix_node,
avg_uptime: health.map(|m| m.avg_uptime),
avg_uptime: bond.uptime,
stake_saturation: bond.stake_saturation,
estimated_operator_apy: bond.estimated_operator_apy,
estimated_delegators_apy: bond.estimated_delegators_apy,
}
})
.collect()
Expand All @@ -205,14 +197,4 @@ impl ThreadsafeMixNodesCache {
guard.active_mixnodes = active_nodes;
guard.valid_until = SystemTime::now() + CACHE_ENTRY_TTL;
}

pub(crate) async fn update_health_cache(&self, all_uptimes: Vec<UptimeResponse>) {
let mut mixnode_health = self.mixnode_health.write().await;
for uptime in all_uptimes {
let health = MixNodeHealth {
avg_uptime: uptime.avg_uptime,
};
mixnode_health.set(&uptime.identity, health);
}
}
}
8 changes: 4 additions & 4 deletions explorer-api/src/ping/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ async fn do_port_check(host: &str, port: u16) -> bool {
trace!("Successfully pinged {}", addr);
true
}
Ok(Err(_stream_err)) => {
warn!("{} ping failed {:}", addr, _stream_err);
Ok(Err(stream_err)) => {
warn!("{} ping failed {:}", addr, stream_err);
// didn't timeout but couldn't open tcp stream
false
}
Err(_timeout) => {
Err(timeout) => {
// timed out
warn!("{} timed out {:}", addr, _timeout);
warn!("{} timed out {:}", addr, timeout);
false
}
},
Expand Down
57 changes: 27 additions & 30 deletions explorer-api/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,39 +64,36 @@ impl ExplorerApiStateContext {
let json_file_path = Path::new(&json_file);
info!("Loading state from file {:?}...", json_file);

match File::open(json_file_path).map(serde_json::from_reader::<_, ExplorerApiStateOnDisk>) {
Ok(Ok(state)) => {
info!("Loaded state from file {:?}: {:?}", json_file, state);
ExplorerApiState {
country_node_distribution:
ThreadsafeCountryNodesDistribution::new_from_distribution(
state.country_node_distribution,
),
gateways: ThreadsafeGatewayCache::new(),
mixnode: ThreadsafeMixNodeCache::new(),
mixnodes: ThreadsafeMixNodesCache::new_with_location_cache(
state.location_cache,
if let Ok(Ok(state)) =
File::open(json_file_path).map(serde_json::from_reader::<_, ExplorerApiStateOnDisk>)
{
info!("Loaded state from file {:?}: {:?}", json_file, state);
ExplorerApiState {
country_node_distribution:
ThreadsafeCountryNodesDistribution::new_from_distribution(
state.country_node_distribution,
),
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
}
gateways: ThreadsafeGatewayCache::new(),
mixnode: ThreadsafeMixNodeCache::new(),
mixnodes: ThreadsafeMixNodesCache::new_with_location_cache(state.location_cache),
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
}
_ => {
warn!(
"Failed to load state from file {:?}, starting with empty state!",
json_file
);
} else {
warn!(
"Failed to load state from file {:?}, starting with empty state!",
json_file
);

ExplorerApiState {
country_node_distribution: ThreadsafeCountryNodesDistribution::new(),
gateways: ThreadsafeGatewayCache::new(),
mixnode: ThreadsafeMixNodeCache::new(),
mixnodes: ThreadsafeMixNodesCache::new(),
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
}
ExplorerApiState {
country_node_distribution: ThreadsafeCountryNodesDistribution::new(),
gateways: ThreadsafeGatewayCache::new(),
mixnode: ThreadsafeMixNodeCache::new(),
mixnodes: ThreadsafeMixNodesCache::new(),
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
}
}
}
Expand Down
32 changes: 1 addition & 31 deletions explorer-api/src/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use std::future::Future;

use mixnet_contract_common::GatewayBond;
use validator_client::models::{MixNodeBondAnnotated, UptimeResponse};
use validator_client::models::MixNodeBondAnnotated;
use validator_client::nymd::error::NymdError;
use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse};
use validator_client::ValidatorClientError;
Expand Down Expand Up @@ -89,17 +89,6 @@ impl ExplorerApiTasks {
.await
}

async fn retrieve_all_mixnode_avg_uptimes(
&self,
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
self.state
.inner
.validator_client
.0
.get_mixnode_avg_uptimes()
.await
}

async fn update_mixnode_cache(&self) {
let all_bonds = self.retrieve_all_mixnodes().await;
let rewarded_nodes = self
Expand All @@ -121,21 +110,6 @@ impl ExplorerApiTasks {
.await;
}

async fn update_mixnode_health_cache(&self) {
match self.retrieve_all_mixnode_avg_uptimes().await {
Ok(response) => {
self.state
.inner
.mixnodes
.update_health_cache(response)
.await
}
Err(e) => {
error!("Failed to get mixnode avg uptimes: {:?}", e)
}
}
}

async fn update_validators_cache(&self) {
match self.retrieve_all_validators().await {
Ok(response) => self.state.inner.validators.update_cache(response).await,
Expand Down Expand Up @@ -172,10 +146,6 @@ impl ExplorerApiTasks {

info!("Updating mix node cache...");
self.update_mixnode_cache().await;

info!("Updating mix node health cache...");
self.update_mixnode_health_cache().await;
info!("Done");
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use thiserror::Error;

pub const ENCRYPTED_ADDRESS_SIZE: usize = DESTINATION_ADDRESS_LENGTH;

/// Replacement for what used to be an 'AuthToken'. We used to be generating an 'AuthToken' based on
/// Replacement for what used to be an `AuthToken`. We used to be generating an `AuthToken` based on
/// local secret and remote address in order to allow for authentication. Due to changes in registration
/// and the fact we are deriving a shared key, we are encrypting remote's address with the previously
/// derived shared key. If the value is as expected, then authentication is successful.
Expand Down

0 comments on commit 1de8b2a

Please sign in to comment.