From b28e953a2b2b5c277e319bdad9d2cc9af9e6240b Mon Sep 17 00:00:00 2001 From: Fran Arbanas Date: Tue, 12 Nov 2024 17:16:59 +0100 Subject: [PATCH 01/22] fix: validator-rewarder GH job --- .github/workflows/push-validator-rewarder.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push-validator-rewarder.yaml b/.github/workflows/push-validator-rewarder.yaml index aa64899798..98508d4361 100644 --- a/.github/workflows/push-validator-rewarder.yaml +++ b/.github/workflows/push-validator-rewarder.yaml @@ -29,7 +29,7 @@ jobs: uses: mikefarah/yq@v4.44.3 id: get_version with: - cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml + cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml - name: Remove existing tag if exists run: | From d2e85f2bfe18a18e09d9624492c5cde4bf0e550d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:32:10 +0000 Subject: [PATCH 02/22] build(deps): bump cross-spawn from 7.0.3 to 7.0.6 in /testnet-faucet Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6. - [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md) - [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6) --- updated-dependencies: - dependency-name: cross-spawn dependency-type: indirect ... Signed-off-by: dependabot[bot] --- testnet-faucet/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testnet-faucet/yarn.lock b/testnet-faucet/yarn.lock index 9c97a04c98..5b18dd9a2c 100644 --- a/testnet-faucet/yarn.lock +++ b/testnet-faucet/yarn.lock @@ -1794,9 +1794,9 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: sha.js "^2.4.8" cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" From 12b9aefa9902bab0a8520aec1184578a9c41dc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 25 Nov 2024 08:39:55 +0000 Subject: [PATCH 03/22] bugfix: correctly expose ecash-related data on nym-api (#5155) * fixed signer related endpoints * fixed aggregation of partial data if the api is not a signer * fixes to swagger docs for global ecash endpoints * remove unused axum_macros * fixed test traits --- nym-api/Cargo.toml | 2 +- nym-api/src/ecash/api_routes/aggregation.rs | 32 +++++++----------- nym-api/src/ecash/api_routes/handlers.rs | 12 +++---- nym-api/src/ecash/api_routes/helpers.rs | 1 - nym-api/src/ecash/api_routes/issued.rs | 18 ++++------ .../src/ecash/api_routes/partial_signing.rs | 28 +++++----------- nym-api/src/ecash/api_routes/spending.rs | 29 +++++----------- nym-api/src/ecash/client.rs | 2 +- nym-api/src/ecash/dkg/client.rs | 8 ++--- nym-api/src/ecash/dkg/dealing.rs | 4 +-- nym-api/src/ecash/dkg/key_derivation.rs | 2 +- nym-api/src/ecash/dkg/key_validation.rs | 8 ++--- nym-api/src/ecash/error.rs | 3 ++ nym-api/src/ecash/state/mod.rs | 12 ++++--- nym-api/src/ecash/tests/fixtures.rs | 2 +- nym-api/src/ecash/tests/helpers.rs | 2 +- nym-api/src/ecash/tests/mod.rs | 13 +++++--- nym-api/src/epoch_operations/error.rs | 3 ++ nym-api/src/epoch_operations/mod.rs | 13 +++++--- nym-api/src/status/handlers.rs | 33 +++++-------------- nym-api/src/status/mod.rs | 29 +++++++++++----- nym-api/src/support/cli/run.rs | 17 ++++------ nym-api/src/support/http/router.rs | 3 ++ nym-api/src/support/http/state.rs | 18 ++++++++++ nym-api/src/support/nyxd/mod.rs | 30 ++++++++--------- 25 files changed, 154 insertions(+), 170 deletions(-) diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 6f9602eb06..ea2e3ed212 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -121,7 +121,7 @@ nym-types = { path = "../common/types" } nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa"] } nym-serde-helpers = { path = "../common/serde-helpers", features = ["date"] } nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" } -nym-statistics-common = {path ="../common/statistics" } +nym-statistics-common = { path = "../common/statistics" } [features] no-reward = [] diff --git a/nym-api/src/ecash/api_routes/aggregation.rs b/nym-api/src/ecash/api_routes/aggregation.rs index 89478fbd09..8b7baef6e6 100644 --- a/nym-api/src/ecash/api_routes/aggregation.rs +++ b/nym-api/src/ecash/api_routes/aggregation.rs @@ -6,7 +6,7 @@ use crate::ecash::error::EcashError; use crate::ecash::state::EcashState; use crate::node_status_api::models::AxumResult; use crate::support::http::state::AppState; -use axum::extract::Path; +use axum::extract::{Query, State}; use axum::{Json, Router}; use nym_api_requests::ecash::models::{ AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse, @@ -21,28 +21,19 @@ use tracing::trace; use utoipa::IntoParams; /// routes with globally aggregated keys, signatures, etc. -pub(crate) fn aggregation_routes(ecash_state: Arc) -> Router { +pub(crate) fn aggregation_routes() -> Router { Router::new() .route( "/master-verification-key", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |epoch_id| master_verification_key(epoch_id, ecash_state) - }), + axum::routing::get(master_verification_key), ) .route( "/aggregated-expiration-date-signatures", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |expiration_date| expiration_date_signatures(expiration_date, ecash_state) - }), + axum::routing::get(expiration_date_signatures), ) .route( "/aggregated-coin-indices-signatures", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |epoch_id| coin_indices_signatures(epoch_id, ecash_state) - }), + axum::routing::get(coin_indices_signatures), ) } @@ -58,8 +49,8 @@ pub(crate) fn aggregation_routes(ecash_state: Arc) -> Router, - state: Arc, + State(state): State>, + Query(EpochIdParam { epoch_id }): Query, ) -> AxumResult> { trace!("aggregated_verification_key request"); @@ -72,7 +63,6 @@ async fn master_verification_key( } #[derive(Deserialize, IntoParams)] -#[into_params(parameter_in = Path)] struct ExpirationDateParam { expiration_date: Option, } @@ -89,8 +79,8 @@ struct ExpirationDateParam { ) )] async fn expiration_date_signatures( - Path(ExpirationDateParam { expiration_date }): Path, - state: Arc, + State(state): State>, + Query(ExpirationDateParam { expiration_date }): Query, ) -> AxumResult> { trace!("aggregated_expiration_date_signatures request"); @@ -126,8 +116,8 @@ async fn expiration_date_signatures( ) )] async fn coin_indices_signatures( - Path(EpochIdParam { epoch_id }): Path, - state: Arc, + Query(EpochIdParam { epoch_id }): Query, + State(state): State>, ) -> AxumResult> { trace!("aggregated_coin_indices_signatures request"); diff --git a/nym-api/src/ecash/api_routes/handlers.rs b/nym-api/src/ecash/api_routes/handlers.rs index e9f97c48b0..9114ebba9f 100644 --- a/nym-api/src/ecash/api_routes/handlers.rs +++ b/nym-api/src/ecash/api_routes/handlers.rs @@ -5,15 +5,13 @@ use crate::ecash::api_routes::aggregation::aggregation_routes; use crate::ecash::api_routes::issued::issued_routes; use crate::ecash::api_routes::partial_signing::partial_signing_routes; use crate::ecash::api_routes::spending::spending_routes; -use crate::ecash::state::EcashState; use crate::support::http::state::AppState; use axum::Router; -use std::sync::Arc; -pub(crate) fn ecash_routes(ecash_state: Arc) -> Router { +pub(crate) fn ecash_routes() -> Router { Router::new() - .merge(aggregation_routes(Arc::clone(&ecash_state))) - .merge(issued_routes(Arc::clone(&ecash_state))) - .merge(partial_signing_routes(Arc::clone(&ecash_state))) - .merge(spending_routes(Arc::clone(&ecash_state))) + .merge(aggregation_routes()) + .merge(issued_routes()) + .merge(partial_signing_routes()) + .merge(spending_routes()) } diff --git a/nym-api/src/ecash/api_routes/helpers.rs b/nym-api/src/ecash/api_routes/helpers.rs index 0e7afa9b39..9d0270acc7 100644 --- a/nym-api/src/ecash/api_routes/helpers.rs +++ b/nym-api/src/ecash/api_routes/helpers.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-only #[derive(serde::Deserialize, utoipa::IntoParams)] -#[into_params(parameter_in = Path)] pub(super) struct EpochIdParam { pub(super) epoch_id: Option, } diff --git a/nym-api/src/ecash/api_routes/issued.rs b/nym-api/src/ecash/api_routes/issued.rs index 023b57f6a3..376b532475 100644 --- a/nym-api/src/ecash/api_routes/issued.rs +++ b/nym-api/src/ecash/api_routes/issued.rs @@ -4,7 +4,7 @@ use crate::ecash::state::EcashState; use crate::node_status_api::models::AxumResult; use crate::support::http::state::AppState; -use axum::extract::Path; +use axum::extract::{Path, State}; use axum::{Json, Router}; use nym_api_requests::ecash::models::{ IssuedTicketbooksChallengeRequest, IssuedTicketbooksChallengeResponse, @@ -17,21 +17,15 @@ use time::Date; use tracing::trace; use utoipa::{IntoParams, ToSchema}; -pub(crate) fn issued_routes(ecash_state: Arc) -> Router { +pub(crate) fn issued_routes() -> Router { Router::new() .route( "/issued-ticketbooks-for/:expiration_date", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |expiration_date| issued_ticketbooks_for(expiration_date, ecash_state) - }), + axum::routing::get(issued_ticketbooks_for), ) .route( "/issued-ticketbooks-challenge", - axum::routing::post({ - let ecash_state = Arc::clone(&ecash_state); - |body| issued_ticketbooks_challenge(body, ecash_state) - }), + axum::routing::post(issued_ticketbooks_challenge), ) } @@ -58,8 +52,8 @@ pub(crate) struct ExpirationDatePathParam { ) )] async fn issued_ticketbooks_for( + State(state): State>, Path(ExpirationDatePathParam { expiration_date }): Path, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; @@ -83,8 +77,8 @@ async fn issued_ticketbooks_for( ) )] async fn issued_ticketbooks_challenge( + State(state): State>, Json(challenge): Json, - state: Arc, ) -> AxumResult> { trace!("replying to ticketbooks challenge: {:?}", challenge); state.ensure_signer().await?; diff --git a/nym-api/src/ecash/api_routes/partial_signing.rs b/nym-api/src/ecash/api_routes/partial_signing.rs index 7deb919d4e..afe522cbee 100644 --- a/nym-api/src/ecash/api_routes/partial_signing.rs +++ b/nym-api/src/ecash/api_routes/partial_signing.rs @@ -7,7 +7,7 @@ use crate::ecash::helpers::blind_sign; use crate::ecash::state::EcashState; use crate::node_status_api::models::AxumResult; use crate::support::http::state::AppState; -use axum::extract::Query; +use axum::extract::{Query, State}; use axum::{Json, Router}; use nym_api_requests::ecash::{ BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse, @@ -22,28 +22,16 @@ use time::Date; use tracing::{debug, trace}; use utoipa::IntoParams; -pub(crate) fn partial_signing_routes(ecash_state: Arc) -> Router { +pub(crate) fn partial_signing_routes() -> Router { Router::new() - .route( - "/blind-sign", - axum::routing::post({ - let ecash_state = Arc::clone(&ecash_state); - |body| post_blind_sign(body, ecash_state) - }), - ) + .route("/blind-sign", axum::routing::post(post_blind_sign)) .route( "/partial-expiration-date-signatures", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |expiration_date| partial_expiration_date_signatures(expiration_date, ecash_state) - }), + axum::routing::get(partial_expiration_date_signatures), ) .route( "/partial-coin-indices-signatures", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - |epoch_id| partial_coin_indices_signatures(epoch_id, ecash_state) - }), + axum::routing::get(partial_coin_indices_signatures), ) } @@ -59,8 +47,8 @@ pub(crate) fn partial_signing_routes(ecash_state: Arc) -> Router>, Json(blind_sign_request_body): Json, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; @@ -134,8 +122,8 @@ struct ExpirationDateParam { ) )] async fn partial_expiration_date_signatures( + State(state): State>, Query(ExpirationDateParam { expiration_date }): Query, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; @@ -172,8 +160,8 @@ async fn partial_expiration_date_signatures( ) )] async fn partial_coin_indices_signatures( + State(state): State>, Query(EpochIdParam { epoch_id }): Query, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; diff --git a/nym-api/src/ecash/api_routes/spending.rs b/nym-api/src/ecash/api_routes/spending.rs index b9788a4585..3bda629174 100644 --- a/nym-api/src/ecash/api_routes/spending.rs +++ b/nym-api/src/ecash/api_routes/spending.rs @@ -5,6 +5,7 @@ use crate::ecash::error::EcashError; use crate::ecash::state::EcashState; use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; use crate::support::http::state::AppState; +use axum::extract::State; use axum::{Json, Router}; use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY; use nym_api_requests::ecash::models::{ @@ -21,28 +22,16 @@ use time::{OffsetDateTime, Time}; use tracing::{error, warn}; #[allow(deprecated)] -pub(crate) fn spending_routes(ecash_state: Arc) -> Router { +pub(crate) fn spending_routes() -> Router { Router::new() - .route( - "/verify-ecash-ticket", - axum::routing::post({ - let ecash_state = Arc::clone(&ecash_state); - |body| verify_ticket(body, ecash_state) - }), - ) + .route("/verify-ecash-ticket", axum::routing::post(verify_ticket)) .route( "/batch-redeem-ecash-tickets", - axum::routing::post({ - let ecash_state = Arc::clone(&ecash_state); - |body| batch_redeem_tickets(body, ecash_state) - }), + axum::routing::post(batch_redeem_tickets), ) .route( "/double-spending-filter-v1", - axum::routing::get({ - let ecash_state = Arc::clone(&ecash_state); - || double_spending_filter_v1(ecash_state) - }), + axum::routing::get(double_spending_filter_v1), ) } @@ -67,9 +56,9 @@ fn reject_ticket( ) )] async fn verify_ticket( + State(state): State>, // TODO in the future: make it send binary data rather than json Json(verify_ticket_body): Json, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; @@ -170,9 +159,9 @@ async fn verify_ticket( ) )] async fn batch_redeem_tickets( + State(state): State>, // TODO in the future: make it send binary data rather than json Json(batch_redeem_credentials_body): Json, - state: Arc, ) -> AxumResult> { state.ensure_signer().await?; @@ -244,8 +233,6 @@ async fn batch_redeem_tickets( ) )] #[deprecated] -async fn double_spending_filter_v1( - _state: Arc, -) -> AxumResult> { +async fn double_spending_filter_v1() -> AxumResult> { AxumResult::Err(AxumErrorResponse::internal_msg("permanently restricted")) } diff --git a/nym-api/src/ecash/client.rs b/nym-api/src/ecash/client.rs index 805252808a..f6887f15e7 100644 --- a/nym-api/src/ecash/client.rs +++ b/nym-api/src/ecash/client.rs @@ -27,7 +27,7 @@ use nym_validator_client::EcashApiClient; #[async_trait] pub trait Client { - async fn address(&self) -> AccountId; + async fn address(&self) -> Result; async fn dkg_contract_address(&self) -> Result; diff --git a/nym-api/src/ecash/dkg/client.rs b/nym-api/src/ecash/dkg/client.rs index 5c8f993690..4cd8d41343 100644 --- a/nym-api/src/ecash/dkg/client.rs +++ b/nym-api/src/ecash/dkg/client.rs @@ -35,7 +35,7 @@ impl DkgClient { } } - pub(crate) async fn get_address(&self) -> AccountId { + pub(crate) async fn get_address(&self) -> Result { self.inner.address().await } @@ -53,7 +53,7 @@ impl DkgClient { pub(crate) async fn group_member(&self) -> Result { self.inner - .group_member(self.get_address().await.to_string()) + .group_member(self.get_address().await?.to_string()) .await } @@ -126,7 +126,7 @@ impl DkgClient { &self, epoch_id: EpochId, ) -> Result, EcashError> { - let address = self.inner.address().await; + let address = self.inner.address().await?; self.get_verification_key_share(epoch_id, address).await } @@ -138,7 +138,7 @@ impl DkgClient { } pub(crate) async fn get_vote(&self, proposal_id: u64) -> Result { - let address = self.get_address().await.to_string(); + let address = self.get_address().await?.to_string(); self.inner.get_vote(proposal_id, address).await } diff --git a/nym-api/src/ecash/dkg/dealing.rs b/nym-api/src/ecash/dkg/dealing.rs index a0ae4ac840..31da8a0ea5 100644 --- a/nym-api/src/ecash/dkg/dealing.rs +++ b/nym-api/src/ecash/dkg/dealing.rs @@ -155,7 +155,7 @@ impl DkgController { resharing: bool, ) -> Result<(), DealingGenerationError> { let dealing_state = self.state.dealing_exchange_state(epoch_id)?; - let address = self.dkg_client.get_address().await.to_string(); + let address = self.dkg_client.get_address().await?.to_string(); let status = self .dkg_client @@ -259,7 +259,7 @@ impl DkgController { .checked_sub(1) .expect("resharing epoch invariant has been broken"); - let address = self.dkg_client.get_address().await; + let address = self.dkg_client.get_address().await?; Ok(self .dkg_client .dealer_in_epoch(previous_epoch_id, address.to_string()) diff --git a/nym-api/src/ecash/dkg/key_derivation.rs b/nym-api/src/ecash/dkg/key_derivation.rs index f3e69e1fe6..46818547d4 100644 --- a/nym-api/src/ecash/dkg/key_derivation.rs +++ b/nym-api/src/ecash/dkg/key_derivation.rs @@ -494,7 +494,7 @@ impl DkgController { // submitted proposals and find the one with our address self.get_validation_proposals() .await? - .get(self.dkg_client.get_address().await.as_ref()) + .get(self.dkg_client.get_address().await?.as_ref()) .copied() .ok_or(KeyDerivationError::UnrecoverableProposalId) } diff --git a/nym-api/src/ecash/dkg/key_validation.rs b/nym-api/src/ecash/dkg/key_validation.rs index aff0ea90ac..cde57df982 100644 --- a/nym-api/src/ecash/dkg/key_validation.rs +++ b/nym-api/src/ecash/dkg/key_validation.rs @@ -155,7 +155,7 @@ impl DkgController { }; // if this is our share, obviously vote for yes without spending time on verification - if owner.as_ref() == self.dkg_client.get_address().await.as_ref() { + if owner.as_ref() == self.dkg_client.get_address().await?.as_ref() { votes.insert(*proposal_id, true); continue; } @@ -313,7 +313,7 @@ mod tests { exchange_dealings(&mut controllers, false).await; derive_keypairs(&mut controllers, false).await; - let first_dealer = controllers[0].dkg_client.get_address().await; + let first_dealer = controllers[0].dkg_client.get_address().await?; { let mut guard = chain.lock().unwrap(); @@ -365,8 +365,8 @@ mod tests { exchange_dealings(&mut controllers, false).await; derive_keypairs(&mut controllers, false).await; - let first_dealer = controllers[0].dkg_client.get_address().await; - let second_dealer = controllers[1].dkg_client.get_address().await; + let first_dealer = controllers[0].dkg_client.get_address().await?; + let second_dealer = controllers[1].dkg_client.get_address().await?; { let mut guard = chain.lock().unwrap(); diff --git a/nym-api/src/ecash/error.rs b/nym-api/src/ecash/error.rs index 7696234ea2..68fd79622f 100644 --- a/nym-api/src/ecash/error.rs +++ b/nym-api/src/ecash/error.rs @@ -25,6 +25,9 @@ pub enum EcashError { #[error(transparent)] IOError(#[from] std::io::Error), + #[error("this instance is running without on-chain signing capabilities so no transactions can be sent")] + ChainSignerNotEnabled, + #[error("this operation couldn't be completed as this nym-api is not an active ecash signer")] NotASigner, diff --git a/nym-api/src/ecash/state/mod.rs b/nym-api/src/ecash/state/mod.rs index b27280b6a6..f262177480 100644 --- a/nym-api/src/ecash/state/mod.rs +++ b/nym-api/src/ecash/state/mod.rs @@ -139,7 +139,9 @@ impl EcashState { .local .active_signer .get_or_init(epoch_id, || async { - let address = self.aux.client.address().await; + let Ok(address) = self.aux.client.address().await else { + return Ok(false); + }; let ecash_signers = self.aux.comm_channel.ecash_clients(epoch_id).await?; // check if any ecash signers for this epoch has the same cosmos address as this api @@ -246,7 +248,7 @@ impl EcashState { let threshold = self.aux.comm_channel.ecash_threshold(epoch_id).await?; // let mut shares = Mutex::new(Vec::with_capacity(all_apis.len())); - let cosmos_address = self.aux.client.address().await; + let cosmos_address = self.aux.client.address().await.ok(); let get_partial_signatures = |api: EcashApiClient| async { // move the api into the closure @@ -256,7 +258,7 @@ impl EcashState { // check if we're attempting to query ourselves, in that case just get local signature // rather than making the http query - let partial = if api.cosmos_address == cosmos_address { + let partial = if Some(api.cosmos_address) == cosmos_address { self.partial_coin_index_signatures(Some(epoch_id)) .await? .signatures @@ -380,7 +382,7 @@ impl EcashState { let all_apis = self.aux.comm_channel.ecash_clients(epoch_id).await?; let threshold = self.aux.comm_channel.ecash_threshold(epoch_id).await?; - let cosmos_address = self.aux.client.address().await; + let cosmos_address = self.aux.client.address().await.ok(); let get_partial_signatures = |api: EcashApiClient| async { // move the api into the closure @@ -390,7 +392,7 @@ impl EcashState { // check if we're attempting to query ourselves, in that case just get local signature // rather than making the http query - let partial = if api.cosmos_address == cosmos_address { + let partial = if Some(api.cosmos_address) == cosmos_address { self.partial_expiration_date_signatures(expiration_date) .await? .signatures diff --git a/nym-api/src/ecash/tests/fixtures.rs b/nym-api/src/ecash/tests/fixtures.rs index 80ee791770..b49b9fc17e 100644 --- a/nym-api/src/ecash/tests/fixtures.rs +++ b/nym-api/src/ecash/tests/fixtures.rs @@ -263,7 +263,7 @@ pub(crate) struct TestingDkgController { impl TestingDkgController { pub async fn address(&self) -> AccountId { - self.dkg_client.get_address().await + self.dkg_client.get_address().await.unwrap() } pub async fn cw_address(&self) -> Addr { diff --git a/nym-api/src/ecash/tests/helpers.rs b/nym-api/src/ecash/tests/helpers.rs index 5cd410056f..7f7e4aaf54 100644 --- a/nym-api/src/ecash/tests/helpers.rs +++ b/nym-api/src/ecash/tests/helpers.rs @@ -51,7 +51,7 @@ pub(crate) async fn initialise_dkg(controllers: &mut [TestingDkgController], res // add every dealer to group contract for controller in controllers.iter() { - let address = controller.dkg_client.get_address().await; + let address = controller.dkg_client.get_address().await.unwrap(); let mut chain = controllers[0].chain_state.lock().unwrap(); chain.add_member(address.as_ref(), 10); } diff --git a/nym-api/src/ecash/tests/mod.rs b/nym-api/src/ecash/tests/mod.rs index 2b0ef471f9..9af4c78052 100644 --- a/nym-api/src/ecash/tests/mod.rs +++ b/nym-api/src/ecash/tests/mod.rs @@ -11,6 +11,7 @@ use crate::node_describe_cache::DescribedNodes; use crate::node_status_api::handlers::unstable; use crate::node_status_api::NodeStatusCache; use crate::nym_contract_cache::cache::NymContractCache; +use crate::status::ApiStatusState; use crate::support::caching::cache::SharedCache; use crate::support::config; use crate::support::http::state::{AppState, ForcedRefresh}; @@ -524,8 +525,8 @@ impl DummyClient { #[async_trait] impl super::client::Client for DummyClient { - async fn address(&self) -> AccountId { - self.validator_address.clone() + async fn address(&self) -> Result { + Ok(self.validator_address.clone()) } async fn dkg_contract_address(&self) -> Result { @@ -1262,7 +1263,7 @@ struct TestFixture { } impl TestFixture { - fn build_app_state(storage: NymApiStorage) -> AppState { + fn build_app_state(storage: NymApiStorage, ecash_state: EcashState) -> AppState { AppState { forced_refresh: ForcedRefresh::new(true), nym_contract_cache: NymContractCache::new(), @@ -1275,6 +1276,8 @@ impl TestFixture { NymNetworkDetails::new_empty(), ), node_info_cache: unstable::NodeInfoCache::default(), + api_status: ApiStatusState::new(None), + ecash_state: Arc::new(ecash_state), } } @@ -1337,8 +1340,8 @@ impl TestFixture { TestFixture { axum: TestServer::new( Router::new() - .nest("/v1/ecash", ecash_routes(Arc::new(ecash_state))) - .with_state(Self::build_app_state(storage.clone())), + .nest("/v1/ecash", ecash_routes()) + .with_state(Self::build_app_state(storage.clone(), ecash_state)), ) .unwrap(), storage, diff --git a/nym-api/src/epoch_operations/error.rs b/nym-api/src/epoch_operations/error.rs index 65fc822dff..a608817425 100644 --- a/nym-api/src/epoch_operations/error.rs +++ b/nym-api/src/epoch_operations/error.rs @@ -11,6 +11,9 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum RewardingError { + #[error("this instance is running without on-chain signing capabilities so no transactions can be sent")] + ChainSignerNotEnabled, + #[error("Our account ({our_address}) is not permitted to update rewarded set and perform rewarding. The allowed address is {allowed_address}")] Unauthorised { our_address: AccountId, diff --git a/nym-api/src/epoch_operations/mod.rs b/nym-api/src/epoch_operations/mod.rs index 6744679411..fe4fd5792d 100644 --- a/nym-api/src/epoch_operations/mod.rs +++ b/nym-api/src/epoch_operations/mod.rs @@ -134,9 +134,12 @@ impl EpochAdvancer { let epoch_status = self.nyxd_client.get_current_epoch_status().await?; if !epoch_status.is_in_progress() { - if epoch_status.being_advanced_by.as_str() - != self.nyxd_client.client_address().await.as_ref() - { + // SAFETY: before `EpochAdvancer` is started, `ensure_rewarding_permission` is called + // which is not allowed to progress if this instance is not using a signing client + #[allow(clippy::unwrap_used)] + let address = self.nyxd_client.client_address().await.unwrap(); + + if epoch_status.being_advanced_by.as_str() != address.as_ref() { // another nym-api is already handling error!("another nym-api ({}) is already advancing the epoch... but we shouldn't have other nym-apis yet!", epoch_status.being_advanced_by); return Ok(()); @@ -318,8 +321,10 @@ impl EpochAdvancer { pub(crate) async fn ensure_rewarding_permission( nyxd_client: &Client, ) -> Result<(), RewardingError> { + let Some(our_address) = nyxd_client.client_address().await else { + return Err(RewardingError::ChainSignerNotEnabled); + }; let allowed_address = nyxd_client.get_rewarding_validator_address().await?; - let our_address = nyxd_client.client_address().await; if allowed_address != our_address { Err(RewardingError::Unauthorised { our_address, diff --git a/nym-api/src/status/handlers.rs b/nym-api/src/status/handlers.rs index 7dc316aac2..8b177cadd6 100644 --- a/nym-api/src/status/handlers.rs +++ b/nym-api/src/status/handlers.rs @@ -4,37 +4,20 @@ use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; use crate::status::ApiStatusState; use crate::support::http::state::AppState; +use axum::extract::State; use axum::Json; use axum::Router; use nym_api_requests::models::{ApiHealthResponse, SignerInformationResponse}; use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_compact_ecash::Base58; -use std::sync::Arc; pub(crate) fn api_status_routes() -> Router { - let api_status_state = Arc::new(ApiStatusState::new()); - Router::new() - .route( - "/health", - axum::routing::get({ - let state = Arc::clone(&api_status_state); - || health(state) - }), - ) - .route( - "/build-information", - axum::routing::get({ - let state = Arc::clone(&api_status_state); - || build_information(state) - }), - ) + .route("/health", axum::routing::get(health)) + .route("/build-information", axum::routing::get(build_information)) .route( "/signer-information", - axum::routing::get({ - let state = Arc::clone(&api_status_state); - || signer_information(state) - }), + axum::routing::get(signer_information), ) } @@ -46,7 +29,7 @@ pub(crate) fn api_status_routes() -> Router { (status = 200, body = ApiHealthResponse) ) )] -async fn health(state: Arc) -> Json { +async fn health(State(state): State) -> Json { let uptime = state.startup_time.elapsed(); let health = ApiHealthResponse::new_healthy(uptime); Json(health) @@ -60,7 +43,9 @@ async fn health(state: Arc) -> Json { (status = 200, body = BinaryBuildInformationOwned) ) )] -async fn build_information(state: Arc) -> Json { +async fn build_information( + State(state): State, +) -> Json { Json(state.build_information.to_owned()) } @@ -73,7 +58,7 @@ async fn build_information(state: Arc) -> Json, + State(state): State, ) -> AxumResult> { let signer_state = state.signer_information.as_ref().ok_or_else(|| { AxumErrorResponse::internal_msg("this api does not expose zk-nym signing functionalities") diff --git a/nym-api/src/status/mod.rs b/nym-api/src/status/mod.rs index 0ed94a1819..f7ff611d6d 100644 --- a/nym-api/src/status/mod.rs +++ b/nym-api/src/status/mod.rs @@ -4,12 +4,25 @@ use crate::ecash; use nym_bin_common::bin_info; use nym_bin_common::build_information::BinaryBuildInformation; - +use std::ops::Deref; +use std::sync::Arc; use tokio::time::Instant; pub(crate) mod handlers; +#[derive(Clone)] pub(crate) struct ApiStatusState { + inner: Arc, +} + +impl Deref for ApiStatusState { + type Target = ApiStatusStateInner; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +pub(crate) struct ApiStatusStateInner { startup_time: Instant, build_information: BinaryBuildInformation, signer_information: Option, @@ -27,15 +40,13 @@ pub(crate) struct SignerState { } impl ApiStatusState { - pub fn new() -> Self { + pub fn new(signer_information: Option) -> Self { ApiStatusState { - startup_time: Instant::now(), - build_information: bin_info!(), - signer_information: None, + inner: Arc::new(ApiStatusStateInner { + startup_time: Instant::now(), + build_information: bin_info!(), + signer_information, + }), } } - - pub fn add_zk_nym_signer(&mut self, signer_information: SignerState) { - self.signer_information = Some(signer_information) - } } diff --git a/nym-api/src/support/cli/run.rs b/nym-api/src/support/cli/run.rs index e7bf4c0776..6436f19be1 100644 --- a/nym-api/src/support/cli/run.rs +++ b/nym-api/src/support/cli/run.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::circulating_supply_api::cache::CirculatingSupplyCache; -use crate::ecash::api_routes::handlers::ecash_routes; use crate::ecash::client::Client; use crate::ecash::comm::QueryCommunicationChannel; use crate::ecash::dkg::controller::keys::{ @@ -138,8 +137,6 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result::new(); let node_info_cache = unstable::NodeInfoCache::default(); - let mut status_state = ApiStatusState::new(); - let ecash_contract = nyxd_client .get_ecash_contract_address() .await @@ -161,8 +158,8 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result anyhow::Result anyhow::Result) -> Self { Self { unfinished_router: self.unfinished_router.nest(path, router), diff --git a/nym-api/src/support/http/state.rs b/nym-api/src/support/http/state.rs index f45d25bbd2..6c277441ea 100644 --- a/nym-api/src/support/http/state.rs +++ b/nym-api/src/support/http/state.rs @@ -2,15 +2,18 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::circulating_supply_api::cache::CirculatingSupplyCache; +use crate::ecash::state::EcashState; use crate::network::models::NetworkDetails; use crate::node_describe_cache::DescribedNodes; use crate::node_status_api::handlers::unstable; use crate::node_status_api::models::AxumErrorResponse; use crate::node_status_api::NodeStatusCache; use crate::nym_contract_cache::cache::{CachedRewardedSet, NymContractCache}; +use crate::status::ApiStatusState; use crate::support::caching::cache::SharedCache; use crate::support::caching::Cache; use crate::support::storage; +use axum::extract::FromRef; use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation}; use nym_mixnet_contract_common::NodeId; use nym_task::TaskManager; @@ -80,6 +83,21 @@ pub(crate) struct AppState { pub(crate) described_nodes_cache: SharedCache, pub(crate) network_details: NetworkDetails, pub(crate) node_info_cache: unstable::NodeInfoCache, + pub(crate) api_status: ApiStatusState, + // todo: refactor it into inner: Arc + pub(crate) ecash_state: Arc, +} + +impl FromRef for ApiStatusState { + fn from_ref(app_state: &AppState) -> Self { + app_state.api_status.clone() + } +} + +impl FromRef for Arc { + fn from_ref(app_state: &AppState) -> Self { + app_state.ecash_state.clone() + } } #[derive(Clone)] diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index 012f9d369d..5e129058b9 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -77,16 +77,6 @@ macro_rules! nyxd_query { }}; } -macro_rules! nyxd_signing_shared { - ($self:expr, $($op:tt)*) => {{ - let guard = $self.inner.read().await; - match &*guard { - $crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*, - $crate::support::nyxd::ClientInner::Query(_) => panic!("attempted to use a signing method on a query client"), - } - }}; -} - macro_rules! nyxd_signing { ($self:expr, $($op:tt)*) => {{ let guard = $self.inner.write().await; @@ -140,13 +130,19 @@ impl Client { self.inner.read().await } - pub(crate) async fn client_address(&self) -> AccountId { - nyxd_signing_shared!(self, address()) + pub(crate) async fn client_address(&self) -> Option { + let guard = self.inner.read().await; + match &*guard { + ClientInner::Signing(client) => Some(client.address()), + ClientInner::Query(_) => None, + } } pub(crate) async fn balance>(&self, denom: S) -> Result { - let address = self.client_address().await; let denom = denom.into(); + let Some(address) = self.client_address().await else { + return Ok(Coin::new(0, denom)); + }; let balance = nyxd_query!(self, get_balance(&address, denom.clone()).await?); match balance { @@ -394,8 +390,10 @@ impl Client { #[async_trait] impl crate::ecash::client::Client for Client { - async fn address(&self) -> AccountId { - self.client_address().await + async fn address(&self) -> Result { + self.client_address() + .await + .ok_or(EcashError::ChainSignerNotEnabled) } async fn dkg_contract_address(&self) -> Result { @@ -481,7 +479,7 @@ impl crate::ecash::client::Client for Client { async fn get_self_registered_dealer_details( &self, ) -> crate::ecash::error::Result { - let self_address = &self.address().await; + let self_address = &self.address().await?; Ok(nyxd_query!(self, get_dealer_details(self_address).await?)) } From fa72f90bfab31c699ea6a7e0ad12fbe2a22c2c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 25 Nov 2024 16:44:23 +0000 Subject: [PATCH 04/22] Remove peers with no allowed ip from storage --- gateway/src/node/mod.rs | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index c399deefa9..48e7975018 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -231,22 +231,28 @@ impl Gateway { forwarding_channel, router_tx, ); - let all_peers = self.client_storage.get_all_wireguard_peers().await?; - let used_private_network_ips = all_peers - .iter() - .cloned() - .map(|wireguard_peer| { - defguard_wireguard_rs::host::Peer::try_from(wireguard_peer).map(|mut peer| { - peer.allowed_ips - .pop() - .ok_or(Box::new(GatewayError::InternalWireguardError(format!( - "no private IP set for peer {}", - peer.public_key - )))) - .map(|p| p.ip) - }) - }) - .collect::, _>, _>>()??; + let mut used_private_network_ips = vec![]; + let mut all_peers = vec![]; + for wireguard_peer in self + .client_storage + .get_all_wireguard_peers() + .await? + .into_iter() + { + let mut peer = defguard_wireguard_rs::host::Peer::try_from(wireguard_peer.clone())?; + let Some(peer) = peer.allowed_ips.pop() else { + tracing::warn!( + "Peer {} has empty allowed ips. It will be removed", + peer.public_key + ); + self.client_storage + .remove_wireguard_peer(&peer.public_key.to_string()) + .await?; + continue; + }; + used_private_network_ips.push(peer.ip); + all_peers.push(wireguard_peer); + } if let Some(wireguard_data) = self.wireguard_data.take() { let (on_start_tx, on_start_rx) = oneshot::channel(); From dcde4c8df1e7017d4dd84240115a56a4d7d0dd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 25 Nov 2024 17:54:32 +0000 Subject: [PATCH 05/22] bugfix: use default value for verloc config when deserialising missing values (#5177) --- nym-node/src/config/mixnode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-node/src/config/mixnode.rs b/nym-node/src/config/mixnode.rs index 92f9279c2f..a62ee4b075 100644 --- a/nym-node/src/config/mixnode.rs +++ b/nym-node/src/config/mixnode.rs @@ -36,7 +36,7 @@ impl MixnodeConfig { } #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] +#[serde(deny_unknown_fields, default)] pub struct Verloc { /// Socket address this node will use for binding its verloc API. /// default: `0.0.0.0:1790` From 2a60b2f057539a081b303dea97a7e5feb4253e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 26 Nov 2024 09:41:18 +0000 Subject: [PATCH 06/22] bugfix: fixed nym-node config migrations (again) (#5179) --- nym-node/src/config/old_configs/old_config_v1.rs | 6 ++++-- nym-node/src/config/old_configs/old_config_v2.rs | 6 ++++-- nym-node/src/config/old_configs/old_config_v3.rs | 6 ++++-- nym-node/src/config/old_configs/old_config_v4.rs | 10 ++++++---- nym-node/src/config/old_configs/old_config_v5.rs | 10 ++++++---- nym-node/src/config/upgrade_helpers.rs | 6 +++++- 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/nym-node/src/config/old_configs/old_config_v1.rs b/nym-node/src/config/old_configs/old_config_v1.rs index a09eee36e7..c67f9358ae 100644 --- a/nym-node/src/config/old_configs/old_config_v1.rs +++ b/nym-node/src/config/old_configs/old_config_v1.rs @@ -11,6 +11,7 @@ use nym_pemstore::store_keypair; use old_configs::old_config_v2::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -756,15 +757,16 @@ fn initialise(config: &WireguardV2) -> std::io::Result<()> { Ok(()) } +#[instrument(skip_all)] pub async fn try_upgrade_config_v1>( path: P, prev_config: Option, ) -> Result { - tracing::debug!("Updating from 1.1.2"); + debug!("attempting to load v1 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config } else { - ConfigV1::read_from_path(&path)? + ConfigV1::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; let wireguard = WireguardV2 { enabled: old_cfg.wireguard.enabled, diff --git a/nym-node/src/config/old_configs/old_config_v2.rs b/nym-node/src/config/old_configs/old_config_v2.rs index 28e35e66bc..2082ed4f70 100644 --- a/nym-node/src/config/old_configs/old_config_v2.rs +++ b/nym-node/src/config/old_configs/old_config_v2.rs @@ -16,6 +16,7 @@ use nym_sphinx_acknowledgements::AckKey; use old_configs::old_config_v3::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -786,15 +787,16 @@ pub async fn initialise( Ok(()) } +#[instrument(skip_all)] pub async fn try_upgrade_config_v2>( path: P, prev_config: Option, ) -> Result { - tracing::debug!("Updating from 1.1.3"); + debug!("attempting to load v2 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config } else { - ConfigV2::read_from_path(&path)? + ConfigV2::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; let authenticator_paths = AuthenticatorPathsV3::new( diff --git a/nym-node/src/config/old_configs/old_config_v3.rs b/nym-node/src/config/old_configs/old_config_v3.rs index 01ce8a9e49..1301230167 100644 --- a/nym-node/src/config/old_configs/old_config_v3.rs +++ b/nym-node/src/config/old_configs/old_config_v3.rs @@ -17,6 +17,7 @@ use old_configs::old_config_v4::*; use persistence::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -938,15 +939,16 @@ pub async fn initialise( Ok(()) } +#[instrument(skip_all)] pub async fn try_upgrade_config_v3>( path: P, prev_config: Option, ) -> Result { - tracing::debug!("Updating from 1.1.4"); + debug!("attempting to load v3 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config } else { - ConfigV3::read_from_path(&path)? + ConfigV3::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; let exit_gateway_paths = ExitGatewayPaths::new( diff --git a/nym-node/src/config/old_configs/old_config_v4.rs b/nym-node/src/config/old_configs/old_config_v4.rs index 8e8e3758d0..03a32b2b39 100644 --- a/nym-node/src/config/old_configs/old_config_v4.rs +++ b/nym-node/src/config/old_configs/old_config_v4.rs @@ -20,6 +20,7 @@ use old_configs::old_config_v5::*; use persistence::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -203,7 +204,7 @@ pub struct MixnetV4 { /// If applicable, custom port announced in the self-described API that other clients and nodes /// will use. /// Useful when the node is behind a proxy. - #[serde(deserialize_with = "de_maybe_port")] + #[serde(deserialize_with = "de_maybe_port", default)] pub announce_port: Option, /// Addresses to nym APIs from which the node gets the view of the network. @@ -385,7 +386,7 @@ pub struct VerlocV4 { /// default: `0.0.0.0:1790` pub bind_address: SocketAddr, - #[serde(deserialize_with = "de_maybe_port")] + #[serde(deserialize_with = "de_maybe_port", default)] pub announce_port: Option, #[serde(default)] @@ -1060,15 +1061,16 @@ pub async fn initialise( Ok(()) } +#[instrument(skip_all)] pub async fn try_upgrade_config_v4>( path: P, prev_config: Option, ) -> Result { - tracing::debug!("Updating from 1.1.5"); + debug!("attempting to load v4 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config } else { - ConfigV4::read_from_path(&path)? + ConfigV4::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; let exit_gateway_paths = ExitGatewayPaths::new( diff --git a/nym-node/src/config/old_configs/old_config_v5.rs b/nym-node/src/config/old_configs/old_config_v5.rs index 936882f113..4a1cb57c64 100644 --- a/nym-node/src/config/old_configs/old_config_v5.rs +++ b/nym-node/src/config/old_configs/old_config_v5.rs @@ -25,6 +25,7 @@ use nym_sphinx_acknowledgements::AckKey; use persistence::*; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; +use tracing::instrument; #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] #[serde(deny_unknown_fields)] @@ -208,7 +209,7 @@ pub struct MixnetV5 { /// If applicable, custom port announced in the self-described API that other clients and nodes /// will use. /// Useful when the node is behind a proxy. - #[serde(deserialize_with = "de_maybe_port")] + #[serde(deserialize_with = "de_maybe_port", default)] pub announce_port: Option, /// Addresses to nym APIs from which the node gets the view of the network. @@ -390,7 +391,7 @@ pub struct VerlocV5 { /// default: `0.0.0.0:1790` pub bind_address: SocketAddr, - #[serde(deserialize_with = "de_maybe_port")] + #[serde(deserialize_with = "de_maybe_port", default)] pub announce_port: Option, #[serde(default)] @@ -1069,15 +1070,16 @@ pub async fn initialise( Ok(()) } +#[instrument(skip_all)] pub async fn try_upgrade_config_v5>( path: P, prev_config: Option, ) -> Result { - tracing::debug!("Updating from 1.1.6"); + debug!("attempting to load v5 config..."); let old_cfg = if let Some(prev_config) = prev_config { prev_config } else { - ConfigV5::read_from_path(&path)? + ConfigV5::read_from_path(&path).inspect_err(|err| debug!("failed: {err}"))? }; let (private_ipv4, private_ipv6) = match old_cfg.wireguard.private_ip { diff --git a/nym-node/src/config/upgrade_helpers.rs b/nym-node/src/config/upgrade_helpers.rs index bae75192ec..c7596b69a6 100644 --- a/nym-node/src/config/upgrade_helpers.rs +++ b/nym-node/src/config/upgrade_helpers.rs @@ -5,6 +5,7 @@ use crate::config::old_configs::*; use crate::config::Config; use crate::error::NymNodeError; use std::path::Path; +use tracing::debug; // currently there are no upgrades async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> { @@ -24,7 +25,10 @@ async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> { pub async fn try_load_current_config>( config_path: P, ) -> Result { - if let Ok(cfg) = Config::read_from_toml_file(config_path.as_ref()) { + if let Ok(cfg) = Config::read_from_toml_file(config_path.as_ref()) + .inspect_err(|err| debug!("didn't manage to load the current config: {err}")) + { + debug!("managed to load the current version of the config"); return Ok(cfg); } From 35aa7e338da5f32d05ca5a479428a751463fbf5a Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 26 Nov 2024 15:01:06 +0100 Subject: [PATCH 07/22] bump binary versions --- Cargo.lock | 16 ++++++++-------- clients/native/Cargo.toml | 2 +- clients/socks5/Cargo.toml | 2 +- explorer-api/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-node/Cargo.toml | 2 +- service-providers/network-requester/Cargo.toml | 2 +- tools/nym-cli/Cargo.toml | 2 +- tools/nymvisor/Cargo.toml | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d004324f62..b2eae3a227 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2428,7 +2428,7 @@ dependencies = [ [[package]] name = "explorer-api" -version = "1.1.42" +version = "1.1.43" dependencies = [ "chrono", "clap 4.5.20", @@ -4451,7 +4451,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nym-api" -version = "1.1.46" +version = "1.1.47" dependencies = [ "anyhow", "async-trait", @@ -4701,7 +4701,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.44" +version = "1.1.45" dependencies = [ "anyhow", "base64 0.22.1", @@ -4782,7 +4782,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.44" +version = "1.1.45" dependencies = [ "bs58", "clap 4.5.20", @@ -5899,7 +5899,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.45" +version = "1.1.46" dependencies = [ "addr", "anyhow", @@ -5950,7 +5950,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.1.11" +version = "1.1.12" dependencies = [ "anyhow", "bip39", @@ -6306,7 +6306,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.44" +version = "1.1.45" dependencies = [ "bs58", "clap 4.5.20", @@ -6908,7 +6908,7 @@ dependencies = [ [[package]] name = "nymvisor" -version = "0.1.9" +version = "0.1.10" dependencies = [ "anyhow", "bytes", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index c5679d02de..03eb55186e 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.44" +version = "1.1.45" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index f5d3cd8967..4363c56387 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.44" +version = "1.1.45" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 11f8672f98..6fe9830dae 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.42" +version = "1.1.43" edition = "2021" license.workspace = true diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index ea2e3ed212..27814af1db 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-api" license = "GPL-3.0" -version = "1.1.46" +version = "1.1.47" authors.workspace = true edition = "2021" rust-version.workspace = true diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 16f66a6fee..92dec65fea 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.1.11" +version = "1.1.12" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index a419abd4f6..1508c7d775 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-network-requester" license = "GPL-3.0" -version = "1.1.45" +version = "1.1.46" authors.workspace = true edition.workspace = true rust-version = "1.70" diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index 2a4bdf0186..c156e28679 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.44" +version = "1.1.45" authors.workspace = true edition = "2021" license.workspace = true diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index a1f0590c8d..d44e9fc12b 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nymvisor" -version = "0.1.9" +version = "0.1.10" authors.workspace = true repository.workspace = true homepage.workspace = true From 8e713d43e1bf3d9de86f0986af2677ffc3b7882d Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Wed, 27 Nov 2024 11:07:39 +0100 Subject: [PATCH 08/22] Add monitor_run and testing_route indexes (#5182) --- .../migrations/20241127110000_add_monitor_run_indexes.sql | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 nym-api/migrations/20241127110000_add_monitor_run_indexes.sql diff --git a/nym-api/migrations/20241127110000_add_monitor_run_indexes.sql b/nym-api/migrations/20241127110000_add_monitor_run_indexes.sql new file mode 100644 index 0000000000..f639bbba7a --- /dev/null +++ b/nym-api/migrations/20241127110000_add_monitor_run_indexes.sql @@ -0,0 +1,8 @@ +/* + * Copyright 2024 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +CREATE INDEX IF NOT EXISTS monitor_run_id on monitor_run(id); +CREATE INDEX IF NOT EXISTS monitor_run_timestamp on monitor_run(timestamp); +CREATE INDEX IF NOT EXISTS testing_route_monitor_run_id on testing_route(monitor_run_id); \ No newline at end of file From 9ac0595a35cd4982ad63468ad9c8ed3f36c17b1f Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 11:49:29 +0100 Subject: [PATCH 09/22] remove duplicate iptable rules --- scripts/network_tunnel_manager.sh | 83 +++++++++++++++---------------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index c135864d90..81020047d0 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -27,11 +27,44 @@ fetch_and_display_ipv6() { ipv6_address=$(ip -6 addr show "$network_device" scope global | grep inet6 | awk '{print $2}') if [[ -z "$ipv6_address" ]]; then echo "no global IPv6 address found on $network_device." - elsen + else echo "IPv6 address on $network_device: $ipv6_address" fi } +remove_duplicate_rules() { + local interface=$1 + local script_name=$(basename "$0") + + if [[ -z "$interface" ]]; then + echo "error: no interface specified. please enter the interface (nymwg or nymtun0):" + read -r interface + fi + + if [[ "$interface" != "nymwg" && "$interface" != "nymtun0" ]]; then + echo "error: invalid interface '$interface'. allowed values are 'nymwg' or 'nymtun0'." >&2 + exit 1 + fi + + echo "removing duplicate rules for $interface..." + + iptables-save | grep "$interface" | while read -r line; do + sudo iptables -D ${line#-A } || echo "Failed to delete rule: $line" + done + + ip6tables-save | grep "$interface" | while read -r line; do + sudo ip6tables -D ${line#-A } || echo "Failed to delete rule: $line" + done + + echo "duplicates removed for $interface." + echo "!!-important-!! you need to now reapply the iptables rules for $interface." + if [ "$interface" == "nymwg" ]; then + echo "run: ./$script_name apply_iptables_rules_wg" + else + echo "run: ./$script_name apply_iptables_rules" + fi +} + adjust_ip_forwarding() { ipv6_forwarding_setting="net.ipv6.conf.all.forwarding=1" ipv4_forwarding_setting="net.ipv4.ip_forward=1" @@ -45,22 +78,10 @@ apply_iptables_rules() { echo "applying IPtables rules for $interface..." sleep 2 - # remove duplicates for IPv4 - sudo iptables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true - sudo iptables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true - sudo iptables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true - - # remove duplicates for IPv6 - sudo ip6tables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true - sudo ip6tables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true - sudo ip6tables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true - - # add new rules for IPv4 sudo iptables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE sudo iptables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT sudo iptables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - # add new rules for IPv6 sudo ip6tables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE sudo ip6tables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT sudo ip6tables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT @@ -69,36 +90,6 @@ apply_iptables_rules() { sudo ip6tables-save | sudo tee /etc/iptables/rules.v6 } -apply_iptables_rules_wg() { - local interface=$wg_tunnel_interface - echo "applying IPtables rules for WireGuard ($interface)..." - sleep 2 - - # remove duplicates for IPv4 - sudo iptables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true - sudo iptables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true - sudo iptables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true - - # remove duplicates for IPv6 - sudo ip6tables -D FORWARD -i "$interface" -o "$network_device" -j ACCEPT 2>/dev/null || true - sudo ip6tables -D FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true - sudo ip6tables -t nat -D POSTROUTING -o "$network_device" -j MASQUERADE 2>/dev/null || true - - # add new rules for IPv4 - sudo iptables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE - sudo iptables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT - sudo iptables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - - # add new rules for IPv6 - sudo ip6tables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE - sudo ip6tables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT - sudo ip6tables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT - - sudo iptables-save | sudo tee /etc/iptables/rules.v4 - sudo ip6tables-save | sudo tee /etc/iptables/rules.v6 -} - - check_tunnel_iptables() { local interface=$1 echo "inspecting IPtables rules for $interface..." @@ -167,7 +158,7 @@ configure_dns_and_icmp_wg() { sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT echo "saving iptables rules..." - sudo iptables-save > /etc/iptables/rules.v4 + sudo iptables-save >/etc/iptables/rules.v4 echo "dns and icmp configuration completed." } @@ -212,6 +203,9 @@ configure_dns_and_icmp_wg) adjust_ip_forwarding) adjust_ip_forwarding ;; +remove_duplicate_rules) + remove_duplicate_rules "$2" + ;; *) echo "Usage: $0 [command]" echo "Commands:" @@ -228,6 +222,7 @@ adjust_ip_forwarding) echo " joke_through_wg_tunnel - Fetch a joke via nymwg." echo " configure_dns_and_icmp_wg - Allows icmp ping tests for probes alongside configuring dns" echo " adjust_ip_forwarding - Enable IPV6 and IPV4 forwarding" + echo " remove_duplicate_rules - Remove duplicate iptables rules. Valid interfaces: nymwg, nymtun0" exit 1 ;; esac From 7c1ad7d20ce08bda9f0235b76aca65dc02129ce3 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 12:02:13 +0100 Subject: [PATCH 10/22] add more output on joke commands this should help the end users debug their machines further --- scripts/network_tunnel_manager.sh | 51 ++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index 81020047d0..df86a57ac6 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -127,23 +127,58 @@ perform_pings() { joke_through_tunnel() { local interface=$1 echo "checking tunnel connectivity and fetching a joke for $interface..." - ipv4_address=$(ip addr show "$interface" | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1) - ipv6_address=$(ip addr show "$interface" | grep 'inet6 ' | awk '{print $2}' | grep -v '^fe80:' | cut -d'/' -f1) + + ipv4_address=$(ip addr show "$interface" | awk '/inet / {print $2}' | cut -d'/' -f1) + ipv6_address=$(ip addr show "$interface" | awk '/inet6 / && $2 !~ /^fe80/ {print $2}' | cut -d'/' -f1) if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then - echo "no IP address found on $interface. Unable to fetch a joke." - return + echo "No IP address found on $interface. Unable to fetch a joke." + echo "Please verify your tunnel configuration and ensure the interface is up." + return 1 fi if [[ -n "$ipv4_address" ]]; then - joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke) - [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv4 joke: $joke" || echo "Failed to fetch a joke via IPv4." + echo "detected IPv4 address: $ipv4_address" + echo "testing IPv4 connectivity..." + echo "if this test succeeds, it confirms your machine can reach the outside world via IPv4." + echo "however, probes and external clients may experience different connectivity to your probe." + + if ping -c 1 -I "$ipv4_address" google.com >/dev/null 2>&1; then + echo "IPv4 connectivity is working. Fetching a joke..." + joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke) + [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv4 joke: $joke" || echo "Failed to fetch a joke via IPv4." + else + echo "IPv4 connectivity is not working for $interface. verify your routing and NAT settings." + fi fi if [[ -n "$ipv6_address" ]]; then - joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke) - [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv6 joke: $joke" || echo "Failed to fetch a joke via IPv6." + echo "detected IPv6 address: $ipv6_address" + echo "testing IPv6 connectivity..." + echo "if this test succeeds, it confirms your machine can reach the outside world via IPv6." + echo "however, probes and external clients may experience different connectivity to your nym-node." + + if ping6 -c 1 -I "$ipv6_address" google.com >/dev/null 2>&1; then + echo "IPv6 connectivity is working. Fetching a joke..." + joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke) + [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv6 joke: $joke" || echo "Failed to fetch a joke via IPv6." + else + echo "IPv6 connectivity is not working for $interface. verify your routing and NAT settings." + fi fi + + echo "joke fetching process completed for $interface." + sleep 1 + echo + echo "### Connectivity Testing Recommendations ###" + echo "- use the following command to test WebSocket connectivity from an external client:" + echo " wscat -c wss://:9001" + echo + echo "- test UDP connectivity on port 51822 (commonly used for nym wireguard):" + echo " From another machine, use tools like nc or socat to send UDP packets:" + echo " echo 'test message' | nc -u 51822" + echo + echo "if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured" } configure_dns_and_icmp_wg() { From af9f7b1c0f57476df66e82b761a5272b07fe7713 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 12:02:45 +0100 Subject: [PATCH 11/22] formatting --- scripts/network_tunnel_manager.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index df86a57ac6..7e922e42ee 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -132,8 +132,8 @@ joke_through_tunnel() { ipv6_address=$(ip addr show "$interface" | awk '/inet6 / && $2 !~ /^fe80/ {print $2}' | cut -d'/' -f1) if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then - echo "No IP address found on $interface. Unable to fetch a joke." - echo "Please verify your tunnel configuration and ensure the interface is up." + echo "no IP address found on $interface. unable to fetch a joke." + echo "please verify your tunnel configuration and ensure the interface is up." return 1 fi @@ -170,7 +170,7 @@ joke_through_tunnel() { echo "joke fetching process completed for $interface." sleep 1 echo - echo "### Connectivity Testing Recommendations ###" + echo "### connectivity testing recommendations ###" echo "- use the following command to test WebSocket connectivity from an external client:" echo " wscat -c wss://:9001" echo From 42de6209511decd831ab9585606f3f58d860698b Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 12:06:03 +0100 Subject: [PATCH 12/22] typo --- scripts/network_tunnel_manager.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index 7e922e42ee..70a8fbcd2a 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -172,10 +172,10 @@ joke_through_tunnel() { echo echo "### connectivity testing recommendations ###" echo "- use the following command to test WebSocket connectivity from an external client:" - echo " wscat -c wss://:9001" + echo " wscat -c wss://:9001" echo echo "- test UDP connectivity on port 51822 (commonly used for nym wireguard):" - echo " From another machine, use tools like nc or socat to send UDP packets:" + echo " from another machine, use tools like nc or socat to send UDP packets:" echo " echo 'test message' | nc -u 51822" echo echo "if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured" From f95f01959c168c6c860840518fe4987312fedb27 Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Thu, 28 Nov 2024 12:29:29 +0100 Subject: [PATCH 13/22] fix multiple forwarding calls also add more logging around joke section --- scripts/network_tunnel_manager.sh | 79 +++++++++++++++++++------------ 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/scripts/network_tunnel_manager.sh b/scripts/network_tunnel_manager.sh index 70a8fbcd2a..93e6f73ada 100644 --- a/scripts/network_tunnel_manager.sh +++ b/scripts/network_tunnel_manager.sh @@ -68,9 +68,16 @@ remove_duplicate_rules() { adjust_ip_forwarding() { ipv6_forwarding_setting="net.ipv6.conf.all.forwarding=1" ipv4_forwarding_setting="net.ipv4.ip_forward=1" + + # remove duplicate entries for these settings from the file + sudo sed -i "/^net.ipv6.conf.all.forwarding=/d" /etc/sysctl.conf + sudo sed -i "/^net.ipv4.ip_forward=/d" /etc/sysctl.conf + echo "$ipv6_forwarding_setting" | sudo tee -a /etc/sysctl.conf echo "$ipv4_forwarding_setting" | sudo tee -a /etc/sysctl.conf - sysctl -p /etc/sysctl.conf + + sudo sysctl -p /etc/sysctl.conf + } apply_iptables_rules() { @@ -126,61 +133,75 @@ perform_pings() { joke_through_tunnel() { local interface=$1 - echo "checking tunnel connectivity and fetching a joke for $interface..." + local green="\033[0;32m" + local reset="\033[0m" + local red="\033[0;31m" + local yellow="\033[0;33m" + + sleep 1 + echo + echo -e "${yellow}checking tunnel connectivity and fetching a joke for $interface...${reset}" + echo -e "${yellow}if these test succeeds, it confirms your machine can reach the outside world via IPv4 and IPv6.${reset}" + echo -e "${yellow}however, probes and external clients may experience different connectivity to your nym-node.${reset}" ipv4_address=$(ip addr show "$interface" | awk '/inet / {print $2}' | cut -d'/' -f1) ipv6_address=$(ip addr show "$interface" | awk '/inet6 / && $2 !~ /^fe80/ {print $2}' | cut -d'/' -f1) if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then - echo "no IP address found on $interface. unable to fetch a joke." - echo "please verify your tunnel configuration and ensure the interface is up." + echo -e "${red}no IP address found on $interface. unable to fetch a joke.${reset}" + echo -e "${red}please verify your tunnel configuration and ensure the interface is up.${reset}" return 1 fi - + if [[ -n "$ipv4_address" ]]; then - echo "detected IPv4 address: $ipv4_address" - echo "testing IPv4 connectivity..." - echo "if this test succeeds, it confirms your machine can reach the outside world via IPv4." - echo "however, probes and external clients may experience different connectivity to your probe." + echo + echo -e "------------------------------------" + echo -e "detected IPv4 address: $ipv4_address" + echo -e "testing IPv4 connectivity..." + echo if ping -c 1 -I "$ipv4_address" google.com >/dev/null 2>&1; then - echo "IPv4 connectivity is working. Fetching a joke..." + echo -e "${green}IPv4 connectivity is working. fetching a joke...${reset}" joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke) - [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv4 joke: $joke" || echo "Failed to fetch a joke via IPv4." + [[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv4 joke: $joke${reset}" || echo -e "failed to fetch a joke via IPv4." else - echo "IPv4 connectivity is not working for $interface. verify your routing and NAT settings." + echo -e "${red}IPv4 connectivity is not working for $interface. verify your routing and NAT settings.${reset}" fi fi if [[ -n "$ipv6_address" ]]; then - echo "detected IPv6 address: $ipv6_address" - echo "testing IPv6 connectivity..." - echo "if this test succeeds, it confirms your machine can reach the outside world via IPv6." - echo "however, probes and external clients may experience different connectivity to your nym-node." + echo + echo -e "------------------------------------" + echo -e "detected IPv6 address: $ipv6_address" + echo -e "testing IPv6 connectivity..." + echo if ping6 -c 1 -I "$ipv6_address" google.com >/dev/null 2>&1; then - echo "IPv6 connectivity is working. Fetching a joke..." + echo -e "${green}IPv6 connectivity is working. fetching a joke...${reset}" joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke) - [[ -n "$joke" && "$joke" != "null" ]] && echo "IPv6 joke: $joke" || echo "Failed to fetch a joke via IPv6." + [[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv6 joke: $joke${reset}" || echo -e "${red}failed to fetch a joke via IPv6.${reset}" else - echo "IPv6 connectivity is not working for $interface. verify your routing and NAT settings." + echo -e "${red}IPv6 connectivity is not working for $interface. verify your routing and NAT settings.${reset}" fi fi - echo "joke fetching process completed for $interface." - sleep 1 + echo -e "${green}joke fetching processes completed for $interface.${reset}" + echo -e "------------------------------------" + + sleep 3 echo - echo "### connectivity testing recommendations ###" - echo "- use the following command to test WebSocket connectivity from an external client:" - echo " wscat -c wss://:9001" - echo - echo "- test UDP connectivity on port 51822 (commonly used for nym wireguard):" - echo " from another machine, use tools like nc or socat to send UDP packets:" - echo " echo 'test message' | nc -u 51822" echo - echo "if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured" + echo -e "${yellow}### connectivity testing recommendations ###${reset}" + echo -e "${yellow}- use the following command to test WebSocket connectivity from an external client:${reset}" + echo -e "${yellow} wscat -c wss://:9001 ${reset}" + echo -e "${yellow}- test UDP connectivity on port 51822 (commonly used for nym wireguard) ${reset}" + echo -e "${yellow} from another machine, use tools like nc or socat to send UDP packets ${reset}" + echo -e "${yellow} echo 'test message' | nc -u 51822 ${reset}" + echo -e "${yellow}if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured ${reset}" + echo } + configure_dns_and_icmp_wg() { echo "allowing icmp (ping)..." sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT From 56206433e649395e9000de3516ac81a7ea7877c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 29 Nov 2024 09:20:45 +0000 Subject: [PATCH 14/22] chore: apply 1.84 linter suggestions (#5192) --- .../src/client/topology_control/accessor.rs | 2 +- .../client-libs/validator-client/src/nyxd/coin.rs | 2 +- .../validator-client/src/nyxd/fee/gas_price.rs | 2 +- .../mixnet-contract/src/interval.rs | 2 +- common/dkg/src/bte/proof_chunking.rs | 3 +-- common/dkg/src/interpolation/polynomial.rs | 6 +++--- .../src/registration/handshake/mod.rs | 2 +- .../src/scheme/coin_indices_signatures.rs | 12 ------------ .../nym_offline_compact_ecash/src/scheme/keygen.rs | 2 +- common/nym_offline_compact_ecash/src/scheme/mod.rs | 2 +- common/nymcoconut/src/elgamal.rs | 4 ++-- common/nymcoconut/src/scheme/keygen.rs | 2 +- common/nymsphinx/addressing/src/clients.rs | 2 +- common/nymsphinx/addressing/src/nodes.rs | 13 ++++++------- .../nymsphinx/anonymous-replies/src/reply_surb.rs | 2 +- common/socks5/requests/src/request.rs | 10 ---------- explorer-api/src/geo_ip/location.rs | 2 +- explorer-api/src/guards/location.rs | 2 +- nym-api/nym-api-requests/src/helpers.rs | 2 +- nym-api/src/ecash/tests/helpers.rs | 2 +- .../monitor/gateway_clients_cache.rs | 2 +- .../nym-credential-proxy/src/http/state/mod.rs | 2 +- sdk/ffi/go/src/lib.rs | 3 +++ .../dkg-bypass-contract/src/contract.rs | 4 ++-- .../testnet-manager/src/manager/dkg_skip.rs | 6 +++--- .../testnet-manager/src/manager/local_apis.rs | 2 +- .../testnet-manager/src/manager/local_client.rs | 2 +- .../testnet-manager/src/manager/local_nodes.rs | 2 +- 28 files changed, 39 insertions(+), 60 deletions(-) diff --git a/common/client-core/src/client/topology_control/accessor.rs b/common/client-core/src/client/topology_control/accessor.rs index fcb272c934..6b12d64562 100644 --- a/common/client-core/src/client/topology_control/accessor.rs +++ b/common/client-core/src/client/topology_control/accessor.rs @@ -38,7 +38,7 @@ pub struct TopologyReadPermit<'a> { permit: RwLockReadGuard<'a, Option>, } -impl<'a> Deref for TopologyReadPermit<'a> { +impl Deref for TopologyReadPermit<'_> { type Target = Option; fn deref(&self) -> &Self::Target { diff --git a/common/client-libs/validator-client/src/nyxd/coin.rs b/common/client-libs/validator-client/src/nyxd/coin.rs index 0fa175a243..8050273a51 100644 --- a/common/client-libs/validator-client/src/nyxd/coin.rs +++ b/common/client-libs/validator-client/src/nyxd/coin.rs @@ -32,7 +32,7 @@ impl Div for Coin { } } -impl<'a> Div for &'a Coin { +impl Div for &Coin { type Output = Gas; fn div(self, rhs: GasPrice) -> Self::Output { diff --git a/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs b/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs index 4f0f2fd0a8..3679cc7500 100644 --- a/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs +++ b/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs @@ -22,7 +22,7 @@ pub struct GasPrice { pub denom: String, } -impl<'a> Mul for &'a GasPrice { +impl Mul for &GasPrice { type Output = Coin; fn mul(self, gas_limit: Gas) -> Self::Output { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs index e36935d66c..2259af4c7a 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs @@ -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 { diff --git a/common/dkg/src/bte/proof_chunking.rs b/common/dkg/src/bte/proof_chunking.rs index 6829a53660..1365aecbd7 100644 --- a/common/dkg/src/bte/proof_chunking.rs +++ b/common/dkg/src/bte/proof_chunking.rs @@ -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>>; diff --git a/common/dkg/src/interpolation/polynomial.rs b/common/dkg/src/interpolation/polynomial.rs index 33c7486a99..671b8e6832 100644 --- a/common/dkg/src/interpolation/polynomial.rs +++ b/common/dkg/src/interpolation/polynomial.rs @@ -196,7 +196,7 @@ impl<'b> Add<&'b Polynomial> for Polynomial { } } -impl<'a> Add for &'a Polynomial { +impl Add for &Polynomial { type Output = Polynomial; fn add(self, rhs: Polynomial) -> Polynomial { @@ -212,10 +212,10 @@ impl Add for Polynomial { } } -impl<'a, 'b> Add<&'b Polynomial> for &'a Polynomial { +impl<'a> Add<&'a Polynomial> for &Polynomial { type Output = Polynomial; - fn add(self, rhs: &'b Polynomial) -> Self::Output { + fn add(self, rhs: &'a Polynomial) -> Self::Output { let len = self.coefficients.len(); let rhs_len = rhs.coefficients.len(); diff --git a/common/gateway-requests/src/registration/handshake/mod.rs b/common/gateway-requests/src/registration/handshake/mod.rs index e5dc1dc59f..4a6f44b7d7 100644 --- a/common/gateway-requests/src/registration/handshake/mod.rs +++ b/common/gateway-requests/src/registration/handshake/mod.rs @@ -37,7 +37,7 @@ pub struct GatewayHandshake<'a> { handshake_future: BoxFuture<'a, Result>, } -impl<'a> Future for GatewayHandshake<'a> { +impl Future for GatewayHandshake<'_> { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { diff --git a/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs b/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs index b08ab368dc..30c7fe5523 100644 --- a/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs +++ b/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs @@ -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::*; diff --git a/common/nym_offline_compact_ecash/src/scheme/keygen.rs b/common/nym_offline_compact_ecash/src/scheme/keygen.rs index f5926d9cb6..406b368be0 100644 --- a/common/nym_offline_compact_ecash/src/scheme/keygen.rs +++ b/common/nym_offline_compact_ecash/src/scheme/keygen.rs @@ -264,7 +264,7 @@ impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth { } } -impl<'a> Mul for &'a VerificationKeyAuth { +impl Mul for &VerificationKeyAuth { type Output = VerificationKeyAuth; #[inline] diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index 8912bf91eb..7f9687a380 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -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 { let ss_len = self.inner.len(); let mut bytes: Vec = Vec::with_capacity(ss_len * 48); diff --git a/common/nymcoconut/src/elgamal.rs b/common/nymcoconut/src/elgamal.rs index 55db7dcdd2..1b814f0bc5 100644 --- a/common/nymcoconut/src/elgamal.rs +++ b/common/nymcoconut/src/elgamal.rs @@ -206,10 +206,10 @@ impl Deref for PublicKey { } } -impl<'a, 'b> Mul<&'b Scalar> for &'a PublicKey { +impl<'a> Mul<&'a Scalar> for &PublicKey { type Output = G1Projective; - fn mul(self, rhs: &'b Scalar) -> Self::Output { + fn mul(self, rhs: &'a Scalar) -> Self::Output { self.0 * rhs } } diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs index 85e057793d..f80c131143 100644 --- a/common/nymcoconut/src/scheme/keygen.rs +++ b/common/nymcoconut/src/scheme/keygen.rs @@ -305,7 +305,7 @@ impl<'b> Add<&'b VerificationKey> for VerificationKey { } } -impl<'a> Mul for &'a VerificationKey { +impl Mul for &VerificationKey { type Output = VerificationKey; #[inline] diff --git a/common/nymsphinx/addressing/src/clients.rs b/common/nymsphinx/addressing/src/clients.rs index a739e7369e..743302edbe 100644 --- a/common/nymsphinx/addressing/src/clients.rs +++ b/common/nymsphinx/addressing/src/clients.rs @@ -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 { diff --git a/common/nymsphinx/addressing/src/nodes.rs b/common/nymsphinx/addressing/src/nodes.rs index e0cd745ae9..0c1558c19c 100644 --- a/common/nymsphinx/addressing/src/nodes.rs +++ b/common/nymsphinx/addressing/src/nodes.rs @@ -1,6 +1,12 @@ // Copyright 2021 - Nym Technologies SA // 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 +18,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; diff --git a/common/nymsphinx/anonymous-replies/src/reply_surb.rs b/common/nymsphinx/anonymous-replies/src/reply_surb.rs index deac5f532d..f25871ba0b 100644 --- a/common/nymsphinx/anonymous-replies/src/reply_surb.rs +++ b/common/nymsphinx/anonymous-replies/src/reply_surb.rs @@ -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 { diff --git a/common/socks5/requests/src/request.rs b/common/socks5/requests/src/request.rs index 23e844db87..28a5182d00 100644 --- a/common/socks5/requests/src/request.rs +++ b/common/socks5/requests/src/request.rs @@ -253,25 +253,15 @@ 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 || // // send: // RequestFlag::Send || CONN_ID || LOCAL_CLOSED || DATA // where DATA: SEQ || TRUE_DATA - pub fn try_from_bytes(b: &[u8]) -> Result { // each request needs to at least contain flag and ConnectionId if b.is_empty() { diff --git a/explorer-api/src/geo_ip/location.rs b/explorer-api/src/geo_ip/location.rs index 59bbb33ad7..263764943e 100644 --- a/explorer-api/src/geo_ip/location.rs +++ b/explorer-api/src/geo_ip/location.rs @@ -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 { diff --git a/explorer-api/src/guards/location.rs b/explorer-api/src/guards/location.rs index 045b03f5b4..f5ce0b7ee2 100644 --- a/explorer-api/src/guards/location.rs +++ b/explorer-api/src/guards/location.rs @@ -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, diff --git a/nym-api/nym-api-requests/src/helpers.rs b/nym-api/nym-api-requests/src/helpers.rs index 651030fc74..292ffb69f4 100644 --- a/nym-api/nym-api-requests/src/helpers.rs +++ b/nym-api/nym-api-requests/src/helpers.rs @@ -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 { diff --git a/nym-api/src/ecash/tests/helpers.rs b/nym-api/src/ecash/tests/helpers.rs index 7f7e4aaf54..458b57946e 100644 --- a/nym-api/src/ecash/tests/helpers.rs +++ b/nym-api/src/ecash/tests/helpers.rs @@ -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 }; diff --git a/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs index 4a321d57cd..c791f9a6af 100644 --- a/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs +++ b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs @@ -59,7 +59,7 @@ impl GatewayClientHandle { } } -impl<'a> UnlockedGatewayClientHandle<'a> { +impl UnlockedGatewayClientHandle<'_> { pub(crate) fn get_mut_unchecked( &mut self, ) -> &mut GatewayClient { diff --git a/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs b/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs index c4c4a8ebe3..bcc62f9c7e 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs @@ -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 { diff --git a/sdk/ffi/go/src/lib.rs b/sdk/ffi/go/src/lib.rs index a5c930a2ec..a0a690c691 100644 --- a/sdk/ffi/go/src/lib.rs +++ b/sdk/ffi/go/src/lib.rs @@ -1,6 +1,9 @@ // Copyright 2023-2024 - Nym Technologies SA // 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"); diff --git a/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs b/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs index c04fae8ef2..df70e604f4 100644 --- a/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs +++ b/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs @@ -33,7 +33,7 @@ pub(crate) struct VkShareIndex<'a> { pub(crate) epoch_id: MultiIndex<'a, EpochId, ContractVKShare, VKShareKey<'a>>, } -impl<'a> IndexList for VkShareIndex<'a> { +impl IndexList for VkShareIndex<'_> { fn get_indexes(&'_ self) -> Box> + '_> { let v: Vec<&dyn Index> = vec![&self.epoch_id]; Box::new(v.into_iter()) @@ -87,7 +87,7 @@ pub fn query(_: Deps<'_>, _: Env, _: EmptyMessage) -> Result, env: Env, msg: MigrateMsg) -> Result { // 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); diff --git a/tools/internal/testnet-manager/src/manager/dkg_skip.rs b/tools/internal/testnet-manager/src/manager/dkg_skip.rs index a132ea6950..7720202665 100644 --- a/tools/internal/testnet-manager/src/manager/dkg_skip.rs +++ b/tools/internal/testnet-manager/src/manager/dkg_skip.rs @@ -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, } -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)?; diff --git a/tools/internal/testnet-manager/src/manager/local_apis.rs b/tools/internal/testnet-manager/src/manager/local_apis.rs index 631f6117f2..124b2a324f 100644 --- a/tools/internal/testnet-manager/src/manager/local_apis.rs +++ b/tools/internal/testnet-manager/src/manager/local_apis.rs @@ -24,7 +24,7 @@ struct LocalApisCtx<'a> { signers: Vec, } -impl<'a> ProgressCtx for LocalApisCtx<'a> { +impl ProgressCtx for LocalApisCtx<'_> { fn progress_tracker(&self) -> &ProgressTracker { &self.progress } diff --git a/tools/internal/testnet-manager/src/manager/local_client.rs b/tools/internal/testnet-manager/src/manager/local_client.rs index c037f53719..fac5e6828a 100644 --- a/tools/internal/testnet-manager/src/manager/local_client.rs +++ b/tools/internal/testnet-manager/src/manager/local_client.rs @@ -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 } diff --git a/tools/internal/testnet-manager/src/manager/local_nodes.rs b/tools/internal/testnet-manager/src/manager/local_nodes.rs index dc19d44436..75edc76b7d 100644 --- a/tools/internal/testnet-manager/src/manager/local_nodes.rs +++ b/tools/internal/testnet-manager/src/manager/local_nodes.rs @@ -30,7 +30,7 @@ struct LocalNodesCtx<'a> { gateways: Vec, } -impl<'a> ProgressCtx for LocalNodesCtx<'a> { +impl ProgressCtx for LocalNodesCtx<'_> { fn progress_tracker(&self) -> &ProgressTracker { &self.progress } From 889d464e9837d60e84bcc95de85e51eefa8c03fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 29 Nov 2024 09:45:12 +0000 Subject: [PATCH 15/22] improvement: make internal gateway clients use the same topology cache (#5191) --- gateway/src/node/helpers.rs | 58 +++++++++++++++++++++++++++++-------- gateway/src/node/mod.rs | 4 +++ 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/gateway/src/node/helpers.rs b/gateway/src/node/helpers.rs index d0862f1688..d87b02afed 100644 --- a/gateway/src/node/helpers.rs +++ b/gateway/src/node/helpers.rs @@ -5,6 +5,8 @@ use async_trait::async_trait; use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; use nym_topology::{gateway, NymTopology, TopologyProvider}; use std::sync::Arc; +use std::time::Duration; +use time::OffsetDateTime; use tokio::sync::Mutex; use tracing::debug; use url::Url; @@ -17,6 +19,7 @@ pub struct GatewayTopologyProvider { impl GatewayTopologyProvider { pub fn new( gateway_node: gateway::LegacyNode, + cache_ttl: Duration, user_agent: UserAgent, nym_api_url: Vec, ) -> GatewayTopologyProvider { @@ -31,6 +34,9 @@ impl GatewayTopologyProvider { env!("CARGO_PKG_VERSION").to_string(), Some(user_agent), ), + cache_ttl, + cached_at: OffsetDateTime::UNIX_EPOCH, + cached: None, gateway_node, })), } @@ -39,25 +45,53 @@ impl GatewayTopologyProvider { struct GatewayTopologyProviderInner { inner: NymApiTopologyProvider, + cache_ttl: Duration, + cached_at: OffsetDateTime, + cached: Option, gateway_node: gateway::LegacyNode, } +impl GatewayTopologyProviderInner { + fn cached_topology(&self) -> Option { + if let Some(cached_topology) = &self.cached { + if self.cached_at + self.cache_ttl > OffsetDateTime::now_utc() { + return Some(cached_topology.clone()); + } + } + + None + } + + async fn update_cache(&mut self) -> Option { + let updated_cache = match self.inner.get_new_topology().await { + None => None, + Some(mut base) => { + if !base.gateway_exists(&self.gateway_node.identity_key) { + debug!( + "{} didn't exist in topology. inserting it.", + self.gateway_node.identity_key + ); + base.insert_gateway(self.gateway_node.clone()); + } + Some(base) + } + }; + + self.cached_at = OffsetDateTime::now_utc(); + self.cached = updated_cache.clone(); + + updated_cache + } +} + #[async_trait] impl TopologyProvider for GatewayTopologyProvider { async fn get_new_topology(&mut self) -> Option { let mut guard = self.inner.lock().await; - match guard.inner.get_new_topology().await { - None => None, - Some(mut base) => { - if !base.gateway_exists(&guard.gateway_node.identity_key) { - debug!( - "{} didn't exist in topology. inserting it.", - guard.gateway_node.identity_key - ); - base.insert_gateway(guard.gateway_node.clone()); - } - Some(base) - } + // check the cache + if let Some(cached) = guard.cached_topology() { + return Some(cached); } + guard.update_cache().await } } diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 48e7975018..90e60f5bfc 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -30,6 +30,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::process; use std::sync::Arc; +use std::time::Duration; use tracing::*; pub(crate) mod client_handling; @@ -148,8 +149,11 @@ impl Gateway { } fn gateway_topology_provider(&self) -> GatewayTopologyProvider { + // TODO: make topology ttl configurable + // (to be done in reeses with the final smooshing) GatewayTopologyProvider::new( self.as_topology_node(), + Duration::from_secs(5 * 60), self.user_agent.clone(), self.config.gateway.nym_api_urls.clone(), ) From ab20260a2fd27eed7a8223937901e2d723ddfb4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Fri, 29 Nov 2024 14:56:39 +0200 Subject: [PATCH 16/22] Guard storage access with cache (#5193) * Guard storage access with cache * Do the sync way less freq * Change sync behaviour for bandwidth too * Use bigger delta --- Cargo.lock | 1 + .../src/bandwidth_storage_manager.rs | 2 +- .../src/client_bandwidth.rs | 4 +- common/wireguard/Cargo.toml | 1 + common/wireguard/src/lib.rs | 3 +- common/wireguard/src/peer_controller.rs | 24 ++- common/wireguard/src/peer_handle.rs | 30 ++-- common/wireguard/src/peer_storage_manager.rs | 138 ++++++++++++++++++ 8 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 common/wireguard/src/peer_storage_manager.rs diff --git a/Cargo.lock b/Cargo.lock index 3be4b4751b..6557b2c99f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6749,6 +6749,7 @@ dependencies = [ "nym-task", "nym-wireguard-types", "thiserror", + "time", "tokio", "tokio-stream", "x25519-dalek", diff --git a/common/credential-verification/src/bandwidth_storage_manager.rs b/common/credential-verification/src/bandwidth_storage_manager.rs index 3e35fd9eb2..7c3136e91c 100644 --- a/common/credential-verification/src/bandwidth_storage_manager.rs +++ b/common/credential-verification/src/bandwidth_storage_manager.rs @@ -111,7 +111,7 @@ impl BandwidthStorageManager { } #[instrument(level = "trace", skip_all)] - async fn sync_storage_bandwidth(&mut self) -> Result<()> { + pub async fn sync_storage_bandwidth(&mut self) -> Result<()> { trace!("syncing client bandwidth with the underlying storage"); let updated = self .storage diff --git a/common/credential-verification/src/client_bandwidth.rs b/common/credential-verification/src/client_bandwidth.rs index 9b764714e8..d98f89b511 100644 --- a/common/credential-verification/src/client_bandwidth.rs +++ b/common/credential-verification/src/client_bandwidth.rs @@ -8,8 +8,8 @@ use std::time::Duration; use time::OffsetDateTime; use tokio::sync::RwLock; -const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5); -const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB +const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_secs(5 * 60); // 5 minutes +const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 5 * 1024 * 1024; // 5MB #[derive(Debug, Clone, Copy)] pub struct BandwidthFlushingBehaviourConfig { diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index f20651acbd..c594e6b891 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -26,6 +26,7 @@ log.workspace = true thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } tokio-stream = { workspace = true } +time = { workspace = true } nym-authenticator-requests = { path = "../authenticator-requests" } nym-credential-verification = { path = "../credential-verification" } diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index ecd1576926..938c44e650 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -20,6 +20,7 @@ const WG_TUN_NAME: &str = "nymwg"; pub(crate) mod error; pub mod peer_controller; pub mod peer_handle; +pub mod peer_storage_manager; pub struct WgApiWrapper { inner: WGApi, @@ -118,7 +119,7 @@ pub async fn start_wireguard storage .insert_wireguard_peer(peer, bandwidth_manager.is_some()) .await?; - peer_bandwidth_managers.insert(peer.public_key.clone(), bandwidth_manager); + peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone())); } wg_api.create_interface()?; let interface_config = InterfaceConfiguration { diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index 8c7d94784a..321b15462f 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -20,9 +20,9 @@ use std::{collections::HashMap, sync::Arc}; use tokio::sync::{mpsc, RwLock}; use tokio_stream::{wrappers::IntervalStream, StreamExt}; -use crate::peer_handle::PeerHandle; use crate::WgApiWrapper; use crate::{error::Error, peer_handle::SharedBandwidthStorageManager}; +use crate::{peer_handle::PeerHandle, peer_storage_manager::PeerStorageManager}; pub enum PeerControlRequest { AddPeer { @@ -79,7 +79,7 @@ impl PeerController { storage: St, wg_api: Arc, initial_host_information: Host, - bw_storage_managers: HashMap>>, + bw_storage_managers: HashMap>, Peer)>, request_tx: mpsc::Sender, request_rx: mpsc::Receiver, task_client: nym_task::TaskClient, @@ -88,11 +88,16 @@ impl PeerController { tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK), ); let host_information = Arc::new(RwLock::new(initial_host_information)); - for (public_key, bandwidth_storage_manager) in bw_storage_managers.iter() { - let mut handle = PeerHandle::new( + for (public_key, (bandwidth_storage_manager, peer)) in bw_storage_managers.iter() { + let peer_storage_manager = PeerStorageManager::new( storage.clone(), + peer.clone(), + bandwidth_storage_manager.is_some(), + ); + let mut handle = PeerHandle::new( public_key.clone(), host_information.clone(), + peer_storage_manager, bandwidth_storage_manager.clone(), request_tx.clone(), &task_client, @@ -103,6 +108,10 @@ impl PeerController { } }); } + let bw_storage_managers = bw_storage_managers + .into_iter() + .map(|(k, (m, _))| (k, m)) + .collect(); PeerController { storage, @@ -184,10 +193,15 @@ impl PeerController { Self::generate_bandwidth_manager(self.storage.clone(), &peer.public_key) .await? .map(|bw_m| Arc::new(RwLock::new(bw_m))); - let mut handle = PeerHandle::new( + let peer_storage_manager = PeerStorageManager::new( self.storage.clone(), + peer.clone(), + bandwidth_storage_manager.is_some(), + ); + let mut handle = PeerHandle::new( peer.public_key.clone(), self.host_information.clone(), + peer_storage_manager, bandwidth_storage_manager.clone(), self.request_tx.clone(), &self.task_client, diff --git a/common/wireguard/src/peer_handle.rs b/common/wireguard/src/peer_handle.rs index 71fa06f847..a6e5f9e502 100644 --- a/common/wireguard/src/peer_handle.rs +++ b/common/wireguard/src/peer_handle.rs @@ -3,6 +3,7 @@ use crate::error::Error; use crate::peer_controller::PeerControlRequest; +use crate::peer_storage_manager::PeerStorageManager; use defguard_wireguard_rs::host::Peer; use defguard_wireguard_rs::{host::Host, key::Key}; use futures::channel::oneshot; @@ -21,9 +22,9 @@ pub(crate) type SharedBandwidthStorageManager = Arc { - storage: St, public_key: Key, host_information: Arc>, + peer_storage_manager: PeerStorageManager, bandwidth_storage_manager: Option>, request_tx: mpsc::Sender, timeout_check_interval: IntervalStream, @@ -33,9 +34,9 @@ pub struct PeerHandle { impl PeerHandle { pub fn new( - storage: St, public_key: Key, host_information: Arc>, + peer_storage_manager: PeerStorageManager, bandwidth_storage_manager: Option>, request_tx: mpsc::Sender, task_client: &TaskClient, @@ -46,9 +47,9 @@ impl PeerHandle { let mut task_client = task_client.fork(format!("peer-{public_key}")); task_client.disarm(); PeerHandle { - storage, public_key, host_information, + peer_storage_manager, bandwidth_storage_manager, request_tx, timeout_check_interval, @@ -84,16 +85,19 @@ impl PeerHandle { .ok_or(Error::InconsistentConsumedBytes)? .try_into() .map_err(|_| Error::InconsistentConsumedBytes)?; - if spent_bandwidth > 0 - && bandwidth_manager + if spent_bandwidth > 0 { + self.peer_storage_manager.update_trx(kernel_peer); + if bandwidth_manager .write() .await .try_use_bandwidth(spent_bandwidth) .await .is_err() - { - let success = self.remove_peer().await?; - return Ok(!success); + { + let success = self.remove_peer().await?; + self.peer_storage_manager.remove_peer(); + return Ok(!success); + } } } else { if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER { @@ -132,7 +136,7 @@ impl PeerHandle { // the host information hasn't beed updated yet continue; }; - let Some(storage_peer) = self.storage.get_wireguard_peer(&self.public_key.to_string()).await? else { + let Some(storage_peer) = self.peer_storage_manager.get_peer() else { log::debug!("Peer {:?} not in storage anymore, shutting down handle", self.public_key); return Ok(()); }; @@ -141,12 +145,18 @@ impl PeerHandle { return Ok(()); } else { // Update storage values - self.storage.insert_wireguard_peer(&kernel_peer, self.bandwidth_storage_manager.is_some()).await?; + self.peer_storage_manager.sync_storage_peer().await?; } } _ = self.task_client.recv() => { log::trace!("PeerHandle: Received shutdown"); + if let Some(bandwidth_manager) = &self.bandwidth_storage_manager { + if let Err(e) = bandwidth_manager.write().await.sync_storage_bandwidth().await { + log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed"); + } + } + log::trace!("PeerHandle: Finished shutdown"); } } } diff --git a/common/wireguard/src/peer_storage_manager.rs b/common/wireguard/src/peer_storage_manager.rs new file mode 100644 index 0000000000..f7992b8bb0 --- /dev/null +++ b/common/wireguard/src/peer_storage_manager.rs @@ -0,0 +1,138 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Error; +use defguard_wireguard_rs::host::Peer; +use nym_gateway_storage::models::WireguardPeer; +use nym_gateway_storage::Storage; +use std::time::Duration; +use time::OffsetDateTime; + +const DEFAULT_PEER_MAX_FLUSHING_RATE: Duration = Duration::from_secs(60 * 60 * 24); // 24h +const DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT: u64 = 512 * 1024 * 1024; // 512MB + +#[derive(Debug, Clone, Copy)] +pub struct PeerFlushingBehaviourConfig { + /// Defines maximum delay between peer information being flushed to the persistent storage. + pub peer_max_flushing_rate: Duration, + + /// Defines a maximum change in peer before it gets flushed to the persistent storage. + pub peer_max_delta_flushing_amount: u64, +} + +impl Default for PeerFlushingBehaviourConfig { + fn default() -> Self { + Self { + peer_max_flushing_rate: DEFAULT_PEER_MAX_FLUSHING_RATE, + peer_max_delta_flushing_amount: DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT, + } + } +} + +pub struct PeerStorageManager { + pub(crate) storage: S, + pub(crate) peer_information: Option, + pub(crate) cfg: PeerFlushingBehaviourConfig, + pub(crate) with_client_id: bool, +} + +impl PeerStorageManager { + pub(crate) fn new(storage: S, peer: Peer, with_client_id: bool) -> Self { + let peer_information = Some(PeerInformation::new(peer)); + Self { + storage, + peer_information, + cfg: PeerFlushingBehaviourConfig::default(), + with_client_id, + } + } + + pub(crate) fn get_peer(&self) -> Option { + self.peer_information + .as_ref() + .map(|p| p.peer.clone().into()) + } + + pub(crate) fn remove_peer(&mut self) { + self.peer_information = None; + } + + pub(crate) fn update_trx(&mut self, kernel_peer: &Peer) { + if let Some(peer_information) = self.peer_information.as_mut() { + peer_information.update_trx_bytes(kernel_peer.tx_bytes, kernel_peer.rx_bytes); + } + } + + pub(crate) async fn sync_storage_peer(&mut self) -> Result<(), Error> { + let Some(peer_information) = self.peer_information.as_mut() else { + return Ok(()); + }; + if !peer_information.should_sync(self.cfg) { + return Ok(()); + } + if self + .storage + .get_wireguard_peer(&peer_information.peer().public_key.to_string()) + .await? + .is_none() + { + self.peer_information = None; + return Ok(()); + } + self.storage + .insert_wireguard_peer(peer_information.peer(), self.with_client_id) + .await?; + + peer_information.resync_peer_with_storage(); + + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct PeerInformation { + pub(crate) peer: Peer, + pub(crate) last_synced: OffsetDateTime, + + pub(crate) bytes_delta_since_sync: u64, +} + +impl PeerInformation { + pub fn new(peer: Peer) -> PeerInformation { + PeerInformation { + peer, + last_synced: OffsetDateTime::now_utc(), + bytes_delta_since_sync: 0, + } + } + + pub(crate) fn should_sync(&self, cfg: PeerFlushingBehaviourConfig) -> bool { + if self.bytes_delta_since_sync >= cfg.peer_max_delta_flushing_amount { + return true; + } + + if self.last_synced + cfg.peer_max_flushing_rate < OffsetDateTime::now_utc() + && self.bytes_delta_since_sync != 0 + { + return true; + } + + false + } + + pub(crate) fn peer(&self) -> &Peer { + &self.peer + } + + pub(crate) fn update_trx_bytes(&mut self, tx_bytes: u64, rx_bytes: u64) { + self.bytes_delta_since_sync += tx_bytes.saturating_sub(self.peer.tx_bytes) + + rx_bytes.saturating_sub(self.peer.rx_bytes); + self.peer.tx_bytes = tx_bytes; + self.peer.rx_bytes = rx_bytes; + } + + pub(crate) fn resync_peer_with_storage(&mut self) { + self.bytes_delta_since_sync = 0; + self.last_synced = OffsetDateTime::now_utc(); + } +} From 96180275f8602defe50251202bae02cda8bd9c15 Mon Sep 17 00:00:00 2001 From: benedetta davico <46782255+benedettadavico@users.noreply.github.com> Date: Fri, 29 Nov 2024 13:57:57 +0100 Subject: [PATCH 17/22] Update Cargo.toml --- nym-node/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 07c8b33476..d687b7825b 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.1.11" +version = "1.1.12" authors.workspace = true repository.workspace = true homepage.workspace = true From 4a9a5579c40ad956163ea02e01d7b53aef2ac8ef Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Fri, 29 Nov 2024 14:06:32 +0100 Subject: [PATCH 18/22] update changelog --- CHANGELOG.md | 4 ++++ Cargo.lock | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46cb35531e..bda75be82a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2024.13-magura-drift] (2024-11-29) + +- Optimised syncing bandwidth information to storage + ## [2024.13-magura-patched] (2024-11-22) - [experimental] allow clients to change between deterministic route selection based on packet headers and a pseudorandom distribution diff --git a/Cargo.lock b/Cargo.lock index 6557b2c99f..8c183a73f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5880,7 +5880,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.1.11" +version = "1.1.12" dependencies = [ "anyhow", "bip39", From 074d705448f746c8f8dc2e5e519ea1c5d27bf755 Mon Sep 17 00:00:00 2001 From: import this <97586125+serinko@users.noreply.github.com> Date: Fri, 29 Nov 2024 13:34:58 +0000 Subject: [PATCH 19/22] [DOCs/operators]: Magura-drift - second patch (#5194) * syntax edits * new version harsh * changelog info - ready to review --- .../docs/pages/operators/changelog.mdx | 25 +++++++++++++++++-- .../nodes/maintenance/manual-upgrade.mdx | 2 +- .../operators/nodes/nym-node/bonding.mdx | 2 +- .../pages/operators/nodes/nym-node/setup.mdx | 10 ++++---- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index b498b5156c..06ac73d2dc 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -34,6 +34,28 @@ This page displays a full list of all the changes during our release cycle from +## `magura-drift` + +Second patch to `v2024.13-magura` release version. + +- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.13-magura-drift) +- [`nym-node`](nodes/nym-node.mdx) version `1.1.12` + +```sh +nym-node +Binary Name: nym-node +Build Timestamp: 2024-11-29T13:10:51.813092288Z +Build Version: 1.1.12 +Commit SHA: 4a9a5579c40ad956163ea02e01d7b53aef2ac8ef +Commit Date: 2024-11-29T14:06:32.000000000+01:00 +Commit Branch: HEAD +rustc Version: 1.83.0 +rustc Channel: stable +cargo Profile: release +``` + +- This patch adds a peer storage manager to fix issues causing external clients to be blocked, ensuring they can successfully connect to different nodes. + ## `v2024.13-magura-patched` - [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.13-magura-patched) @@ -54,12 +76,11 @@ cargo Profile: release -After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Insstead of fixing this outdated explorer, we are working on a new one, coming out soon. +After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Instead of fixing this outdated explorer, we are working on a new one, coming out soon. [Nym Harbourmaster](https://harbourmaster.nymtech.net) has cache of 90min, expect your values to be updated with delay. We are aware of some issues with Nym Harbourmaster and working hard to resolve them in the upcoming explorer v2. To check your routing values in real time, you can use [`nym-gateway-probe`](nodes/performance-and-testing/gateway-probe). - ### Operators Updates & Tools - Updated [`network_tunnel_manager.sh`](https://github.com/nymtech/nym/blob/develop/scripts/network_tunnel_manager.sh) (moved to our monorepo) helps operators to configure their IP tables rules for `nymtun` and `wireguard` routing. diff --git a/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx b/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx index a68d01b1fd..8b3db6b61b 100644 --- a/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx +++ b/documentation/docs/pages/operators/nodes/maintenance/manual-upgrade.mdx @@ -53,7 +53,7 @@ journalctl -f -u nym-node.service -After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Insstead of fixing this outdated explorer, we are working on a new one, coming out soon. +After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Instead of fixing this outdated explorer, we are working on a new one, coming out soon. [Nym Harbourmaster](https://harbourmaster.nymtech.net) has cache of 90min, expect your values to be updated with delay. We are aware of some issues with Nym Harbourmaster and working hard to resolve them in the upcoming explorer v2. To check your routing values in real time, you can use [`nym-gateway-probe`](../performance-and-testing/gateway-probe). diff --git a/documentation/docs/pages/operators/nodes/nym-node/bonding.mdx b/documentation/docs/pages/operators/nodes/nym-node/bonding.mdx index 44a46ac714..3c64fe9b29 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/bonding.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/bonding.mdx @@ -120,7 +120,7 @@ From `nym-wallet` version `1.2.15` onward the application allows and prompts ope ###### 2. Verify the binary and extract it if needed -- Download [`hashes.json`]https://github.com/nymtech/nym/releases/download/nym-wallet-v1.2.15/hashes.json +- Download [`hashes.json`](https://github.com/nymtech/nym/releases/download/nym-wallet-v1.2.15/hashes.json) - Open it with your text editor or print it's content with `cat hashes.json` - Run `sha256sum ` for example `sha256sum ./nym-wallet_1.2.15_amd64.AppImage` - If your have to extract it (like `.tar.gz`) do it diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index 31eb89308c..f2d6197fee 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -17,12 +17,12 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](. ```sh nym-node Binary Name: nym-node -Build Timestamp: 2024-11-22T14:30:48.067329245Z -Build Version: 1.1.11 -Commit SHA: 01c7b2819ee3d328deccd303b4113ff415d7e276 -Commit Date: 2024-11-22T10:50:59.000000000+01:00 +Build Timestamp: 2024-11-29T13:10:51.813092288Z +Build Version: 1.1.12 +Commit SHA: 4a9a5579c40ad956163ea02e01d7b53aef2ac8ef +Commit Date: 2024-11-29T14:06:32.000000000+01:00 Commit Branch: HEAD -rustc Version: 1.82.0 +rustc Version: 1.83.0 rustc Channel: stable cargo Profile: release ``` From a9e62889c3e7ce7c6810feff31c91f1824af42e0 Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:45:55 +0100 Subject: [PATCH 20/22] Remove explorer dependency (#5190) * Move monitor code to a struct - to store state in a struct * explorer deprecation wip * Replace explorer with ipinfo calls * PR feedback * Fix clippy * Bump package version * Remove ipinfo crate due to openssl dep * Add remaining bandwidth log --- Cargo.lock | 6 +- .../src/client/topology_control/accessor.rs | 2 +- .../validator-client/src/nyxd/coin.rs | 2 +- .../src/nyxd/fee/gas_price.rs | 2 +- .../mixnet-contract/src/interval.rs | 2 +- common/dkg/src/bte/proof_chunking.rs | 3 +- common/dkg/src/interpolation/polynomial.rs | 4 +- .../src/registration/handshake/mod.rs | 2 +- .../src/scheme/coin_indices_signatures.rs | 12 - .../src/scheme/keygen.rs | 2 +- .../src/scheme/mod.rs | 2 +- common/nymcoconut/src/elgamal.rs | 2 +- common/nymcoconut/src/scheme/keygen.rs | 2 +- common/nymsphinx/addressing/src/clients.rs | 2 +- common/nymsphinx/addressing/src/nodes.rs | 14 +- .../anonymous-replies/src/reply_surb.rs | 2 +- common/socks5/requests/src/request.rs | 13 - explorer-api/src/geo_ip/location.rs | 2 +- explorer-api/src/guards/location.rs | 2 +- nym-api/nym-api-requests/src/helpers.rs | 2 +- nym-api/src/ecash/tests/helpers.rs | 2 +- .../monitor/gateway_clients_cache.rs | 2 +- .../src/http/state/mod.rs | 2 +- .../nym-node-status-api/Cargo.toml | 2 +- .../nym-node-status-api/src/cli/mod.rs | 13 +- .../nym-node-status-api/src/db/models.rs | 3 +- .../src/db/queries/summary.rs | 12 +- .../nym-node-status-api/src/main.rs | 6 +- .../src/monitor/geodata.rs | 112 +++ .../nym-node-status-api/src/monitor/mod.rs | 701 +++++++++--------- sdk/ffi/go/src/lib.rs | 3 + .../dkg-bypass-contract/src/contract.rs | 4 +- .../testnet-manager/src/manager/dkg_skip.rs | 6 +- .../testnet-manager/src/manager/local_apis.rs | 2 +- .../src/manager/local_client.rs | 2 +- .../src/manager/local_nodes.rs | 2 +- 36 files changed, 539 insertions(+), 415 deletions(-) create mode 100644 nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs diff --git a/Cargo.lock b/Cargo.lock index ca7a56a4d2..936dadfbd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/common/client-core/src/client/topology_control/accessor.rs b/common/client-core/src/client/topology_control/accessor.rs index fcb272c934..6b12d64562 100644 --- a/common/client-core/src/client/topology_control/accessor.rs +++ b/common/client-core/src/client/topology_control/accessor.rs @@ -38,7 +38,7 @@ pub struct TopologyReadPermit<'a> { permit: RwLockReadGuard<'a, Option>, } -impl<'a> Deref for TopologyReadPermit<'a> { +impl Deref for TopologyReadPermit<'_> { type Target = Option; fn deref(&self) -> &Self::Target { diff --git a/common/client-libs/validator-client/src/nyxd/coin.rs b/common/client-libs/validator-client/src/nyxd/coin.rs index 0fa175a243..8050273a51 100644 --- a/common/client-libs/validator-client/src/nyxd/coin.rs +++ b/common/client-libs/validator-client/src/nyxd/coin.rs @@ -32,7 +32,7 @@ impl Div for Coin { } } -impl<'a> Div for &'a Coin { +impl Div for &Coin { type Output = Gas; fn div(self, rhs: GasPrice) -> Self::Output { diff --git a/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs b/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs index 4f0f2fd0a8..3679cc7500 100644 --- a/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs +++ b/common/client-libs/validator-client/src/nyxd/fee/gas_price.rs @@ -22,7 +22,7 @@ pub struct GasPrice { pub denom: String, } -impl<'a> Mul for &'a GasPrice { +impl Mul for &GasPrice { type Output = Coin; fn mul(self, gas_limit: Gas) -> Self::Output { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs index e36935d66c..2259af4c7a 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs @@ -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 { diff --git a/common/dkg/src/bte/proof_chunking.rs b/common/dkg/src/bte/proof_chunking.rs index 6829a53660..1365aecbd7 100644 --- a/common/dkg/src/bte/proof_chunking.rs +++ b/common/dkg/src/bte/proof_chunking.rs @@ -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>>; diff --git a/common/dkg/src/interpolation/polynomial.rs b/common/dkg/src/interpolation/polynomial.rs index 33c7486a99..08f748b649 100644 --- a/common/dkg/src/interpolation/polynomial.rs +++ b/common/dkg/src/interpolation/polynomial.rs @@ -196,7 +196,7 @@ impl<'b> Add<&'b Polynomial> for Polynomial { } } -impl<'a> Add for &'a Polynomial { +impl Add for &Polynomial { type Output = Polynomial; fn add(self, rhs: Polynomial) -> Polynomial { @@ -212,7 +212,7 @@ impl Add 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 { diff --git a/common/gateway-requests/src/registration/handshake/mod.rs b/common/gateway-requests/src/registration/handshake/mod.rs index e5dc1dc59f..4a6f44b7d7 100644 --- a/common/gateway-requests/src/registration/handshake/mod.rs +++ b/common/gateway-requests/src/registration/handshake/mod.rs @@ -37,7 +37,7 @@ pub struct GatewayHandshake<'a> { handshake_future: BoxFuture<'a, Result>, } -impl<'a> Future for GatewayHandshake<'a> { +impl Future for GatewayHandshake<'_> { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { diff --git a/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs b/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs index b08ab368dc..30c7fe5523 100644 --- a/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs +++ b/common/nym_offline_compact_ecash/src/scheme/coin_indices_signatures.rs @@ -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::*; diff --git a/common/nym_offline_compact_ecash/src/scheme/keygen.rs b/common/nym_offline_compact_ecash/src/scheme/keygen.rs index e49c6309df..8db1597e61 100644 --- a/common/nym_offline_compact_ecash/src/scheme/keygen.rs +++ b/common/nym_offline_compact_ecash/src/scheme/keygen.rs @@ -264,7 +264,7 @@ impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth { } } -impl<'a> Mul for &'a VerificationKeyAuth { +impl Mul for &VerificationKeyAuth { type Output = VerificationKeyAuth; #[inline] diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index 8912bf91eb..7f9687a380 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -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 { let ss_len = self.inner.len(); let mut bytes: Vec = Vec::with_capacity(ss_len * 48); diff --git a/common/nymcoconut/src/elgamal.rs b/common/nymcoconut/src/elgamal.rs index 55db7dcdd2..28943568ce 100644 --- a/common/nymcoconut/src/elgamal.rs +++ b/common/nymcoconut/src/elgamal.rs @@ -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 { diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs index 85e057793d..f80c131143 100644 --- a/common/nymcoconut/src/scheme/keygen.rs +++ b/common/nymcoconut/src/scheme/keygen.rs @@ -305,7 +305,7 @@ impl<'b> Add<&'b VerificationKey> for VerificationKey { } } -impl<'a> Mul for &'a VerificationKey { +impl Mul for &VerificationKey { type Output = VerificationKey; #[inline] diff --git a/common/nymsphinx/addressing/src/clients.rs b/common/nymsphinx/addressing/src/clients.rs index a739e7369e..743302edbe 100644 --- a/common/nymsphinx/addressing/src/clients.rs +++ b/common/nymsphinx/addressing/src/clients.rs @@ -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 { diff --git a/common/nymsphinx/addressing/src/nodes.rs b/common/nymsphinx/addressing/src/nodes.rs index e0cd745ae9..1126cb7afd 100644 --- a/common/nymsphinx/addressing/src/nodes.rs +++ b/common/nymsphinx/addressing/src/nodes.rs @@ -1,6 +1,13 @@ // Copyright 2021 - Nym Technologies SA // 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; diff --git a/common/nymsphinx/anonymous-replies/src/reply_surb.rs b/common/nymsphinx/anonymous-replies/src/reply_surb.rs index deac5f532d..f25871ba0b 100644 --- a/common/nymsphinx/anonymous-replies/src/reply_surb.rs +++ b/common/nymsphinx/anonymous-replies/src/reply_surb.rs @@ -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 { diff --git a/common/socks5/requests/src/request.rs b/common/socks5/requests/src/request.rs index 23e844db87..59163daf55 100644 --- a/common/socks5/requests/src/request.rs +++ b/common/socks5/requests/src/request.rs @@ -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 || // // send: // RequestFlag::Send || CONN_ID || LOCAL_CLOSED || DATA // where DATA: SEQ || TRUE_DATA - pub fn try_from_bytes(b: &[u8]) -> Result { // each request needs to at least contain flag and ConnectionId if b.is_empty() { diff --git a/explorer-api/src/geo_ip/location.rs b/explorer-api/src/geo_ip/location.rs index 59bbb33ad7..263764943e 100644 --- a/explorer-api/src/geo_ip/location.rs +++ b/explorer-api/src/geo_ip/location.rs @@ -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 { diff --git a/explorer-api/src/guards/location.rs b/explorer-api/src/guards/location.rs index 045b03f5b4..f5ce0b7ee2 100644 --- a/explorer-api/src/guards/location.rs +++ b/explorer-api/src/guards/location.rs @@ -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, diff --git a/nym-api/nym-api-requests/src/helpers.rs b/nym-api/nym-api-requests/src/helpers.rs index 651030fc74..292ffb69f4 100644 --- a/nym-api/nym-api-requests/src/helpers.rs +++ b/nym-api/nym-api-requests/src/helpers.rs @@ -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 { diff --git a/nym-api/src/ecash/tests/helpers.rs b/nym-api/src/ecash/tests/helpers.rs index 5cd410056f..73fac38606 100644 --- a/nym-api/src/ecash/tests/helpers.rs +++ b/nym-api/src/ecash/tests/helpers.rs @@ -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 }; diff --git a/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs index 4a321d57cd..c791f9a6af 100644 --- a/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs +++ b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs @@ -59,7 +59,7 @@ impl GatewayClientHandle { } } -impl<'a> UnlockedGatewayClientHandle<'a> { +impl UnlockedGatewayClientHandle<'_> { pub(crate) fn get_mut_unchecked( &mut self, ) -> &mut GatewayClient { diff --git a/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs b/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs index c4c4a8ebe3..bcc62f9c7e 100644 --- a/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs +++ b/nym-credential-proxy/nym-credential-proxy/src/http/state/mod.rs @@ -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 { diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index 0dfb0caffb..05d1586ae9 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -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 diff --git a/nym-node-status-api/nym-node-status-api/src/cli/mod.rs b/nym-node-status-api/nym-node-status-api/src/cli/mod.rs index f329e4a9a0..e9d76a1f75 100644 --- a/nym-node-status-api/nym-node-status-api/src/cli/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/cli/mod.rs @@ -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, + /// https://github.com/ipinfo/rust + #[clap(long, env = "IPINFO_API_TOKEN")] + pub(crate) ipinfo_api_token: String, + #[clap( long, default_value_t = 40, diff --git a/nym-node-status-api/nym-node-status-api/src/db/models.rs b/nym-node-status-api/nym-node-status-api/src/db/models.rs index 596f634f2e..7d9e93ed24 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/models.rs @@ -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, 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)] diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs index 103712a9a4..4b2ecd22a5 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs @@ -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) -> HttpResult 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) -> HttpResult // 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) -> HttpResult .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) -> HttpResult 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), - }, }, }) } diff --git a/nym-node-status-api/nym-node-status-api/src/main.rs b/nym-node-status-api/nym-node-status-api/src/main.rs index 6fcdf34473..6274a01fe5 100644 --- a/nym-node-status-api/nym-node-status-api/src/main.rs +++ b/nym-node-status-api/nym-node-status-api/src/main.rs @@ -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"); diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs b/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs new file mode 100644 index 0000000000..1042b15cce --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/monitor/geodata.rs @@ -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) -> Self { + let client = reqwest::Client::new(); + let token = token.into(); + + Self { client, token } + } + + pub(crate) async fn locate_ip(&self, ip: impl AsRef) -> anyhow::Result { + 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 { + 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, + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs index 7b6c85b6d5..459992b0b3 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs @@ -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, +} + // 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::>(); - 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::>(); + 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::>(); - tracing::debug!("Of those, {} mixnodes", mixnodes.len()); + let mixnodes = all_nodes + .iter() + .filter(|node| node.description.declared_role.mixnode) + .collect::>(); + 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::>(); - 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::>(); + } - // 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::>(); - 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 = 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, - explorer_gateways: Vec, - skimmed_gateways: Vec, -) -> anyhow::Result> { - 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 = 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, - delegation_program_members: Vec, -) -> anyhow::Result> { - 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, + gateway_geodata: Vec, + skimmed_gateways: Vec, + ) -> anyhow::Result> { + 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::>(); - let explorer_only = explorer - .iter() - .filter(|gw| !api_gateways.contains(&gw.gateway.identity_key.to_string())) - .collect::>(); + 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, + delegation_program_members: Vec, + ) -> anyhow::Result> { + 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); + } + } } } diff --git a/sdk/ffi/go/src/lib.rs b/sdk/ffi/go/src/lib.rs index a5c930a2ec..a0a690c691 100644 --- a/sdk/ffi/go/src/lib.rs +++ b/sdk/ffi/go/src/lib.rs @@ -1,6 +1,9 @@ // Copyright 2023-2024 - Nym Technologies SA // 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"); diff --git a/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs b/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs index c04fae8ef2..df70e604f4 100644 --- a/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs +++ b/tools/internal/testnet-manager/dkg-bypass-contract/src/contract.rs @@ -33,7 +33,7 @@ pub(crate) struct VkShareIndex<'a> { pub(crate) epoch_id: MultiIndex<'a, EpochId, ContractVKShare, VKShareKey<'a>>, } -impl<'a> IndexList for VkShareIndex<'a> { +impl IndexList for VkShareIndex<'_> { fn get_indexes(&'_ self) -> Box> + '_> { let v: Vec<&dyn Index> = vec![&self.epoch_id]; Box::new(v.into_iter()) @@ -87,7 +87,7 @@ pub fn query(_: Deps<'_>, _: Env, _: EmptyMessage) -> Result, env: Env, msg: MigrateMsg) -> Result { // 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); diff --git a/tools/internal/testnet-manager/src/manager/dkg_skip.rs b/tools/internal/testnet-manager/src/manager/dkg_skip.rs index a132ea6950..7720202665 100644 --- a/tools/internal/testnet-manager/src/manager/dkg_skip.rs +++ b/tools/internal/testnet-manager/src/manager/dkg_skip.rs @@ -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, } -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)?; diff --git a/tools/internal/testnet-manager/src/manager/local_apis.rs b/tools/internal/testnet-manager/src/manager/local_apis.rs index 631f6117f2..124b2a324f 100644 --- a/tools/internal/testnet-manager/src/manager/local_apis.rs +++ b/tools/internal/testnet-manager/src/manager/local_apis.rs @@ -24,7 +24,7 @@ struct LocalApisCtx<'a> { signers: Vec, } -impl<'a> ProgressCtx for LocalApisCtx<'a> { +impl ProgressCtx for LocalApisCtx<'_> { fn progress_tracker(&self) -> &ProgressTracker { &self.progress } diff --git a/tools/internal/testnet-manager/src/manager/local_client.rs b/tools/internal/testnet-manager/src/manager/local_client.rs index c037f53719..fac5e6828a 100644 --- a/tools/internal/testnet-manager/src/manager/local_client.rs +++ b/tools/internal/testnet-manager/src/manager/local_client.rs @@ -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 } diff --git a/tools/internal/testnet-manager/src/manager/local_nodes.rs b/tools/internal/testnet-manager/src/manager/local_nodes.rs index dc19d44436..75edc76b7d 100644 --- a/tools/internal/testnet-manager/src/manager/local_nodes.rs +++ b/tools/internal/testnet-manager/src/manager/local_nodes.rs @@ -30,7 +30,7 @@ struct LocalNodesCtx<'a> { gateways: Vec, } -impl<'a> ProgressCtx for LocalNodesCtx<'a> { +impl ProgressCtx for LocalNodesCtx<'_> { fn progress_tracker(&self) -> &ProgressTracker { &self.progress } From 4851614375ea8b8eb55cb60885ecfcc33172affb Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Mon, 2 Dec 2024 12:15:30 +0100 Subject: [PATCH 21/22] NS API - Gateway stats scraping (#5180) * squashed commit before rebasing * removed blank lines --- Cargo.lock | 3 + common/gateway-stats-storage/Cargo.toml | 1 + common/gateway-stats-storage/src/lib.rs | 3 +- common/gateway-stats-storage/src/models.rs | 37 +-- common/statistics/src/gateways.rs | 36 +++ .../nym-node-status-api/Cargo.toml | 4 +- .../migrations/002_session_stats.sql | 17 ++ .../nym-node-status-api/src/db/models.rs | 43 +++ .../src/db/queries/gateways_stats.rs | 75 +++++ .../nym-node-status-api/src/db/queries/mod.rs | 3 + .../src/http/api/metrics/mod.rs | 10 + .../src/http/api/metrics/sessions.rs | 83 ++++++ .../nym-node-status-api/src/http/api/mod.rs | 4 +- .../nym-node-status-api/src/http/api_docs.rs | 2 +- .../nym-node-status-api/src/http/mod.rs | 3 +- .../nym-node-status-api/src/http/models.rs | 13 + .../nym-node-status-api/src/http/state.rs | 43 +++ .../nym-node-status-api/src/main.rs | 7 + .../src/node_scraper/error.rs | 18 ++ .../src/node_scraper/mod.rs | 279 ++++++++++++++++++ nym-node/nym-node-requests/src/api/client.rs | 6 + .../src/api/v1/metrics/models.rs | 1 + 22 files changed, 650 insertions(+), 41 deletions(-) create mode 100644 nym-node-status-api/nym-node-status-api/migrations/002_session_stats.sql create mode 100644 nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/node_scraper/error.rs create mode 100644 nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 936dadfbd6..cd9744933a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5536,6 +5536,7 @@ version = "0.1.0" dependencies = [ "nym-credentials-interface", "nym-sphinx", + "nym-statistics-common", "sqlx", "thiserror", "time", @@ -6077,6 +6078,7 @@ dependencies = [ "nym-network-defaults", "nym-node-requests", "nym-node-status-client", + "nym-statistics-common", "nym-task", "nym-validator-client", "regex", @@ -6088,6 +6090,7 @@ dependencies = [ "strum 0.26.3", "strum_macros 0.26.4", "thiserror", + "time", "tokio", "tokio-util", "tower-http", diff --git a/common/gateway-stats-storage/Cargo.toml b/common/gateway-stats-storage/Cargo.toml index d439b34a16..1e816e0233 100644 --- a/common/gateway-stats-storage/Cargo.toml +++ b/common/gateway-stats-storage/Cargo.toml @@ -22,6 +22,7 @@ tracing = { workspace = true } nym-sphinx = { path = "../nymsphinx" } nym-credentials-interface = { path = "../credentials-interface" } +nym-statistics-common = { path = "../statistics" } [build-dependencies] diff --git a/common/gateway-stats-storage/src/lib.rs b/common/gateway-stats-storage/src/lib.rs index 453258b606..3f94b40a87 100644 --- a/common/gateway-stats-storage/src/lib.rs +++ b/common/gateway-stats-storage/src/lib.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only use error::StatsStorageError; -use models::{ActiveSession, FinishedSession, SessionType, StoredFinishedSession}; +use models::{ActiveSession, FinishedSession, StoredFinishedSession}; use nym_sphinx::DestinationAddressBytes; +use nym_statistics_common::gateways::SessionType; use sessions::SessionManager; use sqlx::ConnectOptions; use std::path::Path; diff --git a/common/gateway-stats-storage/src/models.rs b/common/gateway-stats-storage/src/models.rs index 6f53cf429a..f35883d28e 100644 --- a/common/gateway-stats-storage/src/models.rs +++ b/common/gateway-stats-storage/src/models.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use nym_credentials_interface::TicketType; +use nym_statistics_common::gateways::SessionType; use sqlx::prelude::FromRow; use time::{Duration, OffsetDateTime}; @@ -25,42 +26,6 @@ pub struct FinishedSession { pub typ: SessionType, } -#[derive(PartialEq)] -pub enum SessionType { - Vpn, - Mixnet, - Unknown, -} - -impl SessionType { - pub fn to_string(&self) -> &str { - match self { - Self::Vpn => "vpn", - Self::Mixnet => "mixnet", - Self::Unknown => "unknown", - } - } - - pub fn from_string(s: &str) -> Self { - match s { - "vpn" => Self::Vpn, - "mixnet" => Self::Mixnet, - _ => Self::Unknown, - } - } -} - -impl From for SessionType { - fn from(value: TicketType) -> Self { - match value { - TicketType::V1MixnetEntry => Self::Mixnet, - TicketType::V1MixnetExit => Self::Mixnet, - TicketType::V1WireguardEntry => Self::Vpn, - TicketType::V1WireguardExit => Self::Vpn, - } - } -} - #[derive(FromRow)] pub(crate) struct StoredActiveSession { start_time: OffsetDateTime, diff --git a/common/statistics/src/gateways.rs b/common/statistics/src/gateways.rs index 398fda559a..97d75334f1 100644 --- a/common/statistics/src/gateways.rs +++ b/common/statistics/src/gateways.rs @@ -87,3 +87,39 @@ pub enum SessionEvent { client: DestinationAddressBytes, }, } + +#[derive(PartialEq)] +pub enum SessionType { + Vpn, + Mixnet, + Unknown, +} + +impl SessionType { + pub fn to_string(&self) -> &str { + match self { + Self::Vpn => "vpn", + Self::Mixnet => "mixnet", + Self::Unknown => "unknown", + } + } + + pub fn from_string(s: &str) -> Self { + match s { + "vpn" => Self::Vpn, + "mixnet" => Self::Mixnet, + _ => Self::Unknown, + } + } +} + +impl From for SessionType { + fn from(value: TicketType) -> Self { + match value { + TicketType::V1MixnetEntry => Self::Mixnet, + TicketType::V1MixnetExit => Self::Mixnet, + TicketType::V1WireguardEntry => Self::Vpn, + TicketType::V1WireguardExit => Self::Vpn, + } + } +} diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index 05d1586ae9..a915d31555 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -26,6 +26,7 @@ nym-node-status-client = { path = "../nym-node-status-client" } nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } nym-explorer-client = { path = "../../explorer-api/explorer-client" } nym-network-defaults = { path = "../../common/network-defaults" } +nym-statistics-common = { path = "../../common/statistics"} nym-validator-client = { path = "../../common/client-libs/validator-client" } nym-task = { path = "../../common/task" } nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] } @@ -36,8 +37,9 @@ serde_json = { workspace = true } serde_json_path = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } -sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite"] } +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "time"] } thiserror = { workspace = true } +time = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"] } tokio-util = { workspace = true } tracing = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-api/migrations/002_session_stats.sql b/nym-node-status-api/nym-node-status-api/migrations/002_session_stats.sql new file mode 100644 index 0000000000..d29fe986a3 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations/002_session_stats.sql @@ -0,0 +1,17 @@ + +CREATE TABLE gateway_session_stats ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + gateway_identity_key VARCHAR NOT NULL, + node_id INTEGER NOT NULL, + day DATE NOT NULL, + unique_active_clients INTEGER NOT NULL, + session_started INTEGER NOT NULL, + users_hashes VARCHAR, + vpn_sessions VARCHAR, + mixnet_sessions VARCHAR, + unknown_sessions VARCHAR, + UNIQUE (node_id, day) -- This constraint automatically creates an index + ); +CREATE INDEX idx_gateway_session_stats_identity_key ON gateway_session_stats (gateway_identity_key); +CREATE INDEX idx_gateway_session_stats_day ON gateway_session_stats (day); + diff --git a/nym-node-status-api/nym-node-status-api/src/db/models.rs b/nym-node-status-api/nym-node-status-api/src/db/models.rs index 7d9e93ed24..9de09271bf 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/models.rs @@ -4,7 +4,9 @@ use crate::{ }; use nym_node_requests::api::v1::node::models::NodeDescription; use serde::{Deserialize, Serialize}; +use sqlx::FromRow; use strum_macros::{EnumString, FromRepr}; +use time::Date; use utoipa::ToSchema; pub(crate) struct GatewayRecord { @@ -333,3 +335,44 @@ pub struct GatewayInfoDto { pub self_described: Option, pub explorer_pretty_bond: Option, } + +#[derive(Debug, Clone, FromRow)] +pub struct GatewaySessionsRecord { + pub gateway_identity_key: String, + pub node_id: i64, + pub day: Date, + pub unique_active_clients: i64, + pub session_started: i64, + pub users_hashes: Option, + pub vpn_sessions: Option, + pub mixnet_sessions: Option, + pub unknown_sessions: Option, +} + +impl TryFrom for http::models::SessionStats { + type Error = anyhow::Error; + + fn try_from(value: GatewaySessionsRecord) -> Result { + let users_hashes = value.users_hashes.clone().unwrap_or("null".to_string()); + let vpn_sessions = value.vpn_sessions.clone().unwrap_or("null".to_string()); + let mixnet_sessions = value.mixnet_sessions.clone().unwrap_or("null".to_string()); + let unknown_sessions = value.unknown_sessions.clone().unwrap_or("null".to_string()); + + let users_hashes = serde_json::from_str(&users_hashes).unwrap_or(None); + let vpn_sessions = serde_json::from_str(&vpn_sessions).unwrap_or(None); + let mixnet_sessions = serde_json::from_str(&mixnet_sessions).unwrap_or(None); + let unknown_sessions = serde_json::from_str(&unknown_sessions).unwrap_or(None); + + Ok(http::models::SessionStats { + gateway_identity_key: value.gateway_identity_key.clone(), + node_id: value.node_id as u32, + day: value.day, + unique_active_clients: value.unique_active_clients, + session_started: value.session_started, + users_hashes, + vpn_sessions, + mixnet_sessions, + unknown_sessions, + }) + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs new file mode 100644 index 0000000000..8ba813ee1f --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs @@ -0,0 +1,75 @@ +use crate::{ + db::{models::GatewaySessionsRecord, DbPool}, + http::models::SessionStats, +}; +use futures_util::TryStreamExt; +use time::Date; +use tracing::error; + +pub(crate) async fn insert_session_records( + pool: &DbPool, + records: Vec, +) -> anyhow::Result<()> { + let mut tx = pool.begin().await?; + for record in records { + sqlx::query!( + "INSERT OR IGNORE INTO gateway_session_stats + (gateway_identity_key, node_id, day, + unique_active_clients, session_started, users_hashes, + vpn_sessions, mixnet_sessions, unknown_sessions) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", + record.gateway_identity_key, + record.node_id, + record.day, + record.unique_active_clients, + record.session_started, + record.users_hashes, + record.vpn_sessions, + record.mixnet_sessions, + record.unknown_sessions, + ) + .execute(&mut *tx) + .await?; + } + tx.commit().await?; + + Ok(()) +} + +pub(crate) async fn get_sessions_stats(pool: &DbPool) -> anyhow::Result> { + let mut conn = pool.acquire().await?; + let items = sqlx::query_as( + "SELECT gateway_identity_key, + node_id, + day, + unique_active_clients, + session_started, + users_hashes, + vpn_sessions, + mixnet_sessions, + unknown_sessions + FROM gateway_session_stats", + ) + .fetch(&mut *conn) + .try_collect::>() + .await?; + + let items: Vec = items + .into_iter() + .map(|item| item.try_into()) + .collect::>>() + .map_err(|e| { + error!("Conversion from database failed: {e}. Invalidly stored data?"); + e + })?; + + Ok(items) +} + +pub(crate) async fn delete_old_records(pool: &DbPool, cut_off: Date) -> anyhow::Result<()> { + let mut conn = pool.acquire().await?; + sqlx::query!("DELETE FROM gateway_session_stats WHERE day <= ?", cut_off) + .execute(&mut *conn) + .await?; + Ok(()) +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs index fe22ec27aa..8c87ab2fc0 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs @@ -1,4 +1,5 @@ mod gateways; +mod gateways_stats; mod misc; mod mixnodes; mod summary; @@ -13,3 +14,5 @@ pub(crate) use mixnodes::{ ensure_mixnodes_still_bonded, get_all_mixnodes, get_daily_stats, insert_mixnodes, }; pub(crate) use summary::{get_summary, get_summary_history}; + +pub(crate) use gateways_stats::{delete_old_records, get_sessions_stats, insert_session_records}; diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs new file mode 100644 index 0000000000..8703f92830 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs @@ -0,0 +1,10 @@ +use axum::Router; + +use crate::http::state::AppState; + +pub(crate) mod sessions; + +pub(crate) fn routes() -> Router { + Router::new().nest("/sessions", sessions::routes()) + //eventually add other metrics type +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs new file mode 100644 index 0000000000..e2e4cd9ced --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/sessions.rs @@ -0,0 +1,83 @@ +use axum::{ + extract::{Query, State}, + Json, Router, +}; +use time::Date; +use tracing::instrument; + +use crate::http::{ + error::{HttpError, HttpResult}, + models::SessionStats, + state::AppState, + PagedResult, Pagination, +}; + +pub(crate) fn routes() -> Router { + Router::new().route("/", axum::routing::get(get_all_sessions)) + // .route("/:node_id", axum::routing::get(get_node_sessions)) + // .route("/:day", axum::routing::get(get_daily_sessions)) +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::IntoParams)] +#[into_params(parameter_in = Query)] +pub(crate) struct SessionQueryParams { + size: Option, + page: Option, + node_id: Option, + day: Option, +} + +#[utoipa::path( + tag = "Sessions", + get, + params( + SessionQueryParams + ), + path = "/v2/metrics/sessions", + responses( + (status = 200, body = PagedSessionStats) + ) +)] +#[instrument(level = tracing::Level::DEBUG, skip(state))] +async fn get_all_sessions( + Query(SessionQueryParams { + size, + page, + node_id, + day, + }): Query, + State(state): State, +) -> HttpResult>> { + let db = state.db_pool(); + let res = state.cache().get_sessions_stats(db).await; + + let day_filtered = if let Some(day) = day { + if let Ok(parsed_day) = + Date::parse(&day, &time::format_description::well_known::Iso8601::DATE) + { + res.into_iter().filter(|s| s.day == parsed_day).collect() + } else { + return Err(HttpError::invalid_input(day)); + } + } else { + res + }; + + let day_and_node_filtered = if let Some(node_id) = node_id { + if let Ok(parsed_node_id) = node_id.parse::() { + day_filtered + .into_iter() + .filter(|s| s.node_id == parsed_node_id) + .collect() + } else { + return Err(HttpError::invalid_input(node_id)); + } + } else { + day_filtered + }; + + Ok(Json(PagedResult::paginate( + Pagination { size, page }, + day_and_node_filtered, + ))) +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs index ed24fa80f5..6483a44519 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs @@ -8,6 +8,7 @@ use utoipa_swagger_ui::SwaggerUi; use crate::http::{server::HttpServer, state::AppState}; pub(crate) mod gateways; +pub(crate) mod metrics; pub(crate) mod mixnodes; pub(crate) mod services; pub(crate) mod summary; @@ -34,7 +35,8 @@ impl RouterBuilder { .nest("/gateways", gateways::routes()) .nest("/mixnodes", mixnodes::routes()) .nest("/services", services::routes()) - .nest("/summary", summary::routes()), + .nest("/summary", summary::routes()) + .nest("/metrics", metrics::routes()), ) .nest( "/internal", diff --git a/nym-node-status-api/nym-node-status-api/src/http/api_docs.rs b/nym-node-status-api/nym-node-status-api/src/http/api_docs.rs index aa86a56ab0..fec4d25cd5 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api_docs.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api_docs.rs @@ -1,4 +1,4 @@ -use crate::http::{Gateway, GatewaySkinny, Mixnode, Service}; +use crate::http::{Gateway, GatewaySkinny, Mixnode, Service, SessionStats}; use utoipa::OpenApi; use utoipauto::utoipauto; diff --git a/nym-node-status-api/nym-node-status-api/src/http/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/mod.rs index 1cc317337f..b1a7bf742d 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/mod.rs @@ -1,4 +1,4 @@ -use models::{Gateway, GatewaySkinny, Mixnode, Service}; +use models::{Gateway, GatewaySkinny, Mixnode, Service, SessionStats}; pub(crate) mod api; pub(crate) mod api_docs; @@ -20,6 +20,7 @@ pub(crate) mod state; PagedGatewaySkinny = PagedResult, PagedMixnode = PagedResult, PagedService = PagedResult, + PagedSessionStats = PagedResult )] pub struct PagedResult { pub page: usize, diff --git a/nym-node-status-api/nym-node-status-api/src/http/models.rs b/nym-node-status-api/nym-node-status-api/src/http/models.rs index aee2124b6c..19060d1ce8 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/models.rs @@ -74,3 +74,16 @@ pub(crate) struct SummaryHistory { pub value_json: serde_json::Value, pub timestamp_utc: String, } + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct SessionStats { + pub gateway_identity_key: String, + pub node_id: u32, + pub day: time::Date, + pub unique_active_clients: i64, + pub session_started: i64, + pub users_hashes: Option, + pub vpn_sessions: Option, + pub mixnet_sessions: Option, + pub unknown_sessions: Option, +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/state.rs b/nym-node-status-api/nym-node-status-api/src/http/state.rs index 393da5c0b8..0c79c0e16f 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/state.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/state.rs @@ -9,6 +9,8 @@ use crate::{ http::models::{DailyStats, Gateway, Mixnode, SummaryHistory}, }; +use super::models::SessionStats; + #[derive(Debug, Clone)] pub(crate) struct AppState { db_pool: DbPool, @@ -53,6 +55,7 @@ static GATEWAYS_LIST_KEY: &str = "gateways"; static MIXNODES_LIST_KEY: &str = "mixnodes"; static MIXSTATS_LIST_KEY: &str = "mixstats"; static SUMMARY_HISTORY_LIST_KEY: &str = "summary-history"; +static SESSION_STATS_LIST_KEY: &str = "session-stats"; #[derive(Debug, Clone)] pub(crate) struct HttpCache { @@ -60,6 +63,7 @@ pub(crate) struct HttpCache { mixnodes: Cache>>>, mixstats: Cache>>>, history: Cache>>>, + session_stats: Cache>>>, } impl HttpCache { @@ -81,6 +85,10 @@ impl HttpCache { .max_capacity(2) .time_to_live(Duration::from_secs(ttl_seconds)) .build(), + session_stats: Cache::builder() + .max_capacity(2) + .time_to_live(Duration::from_secs(ttl_seconds)) + .build(), } } @@ -238,4 +246,39 @@ impl HttpCache { }) .await } + + pub async fn get_sessions_stats(&self, db: &DbPool) -> Vec { + match self.session_stats.get(SESSION_STATS_LIST_KEY).await { + Some(guard) => { + let read_lock = guard.read().await; + read_lock.to_vec() + } + None => { + let session_stats = crate::db::queries::get_sessions_stats(db) + .await + .unwrap_or_default(); + self.upsert_sessions_stats(session_stats.clone()).await; + session_stats + } + } + } + + pub async fn upsert_sessions_stats( + &self, + session_stats: Vec, + ) -> Entry>>> { + self.session_stats + .entry_by_ref(SESSION_STATS_LIST_KEY) + .and_upsert_with(|maybe_entry| async { + if let Some(entry) = maybe_entry { + let v = entry.into_value(); + let mut guard = v.write().await; + *guard = session_stats; + v.clone() + } else { + Arc::new(RwLock::new(session_stats)) + } + }) + .await + } } diff --git a/nym-node-status-api/nym-node-status-api/src/main.rs b/nym-node-status-api/nym-node-status-api/src/main.rs index 6274a01fe5..4fd01d7960 100644 --- a/nym-node-status-api/nym-node-status-api/src/main.rs +++ b/nym-node-status-api/nym-node-status-api/src/main.rs @@ -7,6 +7,7 @@ mod db; mod http; mod logging; mod monitor; +mod node_scraper; mod testruns; #[tokio::main] @@ -44,6 +45,12 @@ async fn main() -> anyhow::Result<()> { testruns::spawn(storage.pool_owned(), args.testruns_refresh_interval).await; + let db_pool_scraper = storage.pool_owned(); + tokio::spawn(async move { + node_scraper::spawn_in_background(db_pool_scraper, args_clone.nym_api_client_timeout).await; + tracing::info!("Started metrics scraper task"); + }); + let shutdown_handles = http::server::start_http_api( storage.pool_owned(), args.http_port, diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/error.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/error.rs new file mode 100644 index 0000000000..42f96647e3 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/error.rs @@ -0,0 +1,18 @@ +use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT; +use nym_node_requests::api::client::NymNodeApiClientError; +use nym_validator_client::client::NodeId; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum NodeScraperError { + #[error("node {node_id} has provided malformed host information ({host}: {source}")] + MalformedHost { + host: String, + node_id: NodeId, + #[source] + source: NymNodeApiClientError, + }, + + #[error("node {node_id} with host '{host}' doesn't seem to expose its declared http port nor any of the standard API ports, i.e.: 80, 443 or {}", DEFAULT_NYM_NODE_HTTP_PORT)] + NoHttpPortsAvailable { host: String, node_id: NodeId }, +} diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs new file mode 100644 index 0000000000..fb8cca4c29 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/mod.rs @@ -0,0 +1,279 @@ +use crate::db::{models::GatewaySessionsRecord, queries, DbPool}; +use error::NodeScraperError; + +use nym_network_defaults::{NymNetworkDetails, DEFAULT_NYM_NODE_HTTP_PORT}; +use nym_node_requests::api::{client::NymNodeApiClientExt, v1::metrics::models::SessionStats}; +use nym_statistics_common::gateways::SessionType; +use nym_validator_client::{ + client::{NodeId, NymNodeDetails}, + models::{DescribedNodeType, NymNodeDescription}, + NymApiClient, +}; +use time::OffsetDateTime; + +use std::collections::HashMap; +use tokio::time::Duration; +use tracing::instrument; + +mod error; + +const FAILURE_RETRY_DELAY: Duration = Duration::from_secs(60); +const REFRESH_INTERVAL: Duration = Duration::from_secs(60 * 60 * 6); //6h, data only update once a day +const STALE_DURATION: Duration = Duration::from_secs(86400 * 365); //one year + +#[instrument(level = "debug", name = "node_scraper", skip_all)] +pub(crate) async fn spawn_in_background(db_pool: DbPool, nym_api_client_timeout: Duration) { + let network_defaults = nym_network_defaults::NymNetworkDetails::new_from_env(); + + loop { + //No graceful shutdown? + tracing::info!("Refreshing node self-described metrics..."); + + if let Err(e) = run(&db_pool, &network_defaults, nym_api_client_timeout).await { + tracing::error!( + "Metrics collection failed: {e}, retrying in {}s...", + FAILURE_RETRY_DELAY.as_secs() + ); + + tokio::time::sleep(FAILURE_RETRY_DELAY).await; + } else { + tracing::info!( + "Metrics successfully collected, sleeping for {}s...", + REFRESH_INTERVAL.as_secs() + ); + tokio::time::sleep(REFRESH_INTERVAL).await; + } + } +} + +async fn run( + pool: &DbPool, + network_details: &NymNetworkDetails, + nym_api_client_timeout: Duration, +) -> 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 api_client = NymApiClient::new_with_timeout(default_api_url, nym_api_client_timeout); + + //SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes + let bonded_nodes = api_client.get_all_bonded_nym_nodes().await?; + let all_nodes = api_client.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet + tracing::debug!("Fetched {} total nodes", all_nodes.len()); + + let mut nodes_to_scrape: HashMap = bonded_nodes + .into_iter() + .map(|n| (n.node_id(), n.into())) + .collect(); + + all_nodes + .into_iter() + .filter(|n| n.contract_node_type != DescribedNodeType::LegacyMixnode) + .for_each(|n| { + nodes_to_scrape.entry(n.node_id).or_insert_with(|| n.into()); + }); + tracing::debug!("Will try to scrape {} nodes", nodes_to_scrape.len()); + + let mut session_records = Vec::new(); + for n in nodes_to_scrape.into_values() { + if let Some(stat) = n.try_scrape_metrics().await { + session_records.push(prepare_session_data(stat, &n)); + } + } + + queries::insert_session_records(pool, session_records) + .await + .map(|_| { + tracing::debug!("Session info written to DB!"); + })?; + let cut_off_date = (OffsetDateTime::now_utc() - STALE_DURATION).date(); + queries::delete_old_records(pool, cut_off_date) + .await + .map(|_| { + tracing::debug!("Cleared old data before {}", cut_off_date); + })?; + + Ok(()) +} + +#[derive(Debug)] +struct MetricsScrapingData { + host: String, + node_id: NodeId, + id_key: String, + port: Option, +} + +impl MetricsScrapingData { + pub fn new( + host: impl Into, + node_id: NodeId, + id_key: String, + port: Option, + ) -> Self { + MetricsScrapingData { + host: host.into(), + node_id, + id_key, + port, + } + } + + async fn try_scrape_metrics(&self) -> Option { + match self.try_get_client().await { + Ok(client) => { + match client.get_sessions_metrics().await { + Ok(session_stats) => { + if session_stats.update_time != OffsetDateTime::UNIX_EPOCH { + Some(session_stats) + } else { + //means no data + None + } + } + Err(e) => { + tracing::error!("[metrics scraper]: {e}"); + None + } + } + } + Err(e) => { + tracing::error!("[metrics scraper]: {e}"); + None + } + } + } + + async fn try_get_client(&self) -> Result { + // first try the standard port in case the operator didn't put the node behind the proxy, + // then default https (443) + // finally default http (80) + let mut addresses_to_try = vec![ + format!("http://{0}:{DEFAULT_NYM_NODE_HTTP_PORT}", self.host), // 'standard' nym-node + format!("https://{0}", self.host), // node behind https proxy (443) + format!("http://{0}", self.host), // node behind http proxy (80) + ]; + + // note: I removed 'standard' legacy mixnode port because it should now be automatically pulled via + // the 'custom_port' since it should have been present in the contract. + + if let Some(port) = self.port { + addresses_to_try.insert(0, format!("http://{0}:{port}", self.host)); + } + + for address in addresses_to_try { + // if provided host was malformed, no point in continuing + let client = match nym_node_requests::api::Client::builder(address).and_then(|b| { + b.with_timeout(Duration::from_secs(5)) + .with_user_agent("node-status-api-metrics-scraper") + .build() + }) { + Ok(client) => client, + Err(err) => { + return Err(NodeScraperError::MalformedHost { + host: self.host.to_string(), + node_id: self.node_id, + source: err, + }); + } + }; + + if let Ok(health) = client.get_health().await { + if health.status.is_up() { + return Ok(client); + } + } + } + + Err(NodeScraperError::NoHttpPortsAvailable { + host: self.host.to_string(), + node_id: self.node_id, + }) + } +} + +impl From for MetricsScrapingData { + fn from(value: NymNodeDetails) -> Self { + MetricsScrapingData::new( + value.bond_information.node.host.clone(), + value.node_id(), + value.bond_information.node.identity_key, + value.bond_information.node.custom_http_port, + ) + } +} + +impl From for MetricsScrapingData { + fn from(value: NymNodeDescription) -> Self { + MetricsScrapingData::new( + value.description.host_information.ip_address[0].to_string(), + value.node_id, + value.ed25519_identity_key().to_base58_string(), + None, + ) + } +} + +fn prepare_session_data( + stat: SessionStats, + node_data: &MetricsScrapingData, +) -> GatewaySessionsRecord { + let users_hashes = if !stat.unique_active_users_hashes.is_empty() { + Some(serde_json::to_string(&stat.unique_active_users_hashes).unwrap()) + } else { + None + }; + let vpn_durations = stat + .sessions + .iter() + .filter(|s| SessionType::from_string(&s.typ) == SessionType::Vpn) + .map(|s| s.duration_ms) + .collect::>(); + + let mixnet_durations = stat + .sessions + .iter() + .filter(|s| SessionType::from_string(&s.typ) == SessionType::Mixnet) + .map(|s| s.duration_ms) + .collect::>(); + + let unkown_durations = stat + .sessions + .iter() + .filter(|s| SessionType::from_string(&s.typ) == SessionType::Unknown) + .map(|s| s.duration_ms) + .collect::>(); + + let vpn_sessions = if !vpn_durations.is_empty() { + Some(serde_json::to_string(&vpn_durations).unwrap()) + } else { + None + }; + let mixnet_sessions = if !mixnet_durations.is_empty() { + Some(serde_json::to_string(&mixnet_durations).unwrap()) + } else { + None + }; + let unknown_sessions = if !unkown_durations.is_empty() { + Some(serde_json::to_string(&unkown_durations).unwrap()) + } else { + None + }; + + GatewaySessionsRecord { + gateway_identity_key: node_data.id_key.clone(), + node_id: node_data.node_id as i64, + day: stat.update_time.date(), + unique_active_clients: stat.unique_active_users as i64, + session_started: stat.sessions_started as i64, + users_hashes, + vpn_sessions, + mixnet_sessions, + unknown_sessions, + } +} diff --git a/nym-node/nym-node-requests/src/api/client.rs b/nym-node/nym-node-requests/src/api/client.rs index 11ec66a4b9..068b128ef2 100644 --- a/nym-node/nym-node-requests/src/api/client.rs +++ b/nym-node/nym-node-requests/src/api/client.rs @@ -19,6 +19,7 @@ use crate::api::v1::network_requester::models::NetworkRequester; pub use nym_http_api_client::Client; use super::v1::gateway::models::Wireguard; +use super::v1::metrics::models::SessionStats; pub type NymNodeApiClientError = HttpClientError; @@ -87,6 +88,11 @@ pub trait NymNodeApiClientExt: ApiClient { self.get_json_from(routes::api::v1::gateway::client_interfaces::wireguard_absolute()) .await } + + async fn get_sessions_metrics(&self) -> Result { + self.get_json_from(routes::api::v1::metrics::sessions_absolute()) + .await + } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] diff --git a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs index a32506db2d..4620db05f9 100644 --- a/nym-node/nym-node-requests/src/api/v1/metrics/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/metrics/models.rs @@ -50,6 +50,7 @@ pub struct SessionStats { pub unique_active_users: u32, + #[serde(default = "Vec::new")] // field was added later pub unique_active_users_hashes: Vec, pub sessions: Vec, From fc79f739d4e6c71df2b376b408e374beef4c3f01 Mon Sep 17 00:00:00 2001 From: dynco-nym <173912580+dynco-nym@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:20:28 +0100 Subject: [PATCH 22/22] Fix overflow (#5204) --- Cargo.lock | 2 +- nym-node-status-api/nym-node-status-api/Cargo.toml | 2 +- nym-node-status-api/nym-node-status-api/src/monitor/mod.rs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc4eb9d37a..ff0b3f7cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6062,7 +6062,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "1.0.0-rc.4" +version = "1.0.0-rc.5" dependencies = [ "anyhow", "axum 0.7.7", diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index a915d31555..1698cbb858 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "1.0.0-rc.4" +version = "1.0.0-rc.5" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs index 459992b0b3..c9205c84c4 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs @@ -249,7 +249,8 @@ impl Monitor { 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 count_bonded_mixnodes_inactive = + count_bonded_mixnodes.saturating_sub(count_bonded_mixnodes_active); let (all_historical_gateways, all_historical_mixnodes) = calculate_stats(&pool).await?;