Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ed0657eb5 | |||
| a60110560a | |||
| 2097b58937 | |||
| ff8c6fd901 | |||
| 49d527b54a | |||
| f1ccc43709 | |||
| 4d118cc41c | |||
| 1806341e34 |
Generated
+3
-3
@@ -6061,7 +6061,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "1.0.0-rc.3"
|
||||
version = "1.0.0-rc.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.7",
|
||||
@@ -7019,9 +7019,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.102"
|
||||
version = "0.9.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
|
||||
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
||||
@@ -38,7 +38,7 @@ pub struct TopologyReadPermit<'a> {
|
||||
permit: RwLockReadGuard<'a, Option<NymTopology>>,
|
||||
}
|
||||
|
||||
impl<'a> Deref for TopologyReadPermit<'a> {
|
||||
impl Deref for TopologyReadPermit<'_> {
|
||||
type Target = Option<NymTopology>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Div<GasPrice> for Coin {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Div<GasPrice> for &'a Coin {
|
||||
impl Div<GasPrice> for &Coin {
|
||||
type Output = Gas;
|
||||
|
||||
fn div(self, rhs: GasPrice) -> Self::Output {
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct GasPrice {
|
||||
pub denom: String,
|
||||
}
|
||||
|
||||
impl<'a> Mul<Gas> for &'a GasPrice {
|
||||
impl Mul<Gas> for &GasPrice {
|
||||
type Output = Coin;
|
||||
|
||||
fn mul(self, gas_limit: Gas) -> Self::Output {
|
||||
|
||||
@@ -32,7 +32,7 @@ pub(crate) mod string_rfc3339_offset_date_time {
|
||||
|
||||
struct Rfc3339OffsetDateTimeVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for Rfc3339OffsetDateTimeVisitor {
|
||||
impl Visitor<'_> for Rfc3339OffsetDateTimeVisitor {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
|
||||
@@ -26,9 +26,8 @@ const PARALLEL_RUNS: usize = 32;
|
||||
/// `lambda` ($\lambda$) in the DKG paper
|
||||
const SECURITY_PARAMETER: usize = 256;
|
||||
|
||||
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
|
||||
/// ceil(SECURITY_PARAMETER / PARALLEL_RUNS) in the paper
|
||||
const NUM_CHALLENGE_BITS: usize = (SECURITY_PARAMETER + PARALLEL_RUNS - 1) / PARALLEL_RUNS;
|
||||
const NUM_CHALLENGE_BITS: usize = SECURITY_PARAMETER.div_ceil(PARALLEL_RUNS);
|
||||
|
||||
// type alias for ease of use
|
||||
type FirstChallenge = Vec<Vec<Vec<u64>>>;
|
||||
|
||||
@@ -196,7 +196,7 @@ impl<'b> Add<&'b Polynomial> for Polynomial {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add<Polynomial> for &'a Polynomial {
|
||||
impl Add<Polynomial> for &Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: Polynomial) -> Polynomial {
|
||||
@@ -212,7 +212,7 @@ impl Add<Polynomial> for Polynomial {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Add<&'b Polynomial> for &'a Polynomial {
|
||||
impl<'b> Add<&'b Polynomial> for &Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: &'b Polynomial) -> Self::Output {
|
||||
|
||||
@@ -37,7 +37,7 @@ pub struct GatewayHandshake<'a> {
|
||||
handshake_future: BoxFuture<'a, Result<SharedGatewayKey, HandshakeError>>,
|
||||
}
|
||||
|
||||
impl<'a> Future for GatewayHandshake<'a> {
|
||||
impl Future for GatewayHandshake<'_> {
|
||||
type Output = Result<SharedGatewayKey, HandshakeError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
|
||||
@@ -324,18 +324,6 @@ pub fn unchecked_aggregate_indices_signatures(
|
||||
_aggregate_indices_signatures(params, vk, signatures_shares, false)
|
||||
}
|
||||
|
||||
/// Generates parameters for the scheme setup.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `total_coins` - it is the number of coins in a freshly generated wallet. It is the public parameter of the scheme.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Parameters` struct containing group parameters, public key, the number of signatures (`total_coins`),
|
||||
/// and a map of signatures for each index `l`.
|
||||
///
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -264,7 +264,7 @@ impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
|
||||
impl Mul<Scalar> for &VerificationKeyAuth {
|
||||
type Output = VerificationKeyAuth;
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -984,7 +984,7 @@ pub struct SerialNumberRef<'a> {
|
||||
pub(crate) inner: &'a [G1Projective],
|
||||
}
|
||||
|
||||
impl<'a> SerialNumberRef<'a> {
|
||||
impl SerialNumberRef<'_> {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let ss_len = self.inner.len();
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(ss_len * 48);
|
||||
|
||||
@@ -206,7 +206,7 @@ impl Deref for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Mul<&'b Scalar> for &'a PublicKey {
|
||||
impl<'b> Mul<&'b Scalar> for &PublicKey {
|
||||
type Output = G1Projective;
|
||||
|
||||
fn mul(self, rhs: &'b Scalar) -> Self::Output {
|
||||
|
||||
@@ -305,7 +305,7 @@ impl<'b> Add<&'b VerificationKey> for VerificationKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<Scalar> for &'a VerificationKey {
|
||||
impl Mul<Scalar> for &VerificationKey {
|
||||
type Output = VerificationKey;
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -64,7 +64,7 @@ impl<'de> Deserialize<'de> for Recipient {
|
||||
{
|
||||
struct RecipientVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for RecipientVisitor {
|
||||
impl Visitor<'_> for RecipientVisitor {
|
||||
type Value = Recipient;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Encodoing and decoding node routing information.
|
||||
//!
|
||||
//! This module is responsible for encoding and decoding node routing information, so that
|
||||
//! they could be later put into an appropriate field in a sphinx header.
|
||||
//! Currently, that routing information is an IP address, but in principle it can be anything
|
||||
//! for as long as it's going to fit in the field.
|
||||
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx_types::{NodeAddressBytes, NODE_ADDRESS_LENGTH};
|
||||
|
||||
@@ -12,13 +19,6 @@ use thiserror::Error;
|
||||
pub type NodeIdentity = identity::PublicKey;
|
||||
pub const NODE_IDENTITY_SIZE: usize = identity::PUBLIC_KEY_LENGTH;
|
||||
|
||||
/// Encodoing and decoding node routing information.
|
||||
///
|
||||
/// This module is responsible for encoding and decoding node routing information, so that
|
||||
/// they could be later put into an appropriate field in a sphinx header.
|
||||
/// Currently, that routing information is an IP address, but in principle it can be anything
|
||||
/// for as long as it's going to fit in the field.
|
||||
|
||||
/// MAX_UNPADDED_LEN represents maximum length an unpadded address could have.
|
||||
/// In this case it's an ipv6 socket address (with version prefix)
|
||||
pub const MAX_NODE_ADDRESS_UNPADDED_LEN: usize = 19;
|
||||
|
||||
@@ -56,7 +56,7 @@ impl<'de> Deserialize<'de> for ReplySurb {
|
||||
{
|
||||
struct ReplySurbVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ReplySurbVisitor {
|
||||
impl Visitor<'_> for ReplySurbVisitor {
|
||||
type Value = ReplySurb;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
||||
@@ -253,25 +253,12 @@ impl Socks5RequestContent {
|
||||
/// Deserialize the request type, connection id, destination address and port,
|
||||
/// and the request body from bytes.
|
||||
///
|
||||
// TODO: this was already inaccurate
|
||||
// /// Serialized bytes looks like this:
|
||||
// ///
|
||||
// /// --------------------------------------------------------------------------------------
|
||||
// /// request_flag | connection_id | address_length | remote_address_bytes | request_data |
|
||||
// /// 1 | 8 | 2 | address_length | ... |
|
||||
// /// --------------------------------------------------------------------------------------
|
||||
///
|
||||
/// The request_flag tells us whether this is a new connection request (`new_connect`),
|
||||
/// an already-established connection we should send up (`new_send`), or
|
||||
/// a request to close an established connection (`new_close`).
|
||||
|
||||
// connect:
|
||||
// RequestFlag::Connect || CONN_ID || ADDR_LEN || ADDR || <RETURN_ADDR>
|
||||
//
|
||||
// send:
|
||||
// RequestFlag::Send || CONN_ID || LOCAL_CLOSED || DATA
|
||||
// where DATA: SEQ || TRUE_DATA
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Socks5RequestContent, RequestDeserializationError> {
|
||||
// each request needs to at least contain flag and ConnectionId
|
||||
if b.is_empty() {
|
||||
|
||||
@@ -166,7 +166,7 @@ impl GeoIp {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&City<'a>> for Location {
|
||||
impl TryFrom<&City<'_>> for Location {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(city: &City) -> Result<Self, Self::Error> {
|
||||
|
||||
@@ -65,7 +65,7 @@ impl<'r> FromRequest<'r> for Location {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OpenApiFromRequest<'a> for Location {
|
||||
impl OpenApiFromRequest<'_> for Location {
|
||||
fn from_request_input(
|
||||
_gen: &mut OpenApiGenerator,
|
||||
_name: String,
|
||||
|
||||
@@ -64,7 +64,7 @@ pub(crate) mod overengineered_offset_date_time_serde {
|
||||
])),
|
||||
];
|
||||
|
||||
impl<'de> Visitor<'de> for OffsetDateTimeVisitor {
|
||||
impl Visitor<'_> for OffsetDateTimeVisitor {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
|
||||
@@ -76,7 +76,7 @@ pub(crate) async fn submit_public_keys(controllers: &mut [TestingDkgController],
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let threshold = (2 * controllers.len() as u64 + 3 - 1) / 3;
|
||||
let threshold = (2 * controllers.len() as u64).div_ceil(3);
|
||||
|
||||
let mut guard = controllers[0].chain_state.lock().unwrap();
|
||||
guard.dkg_contract.epoch.state = EpochState::DealingExchange { resharing };
|
||||
|
||||
@@ -59,7 +59,7 @@ impl GatewayClientHandle {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> UnlockedGatewayClientHandle<'a> {
|
||||
impl UnlockedGatewayClientHandle<'_> {
|
||||
pub(crate) fn get_mut_unchecked(
|
||||
&mut self,
|
||||
) -> &mut GatewayClient<nyxd::Client, PersistentStorage> {
|
||||
|
||||
@@ -674,7 +674,7 @@ pub(crate) struct ChainWritePermit<'a> {
|
||||
inner: RwLockWriteGuard<'a, DirectSigningHttpRpcNyxdClient>,
|
||||
}
|
||||
|
||||
impl<'a> Deref for ChainWritePermit<'a> {
|
||||
impl Deref for ChainWritePermit<'_> {
|
||||
type Target = DirectSigningHttpRpcNyxdClient;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-api"
|
||||
version = "1.0.0-rc.3"
|
||||
version = "1.0.0-rc.4"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -45,11 +45,6 @@ pub(crate) struct Cli {
|
||||
#[arg(value_parser = parse_duration)]
|
||||
pub(crate) nym_api_client_timeout: Duration,
|
||||
|
||||
/// Explorer api client timeout.
|
||||
#[clap(long, default_value = "15", env = "EXPLORER_CLIENT_TIMEOUT")]
|
||||
#[arg(value_parser = parse_duration)]
|
||||
pub(crate) explorer_client_timeout: Duration,
|
||||
|
||||
/// Connection url for the database.
|
||||
#[clap(long, env = "DATABASE_URL")]
|
||||
pub(crate) database_url: String,
|
||||
@@ -70,10 +65,18 @@ pub(crate) struct Cli {
|
||||
#[arg(value_parser = parse_duration)]
|
||||
pub(crate) testruns_refresh_interval: Duration,
|
||||
|
||||
#[clap(long, default_value = "86400", env = "NODE_STATUS_API_GEODATA_TTL")]
|
||||
#[arg(value_parser = parse_duration)]
|
||||
pub(crate) geodata_ttl: Duration,
|
||||
|
||||
#[clap(env = "NODE_STATUS_API_AGENT_KEY_LIST")]
|
||||
#[arg(value_delimiter = ',')]
|
||||
pub(crate) agent_key_list: Vec<String>,
|
||||
|
||||
/// https://github.com/ipinfo/rust
|
||||
#[clap(long, env = "IPINFO_API_TOKEN")]
|
||||
pub(crate) ipinfo_api_token: String,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
default_value_t = 40,
|
||||
|
||||
@@ -12,6 +12,7 @@ pub(crate) struct GatewayRecord {
|
||||
pub(crate) bonded: bool,
|
||||
pub(crate) blacklisted: bool,
|
||||
pub(crate) self_described: String,
|
||||
// TODO dz shouldn't be an option
|
||||
pub(crate) explorer_pretty_bond: Option<String>,
|
||||
pub(crate) last_updated_utc: i64,
|
||||
pub(crate) performance: u8,
|
||||
@@ -215,7 +216,6 @@ pub(crate) const MIXNODES_BONDED_RESERVE: &str = "mixnodes.bonded.reserve";
|
||||
pub(crate) const MIXNODES_BLACKLISTED_COUNT: &str = "mixnodes.blacklisted.count";
|
||||
|
||||
pub(crate) const GATEWAYS_BONDED_COUNT: &str = "gateways.bonded.count";
|
||||
pub(crate) const GATEWAYS_EXPLORER_COUNT: &str = "gateways.explorer.count";
|
||||
pub(crate) const GATEWAYS_BLACKLISTED_COUNT: &str = "gateways.blacklisted.count";
|
||||
|
||||
pub(crate) const MIXNODES_HISTORICAL_COUNT: &str = "mixnodes.historical.count";
|
||||
@@ -272,7 +272,6 @@ pub(crate) mod gateway {
|
||||
pub(crate) bonded: GatewaySummaryBonded,
|
||||
pub(crate) blacklisted: GatewaySummaryBlacklisted,
|
||||
pub(crate) historical: GatewaySummaryHistorical,
|
||||
pub(crate) explorer: GatewaySummaryExplorer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
models::{
|
||||
gateway::{
|
||||
GatewaySummary, GatewaySummaryBlacklisted, GatewaySummaryBonded,
|
||||
GatewaySummaryExplorer, GatewaySummaryHistorical,
|
||||
GatewaySummaryHistorical,
|
||||
},
|
||||
mixnode::{
|
||||
MixnodeSummary, MixnodeSummaryBlacklisted, MixnodeSummaryBonded,
|
||||
@@ -82,7 +82,6 @@ async fn from_summary_dto(items: Vec<SummaryDto>) -> HttpResult<NetworkSummary>
|
||||
const MIXNODES_BONDED_RESERVE: &str = "mixnodes.bonded.reserve";
|
||||
const MIXNODES_BLACKLISTED_COUNT: &str = "mixnodes.blacklisted.count";
|
||||
const GATEWAYS_BONDED_COUNT: &str = "gateways.bonded.count";
|
||||
const GATEWAYS_EXPLORER_COUNT: &str = "gateways.explorer.count";
|
||||
const GATEWAYS_BLACKLISTED_COUNT: &str = "gateways.blacklisted.count";
|
||||
const MIXNODES_HISTORICAL_COUNT: &str = "mixnodes.historical.count";
|
||||
const GATEWAYS_HISTORICAL_COUNT: &str = "gateways.historical.count";
|
||||
@@ -96,7 +95,6 @@ async fn from_summary_dto(items: Vec<SummaryDto>) -> HttpResult<NetworkSummary>
|
||||
// check we have all the keys we are expecting, and build up a map of errors for missing one
|
||||
let keys = [
|
||||
GATEWAYS_BONDED_COUNT,
|
||||
GATEWAYS_EXPLORER_COUNT,
|
||||
GATEWAYS_HISTORICAL_COUNT,
|
||||
GATEWAYS_BLACKLISTED_COUNT,
|
||||
MIXNODES_BLACKLISTED_COUNT,
|
||||
@@ -139,10 +137,6 @@ async fn from_summary_dto(items: Vec<SummaryDto>) -> HttpResult<NetworkSummary>
|
||||
.unwrap_or_default();
|
||||
let gateways_bonded_count: SummaryDto =
|
||||
map.get(GATEWAYS_BONDED_COUNT).cloned().unwrap_or_default();
|
||||
let gateways_explorer_count: SummaryDto = map
|
||||
.get(GATEWAYS_EXPLORER_COUNT)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let mixnodes_historical_count: SummaryDto = map
|
||||
.get(MIXNODES_HISTORICAL_COUNT)
|
||||
.cloned()
|
||||
@@ -187,10 +181,6 @@ async fn from_summary_dto(items: Vec<SummaryDto>) -> HttpResult<NetworkSummary>
|
||||
count: to_count_i32(&gateways_historical_count),
|
||||
last_updated_utc: to_timestamp(&gateways_historical_count),
|
||||
},
|
||||
explorer: GatewaySummaryExplorer {
|
||||
count: to_count_i32(&gateways_explorer_count),
|
||||
last_updated_utc: to_timestamp(&gateways_explorer_count),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -28,13 +28,15 @@ async fn main() -> anyhow::Result<()> {
|
||||
let storage = db::Storage::init(connection_url).await?;
|
||||
let db_pool = storage.pool_owned();
|
||||
let args_clone = args.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
monitor::spawn_in_background(
|
||||
db_pool,
|
||||
args_clone.explorer_client_timeout,
|
||||
args_clone.nym_api_client_timeout,
|
||||
&args_clone.nyxd_addr,
|
||||
args_clone.nyxd_addr,
|
||||
args_clone.monitor_refresh_interval,
|
||||
args_clone.ipinfo_api_token,
|
||||
args_clone.geodata_ttl,
|
||||
)
|
||||
.await;
|
||||
tracing::info!("Started monitor task");
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub(crate) struct IpInfoClient {
|
||||
client: reqwest::Client,
|
||||
token: String,
|
||||
}
|
||||
|
||||
impl IpInfoClient {
|
||||
pub(crate) fn new(token: impl Into<String>) -> Self {
|
||||
let client = reqwest::Client::new();
|
||||
let token = token.into();
|
||||
|
||||
Self { client, token }
|
||||
}
|
||||
|
||||
pub(crate) async fn locate_ip(&self, ip: impl AsRef<str>) -> anyhow::Result<Location> {
|
||||
let url = format!(
|
||||
"https://ipinfo.io/{}/country?token={}",
|
||||
ip.as_ref(),
|
||||
&self.token
|
||||
);
|
||||
let response = self
|
||||
.client
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
// map non 2xx responses to error
|
||||
.and_then(|res| res.error_for_status())
|
||||
.map_err(|err| {
|
||||
if matches!(err.status(), Some(reqwest::StatusCode::TOO_MANY_REQUESTS)) {
|
||||
tracing::error!("ipinfo rate limit exceeded");
|
||||
}
|
||||
anyhow::Error::from(err)
|
||||
})?;
|
||||
let response_text = response.text().await?.trim().to_string();
|
||||
|
||||
Ok(Location {
|
||||
two_letter_iso_country_code: response_text,
|
||||
})
|
||||
}
|
||||
|
||||
/// check DOESN'T consume bandwidth allowance
|
||||
pub(crate) async fn check_remaining_bandwidth(
|
||||
&self,
|
||||
) -> anyhow::Result<ipinfo::MeResponseRequests> {
|
||||
let url = format!("https://ipinfo.io/me?token={}", &self.token);
|
||||
let response = self
|
||||
.client
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
// map non 2xx responses to error
|
||||
.and_then(|res| res.error_for_status())
|
||||
.map_err(|err| {
|
||||
if matches!(err.status(), Some(reqwest::StatusCode::TOO_MANY_REQUESTS)) {
|
||||
tracing::error!("ipinfo rate limit exceeded");
|
||||
}
|
||||
anyhow::Error::from(err)
|
||||
})?;
|
||||
let response: ipinfo::MeResponse = response.json().await?;
|
||||
|
||||
Ok(response.requests)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct NodeGeoData {
|
||||
pub(crate) identity_key: String,
|
||||
pub(crate) owner: Addr,
|
||||
pub(crate) pledge_amount: Coin,
|
||||
pub(crate) location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub(crate) struct Location {
|
||||
pub(crate) two_letter_iso_country_code: String,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self {
|
||||
two_letter_iso_country_code: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod ipinfo {
|
||||
use super::*;
|
||||
|
||||
// clippy doesn't understand it's used for typed deserialization
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
/// `/me` is undocumented in their developers page
|
||||
/// https://ipinfo.io/developers/responses
|
||||
/// but explained here
|
||||
/// https://community.ipinfo.io/t/easy-way-to-check-allowance-usage/5755/2
|
||||
pub(crate) struct MeResponse {
|
||||
token: String,
|
||||
pub(crate) requests: MeResponseRequests,
|
||||
}
|
||||
|
||||
// clippy doesn't understand it's used for typed deserialization
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub(crate) struct MeResponseRequests {
|
||||
pub(crate) day: u64,
|
||||
pub(crate) month: u64,
|
||||
pub(crate) limit: u64,
|
||||
pub(crate) remaining: u64,
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
use crate::db::models::{
|
||||
gateway, mixnode, GatewayRecord, MixnodeRecord, NetworkSummary, GATEWAYS_BLACKLISTED_COUNT,
|
||||
GATEWAYS_BONDED_COUNT, GATEWAYS_EXPLORER_COUNT, GATEWAYS_HISTORICAL_COUNT,
|
||||
MIXNODES_BLACKLISTED_COUNT, MIXNODES_BONDED_ACTIVE, MIXNODES_BONDED_COUNT,
|
||||
MIXNODES_BONDED_INACTIVE, MIXNODES_BONDED_RESERVE, MIXNODES_HISTORICAL_COUNT,
|
||||
GATEWAYS_BONDED_COUNT, GATEWAYS_HISTORICAL_COUNT, MIXNODES_BLACKLISTED_COUNT,
|
||||
MIXNODES_BONDED_ACTIVE, MIXNODES_BONDED_COUNT, MIXNODES_BONDED_INACTIVE,
|
||||
MIXNODES_BONDED_RESERVE, MIXNODES_HISTORICAL_COUNT,
|
||||
};
|
||||
use crate::db::{queries, DbPool};
|
||||
use crate::monitor::geodata::{Location, NodeGeoData};
|
||||
use anyhow::anyhow;
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_explorer_client::{ExplorerClient, PrettyDetailedGatewayBond};
|
||||
use moka::future::Cache;
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::client::{NodeId, NymApiClientExt};
|
||||
use nym_validator_client::models::{
|
||||
LegacyDescribedMixNode, MixNodeBondAnnotated, NymNodeDescription,
|
||||
};
|
||||
@@ -20,40 +21,55 @@ use nym_validator_client::nyxd::contract_traits::PagedMixnetQueryClient;
|
||||
use nym_validator_client::nyxd::{AccountId, NyxdClient};
|
||||
use nym_validator_client::NymApiClient;
|
||||
use reqwest::Url;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::str::FromStr;
|
||||
use tokio::time::Duration;
|
||||
use tracing::instrument;
|
||||
|
||||
pub(crate) use geodata::IpInfoClient;
|
||||
|
||||
mod geodata;
|
||||
|
||||
// TODO dz should be configurable
|
||||
const FAILURE_RETRY_DELAY: Duration = Duration::from_secs(60);
|
||||
|
||||
static DELEGATION_PROGRAM_WALLET: &str = "n1rnxpdpx3kldygsklfft0gech7fhfcux4zst5lw";
|
||||
|
||||
struct Monitor {
|
||||
db_pool: DbPool,
|
||||
network_details: NymNetworkDetails,
|
||||
nym_api_client_timeout: Duration,
|
||||
nyxd_addr: Url,
|
||||
ipinfo: IpInfoClient,
|
||||
geocache: Cache<NodeId, Location>,
|
||||
}
|
||||
|
||||
// TODO dz: query many NYM APIs:
|
||||
// multiple instances running directory cache, ask sachin
|
||||
#[instrument(level = "debug", name = "data_monitor", skip_all)]
|
||||
pub(crate) async fn spawn_in_background(
|
||||
db_pool: DbPool,
|
||||
explorer_client_timeout: Duration,
|
||||
nym_api_client_timeout: Duration,
|
||||
nyxd_addr: &Url,
|
||||
nyxd_addr: Url,
|
||||
refresh_interval: Duration,
|
||||
ipinfo_api_token: String,
|
||||
geodata_ttl: Duration,
|
||||
) {
|
||||
let network_defaults = nym_network_defaults::NymNetworkDetails::new_from_env();
|
||||
let geocache = Cache::builder().time_to_live(geodata_ttl).build();
|
||||
let ipinfo = IpInfoClient::new(ipinfo_api_token.clone());
|
||||
let mut monitor = Monitor {
|
||||
db_pool,
|
||||
network_details: nym_network_defaults::NymNetworkDetails::new_from_env(),
|
||||
nym_api_client_timeout,
|
||||
nyxd_addr,
|
||||
ipinfo,
|
||||
geocache,
|
||||
};
|
||||
|
||||
loop {
|
||||
tracing::info!("Refreshing node info...");
|
||||
|
||||
if let Err(e) = run(
|
||||
&db_pool,
|
||||
&network_defaults,
|
||||
explorer_client_timeout,
|
||||
nym_api_client_timeout,
|
||||
nyxd_addr,
|
||||
)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = monitor.run().await {
|
||||
tracing::error!(
|
||||
"Monitor run failed: {e}, retrying in {}s...",
|
||||
FAILURE_RETRY_DELAY.as_secs()
|
||||
@@ -70,345 +86,370 @@ pub(crate) async fn spawn_in_background(
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(
|
||||
pool: &DbPool,
|
||||
network_details: &NymNetworkDetails,
|
||||
explorer_client_timeout: Duration,
|
||||
nym_api_client_timeout: Duration,
|
||||
nyxd_addr: &Url,
|
||||
) -> anyhow::Result<()> {
|
||||
let default_api_url = network_details
|
||||
.endpoints
|
||||
.first()
|
||||
.expect("rust sdk mainnet default incorrectly configured")
|
||||
.api_url()
|
||||
.clone()
|
||||
.expect("rust sdk mainnet default missing api_url");
|
||||
let default_explorer_url = network_details.explorer_api.clone().map(|url| {
|
||||
url.parse()
|
||||
.expect("rust sdk mainnet default explorer url not parseable")
|
||||
});
|
||||
impl Monitor {
|
||||
async fn run(&mut self) -> anyhow::Result<()> {
|
||||
self.check_ipinfo_bandwidth().await;
|
||||
|
||||
// TODO dz replace explorer api with ipinfo.io
|
||||
let default_explorer_url =
|
||||
default_explorer_url.expect("explorer url missing in network config");
|
||||
let explorer_client =
|
||||
ExplorerClient::new_with_timeout(default_explorer_url, explorer_client_timeout)?;
|
||||
let explorer_gateways = explorer_client
|
||||
.unstable_get_gateways()
|
||||
.await
|
||||
.log_error("unstable_get_gateways")?;
|
||||
let default_api_url = self
|
||||
.network_details
|
||||
.endpoints
|
||||
.first()
|
||||
.expect("rust sdk mainnet default incorrectly configured")
|
||||
.api_url()
|
||||
.clone()
|
||||
.expect("rust sdk mainnet default missing api_url");
|
||||
|
||||
let api_client = NymApiClient::new_with_timeout(default_api_url, nym_api_client_timeout);
|
||||
let api_client =
|
||||
NymApiClient::new_with_timeout(default_api_url, self.nym_api_client_timeout);
|
||||
|
||||
let all_nodes = api_client
|
||||
.get_all_described_nodes()
|
||||
.await
|
||||
.log_error("get_all_described_nodes")?;
|
||||
tracing::debug!("Fetched {} total nodes", all_nodes.len());
|
||||
let all_nodes = api_client
|
||||
.get_all_described_nodes()
|
||||
.await
|
||||
.log_error("get_all_described_nodes")?;
|
||||
tracing::debug!("Fetched {} total nodes", all_nodes.len());
|
||||
|
||||
let gateways = all_nodes
|
||||
.iter()
|
||||
.filter(|node| node.description.declared_role.entry)
|
||||
.collect::<Vec<_>>();
|
||||
tracing::debug!("Of those, {} gateways", gateways.len());
|
||||
for gw in gateways.iter() {
|
||||
tracing::debug!("{}", gw.ed25519_identity_key().to_base58_string());
|
||||
}
|
||||
let gateways = all_nodes
|
||||
.iter()
|
||||
.filter(|node| node.description.declared_role.entry)
|
||||
.collect::<Vec<_>>();
|
||||
tracing::debug!(
|
||||
"{}/{} with declared entry gateway capability",
|
||||
gateways.len(),
|
||||
all_nodes.len()
|
||||
);
|
||||
|
||||
let mixnodes = all_nodes
|
||||
.iter()
|
||||
.filter(|node| node.description.declared_role.mixnode)
|
||||
.collect::<Vec<_>>();
|
||||
tracing::debug!("Of those, {} mixnodes", mixnodes.len());
|
||||
let mixnodes = all_nodes
|
||||
.iter()
|
||||
.filter(|node| node.description.declared_role.mixnode)
|
||||
.collect::<Vec<_>>();
|
||||
tracing::debug!(
|
||||
"{}/{} with declared mixnode capability",
|
||||
mixnodes.len(),
|
||||
all_nodes.len()
|
||||
);
|
||||
|
||||
log_gw_in_explorer_not_api(explorer_gateways.as_slice(), gateways.as_slice());
|
||||
let bonded_node_info = api_client
|
||||
.get_all_bonded_nym_nodes()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|node| (node.bond_information.node_id, node.bond_information))
|
||||
// for faster reads
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let all_skimmed_nodes = api_client
|
||||
.get_all_basic_nodes(None)
|
||||
.await
|
||||
.log_error("get_all_basic_nodes")?;
|
||||
|
||||
let mixnodes = api_client
|
||||
.get_cached_mixnodes()
|
||||
.await
|
||||
.log_error("get_cached_mixnodes")?;
|
||||
tracing::debug!("Fetched {} mixnodes", mixnodes.len());
|
||||
|
||||
// let gateways_blacklisted = gateways.iter().filter(|gw|gw.)
|
||||
let gateways_blacklisted = all_skimmed_nodes
|
||||
.iter()
|
||||
.filter_map(|node| {
|
||||
if node.performance.round_to_integer() <= 50 && node.supported_roles.entry {
|
||||
Some(node.ed25519_identity_pubkey.to_base58_string())
|
||||
} else {
|
||||
None
|
||||
let mut gateway_geodata = Vec::new();
|
||||
for gateway in gateways.iter() {
|
||||
if let Some(node_info) = bonded_node_info.get(&gateway.node_id) {
|
||||
let gw_geodata = NodeGeoData {
|
||||
identity_key: node_info.node.identity_key.to_owned(),
|
||||
owner: node_info.owner.to_owned(),
|
||||
pledge_amount: node_info.original_pledge.to_owned(),
|
||||
location: self.location_cached(gateway).await,
|
||||
};
|
||||
gateway_geodata.push(gw_geodata);
|
||||
}
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
}
|
||||
|
||||
// Cached mixnodes don't include blacklisted nodes
|
||||
// We need that to calculate the total locked tokens later
|
||||
let mixnodes = api_client
|
||||
.nym_api
|
||||
.get_mixnodes_detailed_unfiltered()
|
||||
.await
|
||||
.log_error("get_mixnodes_detailed_unfiltered")?;
|
||||
let mixnodes_described = api_client
|
||||
.nym_api
|
||||
.get_mixnodes_described()
|
||||
.await
|
||||
.log_error("get_mixnodes_described")?;
|
||||
let mixnodes_active = api_client
|
||||
.nym_api
|
||||
.get_active_mixnodes()
|
||||
.await
|
||||
.log_error("get_active_mixnodes")?;
|
||||
let delegation_program_members =
|
||||
get_delegation_program_details(network_details, nyxd_addr).await?;
|
||||
// contains performance data
|
||||
let all_skimmed_nodes = api_client
|
||||
.get_all_basic_nodes(None)
|
||||
.await
|
||||
.log_error("get_all_basic_nodes")?;
|
||||
|
||||
// keep stats for later
|
||||
let count_bonded_mixnodes = mixnodes.len();
|
||||
let count_bonded_gateways = gateways.len();
|
||||
let count_explorer_gateways = explorer_gateways.len();
|
||||
let count_bonded_mixnodes_active = mixnodes_active.len();
|
||||
let gateways_blacklisted = all_skimmed_nodes
|
||||
.iter()
|
||||
.filter_map(|node| {
|
||||
if node.performance.round_to_integer() <= 50 && node.supported_roles.entry {
|
||||
Some(node.ed25519_identity_pubkey.to_base58_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let gateway_records = prepare_gateway_data(
|
||||
&gateways,
|
||||
&gateways_blacklisted,
|
||||
explorer_gateways,
|
||||
all_skimmed_nodes,
|
||||
)?;
|
||||
queries::insert_gateways(pool, gateway_records)
|
||||
.await
|
||||
.map(|_| {
|
||||
tracing::debug!("Gateway info written to DB!");
|
||||
})?;
|
||||
// Cached mixnodes don't include blacklisted nodes
|
||||
// We need that to calculate the total locked tokens later
|
||||
// TODO dz deprecated API, remove
|
||||
let legacy_mixnodes = api_client
|
||||
.nym_api
|
||||
.get_mixnodes_detailed_unfiltered()
|
||||
.await
|
||||
.log_error("get_mixnodes_detailed_unfiltered")?;
|
||||
let mixnodes_described = api_client
|
||||
.nym_api
|
||||
.get_mixnodes_described()
|
||||
.await
|
||||
.log_error("get_mixnodes_described")?;
|
||||
let mixnodes_active = api_client
|
||||
.nym_api
|
||||
.get_basic_active_mixing_assigned_nodes(None, false, None, None)
|
||||
.await
|
||||
.log_error("get_active_mixnodes")?
|
||||
.nodes
|
||||
.data;
|
||||
let delegation_program_members =
|
||||
get_delegation_program_details(&self.network_details, &self.nyxd_addr).await?;
|
||||
|
||||
// instead of counting blacklisted GWs returned from API cache, count from the active set
|
||||
let count_gateways_blacklisted = gateways
|
||||
.iter()
|
||||
.filter(|gw| {
|
||||
let gw_identity = gw.ed25519_identity_key().to_base58_string();
|
||||
gateways_blacklisted.contains(&gw_identity)
|
||||
})
|
||||
.count();
|
||||
// keep stats for later
|
||||
let count_bonded_mixnodes = mixnodes.len();
|
||||
let count_bonded_gateways = gateways.len();
|
||||
let count_bonded_mixnodes_active = mixnodes_active.len();
|
||||
|
||||
if count_gateways_blacklisted > 0 {
|
||||
queries::write_blacklisted_gateways_to_db(pool, gateways_blacklisted.iter())
|
||||
let gateway_records = self.prepare_gateway_data(
|
||||
&gateways,
|
||||
&gateways_blacklisted,
|
||||
gateway_geodata,
|
||||
all_skimmed_nodes,
|
||||
)?;
|
||||
|
||||
let pool = self.db_pool.clone();
|
||||
queries::insert_gateways(&pool, gateway_records)
|
||||
.await
|
||||
.map(|_| {
|
||||
tracing::debug!(
|
||||
"Gateway blacklist info written to DB! {} blacklisted by Nym API",
|
||||
count_gateways_blacklisted
|
||||
)
|
||||
tracing::debug!("Gateway info written to DB!");
|
||||
})?;
|
||||
}
|
||||
|
||||
let mixnode_records =
|
||||
prepare_mixnode_data(&mixnodes, mixnodes_described, delegation_program_members)?;
|
||||
queries::insert_mixnodes(pool, mixnode_records)
|
||||
.await
|
||||
.map(|_| {
|
||||
tracing::debug!("Mixnode info written to DB!");
|
||||
})?;
|
||||
|
||||
let count_mixnodes_blacklisted = mixnodes.iter().filter(|elem| elem.blacklisted).count();
|
||||
|
||||
let recently_unbonded_gateways = queries::ensure_gateways_still_bonded(pool, &gateways).await?;
|
||||
let recently_unbonded_mixnodes = queries::ensure_mixnodes_still_bonded(pool, &mixnodes).await?;
|
||||
|
||||
let count_bonded_mixnodes_reserve = 0; // TODO: NymAPI doesn't report the reserve set size
|
||||
let count_bonded_mixnodes_inactive =
|
||||
count_bonded_mixnodes.saturating_sub(count_bonded_mixnodes_active);
|
||||
|
||||
let (all_historical_gateways, all_historical_mixnodes) = calculate_stats(pool).await?;
|
||||
|
||||
//
|
||||
// write summary keys and values to table
|
||||
//
|
||||
|
||||
let nodes_summary = vec![
|
||||
(MIXNODES_BONDED_COUNT, &count_bonded_mixnodes),
|
||||
(MIXNODES_BONDED_ACTIVE, &count_bonded_mixnodes_active),
|
||||
(MIXNODES_BONDED_INACTIVE, &count_bonded_mixnodes_inactive),
|
||||
(MIXNODES_BONDED_RESERVE, &count_bonded_mixnodes_reserve),
|
||||
(MIXNODES_BLACKLISTED_COUNT, &count_mixnodes_blacklisted),
|
||||
(GATEWAYS_BONDED_COUNT, &count_bonded_gateways),
|
||||
(GATEWAYS_EXPLORER_COUNT, &count_explorer_gateways),
|
||||
(MIXNODES_HISTORICAL_COUNT, &all_historical_mixnodes),
|
||||
(GATEWAYS_HISTORICAL_COUNT, &all_historical_gateways),
|
||||
(GATEWAYS_BLACKLISTED_COUNT, &count_gateways_blacklisted),
|
||||
];
|
||||
|
||||
let last_updated = chrono::offset::Utc::now();
|
||||
let last_updated_utc = last_updated.timestamp().to_string();
|
||||
let network_summary = NetworkSummary {
|
||||
mixnodes: mixnode::MixnodeSummary {
|
||||
bonded: mixnode::MixnodeSummaryBonded {
|
||||
count: count_bonded_mixnodes.cast_checked()?,
|
||||
active: count_bonded_mixnodes_active.cast_checked()?,
|
||||
inactive: count_bonded_mixnodes_inactive.cast_checked()?,
|
||||
reserve: count_bonded_mixnodes_reserve.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
blacklisted: mixnode::MixnodeSummaryBlacklisted {
|
||||
count: count_mixnodes_blacklisted.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
historical: mixnode::MixnodeSummaryHistorical {
|
||||
count: all_historical_mixnodes.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
},
|
||||
gateways: gateway::GatewaySummary {
|
||||
bonded: gateway::GatewaySummaryBonded {
|
||||
count: count_bonded_gateways.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
blacklisted: gateway::GatewaySummaryBlacklisted {
|
||||
count: count_gateways_blacklisted.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
historical: gateway::GatewaySummaryHistorical {
|
||||
count: all_historical_gateways.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
explorer: gateway::GatewaySummaryExplorer {
|
||||
count: count_explorer_gateways.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
queries::insert_summaries(pool, &nodes_summary, &network_summary, last_updated).await?;
|
||||
|
||||
let mut log_lines: Vec<String> = vec![];
|
||||
for (key, value) in nodes_summary.iter() {
|
||||
log_lines.push(format!("{} = {}", key, value));
|
||||
}
|
||||
log_lines.push(format!(
|
||||
"recently_unbonded_mixnodes = {}",
|
||||
recently_unbonded_mixnodes
|
||||
));
|
||||
log_lines.push(format!(
|
||||
"recently_unbonded_gateways = {}",
|
||||
recently_unbonded_gateways
|
||||
));
|
||||
|
||||
tracing::info!("Directory summary: \n{}", log_lines.join("\n"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_gateway_data(
|
||||
gateways: &[&NymNodeDescription],
|
||||
gateways_blacklisted: &HashSet<String>,
|
||||
explorer_gateways: Vec<PrettyDetailedGatewayBond>,
|
||||
skimmed_gateways: Vec<SkimmedNode>,
|
||||
) -> anyhow::Result<Vec<GatewayRecord>> {
|
||||
let mut gateway_records = Vec::new();
|
||||
|
||||
for gateway in gateways {
|
||||
let identity_key = gateway.ed25519_identity_key().to_base58_string();
|
||||
let bonded = true;
|
||||
let last_updated_utc = chrono::offset::Utc::now().timestamp();
|
||||
let blacklisted = gateways_blacklisted.contains(&identity_key);
|
||||
|
||||
let self_described = serde_json::to_string(&gateway.description)?;
|
||||
|
||||
let explorer_pretty_bond = explorer_gateways
|
||||
let count_gateways_blacklisted = gateways
|
||||
.iter()
|
||||
.find(|g| g.gateway.identity_key.eq(&identity_key));
|
||||
let explorer_pretty_bond = explorer_pretty_bond.and_then(|g| serde_json::to_string(g).ok());
|
||||
|
||||
let performance = skimmed_gateways
|
||||
.iter()
|
||||
.find(|g| {
|
||||
g.ed25519_identity_pubkey
|
||||
.to_base58_string()
|
||||
.eq(&identity_key)
|
||||
.filter(|gw| {
|
||||
let gw_identity = gw.ed25519_identity_key().to_base58_string();
|
||||
gateways_blacklisted.contains(&gw_identity)
|
||||
})
|
||||
.map(|g| g.performance)
|
||||
.unwrap_or_default()
|
||||
.round_to_integer();
|
||||
.count();
|
||||
|
||||
gateway_records.push(GatewayRecord {
|
||||
identity_key: identity_key.to_owned(),
|
||||
bonded,
|
||||
blacklisted,
|
||||
self_described,
|
||||
explorer_pretty_bond,
|
||||
last_updated_utc,
|
||||
performance,
|
||||
});
|
||||
if count_gateways_blacklisted > 0 {
|
||||
queries::write_blacklisted_gateways_to_db(&pool, gateways_blacklisted.iter())
|
||||
.await
|
||||
.map(|_| {
|
||||
tracing::debug!(
|
||||
"Gateway blacklist info written to DB! {} blacklisted by Nym API",
|
||||
count_gateways_blacklisted
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
let mixnode_records = self.prepare_mixnode_data(
|
||||
&legacy_mixnodes,
|
||||
mixnodes_described,
|
||||
delegation_program_members,
|
||||
)?;
|
||||
queries::insert_mixnodes(&pool, mixnode_records)
|
||||
.await
|
||||
.map(|_| {
|
||||
tracing::debug!("Mixnode info written to DB!");
|
||||
})?;
|
||||
|
||||
let count_mixnodes_blacklisted = legacy_mixnodes
|
||||
.iter()
|
||||
.filter(|elem| elem.blacklisted)
|
||||
.count();
|
||||
|
||||
let recently_unbonded_gateways =
|
||||
queries::ensure_gateways_still_bonded(&pool, &gateways).await?;
|
||||
let recently_unbonded_mixnodes =
|
||||
queries::ensure_mixnodes_still_bonded(&pool, &legacy_mixnodes).await?;
|
||||
|
||||
let count_bonded_mixnodes_reserve = 0; // TODO: NymAPI doesn't report the reserve set size
|
||||
let count_bonded_mixnodes_inactive = count_bonded_mixnodes - count_bonded_mixnodes_active;
|
||||
|
||||
let (all_historical_gateways, all_historical_mixnodes) = calculate_stats(&pool).await?;
|
||||
|
||||
//
|
||||
// write summary keys and values to table
|
||||
//
|
||||
|
||||
let nodes_summary = vec![
|
||||
(MIXNODES_BONDED_COUNT, &count_bonded_mixnodes),
|
||||
(MIXNODES_BONDED_ACTIVE, &count_bonded_mixnodes_active),
|
||||
(MIXNODES_BONDED_INACTIVE, &count_bonded_mixnodes_inactive),
|
||||
(MIXNODES_BONDED_RESERVE, &count_bonded_mixnodes_reserve),
|
||||
(MIXNODES_BLACKLISTED_COUNT, &count_mixnodes_blacklisted),
|
||||
(GATEWAYS_BONDED_COUNT, &count_bonded_gateways),
|
||||
(MIXNODES_HISTORICAL_COUNT, &all_historical_mixnodes),
|
||||
(GATEWAYS_HISTORICAL_COUNT, &all_historical_gateways),
|
||||
(GATEWAYS_BLACKLISTED_COUNT, &count_gateways_blacklisted),
|
||||
];
|
||||
|
||||
let last_updated = chrono::offset::Utc::now();
|
||||
let last_updated_utc = last_updated.timestamp().to_string();
|
||||
let network_summary = NetworkSummary {
|
||||
mixnodes: mixnode::MixnodeSummary {
|
||||
bonded: mixnode::MixnodeSummaryBonded {
|
||||
count: count_bonded_mixnodes.cast_checked()?,
|
||||
active: count_bonded_mixnodes_active.cast_checked()?,
|
||||
inactive: count_bonded_mixnodes_inactive.cast_checked()?,
|
||||
reserve: count_bonded_mixnodes_reserve.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
blacklisted: mixnode::MixnodeSummaryBlacklisted {
|
||||
count: count_mixnodes_blacklisted.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
historical: mixnode::MixnodeSummaryHistorical {
|
||||
count: all_historical_mixnodes.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
},
|
||||
gateways: gateway::GatewaySummary {
|
||||
bonded: gateway::GatewaySummaryBonded {
|
||||
count: count_bonded_gateways.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
blacklisted: gateway::GatewaySummaryBlacklisted {
|
||||
count: count_gateways_blacklisted.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
historical: gateway::GatewaySummaryHistorical {
|
||||
count: all_historical_gateways.cast_checked()?,
|
||||
last_updated_utc: last_updated_utc.to_owned(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
queries::insert_summaries(&pool, &nodes_summary, &network_summary, last_updated).await?;
|
||||
|
||||
let mut log_lines: Vec<String> = vec![];
|
||||
for (key, value) in nodes_summary.iter() {
|
||||
log_lines.push(format!("{} = {}", key, value));
|
||||
}
|
||||
log_lines.push(format!(
|
||||
"recently_unbonded_mixnodes = {}",
|
||||
recently_unbonded_mixnodes
|
||||
));
|
||||
log_lines.push(format!(
|
||||
"recently_unbonded_gateways = {}",
|
||||
recently_unbonded_gateways
|
||||
));
|
||||
|
||||
tracing::info!("Directory summary: \n{}", log_lines.join("\n"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Ok(gateway_records)
|
||||
}
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
async fn location_cached(&mut self, node: &NymNodeDescription) -> Location {
|
||||
let node_id = node.node_id;
|
||||
|
||||
fn prepare_mixnode_data(
|
||||
mixnodes: &[MixNodeBondAnnotated],
|
||||
mixnodes_described: Vec<LegacyDescribedMixNode>,
|
||||
delegation_program_members: Vec<u32>,
|
||||
) -> anyhow::Result<Vec<MixnodeRecord>> {
|
||||
let mut mixnode_records = Vec::new();
|
||||
|
||||
for mixnode in mixnodes {
|
||||
let mix_id = mixnode.mix_id();
|
||||
let identity_key = mixnode.identity_key();
|
||||
let bonded = true;
|
||||
let total_stake = decimal_to_i64(mixnode.mixnode_details.total_stake());
|
||||
let blacklisted = mixnode.blacklisted;
|
||||
let node_info = mixnode.mix_node();
|
||||
let host = node_info.host.clone();
|
||||
let http_port = node_info.http_api_port;
|
||||
// Contains all the information including what's above
|
||||
let full_details = serde_json::to_string(&mixnode)?;
|
||||
|
||||
let mixnode_described = mixnodes_described.iter().find(|m| m.bond.mix_id == mix_id);
|
||||
let self_described = mixnode_described.and_then(|v| serde_json::to_string(v).ok());
|
||||
let is_dp_delegatee = delegation_program_members.contains(&mix_id);
|
||||
|
||||
let last_updated_utc = chrono::offset::Utc::now().timestamp();
|
||||
|
||||
mixnode_records.push(MixnodeRecord {
|
||||
mix_id,
|
||||
identity_key: identity_key.to_owned(),
|
||||
bonded,
|
||||
total_stake,
|
||||
host,
|
||||
http_port,
|
||||
blacklisted,
|
||||
full_details,
|
||||
self_described,
|
||||
last_updated_utc,
|
||||
is_dp_delegatee,
|
||||
});
|
||||
match self.geocache.get(&node_id).await {
|
||||
Some(location) => return location,
|
||||
None => {
|
||||
for ip in node.description.host_information.ip_address.iter() {
|
||||
if let Ok(location) = self.ipinfo.locate_ip(ip.to_string()).await {
|
||||
self.geocache.insert(node_id, location.clone()).await;
|
||||
return location;
|
||||
}
|
||||
}
|
||||
// if no data could be retrieved
|
||||
tracing::debug!("No geodata could be retrieved for {}", node_id);
|
||||
Location::empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mixnode_records)
|
||||
}
|
||||
fn prepare_gateway_data(
|
||||
&self,
|
||||
gateways: &[&NymNodeDescription],
|
||||
gateways_blacklisted: &HashSet<String>,
|
||||
gateway_geodata: Vec<NodeGeoData>,
|
||||
skimmed_gateways: Vec<SkimmedNode>,
|
||||
) -> anyhow::Result<Vec<GatewayRecord>> {
|
||||
let mut gateway_records = Vec::new();
|
||||
|
||||
fn log_gw_in_explorer_not_api(
|
||||
explorer: &[PrettyDetailedGatewayBond],
|
||||
api_gateways: &[&NymNodeDescription],
|
||||
) {
|
||||
let api_gateways = api_gateways
|
||||
.iter()
|
||||
.map(|gw| gw.ed25519_identity_key().to_base58_string())
|
||||
.collect::<HashSet<_>>();
|
||||
let explorer_only = explorer
|
||||
.iter()
|
||||
.filter(|gw| !api_gateways.contains(&gw.gateway.identity_key.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
for gateway in gateways {
|
||||
let identity_key = gateway.ed25519_identity_key().to_base58_string();
|
||||
let bonded = true;
|
||||
let last_updated_utc = chrono::offset::Utc::now().timestamp();
|
||||
let blacklisted = gateways_blacklisted.contains(&identity_key);
|
||||
|
||||
tracing::debug!(
|
||||
"Gateways listed by explorer but not by Nym API: {}",
|
||||
explorer_only.len()
|
||||
);
|
||||
for gw in explorer_only.iter() {
|
||||
tracing::debug!("{}", gw.gateway.identity_key.to_string());
|
||||
let self_described = serde_json::to_string(&gateway.description)?;
|
||||
|
||||
let explorer_pretty_bond = gateway_geodata
|
||||
.iter()
|
||||
.find(|g| g.identity_key.eq(&identity_key));
|
||||
let explorer_pretty_bond =
|
||||
explorer_pretty_bond.and_then(|g| serde_json::to_string(g).ok());
|
||||
|
||||
let performance = skimmed_gateways
|
||||
.iter()
|
||||
.find(|g| {
|
||||
g.ed25519_identity_pubkey
|
||||
.to_base58_string()
|
||||
.eq(&identity_key)
|
||||
})
|
||||
.map(|g| g.performance)
|
||||
.unwrap_or_default()
|
||||
.round_to_integer();
|
||||
|
||||
gateway_records.push(GatewayRecord {
|
||||
identity_key: identity_key.to_owned(),
|
||||
bonded,
|
||||
blacklisted,
|
||||
self_described,
|
||||
explorer_pretty_bond,
|
||||
last_updated_utc,
|
||||
performance,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(gateway_records)
|
||||
}
|
||||
|
||||
fn prepare_mixnode_data(
|
||||
&self,
|
||||
mixnodes: &[MixNodeBondAnnotated],
|
||||
mixnodes_described: Vec<LegacyDescribedMixNode>,
|
||||
delegation_program_members: Vec<u32>,
|
||||
) -> anyhow::Result<Vec<MixnodeRecord>> {
|
||||
let mut mixnode_records = Vec::new();
|
||||
|
||||
for mixnode in mixnodes {
|
||||
let mix_id = mixnode.mix_id();
|
||||
let identity_key = mixnode.identity_key();
|
||||
let bonded = true;
|
||||
let total_stake = decimal_to_i64(mixnode.mixnode_details.total_stake());
|
||||
let blacklisted = mixnode.blacklisted;
|
||||
let node_info = mixnode.mix_node();
|
||||
let host = node_info.host.clone();
|
||||
let http_port = node_info.http_api_port;
|
||||
// Contains all the information including what's above
|
||||
let full_details = serde_json::to_string(&mixnode)?;
|
||||
|
||||
let mixnode_described = mixnodes_described.iter().find(|m| m.bond.mix_id == mix_id);
|
||||
let self_described = mixnode_described.and_then(|v| serde_json::to_string(v).ok());
|
||||
let is_dp_delegatee = delegation_program_members.contains(&mix_id);
|
||||
|
||||
let last_updated_utc = chrono::offset::Utc::now().timestamp();
|
||||
|
||||
mixnode_records.push(MixnodeRecord {
|
||||
mix_id,
|
||||
identity_key: identity_key.to_owned(),
|
||||
bonded,
|
||||
total_stake,
|
||||
host,
|
||||
http_port,
|
||||
blacklisted,
|
||||
full_details,
|
||||
self_described,
|
||||
last_updated_utc,
|
||||
is_dp_delegatee,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(mixnode_records)
|
||||
}
|
||||
|
||||
async fn check_ipinfo_bandwidth(&self) {
|
||||
match self.ipinfo.check_remaining_bandwidth().await {
|
||||
Ok(bandwidth) => {
|
||||
tracing::info!(
|
||||
"ipinfo monthly bandwidth: {}/{} spent",
|
||||
bandwidth.month,
|
||||
bandwidth.limit
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::debug!("Couldn't check ipinfo bandwidth: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to autogenerated code
|
||||
#![allow(clippy::empty_line_after_doc_comments)]
|
||||
|
||||
use nym_sdk::mixnet::Recipient;
|
||||
use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag;
|
||||
uniffi::include_scaffolding!("bindings");
|
||||
|
||||
@@ -33,7 +33,7 @@ pub(crate) struct VkShareIndex<'a> {
|
||||
pub(crate) epoch_id: MultiIndex<'a, EpochId, ContractVKShare, VKShareKey<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> IndexList<ContractVKShare> for VkShareIndex<'a> {
|
||||
impl IndexList<ContractVKShare> for VkShareIndex<'_> {
|
||||
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<ContractVKShare>> + '_> {
|
||||
let v: Vec<&dyn Index<ContractVKShare>> = vec![&self.epoch_id];
|
||||
Box::new(v.into_iter())
|
||||
@@ -87,7 +87,7 @@ pub fn query(_: Deps<'_>, _: Env, _: EmptyMessage) -> Result<QueryResponse, StdE
|
||||
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
|
||||
pub fn migrate(deps: DepsMut<'_>, env: Env, msg: MigrateMsg) -> Result<Response, StdError> {
|
||||
// on migration immediately attempt to rewrite the storage
|
||||
let threshold = (2 * msg.dealers.len() as u64 + 3 - 1) / 3;
|
||||
let threshold = (2 * msg.dealers.len() as u64).div_ceil(3);
|
||||
let epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
assert_eq!(0, epoch.epoch_id);
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ impl<'a> FakeDkgKey<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PemStorableKey for FakeDkgKey<'a> {
|
||||
impl PemStorableKey for FakeDkgKey<'_> {
|
||||
type Error = NetworkManagerError;
|
||||
|
||||
fn pem_type() -> &'static str {
|
||||
@@ -84,7 +84,7 @@ struct DkgSkipCtx<'a> {
|
||||
ecash_signers: Vec<EcashSignerWithPaths>,
|
||||
}
|
||||
|
||||
impl<'a> ProgressCtx for DkgSkipCtx<'a> {
|
||||
impl ProgressCtx for DkgSkipCtx<'_> {
|
||||
fn progress_tracker(&self) -> &ProgressTracker {
|
||||
&self.progress
|
||||
}
|
||||
@@ -138,7 +138,7 @@ impl NetworkManager {
|
||||
|
||||
// generate required materials
|
||||
let n = api_endpoints.len();
|
||||
let threshold = (2 * n + 3 - 1) / 3;
|
||||
let threshold = (2 * n).div_ceil(3);
|
||||
|
||||
let ecash_keys = ttp_keygen(threshold as u64, n as u64)?;
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ struct LocalApisCtx<'a> {
|
||||
signers: Vec<EcashSignerWithPaths>,
|
||||
}
|
||||
|
||||
impl<'a> ProgressCtx for LocalApisCtx<'a> {
|
||||
impl ProgressCtx for LocalApisCtx<'_> {
|
||||
fn progress_tracker(&self) -> &ProgressTracker {
|
||||
&self.progress
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ struct LocalClientCtx<'a> {
|
||||
network: &'a LoadedNetwork,
|
||||
}
|
||||
|
||||
impl<'a> ProgressCtx for LocalClientCtx<'a> {
|
||||
impl ProgressCtx for LocalClientCtx<'_> {
|
||||
fn progress_tracker(&self) -> &ProgressTracker {
|
||||
&self.progress
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ struct LocalNodesCtx<'a> {
|
||||
gateways: Vec<NymNode>,
|
||||
}
|
||||
|
||||
impl<'a> ProgressCtx for LocalNodesCtx<'a> {
|
||||
impl ProgressCtx for LocalNodesCtx<'_> {
|
||||
fn progress_tracker(&self) -> &ProgressTracker {
|
||||
&self.progress
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user