Compare commits

...

8 Commits

Author SHA1 Message Date
dynco-nym 7ed0657eb5 Add remaining bandwidth log 2024-11-29 14:40:19 +01:00
dynco-nym a60110560a Remove ipinfo crate due to openssl dep 2024-11-29 13:37:06 +01:00
dynco-nym 2097b58937 Bump package version 2024-11-29 12:59:05 +01:00
dynco-nym ff8c6fd901 Fix clippy 2024-11-29 12:59:04 +01:00
dynco-nym 49d527b54a PR feedback 2024-11-29 12:09:14 +01:00
dynco-nym f1ccc43709 Replace explorer with ipinfo calls 2024-11-29 12:09:14 +01:00
dynco-nym 4d118cc41c explorer deprecation wip 2024-11-29 12:09:14 +01:00
dynco-nym 1806341e34 Move monitor code to a struct
- to store state in a struct
2024-11-29 12:09:14 +01:00
36 changed files with 539 additions and 415 deletions
Generated
+3 -3
View File
@@ -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 {
+1 -2
View File
@@ -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>>>;
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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]
+1 -1
View File
@@ -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 {
+7 -7
View File
@@ -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 {
-13
View File
@@ -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() {
+1 -1
View File
@@ -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> {
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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);
}
}
}
}
+3
View File
@@ -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
}