a0fea6edb4
* Migrate nym-api HTTP server from rocket to axum (#4698) Migrate endpoints to Axum * Squashed after PR review Initial WIP - bootstrap axum server with same data as rocket - start axum server alongside rocket - add routes for circulating-supply, contract-cache, network - write simple bash validation that migrated APIs return 200 - mark rocket parts of code as deprecated - start more complicated routes: WIP Init storage always Add coconut routes Add api-status routes Expand tests WIP Migrate unstable APIs with query params Update bash tests Add node-status routes Redirect / to /swagger Update API tests Implement graceful shutdown rustfmt Fix clippy * Add ecash routes after rebase * PR feedback - add CORS layer - move logger to common crate - remove global log filters for nym-api and axum * Serve OpenAPI for all endpoints (#4761) * Playing around with swagger * Generate OpenAPI for /status routes * Phase out static_routes as strings - also nest routers in a clearer way * Generate OpenAPI for /network routes * Generate OpenAPI for /api-status routes * Generate OpenAPI for "nym nodes" routes * Fix some network-monitor routes * Generate OpenAPI for /ecash routes * Add utoipa feature to /common mods * Add OpenAPI for unstable routes * Fix MixNodeDetails field in models * Introduce axum feature flag (#4775) * Add Axum bind_address to config * Introduce axum feature flag * Add comment to template.rs * Add Github action to build wtih `axum` feature * Refactor server start & shutdown (#4777) * Clippy: don't forget axum feature * Refactor router so it's safer * Implement graceful shutdown * Nicer pattern matching * Better Result syntax
222 lines
7.4 KiB
Rust
222 lines
7.4 KiB
Rust
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
use crate::node_status_api::reward_estimate::{compute_apy_from_reward, compute_reward_estimate};
|
|
use crate::support::storage::NymApiStorage;
|
|
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodePerformance};
|
|
use nym_mixnet_contract_common::families::FamilyHead;
|
|
use nym_mixnet_contract_common::{reward_params::Performance, Interval, MixId};
|
|
use nym_mixnet_contract_common::{
|
|
GatewayBond, IdentityKey, MixNodeDetails, RewardedSetNodeStatus, RewardingParams,
|
|
};
|
|
use nym_topology::NetworkAddress;
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::net::ToSocketAddrs;
|
|
use std::str::FromStr;
|
|
|
|
pub(super) 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(), 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
|
|
}
|
|
|
|
pub(super) fn split_into_active_and_rewarded_set(
|
|
mixnodes_annotated: &HashMap<MixId, MixNodeBondAnnotated>,
|
|
rewarded_set_node_status: &HashMap<u32, RewardedSetNodeStatus>,
|
|
) -> (Vec<MixNodeBondAnnotated>, Vec<MixNodeBondAnnotated>) {
|
|
let rewarded_set: Vec<_> = mixnodes_annotated
|
|
.values()
|
|
.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)
|
|
})
|
|
.cloned()
|
|
.collect();
|
|
(rewarded_set, active_set)
|
|
}
|
|
|
|
pub(super) async fn get_mixnode_performance_from_storage(
|
|
storage: &NymApiStorage,
|
|
mix_id: MixId,
|
|
epoch: Interval,
|
|
) -> Option<Performance> {
|
|
storage
|
|
.get_average_mixnode_uptime_in_the_last_24hrs(
|
|
mix_id,
|
|
epoch.current_epoch_end_unix_timestamp(),
|
|
)
|
|
.await
|
|
.ok()
|
|
.map(Into::into)
|
|
}
|
|
|
|
pub(super) async fn get_gateway_performance_from_storage(
|
|
storage: &NymApiStorage,
|
|
gateway_id: &str,
|
|
epoch: Interval,
|
|
) -> Option<Performance> {
|
|
storage
|
|
.get_average_gateway_uptime_in_the_last_24hrs(
|
|
gateway_id,
|
|
epoch.current_epoch_end_unix_timestamp(),
|
|
)
|
|
.await
|
|
.ok()
|
|
.map(Into::into)
|
|
}
|
|
|
|
pub(super) async fn annotate_nodes_with_details(
|
|
storage: &NymApiStorage,
|
|
mixnodes: Vec<MixNodeDetails>,
|
|
interval_reward_params: RewardingParams,
|
|
current_interval: Interval,
|
|
rewarded_set: &HashMap<MixId, RewardedSetNodeStatus>,
|
|
mix_to_family: Vec<(IdentityKey, FamilyHead)>,
|
|
blacklist: &HashSet<MixId>,
|
|
) -> HashMap<MixId, MixNodeBondAnnotated> {
|
|
let mix_to_family = mix_to_family
|
|
.into_iter()
|
|
.collect::<HashMap<IdentityKey, FamilyHead>>();
|
|
|
|
let mut annotated = HashMap::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 rewarded_set_status = rewarded_set.get(&mixnode.mix_id()).copied();
|
|
|
|
// If the performance can't be obtained, because the nym-api was not started with
|
|
// the monitoring (and hence, storage), then reward estimates will be all zero
|
|
let performance =
|
|
get_mixnode_performance_from_storage(storage, mixnode.mix_id(), current_interval)
|
|
.await
|
|
.unwrap_or_default();
|
|
|
|
let reward_estimate = compute_reward_estimate(
|
|
&mixnode,
|
|
performance,
|
|
rewarded_set_status,
|
|
interval_reward_params,
|
|
current_interval,
|
|
);
|
|
|
|
let node_performance = storage
|
|
.construct_mixnode_report(mixnode.mix_id())
|
|
.await
|
|
.map(NodePerformance::from)
|
|
.ok()
|
|
.unwrap_or_default();
|
|
|
|
// safety: this conversion is infallible
|
|
let ip_addresses =
|
|
match NetworkAddress::from_str(&mixnode.bond_information.mix_node.host).unwrap() {
|
|
NetworkAddress::IpAddr(ip) => vec![ip],
|
|
NetworkAddress::Hostname(hostname) => {
|
|
// try to resolve it
|
|
(
|
|
hostname.as_str(),
|
|
mixnode.bond_information.mix_node.mix_port,
|
|
)
|
|
.to_socket_addrs()
|
|
.map(|iter| iter.map(|s| s.ip()).collect::<Vec<_>>())
|
|
.unwrap_or_default()
|
|
}
|
|
};
|
|
|
|
let (estimated_operator_apy, estimated_delegators_apy) =
|
|
compute_apy_from_reward(&mixnode, reward_estimate, current_interval);
|
|
|
|
let family = mix_to_family
|
|
.get(mixnode.bond_information.identity())
|
|
.cloned();
|
|
|
|
annotated.insert(
|
|
mixnode.mix_id(),
|
|
MixNodeBondAnnotated {
|
|
blacklisted: blacklist.contains(&mixnode.mix_id()),
|
|
mixnode_details: mixnode,
|
|
stake_saturation,
|
|
uncapped_stake_saturation,
|
|
performance,
|
|
node_performance,
|
|
estimated_operator_apy,
|
|
estimated_delegators_apy,
|
|
family,
|
|
ip_addresses,
|
|
},
|
|
);
|
|
}
|
|
annotated
|
|
}
|
|
|
|
pub(crate) async fn annotate_gateways_with_details(
|
|
storage: &NymApiStorage,
|
|
gateway_bonds: Vec<GatewayBond>,
|
|
current_interval: Interval,
|
|
blacklist: &HashSet<IdentityKey>,
|
|
) -> HashMap<IdentityKey, GatewayBondAnnotated> {
|
|
let mut annotated = HashMap::new();
|
|
for gateway_bond in gateway_bonds {
|
|
let performance = get_gateway_performance_from_storage(
|
|
storage,
|
|
gateway_bond.identity(),
|
|
current_interval,
|
|
)
|
|
.await
|
|
.unwrap_or_default();
|
|
|
|
let node_performance = storage
|
|
.construct_gateway_report(gateway_bond.identity())
|
|
.await
|
|
.map(NodePerformance::from)
|
|
.ok()
|
|
.unwrap_or_default();
|
|
|
|
// safety: this conversion is infallible
|
|
let ip_addresses = match NetworkAddress::from_str(&gateway_bond.gateway.host).unwrap() {
|
|
NetworkAddress::IpAddr(ip) => vec![ip],
|
|
NetworkAddress::Hostname(hostname) => {
|
|
// try to resolve it
|
|
(hostname.as_str(), gateway_bond.gateway.mix_port)
|
|
.to_socket_addrs()
|
|
.map(|iter| iter.map(|s| s.ip()).collect::<Vec<_>>())
|
|
.unwrap_or_default()
|
|
}
|
|
};
|
|
|
|
annotated.insert(
|
|
gateway_bond.identity().to_string(),
|
|
GatewayBondAnnotated {
|
|
blacklisted: blacklist.contains(&gateway_bond.gateway.identity_key),
|
|
gateway_bond,
|
|
self_described: None,
|
|
performance,
|
|
node_performance,
|
|
ip_addresses,
|
|
},
|
|
);
|
|
}
|
|
annotated
|
|
}
|