From 8dfe8f46784b098486b2b6511a5c814a71483227 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 09:54:09 +0200 Subject: [PATCH 01/44] version bump in toml files fr docs projects --- documentation/dev-portal/book.toml | 3 +-- documentation/docs/book.toml | 3 +-- documentation/operators/book.toml | 5 +---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index d9853f839c..5946ace450 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -48,8 +48,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.27" +platform_release_version = "v1.1.28" wallet_release_version = "v1.2.7" [preprocessor.last-changed] diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index 6d22440ee3..0305c52c51 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -48,8 +48,7 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.27" +platform_release_version = "v1.1.28" wallet_release_version = "v1.2.7" [preprocessor.last-changed] diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index be7233c069..d6c3a61c5b 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -48,16 +48,13 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` # https://gitlab.com/tglman/mdbook-variables/ [preprocessor.variables.variables] minimum_rust_version = "1.66" -# vars for links: TODO think on how to streamline updating -platform_release_version = "v1.1.27" +platform_release_version = "v1.1.28" wallet_release_version = "v1.2.7" - [preprocessor.last-changed] command = "mdbook-last-changed" renderer = ["html"] - # used for grabbing output of binary commands for automation # https://github.com/FauconFan/mdbook-cmdrun [preprocessor.cmdrun] From d4920b82f0193d7b50dd090d79b18612e45c9c32 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 13:59:20 +0200 Subject: [PATCH 02/44] include 2faktor's PR material --- .../docs/src/clients/socks5-client.md | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/clients/socks5-client.md b/documentation/docs/src/clients/socks5-client.md index e3a5759685..052a52f027 100644 --- a/documentation/docs/src/clients/socks5-client.md +++ b/documentation/docs/src/clients/socks5-client.md @@ -146,8 +146,6 @@ You can set this via the `--host` flag during either the `init` or `run` command Alternatively, a custom host can be set in the `config.toml` file under the `socket` section. If you do this, remember to restart your client process. - - ### Running the socks5 client You can run the initalised client by doing this: @@ -156,6 +154,37 @@ You can run the initalised client by doing this: ./nym-socks5-client run --id docs-example ``` +## Automating your socks5 client with systemd + +Create a service file for the socks5 client at `/etc/systemd/system/nym-socks5-client.service`: + +```ini +[Unit] +Description=Nym Socks5 Client ({{platform_release_version}}) +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym # replace this with whatever user you wish +LimitNOFILE=65536 +ExecStart=/home/nym/nym-socks5-client run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +Now enable and start your socks5 client: + +``` +systemctl enable nym-socks5-client.service +systemctl start nym-socks5-client.service +# you can always check your socks5 client has succesfully started with: +systemctl status nym-socks5-client.service +``` + ## Using your Socks5 Client After completing the steps above, your local Socks5 Client will be listening on `localhost:1080` ready to proxy traffic to the Network Requester set as the `--provider` when initialising. From 6d3b198f006e728267653881c05f5936e6ff73fb Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Mon, 21 Aug 2023 14:02:29 +0200 Subject: [PATCH 03/44] change minibolt description --- .../community-resources/community-applications-and-guides.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md index 90e0801e80..5186dceae1 100644 --- a/documentation/dev-portal/src/community-resources/community-applications-and-guides.md +++ b/documentation/dev-portal/src/community-resources/community-applications-and-guides.md @@ -87,7 +87,7 @@ We love seeing our developer community create applications using Nym. If you wou ## Minibolt -> Anonymize your p2p inventory messages and mempool for your Bitcoin & Lightning full nodes on consumer PCs! +> Proxies the clearnet connections of a Bitcoin node and its associated tools using the NYM mixnet. * [Github](https://github.com/minibolt-guide/minibolt) * [Documentation](https://v2.minibolt.info/bonus-guides/system/nym-mixnet#proxying-bitcoin-core) From dffe171b7f060898777b23313463436b7b983ed1 Mon Sep 17 00:00:00 2001 From: Pierre Dommerc Date: Mon, 21 Aug 2023 15:50:18 +0200 Subject: [PATCH 04/44] feat(explorer-api): add gateway geoip location (#3785) * feat(explorer-api): add gateway geoip location * fix lint --- explorer-api/explorer-api-requests/src/lib.rs | 12 ++- .../src/country_statistics/geolocate.rs | 67 +++++++++++++++- explorer-api/src/gateways/http.rs | 6 +- explorer-api/src/gateways/location.rs | 8 ++ explorer-api/src/gateways/mod.rs | 1 + explorer-api/src/gateways/models.rs | 80 +++++++++++++++++-- explorer-api/src/location.rs | 40 ++++++++++ explorer-api/src/main.rs | 1 + explorer-api/src/mix_nodes/location.rs | 37 +-------- explorer-api/src/mix_nodes/models.rs | 11 +-- explorer-api/src/mix_nodes/utils.rs | 4 +- explorer-api/src/state.rs | 17 ++-- nym-api/src/network/mod.rs | 4 +- 13 files changed, 228 insertions(+), 60 deletions(-) create mode 100644 explorer-api/src/gateways/location.rs create mode 100644 explorer-api/src/location.rs diff --git a/explorer-api/explorer-api-requests/src/lib.rs b/explorer-api/explorer-api-requests/src/lib.rs index 6180d65f11..2109348899 100644 --- a/explorer-api/explorer-api-requests/src/lib.rs +++ b/explorer-api/explorer-api-requests/src/lib.rs @@ -1,6 +1,6 @@ use nym_api_requests::models::NodePerformance; use nym_contracts_common::Percent; -use nym_mixnet_contract_common::{Addr, Coin, Layer, MixId, MixNode}; +use nym_mixnet_contract_common::{Addr, Coin, Gateway, Layer, MixId, MixNode}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -42,3 +42,13 @@ pub struct Location { pub latitude: Option, pub longitude: Option, } + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct PrettyDetailedGatewayBond { + pub pledge_amount: Coin, + pub owner: Addr, + pub block_height: u64, + pub gateway: Gateway, + pub proxy: Option, + pub location: Option, +} diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index a80ebc28e4..858f593986 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -17,13 +17,14 @@ impl GeoLocateTask { } pub(crate) fn start(mut self) { - info!("Spawning mix node locator task runner..."); + info!("Spawning locator task runner..."); tokio::spawn(async move { let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50)); while !self.shutdown.is_shutdown() { tokio::select! { _ = interval_timer.tick() => { self.locate_mix_nodes().await; + self.locate_gateways().await; } _ = self.shutdown.recv() => { trace!("Listener: Received shutdown"); @@ -107,4 +108,68 @@ impl GeoLocateTask { trace!("All mix nodes located"); } + + async fn locate_gateways(&mut self) { + let gateways = self.state.inner.gateways.get_gateways().await; + + let geo_ip = self.state.inner.geo_ip.0.clone(); + + for (i, cache_item) in gateways.iter().enumerate() { + if self + .state + .inner + .gateways + .is_location_valid(cache_item.identity().to_owned()) + .await + { + // when the cached location is valid, don't locate and continue to next gateway + continue; + } + + match geo_ip.query(&cache_item.gateway.host, Some(cache_item.gateway.mix_port)) { + Ok(opt) => match opt { + Some(location) => { + let location: Location = location.into(); + + trace!( + "{} gateways already located. Ip {} is located in {:#?}", + i, + cache_item.gateway.host, + location.three_letter_iso_country_code, + ); + + if i > 0 && (i % 100) == 0 { + info!("Located {} gateways...", i + 1,); + } + + self.state + .inner + .gateways + .set_location(cache_item.identity().to_owned(), Some(location)) + .await; + + // one node has been located, so return out of the loop + return; + } + None => { + warn!("❌ Location for {} not found.", cache_item.gateway.host); + self.state + .inner + .gateways + .set_location(cache_item.identity().to_owned(), None) + .await; + } + }, + Err(_e) => { + // warn!( + // "❌ Oh no! Location for {} failed. Error: {:#?}", + // cache_item.gateway.host, + // e + // ); + } + }; + } + + trace!("All gateways located"); + } } diff --git a/explorer-api/src/gateways/http.rs b/explorer-api/src/gateways/http.rs index 1f494d0edd..ac0d85c0b1 100644 --- a/explorer-api/src/gateways/http.rs +++ b/explorer-api/src/gateways/http.rs @@ -1,6 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nym_explorer_api_requests::PrettyDetailedGatewayBond; use rocket::response::status::NotFound; use rocket::serde::json::Json; use rocket::{Route, State}; @@ -9,7 +10,6 @@ use rocket_okapi::openapi_get_routes_spec; use rocket_okapi::settings::OpenApiSettings; use crate::state::ExplorerApiStateContext; -use nym_mixnet_contract_common::GatewayBond; pub fn gateways_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![settings: list] @@ -19,6 +19,6 @@ pub fn gateways_make_default_routes(settings: &OpenApiSettings) -> (Vec, #[get("/")] pub(crate) async fn list( state: &State, -) -> Result>, NotFound> { - Ok(Json(state.inner.gateways.get_gateways().await)) +) -> Result>, NotFound> { + Ok(Json(state.inner.gateways.get_detailed_gateways().await)) } diff --git a/explorer-api/src/gateways/location.rs b/explorer-api/src/gateways/location.rs new file mode 100644 index 0000000000..68420720b3 --- /dev/null +++ b/explorer-api/src/gateways/location.rs @@ -0,0 +1,8 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_contracts_common::IdentityKey; + +use crate::location::LocationCache; + +pub(crate) type GatewayLocationCache = LocationCache; diff --git a/explorer-api/src/gateways/mod.rs b/explorer-api/src/gateways/mod.rs index 5df938f83c..cbce7ef930 100644 --- a/explorer-api/src/gateways/mod.rs +++ b/explorer-api/src/gateways/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod http; +pub(crate) mod location; pub(crate) mod models; diff --git a/explorer-api/src/gateways/models.rs b/explorer-api/src/gateways/models.rs index ab2712c3fc..a3ed406067 100644 --- a/explorer-api/src/gateways/models.rs +++ b/explorer-api/src/gateways/models.rs @@ -1,12 +1,15 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::cache::Cache; +use crate::{cache::Cache, location::LocationCacheItem}; +use nym_explorer_api_requests::{Location, PrettyDetailedGatewayBond}; use nym_mixnet_contract_common::{GatewayBond, IdentityKey}; use serde::Serialize; -use std::sync::Arc; +use std::{sync::Arc, time::SystemTime}; use tokio::sync::RwLock; +use super::location::GatewayLocationCache; + pub(crate) struct GatewayCache { pub(crate) gateways: Cache, } @@ -18,30 +21,93 @@ pub(crate) struct GatewaySummary { #[derive(Clone)] pub(crate) struct ThreadsafeGatewayCache { - inner: Arc>, + gateways: Arc>, + locations: Arc>, } impl ThreadsafeGatewayCache { pub(crate) fn new() -> Self { ThreadsafeGatewayCache { - inner: Arc::new(RwLock::new(GatewayCache { + gateways: Arc::new(RwLock::new(GatewayCache { gateways: Cache::new(), })), + locations: Arc::new(RwLock::new(GatewayLocationCache::new())), + } + } + + fn create_detailed_gateway( + &self, + bond: GatewayBond, + location: Option<&LocationCacheItem>, + ) -> PrettyDetailedGatewayBond { + PrettyDetailedGatewayBond { + pledge_amount: bond.pledge_amount, + owner: bond.owner, + block_height: bond.block_height, + gateway: bond.gateway, + proxy: bond.proxy, + location: location.and_then(|l| l.location.clone()), } } pub(crate) async fn get_gateways(&self) -> Vec { - self.inner.read().await.gateways.get_all() + self.gateways.read().await.gateways.get_all() + } + + pub(crate) async fn get_detailed_gateways(&self) -> Vec { + let gateways_guard = self.gateways.read().await; + let location_guard = self.locations.read().await; + + gateways_guard + .gateways + .get_all() + .iter() + .map(|bond| { + let location = location_guard.get(bond.identity()); + self.create_detailed_gateway(bond.to_owned(), location) + }) + .collect() } pub(crate) async fn get_gateway_summary(&self) -> GatewaySummary { GatewaySummary { - count: self.inner.read().await.gateways.len(), + count: self.gateways.read().await.gateways.len(), } } + pub(crate) fn new_with_location_cache(locations: GatewayLocationCache) -> Self { + ThreadsafeGatewayCache { + gateways: Arc::new(RwLock::new(GatewayCache { + gateways: Cache::new(), + })), + locations: Arc::new(RwLock::new(locations)), + } + } + + pub(crate) async fn is_location_valid(&self, identity_key: IdentityKey) -> bool { + self.locations + .read() + .await + .get(&identity_key) + .map_or(false, |cache_item| { + cache_item.valid_until > SystemTime::now() + }) + } + + pub(crate) async fn get_locations(&self) -> GatewayLocationCache { + self.locations.read().await.clone() + } + + pub(crate) async fn set_location(&self, identy_key: IdentityKey, location: Option) { + // cache the location for this mix node so that it can be used when the mix node list is refreshed + self.locations + .write() + .await + .insert(identy_key, LocationCacheItem::new_from_location(location)); + } + pub(crate) async fn update_cache(&self, gateways: Vec) { - let mut guard = self.inner.write().await; + let mut guard = self.gateways.write().await; for gateway in gateways { guard diff --git a/explorer-api/src/location.rs b/explorer-api/src/location.rs new file mode 100644 index 0000000000..d11572e631 --- /dev/null +++ b/explorer-api/src/location.rs @@ -0,0 +1,40 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_explorer_api_requests::Location; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::time::{Duration, SystemTime}; + +pub(crate) type LocationCache = HashMap; + +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +pub(crate) struct GeoLocation { + pub(crate) ip: String, + pub(crate) country_code: String, + pub(crate) country_name: String, + pub(crate) region_code: String, + pub(crate) region_name: String, + pub(crate) city: String, + pub(crate) zip_code: String, + pub(crate) time_zone: String, + pub(crate) latitude: f32, + pub(crate) longitude: f32, + pub(crate) metro_code: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) struct LocationCacheItem { + pub(crate) location: Option, + pub(crate) valid_until: SystemTime, +} + +impl LocationCacheItem { + pub(crate) fn new_from_location(location: Option) -> Self { + LocationCacheItem { + location, + valid_until: SystemTime::now() + Duration::from_secs(60 * 60 * 24), // valid for 1 day + } + } +} diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 676f1c5cea..0df77aeaf9 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -19,6 +19,7 @@ mod geo_ip; mod guards; mod helpers; mod http; +mod location; mod mix_node; pub(crate) mod mix_nodes; mod overview; diff --git a/explorer-api/src/mix_nodes/location.rs b/explorer-api/src/mix_nodes/location.rs index 4908564697..de3c2e2fd6 100644 --- a/explorer-api/src/mix_nodes/location.rs +++ b/explorer-api/src/mix_nodes/location.rs @@ -1,41 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_explorer_api_requests::Location; use nym_mixnet_contract_common::MixId; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::time::{Duration, SystemTime}; -pub(crate) type LocationCache = HashMap; +use crate::location::LocationCache; -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -pub(crate) struct GeoLocation { - pub(crate) ip: String, - pub(crate) country_code: String, - pub(crate) country_name: String, - pub(crate) region_code: String, - pub(crate) region_name: String, - pub(crate) city: String, - pub(crate) zip_code: String, - pub(crate) time_zone: String, - pub(crate) latitude: f32, - pub(crate) longitude: f32, - pub(crate) metro_code: u32, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct LocationCacheItem { - pub(crate) location: Option, - pub(crate) valid_until: SystemTime, -} - -impl LocationCacheItem { - pub(crate) fn new_from_location(location: Option) -> Self { - LocationCacheItem { - location, - valid_until: SystemTime::now() + Duration::from_secs(60 * 60 * 24), // valid for 1 day - } - } -} +pub(crate) type MixnodeLocationCache = LocationCache; diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs index b107bb2168..fe5ae7fde8 100644 --- a/explorer-api/src/mix_nodes/models.rs +++ b/explorer-api/src/mix_nodes/models.rs @@ -12,10 +12,11 @@ use serde::Serialize; use tokio::sync::{RwLock, RwLockReadGuard}; use crate::helpers::best_effort_small_dec_to_f64; +use crate::location::LocationCacheItem; use nym_validator_client::models::MixNodeBondAnnotated; +use super::location::MixnodeLocationCache; use super::utils::family_numerical_id; -use crate::mix_nodes::location::{LocationCache, LocationCacheItem}; use crate::mix_nodes::CACHE_ENTRY_TTL; #[derive(Clone, Debug, Serialize, JsonSchema)] @@ -83,18 +84,18 @@ impl MixNodesResult { #[derive(Clone)] pub(crate) struct ThreadsafeMixNodesCache { mixnodes: Arc>, - locations: Arc>, + locations: Arc>, } impl ThreadsafeMixNodesCache { pub(crate) fn new() -> Self { ThreadsafeMixNodesCache { mixnodes: Arc::new(RwLock::new(MixNodesResult::new())), - locations: Arc::new(RwLock::new(LocationCache::new())), + locations: Arc::new(RwLock::new(MixnodeLocationCache::new())), } } - pub(crate) fn new_with_location_cache(locations: LocationCache) -> Self { + pub(crate) fn new_with_location_cache(locations: MixnodeLocationCache) -> Self { ThreadsafeMixNodesCache { mixnodes: Arc::new(RwLock::new(MixNodesResult::new())), locations: Arc::new(RwLock::new(locations)), @@ -111,7 +112,7 @@ impl ThreadsafeMixNodesCache { }) } - pub(crate) async fn get_locations(&self) -> LocationCache { + pub(crate) async fn get_locations(&self) -> MixnodeLocationCache { self.locations.read().await.clone() } diff --git a/explorer-api/src/mix_nodes/utils.rs b/explorer-api/src/mix_nodes/utils.rs index 0f7eb6b991..5273d66f55 100644 --- a/explorer-api/src/mix_nodes/utils.rs +++ b/explorer-api/src/mix_nodes/utils.rs @@ -1,13 +1,13 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use isocountry::CountryCode; use nym_mixnet_contract_common::families::FamilyHead; use rand::Rng; use rand_pcg::Pcg64; use rand_seeder::Seeder; -use crate::mix_nodes::location::GeoLocation; -use isocountry::CountryCode; +use crate::location::GeoLocation; #[allow(dead_code)] pub(crate) fn map_2_letter_to_3_letter_country_code(geo: &GeoLocation) -> String { diff --git a/explorer-api/src/state.rs b/explorer-api/src/state.rs index 0011a924d3..8ecee92ae2 100644 --- a/explorer-api/src/state.rs +++ b/explorer-api/src/state.rs @@ -13,9 +13,10 @@ use nym_validator_client::models::MixNodeBondAnnotated; use crate::country_statistics::country_nodes_distribution::{ CountryNodesDistribution, ThreadsafeCountryNodesDistribution, }; +use crate::gateways::location::GatewayLocationCache; use crate::gateways::models::ThreadsafeGatewayCache; use crate::mix_node::models::ThreadsafeMixNodeCache; -use crate::mix_nodes::location::LocationCache; +use crate::mix_nodes::location::MixnodeLocationCache; use crate::mix_nodes::models::ThreadsafeMixNodesCache; use crate::ping::models::ThreadsafePingCache; use crate::validators::models::ThreadsafeValidatorCache; @@ -46,7 +47,8 @@ impl ExplorerApiState { #[derive(Debug, Serialize, Deserialize)] pub struct ExplorerApiStateOnDisk { pub(crate) country_node_distribution: CountryNodesDistribution, - pub(crate) location_cache: LocationCache, + pub(crate) mixnode_location_cache: MixnodeLocationCache, + pub(crate) gateway_location_cache: GatewayLocationCache, pub(crate) as_at: DateTime, } @@ -76,9 +78,13 @@ impl ExplorerApiStateContext { ThreadsafeCountryNodesDistribution::new_from_distribution( state.country_node_distribution, ), - gateways: ThreadsafeGatewayCache::new(), + gateways: ThreadsafeGatewayCache::new_with_location_cache( + state.gateway_location_cache, + ), mixnode: ThreadsafeMixNodeCache::new(), - mixnodes: ThreadsafeMixNodesCache::new_with_location_cache(state.location_cache), + mixnodes: ThreadsafeMixNodesCache::new_with_location_cache( + state.mixnode_location_cache, + ), ping: ThreadsafePingCache::new(), validators: ThreadsafeValidatorCache::new(), validator_client: ThreadsafeValidatorClient::new(), @@ -109,7 +115,8 @@ impl ExplorerApiStateContext { let file = File::create(json_file_path).expect("unable to create state json file"); let state = ExplorerApiStateOnDisk { country_node_distribution: self.inner.country_node_distribution.get_all().await, - location_cache: self.inner.mixnodes.get_locations().await, + mixnode_location_cache: self.inner.mixnodes.get_locations().await, + gateway_location_cache: self.inner.gateways.get_locations().await, as_at: Utc::now(), }; serde_json::to_writer(file, &state).expect("error writing state to disk"); diff --git a/nym-api/src/network/mod.rs b/nym-api/src/network/mod.rs index f06bc5bf16..b0264f092c 100644 --- a/nym-api/src/network/mod.rs +++ b/nym-api/src/network/mod.rs @@ -11,6 +11,8 @@ mod routes; pub(crate) fn network_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![ - settings: routes::network_details, routes::nym_contracts, routes::nym_contracts_detailed + settings: routes::network_details, + routes::nym_contracts, + routes::nym_contracts_detailed ] } From c1e4b87744c68bcfa882b9cb2b37301054be31ff Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Tue, 22 Aug 2023 10:31:37 +0200 Subject: [PATCH 05/44] change ci to run against sandbox for tests --- .github/workflows/run-api-tests.yml | 2 +- nym-api/tests/package.json | 2 +- nym-api/tests/src/config/config.yaml | 11 ++++++----- nym-api/tests/src/config/configHandler.ts | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/run-api-tests.yml b/.github/workflows/run-api-tests.yml index 1d2afec2bd..fbc3d63eea 100644 --- a/.github/workflows/run-api-tests.yml +++ b/.github/workflows/run-api-tests.yml @@ -32,5 +32,5 @@ jobs: run: yarn - name: Run tests - run: yarn test:qa + run: yarn test:sandbox working-directory: nym-api/tests diff --git a/nym-api/tests/package.json b/nym-api/tests/package.json index e93160b7b6..31731d6b16 100644 --- a/nym-api/tests/package.json +++ b/nym-api/tests/package.json @@ -7,7 +7,7 @@ "test": "test" }, "scripts": { - "test:qa": "TEST_ENV=qa jest --forceExit --detectOpenHandles --passWithNoTests", + "test:sandbox": "TEST_ENV=sandbox jest --forceExit --detectOpenHandles --passWithNoTests", "test:prod": "TEST_ENV=prod jest --forceExit --detectOpenHandles --passWithNoTests", "build": "tsc", "lint": "eslint --ext .js,.ts,.tsx .", diff --git a/nym-api/tests/src/config/config.yaml b/nym-api/tests/src/config/config.yaml index d0f51eff0c..85603a7efa 100644 --- a/nym-api/tests/src/config/config.yaml +++ b/nym-api/tests/src/config/config.yaml @@ -2,12 +2,13 @@ common: request_headers: Accept: application/json Content-Type: application/json -qa: - api_base_url: https://qwerty-validator-api.qa.nymte.ch/api/v1 - mix_id: 63 - identity_key: 4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz - gateway_identity: 336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9 +sandbox: + api_base_url: https://sandbox-nym-api1.nymtech.net/api/v1 + mix_id: 7 + identity_key: 2wEzyW6Pihk19xbCK9kB81vYuDFUvTzTAr77arvxdfSg + gateway_identity: HjNEDJuotWV8VD4ufeA1jeheTnfNJ7Jorevp57hgaZua log_level: error + time_zone: utc prod: api_base_url: https://validator.nymtech.net/api/v1 mix_id: 730 diff --git a/nym-api/tests/src/config/configHandler.ts b/nym-api/tests/src/config/configHandler.ts index 44177f0866..1d29402012 100644 --- a/nym-api/tests/src/config/configHandler.ts +++ b/nym-api/tests/src/config/configHandler.ts @@ -6,7 +6,7 @@ import YAML from "yaml"; class ConfigHandler { private static instance: ConfigHandler; - private validEnvironments = ["qa", "prod"]; + private validEnvironments = ["sandbox", "prod"]; public commonConfig: { request_headers: object }; @@ -23,7 +23,7 @@ class ConfigHandler { private constructor() { this.setCommonConfig(); - this.setEnvironmentConfig(process.env.TEST_ENV || "qa" || "prod"); + this.setEnvironmentConfig(process.env.TEST_ENV || "sandbox" || "prod"); } public static getInstance(): ConfigHandler { From 3c69f9c2f991b485e910beff34f434d147425548 Mon Sep 17 00:00:00 2001 From: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com> Date: Tue, 22 Aug 2023 10:35:46 +0200 Subject: [PATCH 06/44] Update nym-connect-publish-windows10.yml --- .github/workflows/nym-connect-publish-windows10.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nym-connect-publish-windows10.yml b/.github/workflows/nym-connect-publish-windows10.yml index 12c296a16d..f90ce1697d 100644 --- a/.github/workflows/nym-connect-publish-windows10.yml +++ b/.github/workflows/nym-connect-publish-windows10.yml @@ -100,6 +100,7 @@ jobs: nym-connect/desktop/target/release/bundle/msi/*.msi.zip* - id: release-info name: Prepare release info + shell: bash run: | ref=${{ github.ref_name }} semver="${ref##nym-connect-}" && semver="${semver##v}" From 0a5a2c67474736ca093c7f8adce4a1421a8e30d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 22 Aug 2023 10:56:23 +0100 Subject: [PATCH 07/44] removed needless_pass_by_ref_mut in mixnet contract tests (#3798) --- contracts/mixnet/src/interval/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mixnet/src/interval/storage.rs b/contracts/mixnet/src/interval/storage.rs index 0e35f3908d..04f3aa999a 100644 --- a/contracts/mixnet/src/interval/storage.rs +++ b/contracts/mixnet/src/interval/storage.rs @@ -176,7 +176,7 @@ mod tests { use cosmwasm_std::Order; use rand_chacha::rand_core::RngCore; - fn read_entire_set(storage: &mut dyn Storage) -> HashMap { + fn read_entire_set(storage: &dyn Storage) -> HashMap { REWARDED_SET .range(storage, None, None, Order::Ascending) .map(|r| r.unwrap()) From f88622ac0805479e50164dcccca69c1f0bf8999e Mon Sep 17 00:00:00 2001 From: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com> Date: Tue, 22 Aug 2023 12:21:44 +0200 Subject: [PATCH 08/44] Delete upload-nyxd-ci.yml not needed anymore removing --- .github/workflows/upload-nyxd-ci.yml | 79 ---------------------------- 1 file changed, 79 deletions(-) delete mode 100644 .github/workflows/upload-nyxd-ci.yml diff --git a/.github/workflows/upload-nyxd-ci.yml b/.github/workflows/upload-nyxd-ci.yml deleted file mode 100644 index 1668bf3184..0000000000 --- a/.github/workflows/upload-nyxd-ci.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: Upload nyxd to CI - -on: - workflow_dispatch: - -jobs: - publish-nyxd: - strategy: - fail-fast: false - matrix: - platform: [ubuntu-20.04] - - runs-on: ${{ matrix.platform }} - steps: - - uses: actions/checkout@v3 - - - name: Prepare build output directory - shell: bash - env: - OUTPUT_DIR: ci-builds/nyxd - run: | - rm -rf ci-builds || true - mkdir -p $OUTPUT_DIR - echo $OUTPUT_DIR - - - name: Install Dependencies (Linux) - run: sudo apt-get update && sudo apt-get -y install build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools git - continue-on-error: true - - - name: Update env variables to include go - run: | - sudo rm -rf /usr/local/go - curl https://dl.google.com/go/go1.19.2.linux-amd64.tar.gz | sudo tar -C/usr/local -zxvf - - cat <<'EOF' >>$HOME/.profile - export GOROOT=/usr/local/go - export GOPATH=$HOME/go - export GO111MODULE=on - export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin - EOF - source $HOME/.profile - - - name: Verify Go is installed - run: go version - - - name: Clone nyxd repo - run: | - git clone https://github.com/tommyv1987/nyxd - cd nyxd - git checkout release/v0.30.2 - - - name: Run nyxd - run: | - pwd - cd nyxd && make build - sleep 10 - ls /home/runner/work/nym/nym/nyxd/build - - - name: Prepare build output - shell: bash - env: - OUTPUT_DIR: ci-builds/nyxd - run: | - cp /home/runner/work/nym/nym/nyxd/build/nyxd $OUTPUT_DIR - WASMVM_SO=$(ldd /home/runner/work/nym/nym/nyxd/build/nyxd | grep "libwasm*" | awk '{ print $3 }') - ls $WASMVM_SO - sleep 3 - cp $(echo $WASMVM_SO) $OUTPUT_DIR - - - name: Deploy nyxd to CI www - continue-on-error: true - uses: easingthemes/ssh-deploy@main - env: - SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }} - ARGS: "-avzr" - SOURCE: "ci-builds/" - REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }} - REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} - TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/ - EXCLUDE: "/dist/, /node_modules/" From b429c64168c6dd873f0ee7c66e3efb3626032a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 22 Aug 2023 12:12:13 +0100 Subject: [PATCH 09/44] [demo] libp2p example with nym-sdk (#3763) * imported libp2p to monorepo * fixed vanilla ping example * added libp2p client to workspace * naively replaced dockerised client with the sdk * moved libp2p code to sdk examples * reduced number of dependencies required for libp2p example * updated readmes * added protobuf compiler to build dependencies * added protoc dependency to readme for chat and ping examples --------- Co-authored-by: mfahampshire --- .../build-and-upload-binaries-ci.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/nightly_build.yml | 2 +- .github/workflows/nightly_build_release.yml | 2 +- Cargo.lock | 654 +++++++++++-- sdk/rust/nym-sdk/Cargo.toml | 10 + .../nym-sdk/examples/libp2p_chat/README.md | 64 ++ sdk/rust/nym-sdk/examples/libp2p_chat/main.rs | 171 ++++ .../nym-sdk/examples/libp2p_ping/README.md | 79 ++ sdk/rust/nym-sdk/examples/libp2p_ping/main.rs | 142 +++ .../examples/libp2p_shared/connection.rs | 418 ++++++++ .../nym-sdk/examples/libp2p_shared/error.rs | 64 ++ .../nym-sdk/examples/libp2p_shared/lib.rs | 10 + .../nym-sdk/examples/libp2p_shared/message.rs | 335 +++++++ .../nym-sdk/examples/libp2p_shared/mixnet.rs | 164 ++++ .../nym-sdk/examples/libp2p_shared/queue.rs | 140 +++ .../examples/libp2p_shared/substream.rs | 422 ++++++++ .../examples/libp2p_shared/transport.rs | 897 ++++++++++++++++++ 18 files changed, 3504 insertions(+), 74 deletions(-) create mode 100644 sdk/rust/nym-sdk/examples/libp2p_chat/README.md create mode 100644 sdk/rust/nym-sdk/examples/libp2p_chat/main.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_ping/README.md create mode 100644 sdk/rust/nym-sdk/examples/libp2p_ping/main.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/error.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/lib.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/message.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/queue.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/substream.rs create mode 100644 sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs diff --git a/.github/workflows/build-and-upload-binaries-ci.yml b/.github/workflows/build-and-upload-binaries-ci.yml index 849bd32a17..f317f90110 100644 --- a/.github/workflows/build-and-upload-binaries-ci.yml +++ b/.github/workflows/build-and-upload-binaries-ci.yml @@ -57,7 +57,7 @@ jobs: echo $OUTPUT_DIR - name: Install Dependencies (Linux) - run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler continue-on-error: true - name: Install Rust stable diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d346df6296..ce9f7820b0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,7 @@ jobs: RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache steps: - name: Install Dependencies (Linux) - run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler continue-on-error: true - name: Check out repository code diff --git a/.github/workflows/nightly_build.yml b/.github/workflows/nightly_build.yml index 1ee391d6a7..84ea9fabec 100644 --- a/.github/workflows/nightly_build.yml +++ b/.github/workflows/nightly_build.yml @@ -24,7 +24,7 @@ jobs: continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }} steps: - name: Install Dependencies (Linux) - run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler continue-on-error: true if: matrix.os == 'ubuntu-20.04' diff --git a/.github/workflows/nightly_build_release.yml b/.github/workflows/nightly_build_release.yml index fa1c465c97..6ddee762e1 100644 --- a/.github/workflows/nightly_build_release.yml +++ b/.github/workflows/nightly_build_release.yml @@ -37,7 +37,7 @@ jobs: continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }} steps: - name: Install Dependencies (Linux) - run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools + run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler continue-on-error: true if: matrix.os == 'ubuntu-20.04' diff --git a/Cargo.lock b/Cargo.lock index cc54eee9dc..26d7b17568 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ "futures-core", "futures-sink", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.12", "tokio", "tokio-util", "tracing", @@ -54,7 +54,7 @@ dependencies = [ "local-channel", "mime", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.12", "rand 0.8.5", "sha1", "smallvec", @@ -123,7 +123,7 @@ checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" dependencies = [ "futures-core", "paste", - "pin-project-lite", + "pin-project-lite 0.2.12", ] [[package]] @@ -133,7 +133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" dependencies = [ "local-waker", - "pin-project-lite", + "pin-project-lite 0.2.12", ] [[package]] @@ -166,7 +166,7 @@ dependencies = [ "log", "mime", "once_cell", - "pin-project-lite", + "pin-project-lite 0.2.12", "regex", "serde", "serde_json", @@ -569,6 +569,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "asn1_der" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" + [[package]] name = "assert_matches" version = "1.5.0" @@ -633,7 +639,7 @@ checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", - "pin-project-lite", + "pin-project-lite 0.2.12", ] [[package]] @@ -668,7 +674,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.12", ] [[package]] @@ -746,7 +752,7 @@ dependencies = [ "memchr", "mime", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.12", "rustversion", "serde", "sync_wrapper", @@ -883,8 +889,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" dependencies = [ "bitcoin_hashes", - "rand 0.6.5", - "rand_core 0.4.2", + "rand 0.8.5", + "rand_core 0.6.4", "serde", "unicode-normalization", "zeroize", @@ -2699,10 +2705,10 @@ dependencies = [ "futures", "futures-util", "lazy_static", - "libp2p", + "libp2p 0.51.3", "libp2p-identity", "log", - "lru", + "lru 0.10.0", "nym-config", "nym-ephemera-common", "nym-task", @@ -2933,6 +2939,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.26" @@ -2940,6 +2952,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", + "libz-sys", "miniz_oxide", ] @@ -3093,7 +3106,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite", + "pin-project-lite 0.2.12", "waker-fn", ] @@ -3150,7 +3163,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.12", "pin-utils", "slab", ] @@ -3610,7 +3623,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite", + "pin-project-lite 0.2.12", ] [[package]] @@ -3666,7 +3679,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite", + "pin-project-lite 0.2.12", "socket2 0.4.9", "tokio", "tower-service", @@ -3718,7 +3731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper", - "pin-project-lite", + "pin-project-lite 0.2.12", "tokio", "tokio-io-timeout", ] @@ -4262,6 +4275,35 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "libp2p" +version = "0.51.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "getrandom 0.2.10", + "instant", + "libp2p-core 0.39.0", + "libp2p-dns 0.39.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "libp2p-gossipsub 0.44.0", + "libp2p-identify", + "libp2p-mdns 0.43.0", + "libp2p-metrics 0.12.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "libp2p-mplex", + "libp2p-noise 0.42.0", + "libp2p-ping", + "libp2p-quic 0.7.0-alpha.2", + "libp2p-swarm 0.42.0", + "libp2p-tcp 0.39.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "libp2p-webrtc 0.4.0-alpha.2", + "libp2p-websocket", + "libp2p-yamux 0.43.0", + "multiaddr 0.17.0", + "pin-project", +] + [[package]] name = "libp2p" version = "0.51.3" @@ -4275,21 +4317,21 @@ dependencies = [ "instant", "libp2p-allow-block-list", "libp2p-connection-limits", - "libp2p-core", - "libp2p-dns", - "libp2p-gossipsub", + "libp2p-core 0.39.2", + "libp2p-dns 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libp2p-gossipsub 0.44.4", "libp2p-identity", "libp2p-kad", - "libp2p-mdns", - "libp2p-metrics", - "libp2p-noise", - "libp2p-quic", + "libp2p-mdns 0.43.1", + "libp2p-metrics 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libp2p-noise 0.42.2", + "libp2p-quic 0.7.0-alpha.3", "libp2p-request-response", - "libp2p-swarm", - "libp2p-tcp", - "libp2p-webrtc", - "libp2p-yamux", - "multiaddr", + "libp2p-swarm 0.42.2", + "libp2p-tcp 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libp2p-webrtc 0.4.0-alpha.4", + "libp2p-yamux 0.43.1", + "multiaddr 0.17.1", "pin-project", ] @@ -4299,9 +4341,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "510daa05efbc25184458db837f6f9a5143888f1caa742426d92e1833ddd38a50" dependencies = [ - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "void", ] @@ -4311,12 +4353,45 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4caa33f1d26ed664c4fe2cca81a08c8e07d4c1c04f2f4ac7655c2dd85467fda0" dependencies = [ - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "void", ] +[[package]] +name = "libp2p-core" +version = "0.39.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "asn1_der", + "bs58 0.4.0", + "ed25519-dalek", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "log", + "multiaddr 0.17.0", + "multihash", + "multistream-select 0.12.1 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "once_cell", + "parking_lot 0.12.1", + "pin-project", + "prost", + "prost-build", + "rand 0.8.5", + "rw-stream-sink 0.3.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "sec1 0.3.0", + "sha2 0.10.7", + "smallvec", + "thiserror", + "unsigned-varint", + "void", + "zeroize", +] + [[package]] name = "libp2p-core" version = "0.39.2" @@ -4330,15 +4405,15 @@ dependencies = [ "instant", "libp2p-identity", "log", - "multiaddr", + "multiaddr 0.17.1", "multihash", - "multistream-select", + "multistream-select 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell", "parking_lot 0.12.1", "pin-project", "quick-protobuf", "rand 0.8.5", - "rw-stream-sink", + "rw-stream-sink 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "smallvec", "thiserror", @@ -4353,13 +4428,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146ff7034daae62077c415c2376b8057368042df6ab95f5432ad5e88568b1554" dependencies = [ "futures", - "libp2p-core", + "libp2p-core 0.39.2", "log", "parking_lot 0.12.1", "smallvec", "trust-dns-resolver", ] +[[package]] +name = "libp2p-dns" +version = "0.39.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "futures", + "libp2p-core 0.39.0", + "log", + "parking_lot 0.12.1", + "smallvec", + "trust-dns-resolver", +] + +[[package]] +name = "libp2p-gossipsub" +version = "0.44.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "asynchronous-codec", + "base64 0.21.2", + "byteorder", + "bytes", + "fnv", + "futures", + "hex_fmt", + "instant", + "libp2p-core 0.39.0", + "libp2p-swarm 0.42.0", + "log", + "prometheus-client", + "prost", + "prost-build", + "prost-codec", + "rand 0.8.5", + "regex", + "sha2 0.10.7", + "smallvec", + "thiserror", + "unsigned-varint", + "wasm-timer", +] + [[package]] name = "libp2p-gossipsub" version = "0.44.4" @@ -4375,9 +4492,9 @@ dependencies = [ "futures", "hex_fmt", "instant", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "log", "prometheus-client", "quick-protobuf", @@ -4393,6 +4510,27 @@ dependencies = [ "wasm-timer", ] +[[package]] +name = "libp2p-identify" +version = "0.42.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-timer", + "libp2p-core 0.39.0", + "libp2p-swarm 0.42.0", + "log", + "lru 0.9.0", + "prost", + "prost-build", + "prost-codec", + "smallvec", + "thiserror", + "void", +] + [[package]] name = "libp2p-identity" version = "0.1.2" @@ -4402,7 +4540,7 @@ dependencies = [ "bs58 0.4.0", "ed25519-dalek", "log", - "multiaddr", + "multiaddr 0.17.1", "multihash", "quick-protobuf", "rand 0.8.5", @@ -4426,9 +4564,9 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "log", "quick-protobuf", "rand 0.8.5", @@ -4441,6 +4579,25 @@ dependencies = [ "void", ] +[[package]] +name = "libp2p-mdns" +version = "0.43.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "data-encoding", + "futures", + "if-watch", + "libp2p-core 0.39.0", + "libp2p-swarm 0.42.0", + "log", + "rand 0.8.5", + "smallvec", + "socket2 0.4.9", + "tokio", + "trust-dns-proto", + "void", +] + [[package]] name = "libp2p-mdns" version = "0.43.1" @@ -4450,9 +4607,9 @@ dependencies = [ "data-encoding", "futures", "if-watch", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "log", "rand 0.8.5", "smallvec", @@ -4468,13 +4625,65 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a42ec91e227d7d0dafa4ce88b333cdf5f277253873ab087555c92798db2ddd46" dependencies = [ - "libp2p-core", - "libp2p-gossipsub", + "libp2p-core 0.39.2", + "libp2p-gossipsub 0.44.4", "libp2p-kad", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "prometheus-client", ] +[[package]] +name = "libp2p-metrics" +version = "0.12.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "libp2p-core 0.39.0", + "libp2p-gossipsub 0.44.0", + "libp2p-identify", + "libp2p-ping", + "libp2p-swarm 0.42.0", + "prometheus-client", +] + +[[package]] +name = "libp2p-mplex" +version = "0.39.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core 0.39.0", + "log", + "nohash-hasher", + "parking_lot 0.12.1", + "rand 0.8.5", + "smallvec", + "unsigned-varint", +] + +[[package]] +name = "libp2p-noise" +version = "0.42.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "bytes", + "curve25519-dalek 3.2.0", + "futures", + "libp2p-core 0.39.0", + "log", + "once_cell", + "prost", + "prost-build", + "rand 0.8.5", + "sha2 0.10.7", + "snow", + "static_assertions", + "thiserror", + "x25519-dalek 1.1.1", + "zeroize", +] + [[package]] name = "libp2p-noise" version = "0.42.2" @@ -4484,7 +4693,7 @@ dependencies = [ "bytes", "curve25519-dalek 3.2.0", "futures", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", "log", "once_cell", @@ -4498,6 +4707,42 @@ dependencies = [ "zeroize", ] +[[package]] +name = "libp2p-ping" +version = "0.42.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "either", + "futures", + "futures-timer", + "instant", + "libp2p-core 0.39.0", + "libp2p-swarm 0.42.0", + "log", + "rand 0.8.5", + "void", +] + +[[package]] +name = "libp2p-quic" +version = "0.7.0-alpha.2" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "if-watch", + "libp2p-core 0.39.0", + "libp2p-tls 0.1.0-alpha.2", + "log", + "parking_lot 0.12.1", + "quinn-proto", + "rand 0.8.5", + "rustls 0.20.8", + "thiserror", + "tokio", +] + [[package]] name = "libp2p-quic" version = "0.7.0-alpha.3" @@ -4508,9 +4753,9 @@ dependencies = [ "futures", "futures-timer", "if-watch", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-tls", + "libp2p-tls 0.1.0", "log", "parking_lot 0.12.1", "quinn-proto", @@ -4529,13 +4774,32 @@ dependencies = [ "async-trait", "futures", "instant", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.42.2", "rand 0.8.5", "smallvec", ] +[[package]] +name = "libp2p-swarm" +version = "0.42.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "libp2p-core 0.39.0", + "libp2p-swarm-derive 0.32.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "log", + "rand 0.8.5", + "smallvec", + "tokio", + "void", +] + [[package]] name = "libp2p-swarm" version = "0.42.2" @@ -4547,9 +4811,9 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-swarm-derive", + "libp2p-swarm-derive 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "log", "rand 0.8.5", "smallvec", @@ -4568,6 +4832,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "libp2p-swarm-derive" +version = "0.32.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "heck 0.4.1", + "quote", + "syn 1.0.109", +] + [[package]] name = "libp2p-tcp" version = "0.39.0" @@ -4578,12 +4852,44 @@ dependencies = [ "futures-timer", "if-watch", "libc", - "libp2p-core", + "libp2p-core 0.39.2", "log", "socket2 0.4.9", "tokio", ] +[[package]] +name = "libp2p-tcp" +version = "0.39.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core 0.39.0", + "log", + "socket2 0.4.9", + "tokio", +] + +[[package]] +name = "libp2p-tls" +version = "0.1.0-alpha.2" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core 0.39.0", + "rcgen 0.10.0", + "ring", + "rustls 0.20.8", + "thiserror", + "webpki 0.22.0", + "x509-parser 0.14.0", + "yasna", +] + [[package]] name = "libp2p-tls" version = "0.1.0" @@ -4592,7 +4898,7 @@ checksum = "ff08d13d0dc66e5e9ba6279c1de417b84fa0d0adc3b03e5732928c180ec02781" dependencies = [ "futures", "futures-rustls", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", "rcgen 0.10.0", "ring", @@ -4603,6 +4909,36 @@ dependencies = [ "yasna", ] +[[package]] +name = "libp2p-webrtc" +version = "0.4.0-alpha.2" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "async-trait", + "asynchronous-codec", + "bytes", + "futures", + "futures-timer", + "hex", + "if-watch", + "libp2p-core 0.39.0", + "libp2p-noise 0.42.0", + "log", + "multihash", + "prost", + "prost-build", + "prost-codec", + "rand 0.8.5", + "rcgen 0.9.3", + "serde", + "stun", + "thiserror", + "tinytemplate", + "tokio", + "tokio-util", + "webrtc", +] + [[package]] name = "libp2p-webrtc" version = "0.4.0-alpha.4" @@ -4616,9 +4952,9 @@ dependencies = [ "futures-timer", "hex", "if-watch", - "libp2p-core", + "libp2p-core 0.39.2", "libp2p-identity", - "libp2p-noise", + "libp2p-noise 0.42.2", "log", "multihash", "quick-protobuf", @@ -4634,6 +4970,36 @@ dependencies = [ "webrtc", ] +[[package]] +name = "libp2p-websocket" +version = "0.41.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "either", + "futures", + "futures-rustls", + "libp2p-core 0.39.0", + "log", + "parking_lot 0.12.1", + "quicksink", + "rw-stream-sink 0.3.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", + "soketto", + "url", + "webpki-roots 0.22.6", +] + +[[package]] +name = "libp2p-yamux" +version = "0.43.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "futures", + "libp2p-core 0.39.0", + "log", + "thiserror", + "yamux", +] + [[package]] name = "libp2p-yamux" version = "0.43.1" @@ -4641,7 +5007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd21d950662700a385d4c6d68e2f5f54d778e97068cdd718522222ef513bda" dependencies = [ "futures", - "libp2p-core", + "libp2p-core 0.39.2", "log", "thiserror", "yamux", @@ -4765,6 +5131,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e7d46de488603ffdd5f30afbc64fbba2378214a2c3a2fb83abf3d33126df17" +dependencies = [ + "hashbrown 0.13.2", +] + [[package]] name = "lru" version = "0.10.0" @@ -4944,6 +5319,23 @@ dependencies = [ "version_check", ] +[[package]] +name = "multiaddr" +version = "0.17.0" +source = "git+https://github.com/ChainSafe/rust-multiaddr.git?rev=2d21365f880622adf1c8e594b72e671dbac9dd64#2d21365f880622adf1c8e594b72e671dbac9dd64" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint", + "url", +] + [[package]] name = "multiaddr" version = "0.17.1" @@ -5003,6 +5395,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "multistream-select" version = "0.12.1" @@ -5017,6 +5415,19 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "multistream-select" +version = "0.12.1" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -6159,6 +6570,8 @@ dependencies = [ "bip39", "dotenvy", "futures", + "hex", + "libp2p 0.51.0", "log", "nym-bandwidth-controller", "nym-bin-common", @@ -6173,12 +6586,15 @@ dependencies = [ "nym-task", "nym-topology", "nym-validator-client", + "parking_lot 0.12.1", "pretty_env_logger", "rand 0.7.3", "reqwest", "tap", "thiserror", "tokio", + "tokio-stream", + "tokio-util", "toml 0.5.11", "url", ] @@ -6821,7 +7237,7 @@ dependencies = [ "futures-util", "indexmap 1.9.3", "once_cell", - "pin-project-lite", + "pin-project-lite 0.2.12", "thiserror", "urlencoding", ] @@ -7147,6 +7563,16 @@ dependencies = [ "sha2 0.10.7", ] +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.0.0", +] + [[package]] name = "pin-project" version = "1.1.3" @@ -7167,6 +7593,12 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + [[package]] name = "pin-project-lite" version = "0.2.12" @@ -7251,7 +7683,7 @@ dependencies = [ "concurrent-queue", "libc", "log", - "pin-project-lite", + "pin-project-lite 0.2.12", "windows-sys 0.48.0", ] @@ -7416,6 +7848,40 @@ dependencies = [ "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck 0.4.1", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-codec" +version = "0.3.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "asynchronous-codec", + "bytes", + "prost", + "thiserror", + "unsigned-varint", +] + [[package]] name = "prost-derive" version = "0.11.9" @@ -7483,6 +7949,17 @@ dependencies = [ "unsigned-varint", ] +[[package]] +name = "quicksink" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77de3c815e5a160b1539c6592796801df2043ae35e123b46d73380cfa57af858" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project-lite 0.1.12", +] + [[package]] name = "quinn-proto" version = "0.9.3" @@ -7937,7 +8414,7 @@ dependencies = [ "native-tls", "once_cell", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.12", "serde", "serde_json", "serde_urlencoded", @@ -8028,7 +8505,7 @@ dependencies = [ "multer", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite", + "pin-project-lite 0.2.12", "rand 0.8.5", "ref-cast", "rocket_codegen", @@ -8093,7 +8570,7 @@ dependencies = [ "memchr", "pear", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.12", "ref-cast", "serde", "smallvec", @@ -8362,6 +8839,16 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "rw-stream-sink" +version = "0.3.0" +source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6#e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + [[package]] name = "ryu" version = "1.0.15" @@ -8911,6 +9398,22 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "soketto" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" +dependencies = [ + "base64 0.13.1", + "bytes", + "flate2", + "futures", + "httparse", + "log", + "rand 0.8.5", + "sha-1 0.9.8", +] + [[package]] name = "sphinx-packet" version = "0.1.0" @@ -9638,7 +10141,7 @@ dependencies = [ "mio", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite", + "pin-project-lite 0.2.12", "signal-hook-registry", "socket2 0.5.3", "tokio-macros", @@ -9652,7 +10155,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite", + "pin-project-lite 0.2.12", "tokio", ] @@ -9718,7 +10221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", - "pin-project-lite", + "pin-project-lite 0.2.12", "tokio", "tokio-util", ] @@ -9785,7 +10288,7 @@ dependencies = [ "futures-sink", "futures-util", "hashbrown 0.12.3", - "pin-project-lite", + "pin-project-lite 0.2.12", "slab", "tokio", "tracing", @@ -9872,7 +10375,7 @@ dependencies = [ "futures-util", "indexmap 1.9.3", "pin-project", - "pin-project-lite", + "pin-project-lite 0.2.12", "rand 0.8.5", "slab", "tokio", @@ -9902,7 +10405,7 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", - "pin-project-lite", + "pin-project-lite 0.2.12", "tracing-attributes", "tracing-core", ] @@ -10871,6 +11374,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "widestring" version = "0.5.1" diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index e2b5d86d7e..f3d1d47aae 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -36,3 +36,13 @@ reqwest = { workspace = true, features = ["json", "socks"] } thiserror = { workspace = true } tokio = { version = "1", features = ["full"] } nym-bin-common = { path = "../../../common/bin-common" } + +# extra dependencies for libp2p examples +libp2p = { git = "https://github.com/ChainSafe/rust-libp2p.git", rev = "e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6", features = [ "identify", "macros", "ping", "tokio", "tcp", "dns", "websocket", "noise", "mplex", "yamux", "gossipsub" ]} +tokio-stream = "0.1.12" +tokio-util = { version = "0.7", features = ["codec"] } +parking_lot = "0.12" +hex = "0.4" + +[features] +libp2p-vanilla = [] \ No newline at end of file diff --git a/sdk/rust/nym-sdk/examples/libp2p_chat/README.md b/sdk/rust/nym-sdk/examples/libp2p_chat/README.md new file mode 100644 index 0000000000..0287329769 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_chat/README.md @@ -0,0 +1,64 @@ +# rust-libp2p-nym + +This repo contains an example implementation of a libp2p transport using the Nym mixnet. It relies on the ChainSafe's fork of libp2p: https://github.com/ChainSafe/rust-libp2p + +## Requirements + +- Rust 1.68.2 +- `Protoc` protobuf compiler. On Debian/Ubuntu distributed via `apt` as `protobuf-compiler` & on Arch/Manjaro via AUR as `[python-protobuf-compiler](https://aur.archlinux.org/packages/python-protobuf-compiler)`. + +## Usage + +To instantiate a libp2p swarm using the transport: + +```rust +use libp2p::core::{muxing::StreamMuxerBox, transport::Transport}; +use libp2p::swarm::{keep_alive::Behaviour, SwarmBuilder}; +use libp2p::{identity, PeerId}; +use nym_sdk::mixnet::MixnetClient; +use rust_libp2p_nym::transport::NymTransport; +use rust_libp2p_nym::test_utils::create_nym_client; +use std::error::Error; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + info!("Local peer id: {local_peer_id:?}"); + + let nym_id = rand::random::().to_string(); + let nym_client = MixnetClient::connect_new().await.unwrap(); + let transport = NymTransport::new(nym_client, local_key.clone()).await?; + let _swarm = SwarmBuilder::with_tokio_executor( + transport + .map(|a, _| (a.0, StreamMuxerBox::new(a.1))) + .boxed(), + Behaviour::default(), + local_peer_id, + ) + .build(); + Ok(()) +} +``` + +## Chat example + +To run the libp2p chat example, run the following in one terminal: +```bash +cargo run --example libp2p_chat +# Local peer id: PeerId("12D3KooWLukBu6q2FerWPFhFFhiYaJkhn2sBmceh9UCaXe6hJf5D") +# Listening on "/nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve" +``` + +In another terminal, run ping again, passing the Nym multiaddress printed previously: +```bash +cargo run --example libp2p_chat -- /nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve +# Local peer id: PeerId("12D3KooWNsuRwG6DHnFJCDR8B3zdvja6xLcfnbtKCsQWJ8eppyWC") +# Dialed /nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve +# Listening on "/nym/2oiRW5C9ivyF3Bo3Gpm4H9EqSKH7A6GpcrRRwVSDVUQ9.EajgCnhzimsP6KskUwKcEj8VFCmHR78s2J6FHWcZ4etR@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf" +``` + +You should see that the nodes connected and sent messages to each other: +```bash +# 2023-08-10T14:06:28.116Z INFO libp2p_chat > Got message: 'hello world' with id: 37393732353836333838333537303637303237 from peer: 12D3KooWB6k8ZGDF44N4FMRhgVBNihwk1wMYSumosxiZq9pUTbAz +``` diff --git a/sdk/rust/nym-sdk/examples/libp2p_chat/main.rs b/sdk/rust/nym-sdk/examples/libp2p_chat/main.rs new file mode 100644 index 0000000000..04e1ecca57 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_chat/main.rs @@ -0,0 +1,171 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! A basic chat application with logs demonstrating libp2p and the gossipsub protocol +//! combined with mDNS for the discovery of peers to gossip with. +//! +//! Using two terminal windows, start two instances, typing the following in each: +//! +//! ```sh +//! cargo run +//! ``` +//! +//! Mutual mDNS discovery may take a few seconds. When each peer does discover the other +//! it will print a message like: +//! +//! ```sh +//! mDNS discovered a new peer: {peerId} +//! ``` +//! +//! Type a message and hit return: the message is sent and printed in the other terminal. +//! Close with Ctrl-c. +//! +//! You can open more terminal windows and add more peers using the same line above. +//! +//! Once an additional peer is mDNS discovered it can participate in the conversation +//! and all peers will receive messages sent from it. +//! +//! If a participant exits (Control-C or otherwise) the other peers will receive an mDNS expired +//! event and remove the expired peer from the list of known peers. + +use crate::rust_libp2p_nym::transport::NymTransport; +use futures::{prelude::*, select}; +use libp2p::Multiaddr; +use libp2p::{ + core::muxing::StreamMuxerBox, + gossipsub, identity, + swarm::NetworkBehaviour, + swarm::{SwarmBuilder, SwarmEvent}, + PeerId, Transport, +}; +use log::{error, info, LevelFilter}; +use nym_sdk::mixnet::MixnetClient; +use std::collections::hash_map::DefaultHasher; +use std::error::Error; +use std::hash::{Hash, Hasher}; +use std::time::Duration; +use tokio::io; +use tokio_util::codec; + +#[path = "../libp2p_shared/lib.rs"] +mod rust_libp2p_nym; + +// We create a custom network behaviour that uses Gossipsub +#[derive(NetworkBehaviour)] +struct Behaviour { + gossipsub: gossipsub::Behaviour, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + pretty_env_logger::formatted_timed_builder() + .filter_level(LevelFilter::Warn) + .filter(Some("libp2p_chat"), LevelFilter::Info) + .init(); + + // Create a random PeerId + let id_keys = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(id_keys.public()); + info!("Local peer id: {local_peer_id}"); + + // To content-address message, we can take the hash of message and use it as an ID. + let message_id_fn = |message: &gossipsub::Message| { + let mut s = DefaultHasher::new(); + message.data.hash(&mut s); + gossipsub::MessageId::from(s.finish().to_string()) + }; + + // Set a custom gossipsub configuration + let gossipsub_config = gossipsub::ConfigBuilder::default() + .heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space + .validation_mode(gossipsub::ValidationMode::Strict) // This sets the kind of message validation. The default is Strict (enforce message signing) + .message_id_fn(message_id_fn) // content-address messages. No two messages of the same content will be propagated. + .build() + .expect("Valid config"); + + // build a gossipsub network behaviour + let mut gossipsub = gossipsub::Behaviour::new( + gossipsub::MessageAuthenticity::Signed(id_keys), + gossipsub_config, + ) + .expect("Correct configuration"); + // Create a Gossipsub topic + let topic = gossipsub::IdentTopic::new("test-net"); + // subscribes to our topic + gossipsub.subscribe(&topic)?; + + let client = MixnetClient::connect_new().await.unwrap(); + info!("client address: {}", client.nym_address()); + + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + info!("Local peer id: {local_peer_id:?}"); + + let transport = NymTransport::new(client, local_key).await?; + + let mut swarm = SwarmBuilder::with_tokio_executor( + transport + .map(|a, _| (a.0, StreamMuxerBox::new(a.1))) + .boxed(), + Behaviour { gossipsub }, + local_peer_id, + ) + .build(); + + if let Some(addr) = std::env::args().nth(1) { + let remote: Multiaddr = addr.parse()?; + swarm.dial(remote)?; + info!("Dialed {addr}") + } + + // Read full lines from stdin + let mut stdin = codec::FramedRead::new(io::stdin(), codec::LinesCodec::new()).fuse(); + + info!("Enter messages via STDIN and they will be sent to connected peers using Gossipsub"); + + // Kick it off + loop { + select! { + line = stdin.select_next_some() => { + if let Err(e) = swarm + .behaviour_mut().gossipsub + .publish(topic.clone(), line.expect("Stdin not to close").as_bytes()) { + error!("Publish error: {e:?}"); + } + }, + event = swarm.select_next_some() => { + match event { + SwarmEvent::Behaviour(BehaviourEvent::Gossipsub(gossipsub::Event::Message { + propagation_source: peer_id, + message_id: id, + message, + })) => info!( + "Got message: '{}' with id: {id} from peer: {peer_id}", + String::from_utf8_lossy(&message.data), + ), + SwarmEvent::NewListenAddr { address, .. } => { + info!("Local node is listening on {address}"); + } + other => {info!("other event: {:?}", other)} + } + } + } + } +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_ping/README.md b/sdk/rust/nym-sdk/examples/libp2p_ping/README.md new file mode 100644 index 0000000000..2218793e89 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_ping/README.md @@ -0,0 +1,79 @@ +# rust-libp2p-nym + +This repo contains an example implementation of a libp2p transport using the Nym mixnet. It relies on the ChainSafe's fork of libp2p: https://github.com/ChainSafe/rust-libp2p + +## Requirements + +- Rust 1.68.2 +- `Protoc` protobuf compiler. On Debian/Ubuntu distributed via `apt` as `protobuf-compiler` & on Arch/Manjaro via AUR as `[python-protobuf-compiler](https://aur.archlinux.org/packages/python-protobuf-compiler)`. + +## Usage + +To instantiate a libp2p swarm using the transport: + +```rust +use libp2p::core::{muxing::StreamMuxerBox, transport::Transport}; +use libp2p::swarm::{keep_alive::Behaviour, SwarmBuilder}; +use libp2p::{identity, PeerId}; +use nym_sdk::mixnet::MixnetClient; +use rust_libp2p_nym::transport::NymTransport; +use rust_libp2p_nym::test_utils::create_nym_client; +use std::error::Error; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + info!("Local peer id: {local_peer_id:?}"); + + let nym_id = rand::random::().to_string(); + let nym_client = MixnetClient::connect_new().await.unwrap(); + let transport = NymTransport::new(nym_client, local_key.clone()).await?; + let _swarm = SwarmBuilder::with_tokio_executor( + transport + .map(|a, _| (a.0, StreamMuxerBox::new(a.1))) + .boxed(), + Behaviour::default(), + local_peer_id, + ) + .build(); + Ok(()) +} +``` + +## Ping example + +To run the libp2p ping example, run the following in one terminal: +```bash +cargo run --example libp2p_ping +# Local peer id: PeerId("12D3KooWLukBu6q2FerWPFhFFhiYaJkhn2sBmceh9UCaXe6hJf5D") +# Listening on "/nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve" +``` + +In another terminal, run ping again, passing the Nym multiaddress printed previously: +```bash +cargo run --example libp2p_ping -- /nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve +# Local peer id: PeerId("12D3KooWNsuRwG6DHnFJCDR8B3zdvja6xLcfnbtKCsQWJ8eppyWC") +# Dialed /nym/FhtkzizQg2JbZ19kGkRKXdjV2QnFbT5ww88ZAKaD4nkF.7Remi4UVYzn1yL3qYtEcQBGh6tzTYxMdYB4uqyHVc5Z4@62F81C9GrHDRja9WCqozemRFSzFPMecY85MbGwn6efve +# Listening on "/nym/2oiRW5C9ivyF3Bo3Gpm4H9EqSKH7A6GpcrRRwVSDVUQ9.EajgCnhzimsP6KskUwKcEj8VFCmHR78s2J6FHWcZ4etR@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf" +``` + +You should see that the nodes connected and pinged each other: +```bash +# Mar 30 22:56:36.400 INFO ping: BehaviourEvent: Event { peer: PeerId("12D3KooWGf2oYd6U2nrLzfDrN9zxsjSQjPsMh2oDJPUQ9hiHMNtf"), result: Ok(Ping { rtt: 1.06836675s }) } +``` +```bash +# Mar 30 22:56:35.595 INFO ping: BehaviourEvent: Event { peer: PeerId("12D3KooWMd5ak31DXuZq7x1JuFSR6toA5RDQrPaHrfXEhy7vqqpC"), result: Ok(Pong) } +``` + +In order to run the ping example with vanilla libp2p, which uses tcp, pass the +`--features libp2p-vanilla` flag to the example and follow the instructions on the +rust-libp2p project as usual. + +```bash +RUST_LOG=ping=debug cargo run --example ping --features libp2p-vanilla +``` + +```bash +RUST_LOG=ping=debug cargo run --example ping --features libp2p-vanilla -- "/ip4/127.0.0.1/tcp/$PORT" +``` diff --git a/sdk/rust/nym-sdk/examples/libp2p_ping/main.rs b/sdk/rust/nym-sdk/examples/libp2p_ping/main.rs new file mode 100644 index 0000000000..24ea089c10 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_ping/main.rs @@ -0,0 +1,142 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Ping example +//! +//! See ../src/tutorial.rs for a step-by-step guide building the example below. +//! +//! In the first terminal window, run: +//! +//! ```sh +//! cargo run --example ping --features=full +//! ``` +//! +//! It will print the PeerId and the listening addresses, e.g. `Listening on +//! "/ip4/0.0.0.0/tcp/24915"` +//! +//! In the second terminal window, start a new instance of the example with: +//! +//! ```sh +//! cargo run --example ping --features=full -- /ip4/127.0.0.1/tcp/24915 +//! ``` +//! +//! The two nodes establish a connection, negotiate the ping protocol +//! and begin pinging each other. + +use libp2p::futures::StreamExt; +use libp2p::ping::Success; +use libp2p::swarm::{keep_alive, NetworkBehaviour, SwarmEvent}; +use libp2p::{identity, ping, Multiaddr, PeerId}; +use log::{debug, info, LevelFilter}; +use nym_sdk::mixnet::MixnetClient; +use std::error::Error; +use std::time::Duration; + +#[path = "../libp2p_shared/lib.rs"] +mod rust_libp2p_nym; + +#[tokio::main] +async fn main() -> Result<(), Box> { + pretty_env_logger::formatted_timed_builder() + .filter_level(LevelFilter::Warn) + .filter(Some("libp2p_ping"), LevelFilter::Debug) + .init(); + + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); + info!("Local peer id: {local_peer_id:?}"); + + #[cfg(not(feature = "libp2p-vanilla"))] + let mut swarm = { + debug!("Running `ping` example using NymTransport"); + use libp2p::core::{muxing::StreamMuxerBox, transport::Transport}; + use libp2p::swarm::SwarmBuilder; + use rust_libp2p_nym::transport::NymTransport; + + let client = MixnetClient::connect_new().await.unwrap(); + + let transport = NymTransport::new(client, local_key.clone()).await?; + SwarmBuilder::with_tokio_executor( + transport + .map(|a, _| (a.0, StreamMuxerBox::new(a.1))) + .boxed(), + Behaviour::default(), + local_peer_id, + ) + .build() + }; + + #[cfg(feature = "libp2p-vanilla")] + let mut swarm = { + debug!("Running `ping` example using the vanilla libp2p tokio_development_transport"); + let transport = libp2p::tokio_development_transport(local_key)?; + let mut swarm = + libp2p::Swarm::with_tokio_executor(transport, Behaviour::default(), local_peer_id); + swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; + swarm + }; + + // Dial the peer identified by the multi-address given as the second + // command-line argument, if any. + if let Some(addr) = std::env::args().nth(1) { + let remote: Multiaddr = addr.parse()?; + swarm.dial(remote)?; + info!("Dialed {addr}") + } + + let mut total_ping_rtt: Duration = Duration::from_micros(0); + let mut counter: u128 = 0; + loop { + match swarm.select_next_some().await { + SwarmEvent::NewListenAddr { address, .. } => info!("Listening on {address:?}"), + SwarmEvent::Behaviour(event) => { + // Get the round-trip duration for the pings. + // This value is already captured in the BehaviourEvent::Ping's `Success::Ping` + // field. + debug!("{event:?}"); + if let BehaviourEvent::Ping(ping_event) = event { + let result: Success = ping_event.result?; + match result { + Success::Ping { rtt } => { + counter += 1; + total_ping_rtt += rtt; + let average_ping_rtt = Duration::from_micros( + (total_ping_rtt.as_micros() / counter).try_into().unwrap(), + ); + info!("Ping RTT: {rtt:?} AVERAGE RTT: ({counter} pings): {average_ping_rtt:?}"); + } + Success::Pong => info!("Pong Event"), + } + } + } + _ => {} + } + } +} + +/// Our network behaviour. +/// +/// For illustrative purposes, this includes the [`KeepAlive`](behaviour::KeepAlive) behaviour so a continuous sequence of +/// pings can be observed. +#[derive(NetworkBehaviour, Default)] +struct Behaviour { + keep_alive: keep_alive::Behaviour, + ping: ping::Behaviour, +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs new file mode 100644 index 0000000000..fd5c41ccd1 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs @@ -0,0 +1,418 @@ +use libp2p::core::{muxing::StreamMuxerEvent, PeerId, StreamMuxer}; +use log::debug; +use nym_sphinx::addressing::clients::Recipient; +use std::{ + collections::{HashMap, HashSet}, + pin::Pin, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, + task::{Context, Poll, Waker}, +}; +use tokio::sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + oneshot, +}; + +use super::error::Error; +use super::message::{ + ConnectionId, Message, OutboundMessage, SubstreamId, SubstreamMessage, SubstreamMessageType, + TransportMessage, +}; +use super::substream::Substream; + +/// Connection represents the result of a connection setup process. +/// It implements `StreamMuxer` and thus has stream multiplexing built in. +#[derive(Debug)] +pub struct Connection { + pub(crate) peer_id: PeerId, + pub(crate) remote_recipient: Recipient, + pub(crate) id: ConnectionId, + + /// receive inbound messages from the `InnerConnection` + pub(crate) inbound_rx: UnboundedReceiver, + + /// substream ID -> outbound pending substream exists + /// the key is deleted when the response is received, or the request times out + pending_substreams: HashSet, + + /// substream ID -> substream's inbound_tx channel + substream_inbound_txs: HashMap>>, + + /// substream ID -> substream's close_tx channel + substream_close_txs: HashMap>, + + /// send messages to the mixnet + /// used for sending `SubstreamMessageType::OpenRequest` messages + /// also passed to each substream so they can write to the mixnet + pub(crate) mixnet_outbound_tx: UnboundedSender, + + /// inbound substream open requests; used in poll_inbound + inbound_open_tx: UnboundedSender, + inbound_open_rx: UnboundedReceiver, + + /// closed substream IDs; used in poll_close + close_tx: UnboundedSender, + close_rx: UnboundedReceiver, + + /// message nonce contains the next nonce that should be used when + /// sending a message over the connection + pub(crate) message_nonce: Arc, + + waker: Option, +} + +impl Connection { + pub(crate) fn new( + peer_id: PeerId, + remote_recipient: Recipient, + id: ConnectionId, + inbound_rx: UnboundedReceiver, + mixnet_outbound_tx: UnboundedSender, + ) -> Self { + let (inbound_open_tx, inbound_open_rx) = unbounded_channel(); + let (close_tx, close_rx) = unbounded_channel(); + + Connection { + peer_id, + remote_recipient, + id, + inbound_rx, + pending_substreams: HashSet::new(), + substream_inbound_txs: HashMap::new(), + substream_close_txs: HashMap::new(), + mixnet_outbound_tx, + inbound_open_tx, + inbound_open_rx, + close_tx, + close_rx, + message_nonce: Arc::new(AtomicU64::new(1)), + waker: None, + } + } + + fn new_outbound_substream(&mut self) -> Result { + let substream_id = SubstreamId::generate(); + let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); + + // send the substream open request that requests to open a substream with the given ID + self.mixnet_outbound_tx + .send(OutboundMessage { + recipient: self.remote_recipient, + message: Message::TransportMessage(TransportMessage { + nonce, + id: self.id.clone(), + message: SubstreamMessage { + substream_id: substream_id.clone(), + message_type: SubstreamMessageType::OpenRequest, + }, + }), + }) + .map_err(|e| Error::OutboundSendError(e.to_string()))?; + + // track pending outbound substreams + // TODO we should probably lock this? storing map values should be atomic + let res = self.new_substream(substream_id.clone()); + if res.is_ok() { + self.pending_substreams.insert(substream_id); + } + res + } + + // creates a new substream instance with the given ID. + fn new_substream(&mut self, id: SubstreamId) -> Result { + // check we don't already have a substream with this ID + if self.substream_inbound_txs.get(&id).is_some() { + return Err(Error::SubstreamIdExists(id)); + } + + let (inbound_tx, inbound_rx) = unbounded_channel::>(); + let (close_tx, close_rx) = oneshot::channel::<()>(); + self.substream_inbound_txs.insert(id.clone(), inbound_tx); + self.substream_close_txs.insert(id.clone(), close_tx); + + if let Some(waker) = self.waker.take() { + waker.wake(); + } + + Ok(Substream::new( + self.remote_recipient, + self.id.clone(), + id, + inbound_rx, + self.mixnet_outbound_tx.clone(), + close_rx, + self.message_nonce.clone(), + )) + } + + fn handle_close(&mut self, substream_id: SubstreamId) -> Result<(), Error> { + if self.substream_inbound_txs.remove(&substream_id).is_none() { + return Err(Error::SubstreamIdDoesNotExist(substream_id)); + } + + // notify substream that it's closed + let close_tx = self.substream_close_txs.remove(&substream_id); + close_tx.unwrap().send(()).unwrap(); + + // notify poll_close that the substream is closed + self.close_tx + .send(substream_id) + .map_err(|e| Error::InboundSendError(e.to_string())) + } +} + +impl StreamMuxer for Connection { + type Substream = Substream; + type Error = Error; + + fn poll_inbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + if let Poll::Ready(Some(substream)) = self.inbound_open_rx.poll_recv(cx) { + return Poll::Ready(Ok(substream)); + } + + Poll::Pending + } + + fn poll_outbound( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(self.new_outbound_substream()) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if let Poll::Ready(Some(_)) = self.close_rx.poll_recv(cx) { + return Poll::Ready(Ok(())); + } + + Poll::Pending + } + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + while let Poll::Ready(Some(msg)) = self.inbound_rx.poll_recv(cx) { + match msg.message_type { + SubstreamMessageType::OpenRequest => { + // create a new substream with the given ID + let substream = self.new_substream(msg.substream_id.clone())?; + let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); + + // send the response to the remote peer + self.mixnet_outbound_tx + .send(OutboundMessage { + recipient: self.remote_recipient, + message: Message::TransportMessage(TransportMessage { + nonce, + id: self.id.clone(), + message: SubstreamMessage { + substream_id: msg.substream_id.clone(), + message_type: SubstreamMessageType::OpenResponse, + }, + }), + }) + .map_err(|e| Error::OutboundSendError(e.to_string()))?; + debug!("wrote OpenResponse for substream: {:?}", &msg.substream_id); + + // send the substream to our own channel to be returned in poll_inbound + self.inbound_open_tx + .send(substream) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + + debug!("new inbound substream: {:?}", &msg.substream_id); + } + SubstreamMessageType::OpenResponse => { + if !self.pending_substreams.remove(&msg.substream_id) { + debug!( + "SubstreamMessageType::OpenResponse no substream pending for ID: {:?}", + &msg.substream_id + ); + } + } + SubstreamMessageType::Close => { + self.handle_close(msg.substream_id)?; + } + SubstreamMessageType::Data(data) => { + debug!("SubstreamMessageType::Data: {:?}", &data); + let inbound_tx = self + .substream_inbound_txs + .get_mut(&msg.substream_id) + .expect("must have a substream channel for substream"); + + // NOTE: this ignores channel closed errors, which is fine because the substream + // might have been closed/dropped + inbound_tx.send(data).ok(); + } + } + } + + self.waker = Some(cx.waker().clone()); + Poll::Pending + } +} + +/// PendingConnection represents a connection that's been initiated, but not completed. +pub(crate) struct PendingConnection { + pub(crate) remote_recipient: Recipient, + pub(crate) connection_tx: oneshot::Sender, +} + +impl PendingConnection { + pub(crate) fn new( + remote_recipient: Recipient, + connection_tx: oneshot::Sender, + ) -> Self { + PendingConnection { + remote_recipient, + connection_tx, + } + } +} + +#[cfg(test)] +mod test { + use super::super::message::InboundMessage; + use super::super::mixnet::initialize_mixnet; + use super::*; + use futures::future::poll_fn; + use futures::{AsyncReadExt, AsyncWriteExt, FutureExt}; + use nym_sdk::mixnet::MixnetClient; + + async fn inbound_receive_and_send( + connection_id: ConnectionId, + mixnet_inbound_rx: &mut UnboundedReceiver, + inbound_tx: &UnboundedSender, + expected_nonce: u64, + ) { + let recv_msg = mixnet_inbound_rx.recv().await.unwrap(); + match recv_msg.0 { + Message::TransportMessage(TransportMessage { + nonce, + id, + message: msg, + }) => { + assert_eq!(nonce, expected_nonce); + assert_eq!(id, connection_id); + inbound_tx.send(msg).unwrap(); + } + _ => panic!("unexpected message"), + } + } + + #[tokio::test] + async fn test_connection_stream_muxer() { + let client = MixnetClient::connect_new().await.unwrap(); + let (sender_address, mut sender_mixnet_inbound_rx, sender_outbound_tx) = + initialize_mixnet(client, None).await.unwrap(); + + let client2 = MixnetClient::connect_new().await.unwrap(); + + let (recipient_address, mut recipient_mixnet_inbound_rx, recipient_outbound_tx) = + initialize_mixnet(client2, None).await.unwrap(); + + let connection_id = ConnectionId::generate(); + + let recipient_peer_id = PeerId::random(); + let sender_peer_id = PeerId::random(); + + // create the connections + let (sender_inbound_tx, sender_inbound_rx) = unbounded_channel::(); + let mut sender_connection = Connection::new( + recipient_peer_id, + recipient_address, + connection_id.clone(), + sender_inbound_rx, + sender_outbound_tx, + ); + let (recipient_inbound_tx, recipient_inbound_rx) = unbounded_channel::(); + let mut recipient_connection = Connection::new( + sender_peer_id, + sender_address, + connection_id.clone(), + recipient_inbound_rx, + recipient_outbound_tx, + ); + + // send the substream OpenRequest to the mixnet + let mut sender_substream = sender_connection.new_outbound_substream().unwrap(); + assert!(sender_connection + .pending_substreams + .contains(&sender_substream.substream_id)); + assert_eq!(sender_connection.message_nonce.load(Ordering::SeqCst), 2); + + // poll the recipient inbound stream; should receive the OpenRequest and create the substream + inbound_receive_and_send( + connection_id.clone(), + &mut recipient_mixnet_inbound_rx, + &recipient_inbound_tx, + 1, + ) + .await; + poll_fn(|cx| Pin::new(&mut recipient_connection).as_mut().poll(cx)).now_or_never(); + assert_eq!(recipient_connection.message_nonce.load(Ordering::SeqCst), 2); + + // poll recipient's poll_inbound to receive the substream + let maybe_recipient_substream = poll_fn(|cx| { + Pin::new(&mut recipient_connection) + .as_mut() + .poll_inbound(cx) + }) + .now_or_never(); + let mut recipient_substream = maybe_recipient_substream.unwrap().unwrap(); + + // poll sender's connection to receive the OpenResponse and send it to the Connection inbound channel + inbound_receive_and_send( + connection_id.clone(), + &mut sender_mixnet_inbound_rx, + &sender_inbound_tx, + 1, + ) + .await; + + // poll sender's poll_outbound to get the substream + poll_fn(|cx| Pin::new(&mut sender_connection).as_mut().poll(cx)).now_or_never(); + assert!(sender_connection.pending_substreams.is_empty()); + + // finally, write message to the substream + let data = b"hello world"; + sender_substream.write_all(data).await.unwrap(); + assert_eq!(sender_connection.message_nonce.load(Ordering::SeqCst), 3); + + // receive message from the mixnet, push to the recipient Connection inbound channel + inbound_receive_and_send( + connection_id.clone(), + &mut recipient_mixnet_inbound_rx, + &recipient_inbound_tx, + 2, + ) + .await; + + // poll the sender's connection to send the msg from the connection inbound channel to the substream's + poll_fn(|cx| Pin::new(&mut sender_connection).as_mut().poll(cx)).now_or_never(); + + // poll the recipient's connection to read the msg from the mixnet and mux it into the substream + poll_fn(|cx| Pin::new(&mut recipient_connection).as_mut().poll(cx)).now_or_never(); + + let mut buf = [0u8; 11]; + let n = recipient_substream.read(&mut buf).await.unwrap(); + assert_eq!(n, 11); + assert_eq!(buf, data[..]); + + // test closing the stream; assert the stream is closed on both sides + sender_substream.close().await.unwrap(); + assert_eq!(sender_connection.message_nonce.load(Ordering::SeqCst), 4); + inbound_receive_and_send( + connection_id.clone(), + &mut recipient_mixnet_inbound_rx, + &recipient_inbound_tx, + 3, + ) + .await; + } +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/error.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/error.rs new file mode 100644 index 0000000000..bf1ebc8175 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/error.rs @@ -0,0 +1,64 @@ +use libp2p::core::multiaddr; +use nym_sphinx::addressing::clients::RecipientFormattingError; + +use super::message::SubstreamId; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("unimplemented")] + Unimplemented, + #[error("failed to format multiaddress from nym address")] + FailedToFormatMultiaddr(#[from] multiaddr::Error), + #[error("unexpected protocol in multiaddress")] + InvalidProtocolForMultiaddr, + #[error("failed to decode message")] + InvalidMessageBytes, + #[error("no connection found for ConnectionResponse")] + NoConnectionForResponse, + #[error("received ConnectionResponse but connection was already established")] + ConnectionAlreadyEstablished, + #[error("received None recipient in ConnectionRequest")] + NoneRecipientInConnectionRequest, + #[error("cannot handle connection request; already have connection with given ID")] + ConnectionIDExists, + #[error("no connection found for TransportMessage")] + NoConnectionForTransportMessage, + #[error("failed to decode ConnectionMessage; too short")] + ConnectionMessageBytesTooShort, + #[error("failed to decode ConnectionMessage; no recipient")] + ConnectionMessageBytesNoRecipient, + #[error("failed to decode ConnectionMessage; no peer ID")] + ConnectionMessageBytesNoPeerId, + #[error("invalid peer ID bytes")] + InvalidPeerIdBytes, + #[error("invalid recipient bytes")] + InvalidRecipientBytes(#[from] RecipientFormattingError), + #[error("invalid recipient prefix byte")] + InvalidRecipientPrefixByte, + #[error("failed to decode TransportMessage; too short")] + TransportMessageBytesTooShort, + #[error("failed to decode TransportMessage; invalid nonce")] + InvalidNonce, + #[error("invalid substream ID")] + InvalidSubstreamMessageBytes, + #[error("invalid substream message type byte")] + InvalidSubstreamMessageType, + #[error("substrean with given ID already exists")] + SubstreamIdExists(SubstreamId), + #[error("no substream found for given ID")] + SubstreamIdDoesNotExist(SubstreamId), + #[error("recv error: channel closed")] + OneshotRecvError(#[from] tokio::sync::oneshot::error::RecvError), + #[error("recv error: channel closed")] + RecvError, + #[error("outbound send error")] + OutboundSendError(String), + #[error("inbound send error")] + InboundSendError(String), + #[error("failed to send new connection; receiver dropped")] + ConnectionSendError, + #[error("failed to send initial TransportEvent::NewAddress")] + SendErrorTransportEvent, + #[error("dial timed out")] + DialTimeout(#[from] tokio::time::error::Elapsed), +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/lib.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/lib.rs new file mode 100644 index 0000000000..ba33f1d91f --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/lib.rs @@ -0,0 +1,10 @@ +pub(crate) mod connection; +pub mod error; +pub(crate) mod message; +pub(crate) mod mixnet; +pub(crate) mod queue; +pub mod substream; +pub mod transport; + +/// The deafult timeout secs for [`transport::Upgrade`] future. +const DEFAULT_HANDSHAKE_TIMEOUT_SECS: u64 = 5; diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/message.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/message.rs new file mode 100644 index 0000000000..9dc4c7085b --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/message.rs @@ -0,0 +1,335 @@ +use libp2p::core::PeerId; +use nym_sphinx::addressing::clients::Recipient; +use rand::rngs::OsRng; +use rand::RngCore; +use std::fmt::{Debug, Formatter}; + +use super::error::Error; + +const RECIPIENT_LENGTH: usize = Recipient::LEN; +const CONNECTION_ID_LENGTH: usize = 32; +const SUBSTREAM_ID_LENGTH: usize = 32; + +const NONCE_BYTES_LEN: usize = 8; // length of u64 +const MIN_CONNECTION_MESSAGE_LEN: usize = CONNECTION_ID_LENGTH + NONCE_BYTES_LEN; + +/// ConnectionId is a unique, randomly-generated per-connection ID that's used to +/// identify which connection a message belongs to. +#[derive(Clone, Default, Eq, Hash, PartialEq)] +pub(crate) struct ConnectionId([u8; 32]); + +impl ConnectionId { + pub(crate) fn generate() -> Self { + let mut bytes = [0u8; 32]; + OsRng.fill_bytes(&mut bytes); + ConnectionId(bytes) + } + + fn from_bytes(bytes: &[u8]) -> Self { + let mut id = [0u8; 32]; + id[..].copy_from_slice(&bytes[0..CONNECTION_ID_LENGTH]); + ConnectionId(id) + } +} + +impl Debug for ConnectionId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} + +/// SubstreamId is a unique, randomly-generated per-substream ID that's used to +/// identify which substream a message belongs to. +#[derive(Clone, Default, Eq, Hash, PartialEq)] +pub struct SubstreamId(pub(crate) [u8; 32]); + +impl SubstreamId { + pub(crate) fn generate() -> Self { + let mut bytes = [0u8; 32]; + OsRng.fill_bytes(&mut bytes); + SubstreamId(bytes) + } + + fn from_bytes(bytes: &[u8]) -> Self { + let mut id = [0u8; 32]; + id[..].copy_from_slice(&bytes[0..SUBSTREAM_ID_LENGTH]); + SubstreamId(id) + } +} + +impl Debug for SubstreamId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} + +#[derive(Debug)] +#[allow(clippy::enum_variant_names)] +pub(crate) enum Message { + ConnectionRequest(ConnectionMessage), + ConnectionResponse(ConnectionMessage), + TransportMessage(TransportMessage), +} + +/// ConnectionMessage is exchanged to open a new connection. +#[derive(Debug)] +pub(crate) struct ConnectionMessage { + pub(crate) peer_id: PeerId, + pub(crate) id: ConnectionId, + /// recipient is the sender's Nym address. + /// only required if this is a ConnectionRequest. + pub(crate) recipient: Option, +} + +/// TransportMessage is sent over a connection after establishment. +#[derive(Debug, Clone)] +pub(crate) struct TransportMessage { + /// increments by 1 for every TransportMessage sent over a connection. + /// required for ordering, since Nym does not guarantee ordering. + /// ConnectionMessages do not need nonces, as we know that they will + /// be the first messages sent over a connection. + /// the first TransportMessage sent over a connection will have nonce 1. + pub(crate) nonce: u64, + pub(crate) message: SubstreamMessage, + pub(crate) id: ConnectionId, +} + +impl Message { + fn try_from_bytes(bytes: Vec) -> Result { + if bytes.len() < 2 { + return Err(Error::InvalidMessageBytes); + } + + Ok(match bytes[0] { + 0 => Message::ConnectionRequest(ConnectionMessage::try_from_bytes(&bytes[1..])?), + 1 => Message::ConnectionResponse(ConnectionMessage::try_from_bytes(&bytes[1..])?), + 2 => Message::TransportMessage(TransportMessage::try_from_bytes(&bytes[1..])?), + _ => return Err(Error::InvalidMessageBytes), + }) + } +} + +impl ConnectionMessage { + fn to_bytes(&self) -> Vec { + let mut bytes = self.id.0.to_vec(); + match self.recipient { + Some(recipient) => { + bytes.push(1u8); + bytes.append(&mut recipient.to_bytes().to_vec()); + } + None => bytes.push(0u8), + } + bytes.append(&mut self.peer_id.to_bytes()); + bytes + } + + fn try_from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < CONNECTION_ID_LENGTH + 1 { + return Err(Error::ConnectionMessageBytesTooShort); + } + + let id = ConnectionId::from_bytes(&bytes[0..CONNECTION_ID_LENGTH]); + let recipient = match bytes[CONNECTION_ID_LENGTH] { + 0u8 => None, + 1u8 => { + if bytes.len() < CONNECTION_ID_LENGTH + 1 + RECIPIENT_LENGTH { + return Err(Error::ConnectionMessageBytesNoRecipient); + } + + let mut recipient_bytes = [0u8; RECIPIENT_LENGTH]; + recipient_bytes[..].copy_from_slice( + &bytes[CONNECTION_ID_LENGTH + 1..CONNECTION_ID_LENGTH + 1 + RECIPIENT_LENGTH], + ); + Some( + Recipient::try_from_bytes(recipient_bytes) + .map_err(Error::InvalidRecipientBytes)?, + ) + } + _ => { + return Err(Error::InvalidRecipientPrefixByte); + } + }; + let peer_id = match recipient { + Some(_) => { + if bytes.len() < CONNECTION_ID_LENGTH + RECIPIENT_LENGTH + 2 { + return Err(Error::ConnectionMessageBytesNoPeerId); + } + PeerId::from_bytes(&bytes[CONNECTION_ID_LENGTH + 1 + RECIPIENT_LENGTH..]) + .map_err(|_| Error::InvalidPeerIdBytes)? + } + None => { + if bytes.len() < CONNECTION_ID_LENGTH + 2 { + return Err(Error::ConnectionMessageBytesNoPeerId); + } + PeerId::from_bytes(&bytes[CONNECTION_ID_LENGTH + 1..]) + .map_err(|_| Error::InvalidPeerIdBytes)? + } + }; + Ok(ConnectionMessage { + peer_id, + recipient, + id, + }) + } +} + +impl TransportMessage { + fn to_bytes(&self) -> Vec { + let mut bytes = self.nonce.to_be_bytes().to_vec(); + bytes.extend_from_slice(self.id.0.as_ref()); + bytes.extend_from_slice(&self.message.to_bytes()); + bytes + } + + fn try_from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < MIN_CONNECTION_MESSAGE_LEN + 1 { + return Err(Error::TransportMessageBytesTooShort); + } + + let nonce = u64::from_be_bytes( + bytes[0..NONCE_BYTES_LEN] + .to_vec() + .try_into() + .map_err(|_| Error::InvalidNonce)?, + ); + let id = ConnectionId::from_bytes(&bytes[NONCE_BYTES_LEN..MIN_CONNECTION_MESSAGE_LEN]); + let message = SubstreamMessage::try_from_bytes(&bytes[MIN_CONNECTION_MESSAGE_LEN..])?; + Ok(TransportMessage { nonce, message, id }) + } +} + +impl Ord for TransportMessage { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.nonce.cmp(&other.nonce) + } +} + +impl std::cmp::PartialOrd for TransportMessage { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl std::cmp::Eq for TransportMessage {} + +impl std::cmp::PartialEq for TransportMessage { + fn eq(&self, other: &Self) -> bool { + self.nonce == other.nonce + } +} + +#[derive(Debug, Clone)] +pub(crate) enum SubstreamMessageType { + OpenRequest, + OpenResponse, + Close, + Data(Vec), +} + +impl SubstreamMessageType { + fn to_u8(&self) -> u8 { + match self { + SubstreamMessageType::OpenRequest => 0, + SubstreamMessageType::OpenResponse => 1, + SubstreamMessageType::Close => 2, + SubstreamMessageType::Data(_) => 3, + } + } +} + +/// SubstreamMessage is a message sent over a substream. +#[derive(Debug, Clone)] +pub(crate) struct SubstreamMessage { + pub(crate) substream_id: SubstreamId, + pub(crate) message_type: SubstreamMessageType, +} + +impl SubstreamMessage { + pub(crate) fn new_with_data(substream_id: SubstreamId, message: Vec) -> Self { + SubstreamMessage { + substream_id, + message_type: SubstreamMessageType::Data(message), + } + } + + pub(crate) fn new_close(substream_id: SubstreamId) -> Self { + SubstreamMessage { + substream_id, + message_type: SubstreamMessageType::Close, + } + } + + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes = self.substream_id.0.clone().to_vec(); + bytes.push(self.message_type.to_u8()); + if let SubstreamMessageType::Data(message) = &self.message_type { + bytes.extend_from_slice(message); + } + bytes + } + + pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < SUBSTREAM_ID_LENGTH + 1 { + return Err(Error::InvalidSubstreamMessageBytes); + } + + let substream_id = SubstreamId::from_bytes(&bytes[0..SUBSTREAM_ID_LENGTH]); + let message_type = match bytes[SUBSTREAM_ID_LENGTH] { + 0 => SubstreamMessageType::OpenRequest, + 1 => SubstreamMessageType::OpenResponse, + 2 => SubstreamMessageType::Close, + 3 => { + if bytes.len() < SUBSTREAM_ID_LENGTH + 2 { + return Err(Error::InvalidSubstreamMessageBytes); + } + SubstreamMessageType::Data(bytes[SUBSTREAM_ID_LENGTH + 1..].to_vec()) + } + _ => return Err(Error::InvalidSubstreamMessageType), + }; + + Ok(SubstreamMessage { + substream_id, + message_type, + }) + } +} + +impl Message { + pub(crate) fn to_bytes(&self) -> Vec { + match self { + Message::ConnectionRequest(msg) => { + let mut bytes = 0_u8.to_be_bytes().to_vec(); + bytes.append(&mut msg.to_bytes()); + bytes + } + Message::ConnectionResponse(msg) => { + let mut bytes = 1_u8.to_be_bytes().to_vec(); + bytes.append(&mut msg.to_bytes()); + bytes + } + Message::TransportMessage(msg) => { + let mut bytes = 2_u8.to_be_bytes().to_vec(); + bytes.append(&mut msg.to_bytes()); + bytes + } + } + } +} + +/// InboundMessage represents an inbound mixnet message. +pub(crate) struct InboundMessage(pub(crate) Message); + +/// OutboundMessage represents an outbound mixnet message. +#[derive(Debug)] +pub(crate) struct OutboundMessage { + pub(crate) message: Message, + pub(crate) recipient: Recipient, +} + +pub(crate) fn parse_message_data(data: &[u8]) -> Result { + if data.len() < 2 { + return Err(Error::InvalidMessageBytes); + } + let msg = Message::try_from_bytes(data.to_vec())?; + Ok(InboundMessage(msg)) +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs new file mode 100644 index 0000000000..f75048203b --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs @@ -0,0 +1,164 @@ +use futures::{pin_mut, select}; +use futures::{FutureExt, StreamExt}; +use log::debug; +use nym_sdk::mixnet::{IncludedSurbs, MixnetClient, MixnetClientSender, MixnetMessageSender}; +use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::receiver::ReconstructedMessage; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; + +use super::error::Error; +use super::message::*; + +/// initialize_mixnet initializes a read/write connection to a Nym websockets endpoint. +/// It starts a task that listens for inbound messages from the endpoint and writes outbound messages to the endpoint. +pub(crate) async fn initialize_mixnet( + client: MixnetClient, + notify_inbound_tx: Option>, +) -> Result< + ( + Recipient, + UnboundedReceiver, + UnboundedSender, + ), + Error, +> { + let recipient = *client.nym_address(); + + // a channel of inbound messages from the mixnet.. + // the transport reads from (listens) to the inbound_rx. + // TODO: this is probably a DOS vector; we should limit the size of the channel. + let (inbound_tx, inbound_rx) = unbounded_channel::(); + + // a channel of outbound messages to be written to the mixnet. + // the transport writes to outbound_tx. + let (outbound_tx, mut outbound_rx) = unbounded_channel::(); + + let sink = client.split_sender(); + let mut stream = client; + + tokio::task::spawn(async move { + loop { + let t1 = check_inbound(&mut stream, &inbound_tx, ¬ify_inbound_tx).fuse(); + let t2 = check_outbound(&sink, &mut outbound_rx).fuse(); + + pin_mut!(t1, t2); + + select! { + _ = t1 => {}, + _ = t2 => {}, + }; + } + }); + + Ok((recipient, inbound_rx, outbound_tx)) +} + +async fn check_inbound( + client: &mut MixnetClient, + inbound_tx: &UnboundedSender, + notify_inbound_tx: &Option>, +) -> Result<(), Error> { + if let Some(msg) = client.next().await { + if let Some(notify_tx) = notify_inbound_tx { + notify_tx + .send(()) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + } + + handle_inbound(msg, inbound_tx).await?; + } + + Err(Error::Unimplemented) +} + +async fn handle_inbound( + msg: ReconstructedMessage, + inbound_tx: &UnboundedSender, +) -> Result<(), Error> { + let data = parse_message_data(&msg.message)?; + inbound_tx + .send(data) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + Ok(()) +} + +async fn check_outbound( + mixnet_sender: &MixnetClientSender, + outbound_rx: &mut UnboundedReceiver, +) -> Result<(), Error> { + match outbound_rx.recv().await { + Some(message) => { + write_bytes( + mixnet_sender, + message.recipient, + &message.message.to_bytes(), + ) + .await + } + None => Err(Error::RecvError), + } +} + +async fn write_bytes( + mixnet_sender: &MixnetClientSender, + recipient: Recipient, + message: &[u8], +) -> Result<(), Error> { + if let Err(_err) = mixnet_sender + .send_message(recipient, message, IncludedSurbs::ExposeSelfAddress) + .await + { + return Err(Error::Unimplemented); + } + + debug!( + "wrote message to mixnet: recipient: {:?}", + recipient.to_string() + ); + Ok(()) +} + +#[cfg(test)] +mod test { + use super::super::message::{ + self, ConnectionId, Message, SubstreamId, SubstreamMessage, SubstreamMessageType, + TransportMessage, + }; + use super::super::mixnet::initialize_mixnet; + use nym_sdk::mixnet::MixnetClient; + + #[tokio::test] + async fn test_mixnet_poll_inbound_and_outbound() { + let client = MixnetClient::connect_new().await.unwrap(); + let (self_address, mut inbound_rx, outbound_tx) = + initialize_mixnet(client, None).await.unwrap(); + let msg_inner = "hello".as_bytes(); + let substream_id = SubstreamId::generate(); + let msg = Message::TransportMessage(TransportMessage { + nonce: 1, // arbitrary + id: ConnectionId::generate(), + message: SubstreamMessage::new_with_data(substream_id.clone(), msg_inner.to_vec()), + }); + + // send a message to ourselves through the mixnet + let out_msg = message::OutboundMessage { + message: msg, + recipient: self_address, + }; + + outbound_tx.send(out_msg).unwrap(); + + // receive the message from ourselves over the mixnet + let received_msg = inbound_rx.recv().await.unwrap(); + if let Message::TransportMessage(recv_msg) = received_msg.0 { + assert_eq!(substream_id, recv_msg.message.substream_id); + if let SubstreamMessageType::Data(data) = recv_msg.message.message_type { + assert_eq!(msg_inner, data.as_slice()); + } else { + panic!("expected SubstreamMessage::Data") + } + } else { + panic!("expected Message::TransportMessage") + } + } +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/queue.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/queue.rs new file mode 100644 index 0000000000..2b59c82877 --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/queue.rs @@ -0,0 +1,140 @@ +use log::{debug, warn}; +use std::collections::BTreeSet; + +use super::message::TransportMessage; + +/// MessageQueue is a queue of messages, ordered by nonce, that we've +/// received but are not yet able to process because we're waiting for +/// a message with the next expected nonce first. +/// This is required because Nym does not guarantee any sort of message +/// ordering, only delivery. +/// TODO: is there a DOS vector here where a malicious peer sends us +/// messages only with nonce higher than the next expected nonce? +pub(crate) struct MessageQueue { + /// nonce of the next message we expect to receive on the + /// connection. + /// any messages with a nonce greater than this are pushed into + /// the queue. + /// if we get a message with a nonce equal to this, then we + /// immediately handle it in the transport and increment the nonce. + next_expected_nonce: u64, + + /// the actual queue of messages, ordered by nonce. + /// the head of the queue's nonce is always greater + /// than the next expected nonce. + queue: BTreeSet, +} + +impl MessageQueue { + pub(crate) fn new() -> Self { + MessageQueue { + next_expected_nonce: 0, + queue: BTreeSet::new(), + } + } + + pub(crate) fn print_nonces(&self) { + let nonces = self.queue.iter().map(|msg| msg.nonce).collect::>(); + debug!("MessageQueue: {:?}", nonces); + } + + /// sets the next expected nonce to 1, indicating that we've received + /// a ConnectionRequest or ConnectionResponse. + pub(crate) fn set_connection_message_received(&mut self) { + if self.next_expected_nonce != 0 { + panic!("connection message received twice"); + } + + self.next_expected_nonce = self.next_expected_nonce.wrapping_add(1); + } + + /// tries to push a message into the queue. + /// if the message has the next expected nonce, then the message is returned, + /// and should be processed by the caller. + /// in that case, the internal queue's next expected nonce is incremented. + pub(crate) fn try_push(&mut self, msg: TransportMessage) -> Option { + if msg.nonce == self.next_expected_nonce { + self.next_expected_nonce = self.next_expected_nonce.wrapping_add(1); + Some(msg) + } else { + if msg.nonce < self.next_expected_nonce { + // this shouldn't happen normally, only if the other node + // is not following the protocol + warn!("received a message with a nonce that is too low"); + return None; + } + + if !self.queue.insert(msg) { + // this shouldn't happen normally, only if the other node + // is not following the protocol + warn!("received a message with a duplicate nonce"); + return None; + } + + None + } + } + + pub(crate) fn pop(&mut self) -> Option { + let Some(head) = self.queue.first() else { + return None; + }; + + if head.nonce == self.next_expected_nonce { + self.next_expected_nonce = self.next_expected_nonce.wrapping_add(1); + Some(self.queue.pop_first().unwrap()) + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use super::super::message::{ConnectionId, SubstreamId, SubstreamMessage}; + + use super::*; + + impl TransportMessage { + fn new(nonce: u64, message: SubstreamMessage, id: ConnectionId) -> Self { + TransportMessage { nonce, message, id } + } + } + + #[test] + fn test_message_queue() { + let mut queue = MessageQueue::new(); + + let test_substream_message = + SubstreamMessage::new_with_data(SubstreamId::generate(), vec![1, 2, 3]); + let connection_id = ConnectionId::generate(); + + let msg1 = TransportMessage::new(1, test_substream_message.clone(), connection_id.clone()); + let msg2 = TransportMessage::new(2, test_substream_message.clone(), connection_id.clone()); + let msg3 = TransportMessage::new(3, test_substream_message.clone(), connection_id.clone()); + + assert_eq!(queue.try_push(msg1.clone()), None); + assert_eq!(queue.try_push(msg3.clone()), None); + assert_eq!(queue.try_push(msg2.clone()), None); + + assert_eq!(queue.pop(), None); + + // set expected nonce to 1 + queue.set_connection_message_received(); + assert_eq!(queue.pop(), Some(msg1)); + + let msg4 = TransportMessage::new(4, test_substream_message.clone(), connection_id.clone()); + assert_eq!(queue.try_push(msg4.clone()), None); + + assert_eq!(queue.pop(), Some(msg2)); + assert_eq!(queue.pop(), Some(msg3)); + assert_eq!(queue.pop(), Some(msg4)); + assert_eq!(queue.pop(), None); + assert_eq!(queue.next_expected_nonce, 5); + + // should just return the message and increment nonce when message nonce = next expected nonce + let msg5 = TransportMessage::new(5, test_substream_message, connection_id); + assert_eq!(queue.try_push(msg5.clone()), Some(msg5)); + assert_eq!(queue.next_expected_nonce, 6); + } +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/substream.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/substream.rs new file mode 100644 index 0000000000..f7620a835c --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/substream.rs @@ -0,0 +1,422 @@ +use super::message::{ + ConnectionId, Message, OutboundMessage, SubstreamId, SubstreamMessage, TransportMessage, +}; +use futures::{ + io::{Error as IoError, ErrorKind}, + AsyncRead, AsyncWrite, +}; +use log::debug; +use nym_sphinx::addressing::clients::Recipient; +use parking_lot::Mutex; +use std::{ + pin::Pin, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, + task::{Context, Poll}, +}; +use tokio::sync::{ + mpsc::{UnboundedReceiver, UnboundedSender}, + oneshot::Receiver, +}; + +#[derive(Debug)] +pub struct Substream { + remote_recipient: Recipient, + connection_id: ConnectionId, + pub(crate) substream_id: SubstreamId, + + /// inbound messages; inbound_tx is in the corresponding Connection + pub(crate) inbound_rx: UnboundedReceiver>, + + /// outbound messages; go directly to the mixnet + outbound_tx: UnboundedSender, + + /// used to signal when the substream is closed + close_rx: Receiver<()>, + closed: Mutex, + + // buffer of data that's been written to the stream, + // but not yet read by the application. + unread_data: Mutex>, + + message_nonce: Arc, +} + +impl Substream { + pub(crate) fn new( + remote_recipient: Recipient, + connection_id: ConnectionId, + substream_id: SubstreamId, + inbound_rx: UnboundedReceiver>, + outbound_tx: UnboundedSender, + close_rx: Receiver<()>, + message_nonce: Arc, + ) -> Self { + Substream { + remote_recipient, + connection_id, + substream_id, + inbound_rx, + outbound_tx, + close_rx, + closed: Mutex::new(false), + unread_data: Mutex::new(vec![]), + message_nonce, + } + } + + fn check_closed(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Result<(), IoError> { + let closed_err = IoError::new(ErrorKind::Other, "stream closed"); + + // close_rx will return an error if the channel is closed (ie. sender was dropped), + // or if it's empty + let received_closed = self.close_rx.try_recv(); + + let mut closed = self.closed.lock(); + if *closed { + return Err(closed_err); + } + + if received_closed.is_ok() { + *closed = true; + return Err(closed_err); + } + + Ok(()) + } +} + +impl AsyncRead for Substream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let closed_result = self.as_mut().check_closed(cx); + if let Err(e) = closed_result { + return Poll::Ready(Err(e)); + } + + let inbound_rx_data = self.inbound_rx.poll_recv(cx); + + // first, write any previously unread data to the buf + let mut unread_data = self.unread_data.lock(); + let filled_len = if unread_data.len() > 0 { + let unread_len = unread_data.len(); + let buf_len = buf.len(); + let copy_len = std::cmp::min(unread_len, buf_len); + buf[..copy_len].copy_from_slice(&unread_data[..copy_len]); + *unread_data = unread_data[copy_len..].to_vec(); + copy_len + } else { + 0 + }; + + if let Poll::Ready(Some(data)) = inbound_rx_data { + if filled_len == buf.len() { + // we've filled the buffer, so we'll have to save the rest for later + let mut new = vec![]; + new.extend(unread_data.drain(..)); + new.extend(data.iter()); + *unread_data = new; + return Poll::Ready(Ok(filled_len)); + } + + // otherwise, there's still room in the buffer, so we'll copy the rest of the data + let remaining_len = buf.len() - filled_len; + let data_len = data.len(); + + // we have more data than buffer room remaining, save the extra for later + if remaining_len < data_len { + unread_data.extend_from_slice(&data[remaining_len..]); + } + + let copied = std::cmp::min(remaining_len, data_len); + buf[filled_len..filled_len + copied].copy_from_slice(&data[..copied]); + debug!("poll_read copied {} bytes: data {:?}", copied, buf); + return Poll::Ready(Ok(copied)); + } + + if filled_len > 0 { + debug!("poll_read copied {} bytes: data {:?}", filled_len, buf); + return Poll::Ready(Ok(filled_len)); + } + + Poll::Pending + } +} + +impl AsyncWrite for Substream { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + if let Err(e) = self.as_mut().check_closed(cx) { + return Poll::Ready(Err(e)); + } + + let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); + + self.outbound_tx + .send(OutboundMessage { + recipient: self.remote_recipient, + message: Message::TransportMessage(TransportMessage { + nonce, + id: self.connection_id.clone(), + message: SubstreamMessage::new_with_data( + self.substream_id.clone(), + buf.to_vec(), + ), + }), + }) + .map_err(|e| { + IoError::new( + ErrorKind::Other, + format!("poll_write outbound_tx error: {}", e), + ) + })?; + + Poll::Ready(Ok(buf.len())) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if let Err(e) = self.check_closed(cx) { + return Poll::Ready(Err(e)); + } + + Poll::Ready(Ok(())) + } + + fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); + + let mut closed = self.closed.lock(); + if *closed { + return Poll::Ready(Err(IoError::new(ErrorKind::Other, "stream closed"))); + } + + *closed = true; + + // send a close message to the mixnet + self.outbound_tx + .send(OutboundMessage { + recipient: self.remote_recipient, + message: Message::TransportMessage(TransportMessage { + nonce, + id: self.connection_id.clone(), + message: SubstreamMessage::new_close(self.substream_id.clone()), + }), + }) + .map_err(|e| { + IoError::new( + ErrorKind::Other, + format!("poll_close outbound_rx error: {}", e), + ) + })?; + + Poll::Ready(Ok(())) + } +} + +#[cfg(test)] +mod test { + use super::super::message::{ + ConnectionId, Message, SubstreamId, SubstreamMessage, TransportMessage, + }; + use super::super::mixnet::initialize_mixnet; + use super::Substream; + use futures::{AsyncReadExt, AsyncWriteExt}; + use nym_sdk::mixnet::MixnetClient; + use nym_sphinx::addressing::clients::Recipient; + use std::sync::atomic::AtomicU64; + use std::sync::Arc; + + #[tokio::test] + async fn test_substream_poll_read_unread_data() { + let (outbound_tx, _) = tokio::sync::mpsc::unbounded_channel(); + let connection_id = ConnectionId::generate(); + let substream_id = SubstreamId::generate(); + + let (inbound_tx, inbound_rx) = tokio::sync::mpsc::unbounded_channel(); + let (_, close_rx) = tokio::sync::oneshot::channel(); + + let mut substream = Substream::new( + Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), + connection_id, + substream_id, + inbound_rx, + outbound_tx, + close_rx, + Arc::new(AtomicU64::new(1)), + ); + + // test writing and reading w/ same length data + let data = b"hello".to_vec(); + inbound_tx.send(data.clone()).unwrap(); + let mut buf = [0u8; 5]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, data.len()); + assert_eq!(buf.to_vec(), data); + + // test writing data longer than read buffer + let data = b"nootwashere".to_vec(); + inbound_tx.send(data.clone()).unwrap(); + + let mut buf = [0u8; 4]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, buf.len()); + assert_eq!(buf.to_vec(), b"noot".to_vec()); + + let mut buf = [0u8; 7]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, buf.len()); + assert_eq!(buf.to_vec(), b"washere".to_vec()); + + // test read buffer larger than written data + let data = b"nootwashere".to_vec(); + inbound_tx.send(data.clone()).unwrap(); + let mut buf = [0u8; 16]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, data.len()); + assert_eq!(buf[..data.len()], data); + assert_eq!(buf[data.len()..].to_vec(), vec![0u8; 16 - data.len()]); + + // test writing data longer than read buffer multiple times + let data = b"nootwashere".to_vec(); + inbound_tx.send(data.clone()).unwrap(); + + let mut buf = [0u8; 4]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, buf.len()); + assert_eq!(buf.to_vec(), b"noot".to_vec()); + + let data = b"asdf".to_vec(); + inbound_tx.send(data.clone()).unwrap(); + + let mut buf = [0u8; 4]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, buf.len()); + assert_eq!(buf.to_vec(), b"wash".to_vec()); + + let mut buf = [0u8; 8]; + let read_len = substream.read(&mut buf).await.unwrap(); + assert_eq!(read_len, 7); + assert_eq!(buf[..7], b"ereasdf".to_vec()); + } + + #[tokio::test] + async fn test_substream_read_write() { + let client = MixnetClient::connect_new().await.unwrap(); + let (self_address, mut mixnet_inbound_rx, outbound_tx) = + initialize_mixnet(client, None).await.unwrap(); + + const MSG_INNER: &[u8] = "hello".as_bytes(); + let connection_id = ConnectionId::generate(); + let substream_id = SubstreamId::generate(); + + let (inbound_tx, inbound_rx) = tokio::sync::mpsc::unbounded_channel(); + let (_, close_rx) = tokio::sync::oneshot::channel(); + + let mut substream = Substream::new( + self_address, + connection_id, + substream_id, + inbound_rx, + outbound_tx, + close_rx, + Arc::new(AtomicU64::new(1)), + ); + + // send message to ourselves over the mixnet + substream.write_all(MSG_INNER).await.unwrap(); + + // receive full message over the mixnet + let recv_msg = mixnet_inbound_rx.recv().await.unwrap(); + match recv_msg.0 { + Message::TransportMessage(TransportMessage { + nonce, + id: _, + message: + SubstreamMessage { + substream_id: _, + message_type: msg, + }, + }) => { + assert_eq!(nonce, 1); + match msg { + super::super::message::SubstreamMessageType::Data(data) => { + assert_eq!(data, MSG_INNER); + // send message to substream inbound channel + inbound_tx.send(data).unwrap(); + } + _ => panic!("unexpected message type"), + } + } + _ => panic!("unexpected message"), + } + + // read message from substream + let mut buf = [0u8; MSG_INNER.len()]; + substream.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, MSG_INNER); + + // close substream + substream.close().await.unwrap(); + + // try to read/write to closed substream; should error + substream.write_all(MSG_INNER).await.unwrap_err(); + substream.read_exact(&mut buf).await.unwrap_err(); + + // assert a close message was sent over the mixnet + let recv_msg = mixnet_inbound_rx.recv().await.unwrap(); + match recv_msg.0 { + Message::TransportMessage(TransportMessage { + nonce: _, + id: _, + message: + SubstreamMessage { + substream_id: _, + message_type: msg, + }, + }) => match msg { + super::super::message::SubstreamMessageType::Close => {} + _ => panic!("unexpected message type"), + }, + _ => panic!("unexpected message: {:?}", recv_msg.0), + } + } + + #[tokio::test] + async fn test_substream_recv_close() { + let client = MixnetClient::connect_new().await.unwrap(); + let (self_address, _, outbound_tx) = initialize_mixnet(client, None).await.unwrap(); + + const MSG_INNER: &[u8] = "hello".as_bytes(); + let connection_id = ConnectionId::generate(); + let substream_id = SubstreamId::generate(); + + let (_, inbound_rx) = tokio::sync::mpsc::unbounded_channel(); + let (close_tx, close_rx) = tokio::sync::oneshot::channel(); + + let mut substream = Substream::new( + self_address, + connection_id, + substream_id, + inbound_rx, + outbound_tx, + close_rx, + Arc::new(AtomicU64::new(1)), + ); + + // close substream + close_tx.send(()).unwrap(); + + // try to read/write to closed substream; should error + substream.write_all(MSG_INNER).await.unwrap_err(); + let mut buf = [0u8; MSG_INNER.len()]; + substream.read_exact(&mut buf).await.unwrap_err(); + } +} diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs new file mode 100644 index 0000000000..88142cc37f --- /dev/null +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs @@ -0,0 +1,897 @@ +use futures::prelude::*; +use libp2p::core::{ + identity::Keypair, + multiaddr::{Multiaddr, Protocol}, + transport::{ListenerId, TransportError, TransportEvent}, + PeerId, Transport, +}; +use log::debug; +use nym_sdk::mixnet::MixnetClient; +use nym_sphinx::addressing::clients::Recipient; +use std::{ + collections::HashMap, + pin::Pin, + str::FromStr, + task::{Context, Poll, Waker}, +}; +use tokio::{ + sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + oneshot, + }, + time::{timeout, Duration}, +}; +use tokio_stream::wrappers::UnboundedReceiverStream; + +use super::connection::{Connection, PendingConnection}; +use super::error::Error; +use super::message::{ + ConnectionId, ConnectionMessage, InboundMessage, Message, OutboundMessage, SubstreamMessage, + TransportMessage, +}; +use super::mixnet::initialize_mixnet; +use super::queue::MessageQueue; +use super::DEFAULT_HANDSHAKE_TIMEOUT_SECS; + +/// InboundTransportEvent represents an inbound event from the mixnet. +pub enum InboundTransportEvent { + ConnectionRequest(Upgrade), + ConnectionResponse, + TransportMessage, +} + +/// NymTransport implements the Transport trait using the Nym mixnet. +pub struct NymTransport { + /// our Nym address + self_address: Recipient, + pub(crate) listen_addr: Multiaddr, + pub(crate) listener_id: ListenerId, + + /// our libp2p keypair; currently not really used + keypair: Keypair, + + /// established connections -> channel which sends messages received from + /// the mixnet to the corresponding Connection + connections: HashMap>, + + /// outbound pending dials + pending_dials: HashMap, + + /// connection message queues + message_queues: HashMap, + + /// inbound mixnet messages + inbound_stream: UnboundedReceiverStream, + + /// outbound mixnet messages + outbound_tx: UnboundedSender, + + /// inbound messages for Transport.poll() + poll_rx: UnboundedReceiver>, + + /// outbound messages to Transport.poll() + poll_tx: UnboundedSender>, + + waker: Option, + + /// Timeout for the [`Upgrade`] future. + handshake_timeout: Duration, +} + +impl NymTransport { + /// New transport. + pub async fn new(client: MixnetClient, keypair: Keypair) -> Result { + Self::new_maybe_with_notify_inbound(client, keypair, None, None).await + } + + /// New transport with a timeout. + #[allow(dead_code)] + pub async fn new_with_timeout( + client: MixnetClient, + keypair: Keypair, + timeout: Duration, + ) -> Result { + Self::new_maybe_with_notify_inbound(client, keypair, None, Some(timeout)).await + } + + /// Add timeout to transport and return self. + #[allow(dead_code)] + pub fn with_timeout(mut self, timeout: Duration) -> Self { + self.handshake_timeout = timeout; + self + } + + async fn new_maybe_with_notify_inbound( + client: MixnetClient, + keypair: Keypair, + notify_inbound_tx: Option>, + timeout: Option, + ) -> Result { + let (self_address, inbound_rx, outbound_tx) = + initialize_mixnet(client, notify_inbound_tx).await?; + let listen_addr = nym_address_to_multiaddress(self_address)?; + let listener_id = ListenerId::new(); + + let (poll_tx, poll_rx) = unbounded_channel::>(); + + poll_tx + .send(TransportEvent::NewAddress { + listener_id, + listen_addr: listen_addr.clone(), + }) + .map_err(|_| Error::SendErrorTransportEvent)?; + + let inbound_stream = UnboundedReceiverStream::new(inbound_rx); + let handshake_timeout = + timeout.unwrap_or_else(|| Duration::from_secs(DEFAULT_HANDSHAKE_TIMEOUT_SECS)); + + Ok(Self { + self_address, + listen_addr, + listener_id, + keypair, + connections: HashMap::new(), + pending_dials: HashMap::new(), + message_queues: HashMap::new(), + inbound_stream, + outbound_tx, + poll_rx, + poll_tx, + waker: None, + handshake_timeout, + }) + } + + pub(crate) fn peer_id(&self) -> PeerId { + PeerId::from_public_key(&self.keypair.public()) + } + + fn handle_message_queue_on_connection_initiation( + &mut self, + id: &ConnectionId, + ) -> Result<(), Error> { + debug!("handle_message_queue_on_connection_initiation"); + let Some(inbound_tx) = self.connections.get(id) else { + // this should not happen + return Err(Error::NoConnectionForTransportMessage); + }; + + match self.message_queues.get_mut(id) { + Some(queue) => { + // update expected nonce + queue.set_connection_message_received(); + + // push pending inbound some messages in this case + while let Some(msg) = queue.pop() { + debug!( + "popped queued message with nonce {} for connection", + msg.nonce + ); + inbound_tx + .send(msg.message.clone()) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + } + } + None => { + // no queue exists for this connection, create one + let queue = MessageQueue::new(); + self.message_queues.insert(id.clone(), queue); + let queue = self.message_queues.get_mut(id).unwrap(); + queue.set_connection_message_received(); + } + }; + + debug!("returning from handle_message_queue_on_connection_initiation"); + Ok(()) + } + + // handle_connection_response resolves the pending connection corresponding to the response + // (if there is one) into a Connection. + fn handle_connection_response(&mut self, msg: &ConnectionMessage) -> Result<(), Error> { + if self.connections.contains_key(&msg.id) { + return Err(Error::ConnectionAlreadyEstablished); + } + + if let Some(pending_conn) = self.pending_dials.remove(&msg.id) { + // resolve connection and put into pending_conn channel + let (conn, conn_tx) = self.create_connection_types( + msg.peer_id, + pending_conn.remote_recipient, + msg.id.clone(), + ); + + self.connections.insert(msg.id.clone(), conn_tx); + self.handle_message_queue_on_connection_initiation(&msg.id)?; + + pending_conn + .connection_tx + .send(conn) + .map_err(|_| Error::ConnectionSendError)?; + + if let Some(waker) = self.waker.take() { + waker.wake(); + } + + Ok(()) + } else { + Err(Error::NoConnectionForResponse) + } + } + + /// handle_connection_request handles an incoming connection request, sends back a + /// connection response, and finally completes the upgrade into a Connection. + fn handle_connection_request(&mut self, msg: &ConnectionMessage) -> Result { + if msg.recipient.is_none() { + return Err(Error::NoneRecipientInConnectionRequest); + } + + // ensure we don't already have a conn with the same id + if self.connections.get(&msg.id).is_some() { + return Err(Error::ConnectionIDExists); + } + + let (conn, conn_tx) = + self.create_connection_types(msg.peer_id, msg.recipient.unwrap(), msg.id.clone()); + self.connections.insert(msg.id.clone(), conn_tx); + self.handle_message_queue_on_connection_initiation(&msg.id)?; + + let resp = ConnectionMessage { + peer_id: self.peer_id(), + recipient: None, + id: msg.id.clone(), + }; + + self.outbound_tx + .send(OutboundMessage { + message: Message::ConnectionResponse(resp), + recipient: msg.recipient.unwrap(), + }) + .map_err(|e| Error::OutboundSendError(e.to_string()))?; + + if let Some(waker) = self.waker.take() { + waker.wake(); + } + + Ok(conn) + } + + fn handle_transport_message(&mut self, msg: TransportMessage) -> Result<(), Error> { + let queue = match self.message_queues.get_mut(&msg.id) { + Some(queue) => queue, + None => { + // no queue exists for this connection, create one + let queue = MessageQueue::new(); + self.message_queues.insert(msg.id.clone(), queue); + self.message_queues.get_mut(&msg.id).unwrap() + } + }; + + queue.print_nonces(); + + let nonce = msg.nonce; + let Some(msg) = queue.try_push(msg) else { + // don't push the message yet, it's been queued + debug!("message with nonce {} queued for connection", nonce); + return Ok(()); + }; + + let Some(inbound_tx) = self.connections.get(&msg.id) else { + return Err(Error::NoConnectionForTransportMessage); + }; + + // send original message + debug!( + "sending original message with nonce {} for connection", + nonce + ); + inbound_tx + .send(msg.message.clone()) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + + // try to pop queued messages and send them on inbound channel + while let Some(msg) = queue.pop() { + debug!( + "popped queued message with nonce {} for connection", + msg.nonce + ); + inbound_tx + .send(msg.message.clone()) + .map_err(|e| Error::InboundSendError(e.to_string()))?; + } + + if let Some(waker) = self.waker.clone().take() { + waker.wake(); + } + + Ok(()) + } + + fn create_connection_types( + &self, + remote_peer_id: PeerId, + recipient: Recipient, + id: ConnectionId, + ) -> (Connection, UnboundedSender) { + let (inbound_tx, inbound_rx) = unbounded_channel::(); + + // representation of a connection; this contains channels for applications to read/write to. + let conn = Connection::new( + remote_peer_id, + recipient, + id, + inbound_rx, + self.outbound_tx.clone(), + ); + + // inbound_tx is what we write to when receiving messages on the mixnet, + (conn, inbound_tx) + } + + /// handle_inbound handles an inbound message from the mixnet, received via self.inbound_stream. + fn handle_inbound(&mut self, msg: Message) -> Result { + match msg { + Message::ConnectionRequest(inner) => { + debug!("got inbound connection request {:?}", inner); + match self.handle_connection_request(&inner) { + Ok(conn) => { + let (connection_tx, connection_rx) = + oneshot::channel::<(PeerId, Connection)>(); + let upgrade = Upgrade::new(connection_rx); + connection_tx + .send((inner.peer_id, conn)) + .map_err(|_| Error::ConnectionSendError)?; + Ok(InboundTransportEvent::ConnectionRequest(upgrade)) + } + Err(e) => Err(e), + } + } + Message::ConnectionResponse(msg) => { + debug!("got inbound connection response {:?}", msg); + self.handle_connection_response(&msg) + .map(|_| InboundTransportEvent::ConnectionResponse) + } + Message::TransportMessage(msg) => { + debug!("got inbound TransportMessage: {:?}", msg); + self.handle_transport_message(msg) + .map(|_| InboundTransportEvent::TransportMessage) + } + } + } +} + +/// Upgrade represents a transport listener upgrade. +/// Note: we immediately upgrade a connection request to a connection, +/// so this only contains a channel for receiving that connection. +pub struct Upgrade { + connection_tx: oneshot::Receiver<(PeerId, Connection)>, +} + +impl Upgrade { + fn new(connection_tx: oneshot::Receiver<(PeerId, Connection)>) -> Upgrade { + Upgrade { connection_tx } + } +} + +impl Future for Upgrade { + type Output = Result<(PeerId, Connection), Error>; + + // poll checks if the upgrade has turned into a connection yet + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.connection_tx + .poll_unpin(cx) + .map_err(|_| Error::RecvError) + } +} + +impl Transport for NymTransport { + type Output = (PeerId, Connection); + type Error = Error; + type ListenerUpgrade = Upgrade; + type Dial = Pin> + Send>>; + + fn listen_on(&mut self, _: Multiaddr) -> Result> { + // we should only allow listening on the multiaddress containing our Nym address + Ok(self.listener_id) + } + + fn remove_listener(&mut self, id: ListenerId) -> bool { + if self.listener_id != id { + return false; + } + + // TODO: close channels? + self.poll_tx + .send(TransportEvent::ListenerClosed { + listener_id: id, + reason: Ok(()), + }) + .expect("failed to send listener closed event"); + true + } + + fn dial(&mut self, addr: Multiaddr) -> Result> { + debug!("dialing {}", addr); + + let id = ConnectionId::generate(); + + // create remote recipient address + let recipient = multiaddress_to_nym_address(addr).map_err(TransportError::Other)?; + + // create pending conn structs and store + let (connection_tx, connection_rx) = oneshot::channel::(); + + let inner_pending_conn = PendingConnection::new(recipient, connection_tx); + self.pending_dials.insert(id.clone(), inner_pending_conn); + + // put ConnectionRequest message into outbound message channel + let msg = ConnectionMessage { + peer_id: self.peer_id(), + recipient: Some(self.self_address), + id, + }; + + let outbound_tx = self.outbound_tx.clone(); + + let mut waker = self.waker.clone(); + let handshake_timeout = self.handshake_timeout; + Ok(async move { + outbound_tx + .send(OutboundMessage { + message: Message::ConnectionRequest(msg), + recipient, + }) + .map_err(|e| Error::OutboundSendError(e.to_string()))?; + + debug!("sent outbound ConnectionRequest"); + if let Some(waker) = waker.take() { + waker.wake(); + }; + + let conn = timeout(handshake_timeout, connection_rx).await??; + Ok((conn.peer_id, conn)) + } + .boxed()) + } + + // dial_as_listener currently just calls self.dial(). + fn dial_as_listener( + &mut self, + addr: Multiaddr, + ) -> Result> { + self.dial(addr) + } + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + // new addresses + listener close events + if let Poll::Ready(Some(res)) = self.poll_rx.recv().boxed().poll_unpin(cx) { + return Poll::Ready(res); + } + + // check for and handle inbound messages + while let Poll::Ready(Some(msg)) = self.inbound_stream.poll_next_unpin(cx) { + match self.handle_inbound(msg.0) { + Ok(event) => match event { + InboundTransportEvent::ConnectionRequest(upgrade) => { + debug!("InboundTransportEvent::ConnectionRequest"); + return Poll::Ready(TransportEvent::Incoming { + listener_id: self.listener_id, + upgrade, + local_addr: self.listen_addr.clone(), + send_back_addr: self.listen_addr.clone(), + }); + } + InboundTransportEvent::ConnectionResponse => { + debug!("InboundTransportEvent::ConnectionResponse"); + } + InboundTransportEvent::TransportMessage => { + debug!("InboundTransportEvent::TransportMessage"); + } + }, + Err(e) => { + return Poll::Ready(TransportEvent::ListenerError { + listener_id: self.listener_id, + error: e, + }); + } + }; + } + + self.waker = Some(cx.waker().clone()); + Poll::Pending + } + + fn address_translation(&self, _listen: &Multiaddr, _observed: &Multiaddr) -> Option { + None + } +} + +fn nym_address_to_multiaddress(addr: Recipient) -> Result { + Multiaddr::from_str(&format!("/nym/{}", addr)).map_err(Error::FailedToFormatMultiaddr) +} + +fn multiaddress_to_nym_address(multiaddr: Multiaddr) -> Result { + let mut multiaddr = multiaddr; + match multiaddr.pop().unwrap() { + Protocol::Nym(addr) => Recipient::from_str(&addr).map_err(Error::InvalidRecipientBytes), + _ => Err(Error::InvalidProtocolForMultiaddr), + } +} + +#[cfg(test)] +mod test { + use super::super::connection::Connection; + use super::super::error::Error; + use super::super::message::{ + Message, OutboundMessage, SubstreamId, SubstreamMessage, SubstreamMessageType, + TransportMessage, + }; + use super::super::substream::Substream; + use super::{nym_address_to_multiaddress, NymTransport}; + use futures::{future::poll_fn, AsyncReadExt, AsyncWriteExt, FutureExt}; + use libp2p::core::{ + identity::Keypair, + transport::{Transport, TransportEvent}, + Multiaddr, StreamMuxer, + }; + use log::info; + use nym_bin_common::logging::setup_logging; + use nym_sdk::mixnet::MixnetClient; + use std::{pin::Pin, str::FromStr, sync::atomic::Ordering}; + use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; + + impl Connection { + fn write(&self, msg: SubstreamMessage) -> Result<(), Error> { + let nonce = self.message_nonce.fetch_add(1, Ordering::SeqCst); + self.mixnet_outbound_tx + .send(OutboundMessage { + recipient: self.remote_recipient, + message: Message::TransportMessage(TransportMessage { + nonce, + id: self.id.clone(), + message: msg, + }), + }) + .map_err(|e| Error::OutboundSendError(e.to_string()))?; + Ok(()) + } + } + + impl NymTransport { + async fn new_with_notify_inbound( + client: MixnetClient, + notify_inbound_tx: UnboundedSender<()>, + ) -> Result { + let local_key = Keypair::generate_ed25519(); + Self::new_maybe_with_notify_inbound(client, local_key, Some(notify_inbound_tx), None) + .await + } + } + + #[tokio::test] + async fn test_transport_connection() { + setup_logging(); + + let client = MixnetClient::connect_new().await.unwrap(); + let (dialer_notify_inbound_tx, mut dialer_notify_inbound_rx) = unbounded_channel(); + let mut dialer_transport = + NymTransport::new_with_notify_inbound(client, dialer_notify_inbound_tx) + .await + .unwrap(); + + let client2 = MixnetClient::connect_new().await.unwrap(); + let (listener_notify_inbound_tx, mut listener_notify_inbound_rx) = unbounded_channel(); + let mut listener_transport = + NymTransport::new_with_notify_inbound(client2, listener_notify_inbound_tx) + .await + .unwrap(); + let listener_multiaddr = + nym_address_to_multiaddress(listener_transport.self_address).unwrap(); + assert_new_address_event(Pin::new(&mut dialer_transport)).await; + assert_new_address_event(Pin::new(&mut listener_transport)).await; + + // dial the remote peer + let mut dial = dialer_transport.dial(listener_multiaddr).unwrap(); + + // poll the dial to send the connection request message + assert!(poll_fn(|cx| Pin::new(&mut dial).as_mut().poll_unpin(cx)) + .now_or_never() + .is_none()); + listener_notify_inbound_rx.recv().await.unwrap(); + + // should receive the connection request from the mixnet and send the connection response + let res = poll_fn(|cx| Pin::new(&mut listener_transport).as_mut().poll(cx)).await; + let mut upgrade = match res { + TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + } => { + assert_eq!(listener_id, listener_transport.listener_id); + assert_eq!(local_addr, listener_transport.listen_addr); + assert_eq!(send_back_addr, listener_transport.listen_addr); + upgrade + } + _ => panic!("expected TransportEvent::Incoming, got {:?}", res), + }; + dialer_notify_inbound_rx.recv().await.unwrap(); + + // should receive the connection response from the mixnet + assert!( + poll_fn(|cx| Pin::new(&mut dialer_transport).as_mut().poll(cx)) + .now_or_never() + .is_none() + ); + info!("waiting for connections..."); + + // should be able to resolve the connections now + let (_, mut listener_conn) = poll_fn(|cx| Pin::new(&mut upgrade).as_mut().poll_unpin(cx)) + .now_or_never() + .expect("the upgrade should be ready") + .expect("the upgrade should not error"); + let (_, mut dialer_conn) = poll_fn(|cx| Pin::new(&mut dial).as_mut().poll_unpin(cx)) + .now_or_never() + .expect("the upgrade should be ready") + .expect("the upgrade should not error"); + info!("connections established"); + + // write messages from the dialer to the listener and vice versa + send_and_receive_over_conns( + b"hello".to_vec(), + &mut dialer_conn, + &mut listener_conn, + Pin::new(&mut listener_transport), + &mut listener_notify_inbound_rx, + ) + .await; + send_and_receive_over_conns( + b"hi".to_vec(), + &mut dialer_conn, + &mut listener_conn, + Pin::new(&mut listener_transport), + &mut listener_notify_inbound_rx, + ) + .await; + send_and_receive_over_conns( + b"world".to_vec(), + &mut listener_conn, + &mut dialer_conn, + Pin::new(&mut dialer_transport), + &mut dialer_notify_inbound_rx, + ) + .await; + } + + async fn assert_new_address_event(mut transport: Pin<&mut NymTransport>) { + match poll_fn(|cx| transport.as_mut().poll(cx)).await { + TransportEvent::NewAddress { + listener_id, + listen_addr, + } => { + assert_eq!(listener_id, transport.listener_id); + assert_eq!(listen_addr, transport.listen_addr); + } + _ => panic!("expected TransportEvent::NewAddress"), + } + } + + async fn send_and_receive_over_conns( + msg: Vec, + conn1: &mut Connection, + conn2: &mut Connection, + mut transport2: Pin<&mut NymTransport>, + notify_inbound_rx: &mut UnboundedReceiver<()>, + ) { + // send message over conn1 to conn2 + let substream_id = SubstreamId::generate(); + conn1 + .write(SubstreamMessage::new_with_data( + substream_id.clone(), + msg.clone(), + )) + .unwrap(); + notify_inbound_rx.recv().await.unwrap(); + + // poll transport2 to push message from transport to connection + assert!(poll_fn(|cx| transport2.as_mut().poll(cx)) + .now_or_never() + .is_none()); + let substream_msg = conn2.inbound_rx.recv().await.unwrap(); + if let SubstreamMessageType::Data(data) = substream_msg.message_type { + assert_eq!(data, msg); + } else { + panic!("expected data message"); + } + } + + #[tokio::test] + async fn test_transport_substream() { + let client = MixnetClient::connect_new().await.unwrap(); + + let (dialer_notify_inbound_tx, mut dialer_notify_inbound_rx) = unbounded_channel(); + let mut dialer_transport = + NymTransport::new_with_notify_inbound(client, dialer_notify_inbound_tx) + .await + .unwrap(); + + let client2 = MixnetClient::connect_new().await.unwrap(); + + let (listener_notify_inbound_tx, mut listener_notify_inbound_rx) = unbounded_channel(); + let mut listener_transport = + NymTransport::new_with_notify_inbound(client2, listener_notify_inbound_tx) + .await + .unwrap(); + let listener_multiaddr = + nym_address_to_multiaddress(listener_transport.self_address).unwrap(); + assert_new_address_event(Pin::new(&mut dialer_transport)).await; + assert_new_address_event(Pin::new(&mut listener_transport)).await; + + // dial the remote peer + let mut dial = dialer_transport.dial(listener_multiaddr).unwrap(); + + // poll the dial to send the connection request message + assert!(poll_fn(|cx| Pin::new(&mut dial).as_mut().poll_unpin(cx)) + .now_or_never() + .is_none()); + listener_notify_inbound_rx.recv().await.unwrap(); + + // should receive the connection request from the mixnet and send the connection response + let res = poll_fn(|cx| Pin::new(&mut listener_transport).as_mut().poll(cx)).await; + let mut upgrade = match res { + TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + } => { + assert_eq!(listener_id, listener_transport.listener_id); + assert_eq!(local_addr, listener_transport.listen_addr); + assert_eq!(send_back_addr, listener_transport.listen_addr); + upgrade + } + _ => panic!("expected TransportEvent::Incoming, got {:?}", res), + }; + dialer_notify_inbound_rx.recv().await.unwrap(); + + // should receive the connection response from the mixnet + assert!( + poll_fn(|cx| Pin::new(&mut dialer_transport).as_mut().poll(cx)) + .now_or_never() + .is_none() + ); + info!("waiting for connections..."); + + // should be able to resolve the connections now + let (_, mut listener_conn) = poll_fn(|cx| Pin::new(&mut upgrade).as_mut().poll_unpin(cx)) + .now_or_never() + .unwrap() + .unwrap(); + let (_, mut dialer_conn) = poll_fn(|cx| Pin::new(&mut dial).as_mut().poll_unpin(cx)) + .now_or_never() + .unwrap() + .unwrap(); + info!("connections established"); + + // initiate a new substream from the dialer + let mut dialer_substream = + poll_fn(|cx| Pin::new(&mut dialer_conn).as_mut().poll_outbound(cx)) + .await + .unwrap(); + listener_notify_inbound_rx.recv().await.unwrap(); + + // accept the substream on the listener + assert!( + poll_fn(|cx| Pin::new(&mut listener_transport).as_mut().poll(cx)) + .now_or_never() + .is_none() + ); + poll_fn(|cx| Pin::new(&mut listener_conn).as_mut().poll(cx)).now_or_never(); + + // poll recipient's poll_inbound to receive the substream; sends a response to the sender + let mut listener_substream = + poll_fn(|cx| Pin::new(&mut listener_conn).as_mut().poll_inbound(cx)) + .now_or_never() + .unwrap() + .unwrap(); + info!("got listener substream"); + dialer_notify_inbound_rx.recv().await.unwrap(); + + // poll sender to finalize the substream + assert!( + poll_fn(|cx| Pin::new(&mut dialer_transport).as_mut().poll(cx)) + .now_or_never() + .is_none() + ); + poll_fn(|cx| Pin::new(&mut dialer_conn).as_mut().poll(cx)).now_or_never(); + info!("got dialer substream"); + + // write message from dialer to listener + send_and_receive_substream_message( + b"hello world".to_vec(), + Pin::new(&mut dialer_substream), + Pin::new(&mut listener_substream), + Pin::new(&mut listener_transport), + Pin::new(&mut listener_conn), + &mut listener_notify_inbound_rx, + ) + .await; + + // write message from listener to dialer + send_and_receive_substream_message( + b"hello back".to_vec(), + Pin::new(&mut listener_substream), + Pin::new(&mut dialer_substream), + Pin::new(&mut dialer_transport), + Pin::new(&mut dialer_conn), + &mut dialer_notify_inbound_rx, + ) + .await; + + // close the substream from the dialer side + info!("closing dialer substream"); + dialer_substream.close().await.unwrap(); + listener_notify_inbound_rx.recv().await.unwrap(); + info!("dialer substream closed"); + + // assert we can't read or write to either substream + dialer_substream.write_all(b"hello").await.unwrap_err(); + // poll listener transport and conn to receive the substream close message + poll_fn(|cx| Pin::new(&mut listener_transport).as_mut().poll(cx)).now_or_never(); + poll_fn(|cx| Pin::new(&mut listener_conn).as_mut().poll(cx)).now_or_never(); + listener_substream.write_all(b"hello").await.unwrap_err(); + let mut buf = vec![0u8; 5]; + dialer_substream.read(&mut buf).await.unwrap_err(); + listener_substream.read(&mut buf).await.unwrap_err(); + dialer_substream.close().await.unwrap_err(); + listener_substream.close().await.unwrap_err(); + } + + async fn send_and_receive_substream_message( + data: Vec, + mut sender_substream: Pin<&mut Substream>, + mut recipient_substream: Pin<&mut Substream>, + mut recipient_transport: Pin<&mut NymTransport>, + mut recipient_conn: Pin<&mut Connection>, + recipient_notify_inbound_rx: &mut UnboundedReceiver<()>, + ) { + // write message + sender_substream.write_all(&data).await.unwrap(); + recipient_notify_inbound_rx.recv().await.unwrap(); + + // poll recipient for message + poll_fn(|cx| recipient_transport.as_mut().poll(cx)).now_or_never(); + poll_fn(|cx| recipient_conn.as_mut().poll(cx)).now_or_never(); + let mut buf = vec![0u8; data.len()]; + let n = recipient_substream.read(&mut buf).await.unwrap(); + assert_eq!(n, data.len()); + assert_eq!(buf, data[..]); + } + + #[tokio::test] + async fn test_transport_timeout() { + let client = MixnetClient::connect_new().await.unwrap(); + + let (dialer_notify_inbound_tx, _) = unbounded_channel(); + let mut dialer_transport = + NymTransport::new_with_notify_inbound(client, dialer_notify_inbound_tx) + .await + .unwrap() + .with_timeout(std::time::Duration::from_millis(100)); + + // mock a transport that will never resolve the connection. + let empty_addr = Multiaddr::from_str( + "/nym/Hmer6Ndt3PV13YW53HM8ri4NvqqtfDQUQBhzvKqb1dag.2g478dyxtrQXGWc1Mk2VEqdPcWXpz7EhAcjhdAJtVZdA@AnnYnEtBjB2a5sHmeRCnBq43qxyHDf95Bqd7cwQyKNLR" + ) + .expect("unable to parse multiaddress"); + + let dial = dialer_transport.dial(empty_addr).unwrap(); + assert!(dial + .await + .expect_err("should have timed out") + .to_string() + .contains("dial timed out")); + } +} From 92350daca875523226a889d7d634f9ee883606af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 22 Aug 2023 14:17:03 +0200 Subject: [PATCH 10/44] network-requester: disable poisson process by default (#3783) * network-requester: disable poisson process by default * network-requester: instead add new top-level config field * Remove quoation marks in template --- contracts/name-service/src/contract.rs | 1 - .../name-service/src/integration_tests/register.rs | 1 - .../network-requester/src/allowed_hosts/hosts.rs | 2 +- .../network-requester/src/cli/init.rs | 13 +++++++------ service-providers/network-requester/src/cli/mod.rs | 7 ++++++- service-providers/network-requester/src/cli/run.rs | 1 + .../network-requester/src/config/mod.rs | 14 ++++++++++++-- .../src/config/old_config_v1_1_20_2.rs | 2 +- .../network-requester/src/config/template.rs | 10 +++++++++- 9 files changed, 37 insertions(+), 14 deletions(-) diff --git a/contracts/name-service/src/contract.rs b/contracts/name-service/src/contract.rs index 2001341212..35294382c8 100644 --- a/contracts/name-service/src/contract.rs +++ b/contracts/name-service/src/contract.rs @@ -286,7 +286,6 @@ mod tests { let owner = "steve"; let (name, owner_signature) = new_name_details_with_sign(deps.as_mut(), &mut rng, "my-name", owner, deposit.clone()); - dbg!(&name); // Register let msg = ExecuteMsg::Register { diff --git a/contracts/name-service/src/integration_tests/register.rs b/contracts/name-service/src/integration_tests/register.rs index aeed4add3d..e46499ce32 100644 --- a/contracts/name-service/src/integration_tests/register.rs +++ b/contracts/name-service/src/integration_tests/register.rs @@ -265,7 +265,6 @@ fn can_register_multiple_names_for_the_same_nym_address(mut setup: TestSetup) { let reg_name2 = reg_name2.sign(payload); setup.register(®_name2, &owner); - dbg!(&setup.query_all().names); assert_eq!( setup.query_all().names, vec![ diff --git a/service-providers/network-requester/src/allowed_hosts/hosts.rs b/service-providers/network-requester/src/allowed_hosts/hosts.rs index 11aa3dd276..b9d534fe89 100644 --- a/service-providers/network-requester/src/allowed_hosts/hosts.rs +++ b/service-providers/network-requester/src/allowed_hosts/hosts.rs @@ -100,7 +100,7 @@ impl HostsStore { .unwrap(); if let Err(e) = writeln!(file, "{text}") { - eprintln!("Couldn't write to file: {e}"); + log::error!("Couldn't write to file: {e}"); } } diff --git a/service-providers/network-requester/src/cli/init.rs b/service-providers/network-requester/src/cli/init.rs index a1eb89f518..30797dc26e 100644 --- a/service-providers/network-requester/src/cli/init.rs +++ b/service-providers/network-requester/src/cli/init.rs @@ -106,7 +106,7 @@ fn init_paths(id: &str) -> io::Result<()> { } pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { - eprintln!("Initialising client..."); + log::info!("Initialising client..."); let id = &args.id; @@ -114,7 +114,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { // in case we're using old config, try to upgrade it // (if we're using the current version, it's a no-op) try_upgrade_config(id)?; - eprintln!("Client \"{id}\" was already initialised before"); + log::info!("Client \"{id}\" was already initialised before"); true } else { init_paths(&args.id)?; @@ -125,7 +125,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { // re-registering if wanted. let user_wants_force_register = args.force_register_gateway; if user_wants_force_register { - eprintln!("Instructed to force registering gateway. This might overwrite keys!"); + log::warn!("Instructed to force registering gateway. This might overwrite keys!"); } // If the client was already initialized, don't generate new keys and don't re-register with @@ -142,6 +142,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { // Load and potentially override config let config = override_config(Config::new(id), OverrideConfig::from(args.clone())); + log::debug!("Using config: {:#?}", config); // Setup gateway by either registering a new one, or creating a new config from the selected // one but with keys kept, or reusing the gateway configuration. @@ -156,21 +157,21 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> { Some(&config.base.client.nym_api_urls), ) .await - .tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))? + .tap_err(|err| log::error!("Failed to setup gateway\nError: {err}"))? .details; let config_save_location = config.default_location(); config.save_to_default_location().tap_err(|_| { log::error!("Failed to save the config file"); })?; - eprintln!( + log::info!( "Saved configuration file to {}", config_save_location.display() ); let address = init_details.client_address()?; - eprintln!("Client configuration completed.\n"); + log::info!("Client configuration completed.\n"); let init_results = InitResults::new(&config, &address, &init_details.gateway_details); println!("{}", args.output.format(&init_results)); diff --git a/service-providers/network-requester/src/cli/mod.rs b/service-providers/network-requester/src/cli/mod.rs index 80dd4751a5..1eeed74c28 100644 --- a/service-providers/network-requester/src/cli/mod.rs +++ b/service-providers/network-requester/src/cli/mod.rs @@ -84,7 +84,12 @@ pub(crate) struct OverrideConfig { } pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { - let disable_cover_traffic_with_keepalive = args.medium_toggle; + // These flags have overlapping effects, meaning the order matters here. Making it a bit messy. + // Since a big chunk of these are hidden experimental flags there is hope we can remove them + // soonish and clean this up. + + let disable_cover_traffic_with_keepalive = + config.network_requester.disable_poisson_rate || args.medium_toggle; let secondary_packet_size = args.medium_toggle.then_some(PacketSize::ExtendedPacket16); let no_per_hop_delays = args.medium_toggle; diff --git a/service-providers/network-requester/src/cli/run.rs b/service-providers/network-requester/src/cli/run.rs index 3c3e31ecbb..6827c3ed82 100644 --- a/service-providers/network-requester/src/cli/run.rs +++ b/service-providers/network-requester/src/cli/run.rs @@ -84,6 +84,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), NetworkRequesterError> { let mut config = try_load_current_config(&args.id)?; config = override_config(config, OverrideConfig::from(args.clone())); + log::debug!("Using config: {:#?}", config); if !version_check(&config) { error!("failed the local version check"); diff --git a/service-providers/network-requester/src/config/mod.rs b/service-providers/network-requester/src/config/mod.rs index ee7b4ce60e..e4ba1fa9da 100644 --- a/service-providers/network-requester/src/config/mod.rs +++ b/service-providers/network-requester/src/config/mod.rs @@ -159,9 +159,19 @@ impl Config { } } -#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] -pub struct NetworkRequester {} +pub struct NetworkRequester { + pub disable_poisson_rate: bool, +} + +impl Default for NetworkRequester { + fn default() -> Self { + NetworkRequester { + disable_poisson_rate: true, + } + } +} #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] diff --git a/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs b/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs index 92defcd702..20e3634132 100644 --- a/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs +++ b/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs @@ -79,7 +79,7 @@ pub struct NetworkRequesterV1_1_20_2 {} impl From for NetworkRequester { fn from(_value: NetworkRequesterV1_1_20_2) -> Self { - NetworkRequester {} + NetworkRequester::default() } } diff --git a/service-providers/network-requester/src/config/template.rs b/service-providers/network-requester/src/config/template.rs index 34df005821..811c9602f4 100644 --- a/service-providers/network-requester/src/config/template.rs +++ b/service-providers/network-requester/src/config/template.rs @@ -37,7 +37,7 @@ nym_api_urls = [ {{/each}} ] -[storage_paths] +[storage_paths] # Path to file containing private identity key. keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}' @@ -76,6 +76,14 @@ allowed_list_location = '{{ storage_paths.allowed_list_location }}' unknown_list_location = '{{ storage_paths.unknown_list_location }}' +[network_requester] + +# Disable Poisson sending rate, and only send cover traffic occasionally as keepalive messages. +# This is equivalent to setting debug.traffic.disable_main_poisson_packet_distribution = true, +# and debug.cover_traffic.loop_cover_traffic_average_delay = 5s. +disable_poisson_rate = {{ network_requester.disable_poisson_rate }} + + ##### logging configuration options ##### [logging] From af4e8241e7aa2bf872c3d9f2a3b113eda9c82721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 23 Aug 2023 10:07:45 +0200 Subject: [PATCH 11/44] Fix rocksdb compilation (#3801) * Fix rocksdb compilation * Add ephemera to CI build * Add nym-nr-query to CI build * Fix clippy in ephemera rocksdb --- .github/workflows/build.yml | 4 ++++ Cargo.lock | 29 ++++++++++++++++++--------- ephemera/Cargo.toml | 2 +- ephemera/src/core/builder.rs | 2 +- ephemera/src/storage/rocksdb/mod.rs | 2 +- ephemera/src/storage/rocksdb/query.rs | 2 +- ephemera/src/storage/rocksdb/store.rs | 3 ++- 7 files changed, 30 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce9f7820b0..df3ad85309 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,7 @@ on: - 'clients/**' - 'common/**' - 'explorer-api/**' + - 'ephemera/**' - 'gateway/**' - 'integrations/**' - 'mixnode/**' @@ -15,6 +16,7 @@ on: - 'nym-api/**' - 'nym-outfox/**' - 'tools/nym-cli/**' + - 'tools/nym-nr-query/**' - 'tools/ts-rs-cli/**' - 'Cargo.toml' pull_request: @@ -22,6 +24,7 @@ on: - 'clients/**' - 'common/**' - 'explorer-api/**' + - 'ephemera/**' - 'gateway/**' - 'integrations/**' - 'mixnode/**' @@ -31,6 +34,7 @@ on: - 'nym-api/**' - 'nym-outfox/**' - 'tools/nym-cli/**' + - 'tools/nym-nr-query/**' - 'tools/ts-rs-cli/**' - 'Cargo.toml' diff --git a/Cargo.lock b/Cargo.lock index 26d7b17568..9024fa7ad6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -846,9 +846,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.64.0" +version = "0.65.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" dependencies = [ "bitflags 1.3.2", "cexpr", @@ -856,12 +856,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", + "prettyplease 0.2.12", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] @@ -5015,9 +5016,9 @@ dependencies = [ [[package]] name = "librocksdb-sys" -version = "0.10.0+7.9.2" +version = "0.11.0+8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe4d5874f5ff2bc616e55e8c6086d478fcda13faf9495768a4aa1c22042d30b" +checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" dependencies = [ "bindgen", "bzip2-sys", @@ -7759,6 +7760,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prettyplease" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +dependencies = [ + "proc-macro2", + "syn 2.0.28", +] + [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -7861,7 +7872,7 @@ dependencies = [ "log", "multimap", "petgraph", - "prettyplease", + "prettyplease 0.1.25", "prost", "prost-types", "regex", @@ -8612,9 +8623,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "015439787fce1e75d55f279078d33ff14b4af5d93d995e8838ee4631301c8a99" +checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" dependencies = [ "libc", "librocksdb-sys", @@ -8879,7 +8890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b08f58cf71a58bda5734758eb20051cdb66c06c9243badbc45092ced1be834df" dependencies = [ "macro_rules_attribute", - "prettyplease", + "prettyplease 0.1.25", "proc-macro2", "quote", "syn 1.0.109", diff --git a/ephemera/Cargo.toml b/ephemera/Cargo.toml index c831726134..50743df2fe 100644 --- a/ephemera/Cargo.toml +++ b/ephemera/Cargo.toml @@ -37,7 +37,7 @@ nym-ephemera-common = { path = "../common/cosmwasm-smart-contracts/ephemera" } pretty_env_logger = "0.4" refinery = { version = "0.8.7", features = ["rusqlite"], optional = true } reqwest = { version = "0.11.6", features = ["json"] } -rocksdb = { version = "0.20.1", optional = true } +rocksdb = { version = "0.21.0", optional = true } rusqlite = { version = "0.27.0", features = ["bundled"], optional = true } serde = { version = "1.0", features = ["derive"] } serde_derive = "1.0.149" diff --git a/ephemera/src/core/builder.rs b/ephemera/src/core/builder.rs index db9b3e8254..58d4b7cd08 100644 --- a/ephemera/src/core/builder.rs +++ b/ephemera/src/core/builder.rs @@ -211,7 +211,7 @@ impl EphemeraStarterWithApplication { #[cfg(feature = "rocksdb_storage")] fn connect_rocksdb(&self) -> anyhow::Result { info!("Opening database..."); - RocksDbStorage::open(self.init.config.storage.clone()) + RocksDbStorage::open(&self.init.config.storage) .map_err(|e| anyhow::anyhow!("Failed to open database: {}", e)) } diff --git a/ephemera/src/storage/rocksdb/mod.rs b/ephemera/src/storage/rocksdb/mod.rs index dc86a4bd7f..24e5916bc3 100644 --- a/ephemera/src/storage/rocksdb/mod.rs +++ b/ephemera/src/storage/rocksdb/mod.rs @@ -104,7 +104,7 @@ fn block_hash_key(block_hash: &str) -> String { format!("{PREFIX_BLOCK_HASH}:{block_hash}") } -fn block_height_key(height: &u64) -> String { +fn block_height_key(height: u64) -> String { format!("{PREFIX_BLOCK_HEIGHT}:{height}") } diff --git a/ephemera/src/storage/rocksdb/query.rs b/ephemera/src/storage/rocksdb/query.rs index a2586557e5..a41bde78e9 100644 --- a/ephemera/src/storage/rocksdb/query.rs +++ b/ephemera/src/storage/rocksdb/query.rs @@ -53,7 +53,7 @@ impl Database { pub(crate) fn get_block_by_height(&self, height: u64) -> anyhow::Result> { trace!("Getting block by height: {}", height); - if let Some(block_hash) = self.database.get(block_height_key(&height))? { + if let Some(block_hash) = self.database.get(block_height_key(height))? { let block_hash = String::from_utf8(block_hash)?; self.get_block_by_hash(&block_hash) } else { diff --git a/ephemera/src/storage/rocksdb/store.rs b/ephemera/src/storage/rocksdb/store.rs index 66e1f6d2de..9b65137863 100644 --- a/ephemera/src/storage/rocksdb/store.rs +++ b/ephemera/src/storage/rocksdb/store.rs @@ -12,6 +12,7 @@ use rocksdb::{TransactionDB, WriteBatchWithTransaction}; use crate::utilities::crypto::Certificate; +#[allow(clippy::module_name_repetitions)] pub struct DbStore { connection: Arc, } @@ -34,7 +35,7 @@ impl DbStore { let block_id_key = block_hash_key(&hash_str); let certificates_key = certificates_key(&hash_str); - let height_key = block_height_key(&block.header.height); + let height_key = block_height_key(block.header.height); let members_key = members_key(&hash_str); let merkle_tree_key = merkle_tree_key(&hash_str); From 2cd2b1ccd4a080e3328d6879a606f53541ca4734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 23 Aug 2023 09:09:44 +0100 Subject: [PATCH 12/44] always using http client in v034 compat mode --- .../examples/offline_signing.rs | 4 ++-- .../validator-client/src/client.rs | 5 +++-- .../client-libs/validator-client/src/lib.rs | 2 ++ .../src/nyxd/cosmwasm_client/mod.rs | 7 +++++-- .../validator-client/src/nyxd/mod.rs | 9 ++++++--- .../validator-client/src/rpc/mod.rs | 19 +++++++++++++++++++ 6 files changed, 37 insertions(+), 9 deletions(-) diff --git a/common/client-libs/validator-client/examples/offline_signing.rs b/common/client-libs/validator-client/examples/offline_signing.rs index baa2d10035..0e5bce5681 100644 --- a/common/client-libs/validator-client/examples/offline_signing.rs +++ b/common/client-libs/validator-client/examples/offline_signing.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use cosmrs::bank::MsgSend; -use cosmrs::rpc::HttpClient; use cosmrs::tx::Msg; use cosmrs::{tx, AccountId, Coin, Denom}; +use nym_validator_client::http_client; use nym_validator_client::nyxd::CosmWasmClient; use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet; use nym_validator_client::signing::tx_signer::TxSigner; @@ -27,7 +27,7 @@ async fn main() { // possibly remote client that doesn't do ANY signing // (only broadcasts + queries for sequence numbers) - let broadcaster = HttpClient::new(validator).unwrap(); + let broadcaster = http_client(validator).unwrap(); // get signer information let sequence_response = broadcaster.get_sequence(&signer_address).await.unwrap(); diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index e70cbdaf2f..22a986558a 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -26,6 +26,7 @@ pub use nym_mixnet_contract_common::{ // re-export the type to not break existing imports pub use crate::coconut::CoconutApiClient; +use crate::rpc::http_client; #[cfg(feature = "http-client")] use crate::{DirectSigningHttpRpcValidatorClient, HttpRpcClient, QueryHttpRpcValidatorClient}; @@ -95,7 +96,7 @@ impl Client { config: Config, mnemonic: bip39::Mnemonic, ) -> Result { - let rpc_client = HttpRpcClient::new(config.nyxd_url.as_str())?; + let rpc_client = http_client(config.nyxd_url.as_str())?; let prefix = &config.nyxd_config.chain_details.bech32_account_prefix; let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); @@ -126,7 +127,7 @@ impl Client { #[cfg(feature = "http-client")] impl Client { pub fn new_query(config: Config) -> Result { - let rpc_client = HttpRpcClient::new(config.nyxd_url.as_str())?; + let rpc_client = http_client(config.nyxd_url.as_str())?; Ok(Self::new_with_rpc_client(config, rpc_client)) } diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index c6391673a0..b3dca0a473 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -20,6 +20,8 @@ pub use nym_api_requests::*; #[cfg(feature = "http-client")] pub use cosmrs::rpc::HttpClient as HttpRpcClient; +#[cfg(feature = "http-client")] +pub use rpc::http_client; // some type aliasing diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs index 7ebff2ef16..44c917e25d 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs @@ -10,11 +10,14 @@ use crate::signing::{ AccountData, }; use async_trait::async_trait; +use cosmrs::tx::{Raw, SignDoc}; +use std::fmt::Debug; use tendermint_rpc::{Error as TendermintRpcError, SimpleRequest}; +#[cfg(feature = "http-client")] +use crate::http_client; #[cfg(feature = "http-client")] use cosmrs::rpc::{HttpClient, HttpClientUrl}; -use cosmrs::tx::{Raw, SignDoc}; pub mod client_traits; mod helpers; @@ -73,7 +76,7 @@ impl MaybeSigningClient { where U: TryInto, { - self.client = HttpClient::new(new_endpoint)?; + self.client = http_client(new_endpoint)?; Ok(()) } } diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 16027a1970..550608ad9d 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -50,9 +50,12 @@ use crate::signing::direct_wallet::DirectSecp256k1HdWallet; #[cfg(feature = "http-client")] use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient}; use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient}; +use url::Url; + +#[cfg(feature = "http-client")] +use crate::http_client; #[cfg(feature = "http-client")] use cosmrs::rpc::{HttpClient, HttpClientUrl}; -use url::Url; pub mod coin; pub mod contract_traits; @@ -97,7 +100,7 @@ impl NyxdClient { where U: TryInto, { - let client = HttpClient::new(endpoint)?; + let client = http_client(endpoint)?; Ok(NyxdClient { client: MaybeSigningClient::new(client, (&config).into()), @@ -140,7 +143,7 @@ impl NyxdClient { where U: TryInto, { - let client = HttpClient::new(endpoint)?; + let client = http_client(endpoint)?; let prefix = &config.chain_details.bech32_account_prefix; let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); diff --git a/common/client-libs/validator-client/src/rpc/mod.rs b/common/client-libs/validator-client/src/rpc/mod.rs index f483f1067c..fd3f7991ba 100644 --- a/common/client-libs/validator-client/src/rpc/mod.rs +++ b/common/client-libs/validator-client/src/rpc/mod.rs @@ -11,8 +11,27 @@ use tendermint_rpc::{ Error, Order, Paging, SimpleRequest, }; +#[cfg(feature = "http-client")] +use crate::error::TendermintRpcError; +#[cfg(feature = "http-client")] +use crate::HttpRpcClient; +#[cfg(feature = "http-client")] +use tendermint_rpc::client::CompatMode; +#[cfg(feature = "http-client")] +use tendermint_rpc::HttpClientUrl; + pub mod reqwest; +#[cfg(feature = "http-client")] +pub fn http_client(url: U) -> Result +where + U: TryInto, +{ + HttpRpcClient::builder(url.try_into()?) + .compat_mode(CompatMode::V0_34) + .build() +} + // we have to create a sealed trait since `TendermintClient` needs T: Send (due to how async trait is created) // which we can't do in wasm #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] From 2651784e8b44c35de84a9428dc8dfc808674fda6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 23 Aug 2023 10:42:09 +0100 Subject: [PATCH 13/44] fully delegating 'TendermintRpcClient' trait --- .../src/nyxd/cosmwasm_client/mod.rs | 218 ++++++++++++++++- .../validator-client/src/nyxd/mod.rs | 230 +++++++++++++++++- nym-connect/desktop/Cargo.lock | 2 +- 3 files changed, 438 insertions(+), 12 deletions(-) diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs index 44c917e25d..4b3fb7deb1 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs @@ -3,16 +3,20 @@ use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient}; use crate::nyxd::error::NyxdError; -use crate::nyxd::{Config, GasPrice}; +use crate::nyxd::{Config, GasPrice, Hash, Height}; use crate::rpc::TendermintRpcClient; use crate::signing::{ signer::{NoSigner, OfflineSigner}, AccountData, }; use async_trait::async_trait; +use cosmrs::rpc::endpoint::*; +use cosmrs::tendermint::{abci, evidence::Evidence, Genesis}; use cosmrs::tx::{Raw, SignDoc}; +use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; -use tendermint_rpc::{Error as TendermintRpcError, SimpleRequest}; +use tendermint_rpc::query::Query; +use tendermint_rpc::{Error as TendermintRpcError, Order, Paging, SimpleRequest}; #[cfg(feature = "http-client")] use crate::http_client; @@ -88,6 +92,216 @@ where C: TendermintRpcClient + Send + Sync, S: Send + Sync, { + async fn abci_info(&self) -> Result { + self.client.abci_info().await + } + + async fn abci_query( + &self, + path: Option, + data: V, + height: Option, + prove: bool, + ) -> Result + where + V: Into> + Send, + { + self.client.abci_query(path, data, height, prove).await + } + + async fn block(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.block(height).await + } + + async fn block_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.block_by_hash(hash).await + } + + async fn latest_block(&self) -> Result { + self.client.latest_block().await + } + + async fn header(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.header(height).await + } + + async fn header_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.header_by_hash(hash).await + } + + async fn block_results( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.block_results(height).await + } + + async fn latest_block_results(&self) -> Result { + self.client.latest_block_results().await + } + + async fn block_search( + &self, + query: Query, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.client.block_search(query, page, per_page, order).await + } + + async fn blockchain( + &self, + min: H, + max: H, + ) -> Result + where + H: Into + Send, + { + self.client.blockchain(min, max).await + } + + async fn broadcast_tx_async( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + self.client.broadcast_tx_async(tx).await + } + + async fn broadcast_tx_sync( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + self.client.broadcast_tx_sync(tx).await + } + + async fn broadcast_tx_commit( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + self.client.broadcast_tx_commit(tx).await + } + + async fn commit(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.commit(height).await + } + + async fn consensus_params( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.consensus_params(height).await + } + + async fn consensus_state(&self) -> Result { + self.client.consensus_state().await + } + + async fn validators( + &self, + height: H, + paging: Paging, + ) -> Result + where + H: Into + Send, + { + self.client.validators(height, paging).await + } + + async fn latest_consensus_params( + &self, + ) -> Result { + self.client.latest_consensus_params().await + } + + async fn latest_commit(&self) -> Result { + self.client.latest_commit().await + } + + async fn health(&self) -> Result<(), TendermintRpcError> { + self.client.health().await + } + + async fn genesis(&self) -> Result, TendermintRpcError> + where + AppState: Debug + Serialize + DeserializeOwned + Send, + { + self.client.genesis().await + } + + async fn net_info(&self) -> Result { + self.client.net_info().await + } + + async fn status(&self) -> Result { + self.client.status().await + } + + async fn broadcast_evidence( + &self, + e: Evidence, + ) -> Result { + self.client.broadcast_evidence(e).await + } + + async fn tx(&self, hash: Hash, prove: bool) -> Result { + self.client.tx(hash, prove).await + } + + async fn tx_search( + &self, + query: Query, + prove: bool, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.client + .tx_search(query, prove, page, per_page, order) + .await + } + + #[cfg(any( + feature = "tendermint-rpc/http-client", + feature = "tendermint-rpc/websocket-client" + ))] + async fn wait_until_healthy(&self, timeout: T) -> Result<(), Error> + where + T: Into + Send, + { + self.client.wait_until_healthy(timeout).await + } + async fn perform(&self, request: R) -> Result where R: SimpleRequest, diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 550608ad9d..489b0193db 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -9,19 +9,25 @@ use crate::nyxd::cosmwasm_client::types::{ use crate::nyxd::cosmwasm_client::MaybeSigningClient; use crate::nyxd::error::NyxdError; use crate::nyxd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER; +use crate::signing::direct_wallet::DirectSecp256k1HdWallet; use crate::signing::signer::NoSigner; use crate::signing::signer::OfflineSigner; use crate::signing::tx_signer::TxSigner; use crate::signing::AccountData; +use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient}; use async_trait::async_trait; use cosmrs::cosmwasm; +use cosmrs::rpc::endpoint::*; +use cosmrs::tendermint::{abci, evidence::Evidence, Genesis}; use cosmrs::tx::{Msg, Raw, SignDoc}; use cosmwasm_std::Addr; use nym_network_defaults::{ChainDetails, NymNetworkDetails}; -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; +use std::fmt::Debug; use std::time::SystemTime; use tendermint_rpc::endpoint::block::Response as BlockResponse; -use tendermint_rpc::Error as TendermintRpcError; +use tendermint_rpc::{Error as TendermintRpcError, Order}; +use url::Url; pub use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient}; pub use crate::nyxd::fee::Fee; @@ -45,17 +51,13 @@ pub use tendermint_rpc::{ }; pub use tendermint_rpc::{Request, Response, SimpleRequest}; -// #[cfg(feature = "http-client")] -use crate::signing::direct_wallet::DirectSecp256k1HdWallet; -#[cfg(feature = "http-client")] -use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient}; -use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient}; -use url::Url; - #[cfg(feature = "http-client")] use crate::http_client; #[cfg(feature = "http-client")] +use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient}; +#[cfg(feature = "http-client")] use cosmrs::rpc::{HttpClient, HttpClientUrl}; +use tendermint_rpc::query::Query; pub mod coin; pub mod contract_traits; @@ -571,6 +573,216 @@ where C: TendermintRpcClient + Send + Sync, S: Send + Sync, { + async fn abci_info(&self) -> Result { + self.client.abci_info().await + } + + async fn abci_query( + &self, + path: Option, + data: V, + height: Option, + prove: bool, + ) -> Result + where + V: Into> + Send, + { + self.client.abci_query(path, data, height, prove).await + } + + async fn block(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.block(height).await + } + + async fn block_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.block_by_hash(hash).await + } + + async fn latest_block(&self) -> Result { + self.client.latest_block().await + } + + async fn header(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.header(height).await + } + + async fn header_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.header_by_hash(hash).await + } + + async fn block_results( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.block_results(height).await + } + + async fn latest_block_results(&self) -> Result { + self.client.latest_block_results().await + } + + async fn block_search( + &self, + query: Query, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.client.block_search(query, page, per_page, order).await + } + + async fn blockchain( + &self, + min: H, + max: H, + ) -> Result + where + H: Into + Send, + { + self.client.blockchain(min, max).await + } + + async fn broadcast_tx_async( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + TendermintRpcClient::broadcast_tx_async(&self.client, tx).await + } + + async fn broadcast_tx_sync( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + TendermintRpcClient::broadcast_tx_sync(&self.client, tx).await + } + + async fn broadcast_tx_commit( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + TendermintRpcClient::broadcast_tx_commit(&self.client, tx).await + } + + async fn commit(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.commit(height).await + } + + async fn consensus_params( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.consensus_params(height).await + } + + async fn consensus_state(&self) -> Result { + self.client.consensus_state().await + } + + async fn validators( + &self, + height: H, + paging: Paging, + ) -> Result + where + H: Into + Send, + { + self.client.validators(height, paging).await + } + + async fn latest_consensus_params( + &self, + ) -> Result { + self.client.latest_consensus_params().await + } + + async fn latest_commit(&self) -> Result { + self.client.latest_commit().await + } + + async fn health(&self) -> Result<(), TendermintRpcError> { + self.client.health().await + } + + async fn genesis(&self) -> Result, TendermintRpcError> + where + AppState: Debug + Serialize + DeserializeOwned + Send, + { + self.client.genesis().await + } + + async fn net_info(&self) -> Result { + self.client.net_info().await + } + + async fn status(&self) -> Result { + self.client.status().await + } + + async fn broadcast_evidence( + &self, + e: Evidence, + ) -> Result { + self.client.broadcast_evidence(e).await + } + + async fn tx(&self, hash: Hash, prove: bool) -> Result { + self.client.tx(hash, prove).await + } + + async fn tx_search( + &self, + query: Query, + prove: bool, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.client + .tx_search(query, prove, page, per_page, order) + .await + } + + #[cfg(any( + feature = "tendermint-rpc/http-client", + feature = "tendermint-rpc/websocket-client" + ))] + async fn wait_until_healthy(&self, timeout: T) -> Result<(), Error> + where + T: Into + Send, + { + self.client.wait_until_healthy(timeout).await + } + async fn perform(&self, request: R) -> Result where R: SimpleRequest, diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 47977894ba..612a9d8149 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3991,7 +3991,7 @@ dependencies = [ [[package]] name = "nym-connect" -version = "1.1.17" +version = "1.1.18" dependencies = [ "anyhow", "bip39", From 5241047f450f139fb1d02f8b6dd14bdd8f3f5a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 23 Aug 2023 11:05:31 +0100 Subject: [PATCH 14/44] implemented compatibility mode for reqwest client --- .../validator-client/src/client.rs | 1 + .../src/nyxd/cosmwasm_client/mod.rs | 2 +- .../validator-client/src/nyxd/mod.rs | 2 +- .../validator-client/src/rpc/reqwest.rs | 151 ++++++++++++++++-- 4 files changed, 141 insertions(+), 15 deletions(-) diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 22a986558a..6352dabff0 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -26,6 +26,7 @@ pub use nym_mixnet_contract_common::{ // re-export the type to not break existing imports pub use crate::coconut::CoconutApiClient; +#[cfg(feature = "http-client")] use crate::rpc::http_client; #[cfg(feature = "http-client")] use crate::{DirectSigningHttpRpcValidatorClient, HttpRpcClient, QueryHttpRpcValidatorClient}; diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs index 4b3fb7deb1..ab78a8c418 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs @@ -10,11 +10,11 @@ use crate::signing::{ AccountData, }; use async_trait::async_trait; -use cosmrs::rpc::endpoint::*; use cosmrs::tendermint::{abci, evidence::Evidence, Genesis}; use cosmrs::tx::{Raw, SignDoc}; use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; +use tendermint_rpc::endpoint::*; use tendermint_rpc::query::Query; use tendermint_rpc::{Error as TendermintRpcError, Order, Paging, SimpleRequest}; diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 489b0193db..10cca6de7e 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -17,7 +17,6 @@ use crate::signing::AccountData; use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient}; use async_trait::async_trait; use cosmrs::cosmwasm; -use cosmrs::rpc::endpoint::*; use cosmrs::tendermint::{abci, evidence::Evidence, Genesis}; use cosmrs::tx::{Msg, Raw, SignDoc}; use cosmwasm_std::Addr; @@ -26,6 +25,7 @@ use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; use std::time::SystemTime; use tendermint_rpc::endpoint::block::Response as BlockResponse; +use tendermint_rpc::endpoint::*; use tendermint_rpc::{Error as TendermintRpcError, Order}; use url::Url; diff --git a/common/client-libs/validator-client/src/rpc/reqwest.rs b/common/client-libs/validator-client/src/rpc/reqwest.rs index 9c8411d666..ffad7dd815 100644 --- a/common/client-libs/validator-client/src/rpc/reqwest.rs +++ b/common/client-libs/validator-client/src/rpc/reqwest.rs @@ -3,12 +3,32 @@ use crate::rpc::TendermintRpcClient; use async_trait::async_trait; +use cosmrs::tendermint::{block::Height, evidence::Evidence, Hash}; use reqwest::header::HeaderMap; use reqwest::{header, RequestBuilder}; -use tendermint_rpc::{Error, Response, SimpleRequest}; +use tendermint_rpc::{ + client::CompatMode, + dialect::{self, Dialect}, + endpoint::{self, *}, + query::Query, + Error, Order, Response, SimpleRequest, +}; + use url::Url; +// copied macro from tendermint-rpc crate because that's exactly what we have to do here too +macro_rules! perform_with_compat { + ($self:expr, $request:expr) => {{ + let request = $request; + match $self.compat { + CompatMode::V0_37 => $self.perform_v0_37(request).await, + CompatMode::V0_34 => $self.perform_v0_34(request).await, + } + }}; +} + pub struct ReqwestRpcClient { + compat: CompatMode, inner: reqwest::Client, url: Url, } @@ -16,12 +36,21 @@ pub struct ReqwestRpcClient { impl ReqwestRpcClient { pub fn new(url: Url) -> Self { ReqwestRpcClient { + compat: CompatMode::V0_34, inner: reqwest::Client::new(), url, } } - fn build_request(&self, request: R) -> RequestBuilder { + pub fn set_compat_mode(&mut self, compat: CompatMode) { + self.compat = compat; + } + + fn build_request(&self, request: R) -> RequestBuilder + where + R: SimpleRequest, + S: Dialect, + { let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap()); headers.insert( @@ -39,6 +68,38 @@ impl ReqwestRpcClient { .body(request.into_json().into_bytes()) .headers(headers) } + + async fn perform_request(&self, request: R) -> Result + where + R: SimpleRequest, + S: Dialect, + { + let request = self.build_request(request); + // that's extremely unfortunate. the trait requires returning tendermint rpc error so we have to make best effort error mapping + let response = request + .send() + .await + .map_err(TendermintRpcErrorMap::into_rpc_err)?; + let bytes = response + .bytes() + .await + .map_err(TendermintRpcErrorMap::into_rpc_err)?; + R::Response::from_string(bytes).map(Into::into) + } + + async fn perform_v0_34(&self, request: R) -> Result + where + R: SimpleRequest, + { + self.perform_request(request).await + } + + async fn perform_v0_37(&self, request: R) -> Result + where + R: SimpleRequest, + { + self.perform_request(request).await + } } trait TendermintRpcErrorMap { @@ -58,17 +119,81 @@ impl TendermintRpcClient for ReqwestRpcClient { where R: SimpleRequest, { - let request = self.build_request(request); - // that's extremely unfortunate. the trait requires returning tendermint rpc error so we have to make best effort error mapping - let response = request - .send() - .await - .map_err(TendermintRpcErrorMap::into_rpc_err)?; - let bytes = response - .bytes() - .await - .map_err(TendermintRpcErrorMap::into_rpc_err)?; - R::Response::from_string(bytes).map(Into::into) + self.perform_request(request).await + } + + async fn block_results(&self, height: H) -> Result + where + H: Into + Send, + { + perform_with_compat!(self, block_results::Request::new(height.into())) + } + + async fn latest_block_results(&self) -> Result { + perform_with_compat!(self, block_results::Request::default()) + } + + async fn header(&self, height: H) -> Result + where + H: Into + Send, + { + let height = height.into(); + match self.compat { + CompatMode::V0_37 => self.perform(endpoint::header::Request::new(height)).await, + CompatMode::V0_34 => { + // Back-fill with a request to /block endpoint and + // taking just the header from the response. + let resp = self.perform_v0_34(block::Request::new(height)).await?; + Ok(resp.into()) + } + } + } + + async fn header_by_hash(&self, hash: Hash) -> Result { + match self.compat { + CompatMode::V0_37 => self.perform(header_by_hash::Request::new(hash)).await, + CompatMode::V0_34 => { + // Back-fill with a request to /block_by_hash endpoint and + // taking just the header from the response. + let resp = self + .perform_v0_34(block_by_hash::Request::new(hash)) + .await?; + Ok(resp.into()) + } + } + } + + /// `/broadcast_evidence`: broadcast an evidence. + async fn broadcast_evidence(&self, e: Evidence) -> Result { + match self.compat { + CompatMode::V0_37 => self.perform(evidence::Request::new(e)).await, + CompatMode::V0_34 => self.perform_v0_34(evidence::Request::new(e)).await, + } + } + + async fn tx(&self, hash: Hash, prove: bool) -> Result { + perform_with_compat!(self, tx::Request::new(hash, prove)) + } + + async fn tx_search( + &self, + query: Query, + prove: bool, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + perform_with_compat!( + self, + tx_search::Request::new(query, prove, page, per_page, order) + ) + } + + async fn broadcast_tx_commit(&self, tx: T) -> Result + where + T: Into> + Send, + { + perform_with_compat!(self, broadcast::tx_commit::Request::new(tx)) } } From f8b70097d3f6a8955a478a7911b846602d87ef2e Mon Sep 17 00:00:00 2001 From: pierre Date: Wed, 23 Aug 2023 16:12:14 +0200 Subject: [PATCH 15/44] use geoip location for gateways --- explorer-api/.env.dev | 44 ++++ explorer-api/.gitignore | 1 + explorer-api/README.md | 18 +- explorer/.env.dev | 9 + explorer/.gitignore | 3 +- explorer/README.md | 15 ++ explorer/package.json | 29 +-- explorer/src/api/constants.ts | 1 + explorer/src/api/index.ts | 7 + explorer/src/components/Gateways.ts | 4 +- explorer/src/typeDefs/explorer-api.ts | 18 ++ yarn.lock | 339 ++------------------------ 12 files changed, 140 insertions(+), 348 deletions(-) create mode 100644 explorer-api/.env.dev create mode 100644 explorer/.env.dev diff --git a/explorer-api/.env.dev b/explorer-api/.env.dev new file mode 100644 index 0000000000..0020aba233 --- /dev/null +++ b/explorer-api/.env.dev @@ -0,0 +1,44 @@ +CONFIGURED=true + +RUST_LOG=info +RUST_BACKTRACE=1 + +BECH32_PREFIX=n +MIX_DENOM=unym +MIX_DENOM_DISPLAY=nym +STAKE_DENOM=unyx +STAKE_DENOM_DISPLAY=nyx +DENOMS_EXPONENT=6 + +REWARDING_VALIDATOR_ADDRESS=n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa +MIXNET_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav +VESTING_CONTRACT_ADDRESS=n1unyuj8qnmygvzuex3dwmg9yzt9alhvyeat0uu0jedg2wj33efl5qackslz +BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 +COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n16a32stm6kknhq5cc8rx77elr66pygf2hfszw7wvpq746x3uffylqkjar4l +GROUP_CONTRACT_ADDRESS=n1pd7kfgvr5tpcv0xnlv46c4jsq9jg2r799xxrcwqdm4l2jhq2pjwqrmz5ju +MULTISIG_CONTRACT_ADDRESS=n14ph4e660eyqz0j36zlkaey4zgzexm5twkmjlqaequxr2cjm9eprqsmad6k +COCONUT_DKG_CONTRACT_ADDRESS=n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865fltekyrfsesac6a +NAME_SERVICE_CONTRACT_ADDRESS=n12ne7qtmdwd0j03t9t5es8md66wq4e5xg9neladrsag8fx3y89rcs36asfp +SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ps5yutd7sufwg058qd7ac7ldnlazsvmhzqwucsfxmm445d70u8asqxpur4 + +STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" +NYXD="https://sandbox-validator1.nymtech.net" +NYM_API="https://sandbox-nym-api1.nymtech.net/api" + +GEOIP_DB_PATH=geo_ip/GeoLite2-City.mmdb +# MaxMind account ID +# TODO replace with your own account ID +GEOIPUPDATE_ACCOUNT_ID=xxx +# MaxMind license key +# TODO replace with your own license key +GEOIPUPDATE_LICENSE_KEY=xxx +# List of space-separated database edition IDs. Edition IDs may +# consist of letters, digits, and dashes. For example, GeoIP2-City +# would download the GeoIP2 City database (GeoIP2-City). +GEOIPUPDATE_EDITION_IDS=GeoLite2-City +# The number of hours between geoipupdate runs. If this is not set +# or is set to 0, geoipupdate will run once and exit. +GEOIPUPDATE_FREQUENCY=72 +# The path to the directory where geoipupdate will download the +# database. +GEOIP_DB_DIRECTORY=./geo_ip diff --git a/explorer-api/.gitignore b/explorer-api/.gitignore index 66ab76436b..fd6f795b06 100644 --- a/explorer-api/.gitignore +++ b/explorer-api/.gitignore @@ -1,3 +1,4 @@ target explorer-api-state.json /geo_ip +!.env.dev diff --git a/explorer-api/README.md b/explorer-api/README.md index 86e64e4c12..8ab514d616 100644 --- a/explorer-api/README.md +++ b/explorer-api/README.md @@ -8,9 +8,17 @@ Features: - calculates how many nodes are in each country - proxies mixnode API requests to add HTTPS +## Development + +Several environment variables are required. They can be +provisioned via a `.env` file. For convenience a `.env.dev` is +provided, just copy its content into `.env`. + +Follow the steps to setup the geoip database. + ## GeoIP db install/update -First we need to install the geoip database. +A geoip database needs to be installed. We use https://github.com/maxmind/geoipupdate to automatically download and update GeoLite2 binary database. For convenience we @@ -37,13 +45,7 @@ When starting the explorer-api, supply the environment variable `GEOIP_DB_PATH`, pointing to the GeoLite2 binary database file. It should be previously installed thanks to `geoipupdate` service. -For example: - -```shell -GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb cargo run -``` - -Note: explorer-api binary reads the provided `.env` file. +Note: As mentioned above the explorer-api binary reads the provided `.env` file. Run as a service and reverse proxy with `nginx` to add `https` with Lets Encrypt. diff --git a/explorer/.env.dev b/explorer/.env.dev new file mode 100644 index 0000000000..fe4119f671 --- /dev/null +++ b/explorer/.env.dev @@ -0,0 +1,9 @@ +# When running the explorer API locally +#EXPLORER_API_URL=http://localhost:8000/v1 + +EXPLORER_API_URL=https://sandbox-explorer.nymtech.net/api/v1 +NYM_API_URL=https://sandbox-nym-api1.nymtech.net +VALIDATOR_URL=https://sandbox-validator1.nymtech.net +BIG_DIPPER_URL=https://sandbox-blocks.nymtech.net +CURRENCY_DENOM=unym +CURRENCY_STAKING_DENOM=unyx diff --git a/explorer/.gitignore b/explorer/.gitignore index b9a105e264..b8b1827577 100644 --- a/explorer/.gitignore +++ b/explorer/.gitignore @@ -1,3 +1,4 @@ dist .DS_Store -detailAPI.md \ No newline at end of file +detailAPI.md +!.env.dev diff --git a/explorer/README.md b/explorer/README.md index cc8c7f0363..a7bbc2346a 100644 --- a/explorer/README.md +++ b/explorer/README.md @@ -32,6 +32,21 @@ You can then open a browser to http://localhost:3000 and start development. Documentation for developers [can be found here](./docs). +Several environment variables are required. They can be +provisioned via a `.env` file. For convenience a `.env.dev` is +provided, just copy its content into `.env`. + +#### Required env vars + +``` +EXPLORER_API_URL +NYM_API_URL +VALIDATOR_URL +BIG_DIPPER_URL +CURRENCY_DENOM +CURRENCY_STAKING_DENOM +``` + ## Deployment Build the UI with (starting in the repository root): diff --git a/explorer/package.json b/explorer/package.json index 094e705132..885f378254 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -3,6 +3,20 @@ "version": "1.0.7", "private": true, "license": "Apache-2.0", + "scripts": { + "start": "webpack serve --progress --port 3000", + "build": "webpack build --progress --config webpack.prod.js", + "build:serve": "npx serve dist", + "test": "jest", + "test:watch": "jest --watch", + "tsc": "tsc --noEmit true", + "tsc:watch": "tsc --watch --noEmit true", + "lint": "eslint src", + "lint:fix": "eslint src --fix", + "prestorybook": "yarn --cwd .. build", + "storybook": "start-storybook -p 6006", + "storybook:build": "build-storybook" + }, "dependencies": { "@cosmjs/math": "^0.26.2", "@emotion/react": "^11.4.1", @@ -18,6 +32,7 @@ "big.js": "^6.2.1", "d3-scale": "^4.0.0", "date-fns": "^2.24.0", + "lodash": "^4.17.21", "i18n-iso-countries": "^6.8.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -95,20 +110,6 @@ "webpack-favicons": "^1.3.8", "webpack-merge": "^5.8.0" }, - "scripts": { - "start": "webpack serve --progress --port 3000", - "build": "webpack build --progress --config webpack.prod.js", - "build:serve": "npx serve dist", - "test": "jest", - "test:watch": "jest --watch", - "tsc": "tsc --noEmit true", - "tsc:watch": "tsc --watch --noEmit true", - "lint": "eslint src", - "lint:fix": "eslint src --fix", - "prestorybook": "yarn --cwd .. build", - "storybook": "start-storybook -p 6006", - "storybook:build": "build-storybook" - }, "browserslist": { "production": [ ">0.2%", diff --git a/explorer/src/api/constants.ts b/explorer/src/api/constants.ts index e3f39a3cae..f00c9ec280 100644 --- a/explorer/src/api/constants.ts +++ b/explorer/src/api/constants.ts @@ -9,6 +9,7 @@ export const OVERVIEW_API = `${API_BASE_URL}/overview`; export const MIXNODE_PING = `${API_BASE_URL}/ping`; export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`; export const MIXNODE_API = `${API_BASE_URL}/mix-node`; +export const GATEWAYS_EXPLORER_API = `${API_BASE_URL}/gateways`; export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/status/gateways/detailed`; export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`; export const BLOCK_API = `${NYM_API_BASE_URL}/block`; diff --git a/explorer/src/api/index.ts b/explorer/src/api/index.ts index ceb5878e97..a2b134d4ae 100644 --- a/explorer/src/api/index.ts +++ b/explorer/src/api/index.ts @@ -1,3 +1,4 @@ +import keyBy from 'lodash/keyBy'; import { API_BASE_URL, BLOCK_API, @@ -11,6 +12,7 @@ import { UPTIME_STORY_API, VALIDATORS_API, SERVICE_PROVIDERS, + GATEWAYS_EXPLORER_API, } from './constants'; import { @@ -32,6 +34,7 @@ import { GatewayBondAnnotated, GatewayBond, DirectoryServiceProvider, + LocatedGateway, } from '../typeDefs/explorer-api'; function getFromCache(key: string) { @@ -98,9 +101,13 @@ export class Api { static fetchGateways = async (): Promise => { const res = await fetch(GATEWAYS_API); const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json(); + const res2 = await fetch(GATEWAYS_EXPLORER_API); + const locatedGateways: LocatedGateway[] = await res2.json(); + const locatedGatewaysByOwner = keyBy(locatedGateways, 'owner'); return gatewaysAnnotated.map(({ gateway_bond, node_performance }) => ({ ...gateway_bond, node_performance, + location: locatedGatewaysByOwner[gateway_bond.owner]?.location, })); }; diff --git a/explorer/src/components/Gateways.ts b/explorer/src/components/Gateways.ts index b78b1320da..39419257d4 100644 --- a/explorer/src/components/Gateways.ts +++ b/explorer/src/components/Gateways.ts @@ -26,7 +26,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy id: gw.owner, owner: gw.owner, identity_key: gw.gateway.identity_key || '', - location: gw?.gateway?.location || '', + location: gw.location?.country_name || '', bond: gw.pledge_amount.amount || 0, host: gw.gateway.host || '', version: gw.gateway.version || '', @@ -39,7 +39,7 @@ export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayRe id: gateway.owner, owner: gateway.owner, identity_key: gateway.gateway.identity_key || '', - location: gateway?.gateway?.location || '', + location: gateway.location?.country_name || '', bond: gateway.pledge_amount.amount || 0, host: gateway.gateway.host || '', version: gateway.gateway.version || '', diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts index 736c6e8ffa..9319c33952 100644 --- a/explorer/src/typeDefs/explorer-api.ts +++ b/explorer/src/typeDefs/explorer-api.ts @@ -131,6 +131,7 @@ export interface GatewayBond { owner: string; gateway: Gateway; node_performance: NodePerformance; + location?: Location; } export interface GatewayBondAnnotated { @@ -138,6 +139,23 @@ export interface GatewayBondAnnotated { node_performance: NodePerformance; } +export interface Location { + two_letter_iso_country_code: string; + three_letter_iso_country_code: string; + country_name: string; + latitude?: number; + longitude?: number; +} + +export interface LocatedGateway { + pledge_amount: Amount; + owner: string; + block_height: number; + gateway: Gateway; + proxy?: string; + location?: Location; +} + export type GatewayResponse = GatewayBond[]; export interface GatewayReportResponse { diff --git a/yarn.lock b/yarn.lock index 10f93b7eaf..ca5ac7a967 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1724,23 +1724,6 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@emotion/babel-plugin@^11.10.6": - version "11.10.6" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz#a68ee4b019d661d6f37dec4b8903255766925ead" - integrity sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ== - dependencies: - "@babel/helper-module-imports" "^7.16.7" - "@babel/runtime" "^7.18.3" - "@emotion/hash" "^0.9.0" - "@emotion/memoize" "^0.8.0" - "@emotion/serialize" "^1.1.1" - babel-plugin-macros "^3.1.0" - convert-source-map "^1.5.0" - escape-string-regexp "^4.0.0" - find-root "^1.1.0" - source-map "^0.5.7" - stylis "4.1.3" - "@emotion/babel-plugin@^11.11.0": version "11.11.0" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" @@ -1769,19 +1752,19 @@ "@emotion/weak-memoize" "^0.3.1" stylis "4.2.0" -"@emotion/hash@^0.9.0", "@emotion/hash@^0.9.1": +"@emotion/hash@^0.9.1": version "0.9.1" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== -"@emotion/is-prop-valid@^1.2.0", "@emotion/is-prop-valid@^1.2.1": +"@emotion/is-prop-valid@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== dependencies: "@emotion/memoize" "^0.8.1" -"@emotion/memoize@^0.8.0", "@emotion/memoize@^0.8.1": +"@emotion/memoize@^0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== @@ -1800,7 +1783,7 @@ "@emotion/weak-memoize" "^0.3.1" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.1.1", "@emotion/serialize@^1.1.2": +"@emotion/serialize@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51" integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== @@ -1828,29 +1811,17 @@ "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" "@emotion/utils" "^1.2.1" -"@emotion/styled@^11.7.0": - version "11.10.6" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.6.tgz#d886afdc51ef4d66c787ebde848f3cc8b117ebba" - integrity sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og== - dependencies: - "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.10.6" - "@emotion/is-prop-valid" "^1.2.0" - "@emotion/serialize" "^1.1.1" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.0" - "@emotion/utils" "^1.2.0" - "@emotion/unitless@^0.8.1": version "0.8.1" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== -"@emotion/use-insertion-effect-with-fallbacks@^1.0.0", "@emotion/use-insertion-effect-with-fallbacks@^1.0.1": +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== -"@emotion/utils@^1.2.0", "@emotion/utils@^1.2.1": +"@emotion/utils@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== @@ -1942,11 +1913,6 @@ resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-2.9.11.tgz#9ce96e7746625a89239f68ca57c4f654264c17ef" integrity sha512-bA3aZ79UgcHj7tFV7RlgThzwSSHZgvfbt2wprldRkYBcMopdMvHyO17Wwp/twcJasNFischFfS7oz8Katz8DdQ== -"@hookform/resolvers@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.1.0.tgz#ff83ef4aa6078173201da131ceea4c3583b67034" - integrity sha512-z0A8K+Nxq+f83Whm/ajlwE6VtQlp/yPHZnXw7XWVPIGm1Vx0QV8KThU3BpbBRfAZ7/dYqCKKBNnQh85BkmBKkA== - "@humanwhocodes/config-array@^0.11.10": version "0.11.10" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" @@ -2691,20 +2657,6 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@mui/base@5.0.0-beta.0": - version "5.0.0-beta.0" - resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.0.tgz#4145f8a700e04d68d703da70d3c26c62d278f4e5" - integrity sha512-ap+juKvt8R8n3cBqd/pGtZydQ4v2I/hgJKnvJRGjpSh3RvsvnDHO4rXov8MHQlH6VqpOekwgilFLGxMZjNTucA== - dependencies: - "@babel/runtime" "^7.21.0" - "@emotion/is-prop-valid" "^1.2.0" - "@mui/types" "^7.2.4" - "@mui/utils" "^5.12.3" - "@popperjs/core" "^2.11.7" - clsx "^1.2.1" - prop-types "^15.8.1" - react-is "^18.2.0" - "@mui/base@5.0.0-beta.4": version "5.0.0-beta.4" resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.4.tgz#e3f4f4a056b88ab357194a245e223177ce35e0b0" @@ -2733,11 +2685,6 @@ prop-types "^15.8.1" react-is "^18.2.0" -"@mui/core-downloads-tracker@^5.13.0": - version "5.13.7" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.7.tgz#f4d9af5fe113b80b98b2cb158263d7b8f77e61c7" - integrity sha512-/suIo4WoeL/OyO3KUsFVpdOmKiSAr6NpWXmQ4WLSxwKrTiha1FJxM6vwAki5W/5kR9WnVLw5E8JC4oHHsutT8w== - "@mui/core-downloads-tracker@^5.13.4": version "5.13.4" resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.4.tgz#7e4b491d8081b6d45ae51556d82cb16b31315a19" @@ -2750,13 +2697,6 @@ dependencies: "@babel/runtime" "^7.21.0" -"@mui/icons-material@^5.11.11": - version "5.11.11" - resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.11.11.tgz#d4e01bd405b0dac779f5e060586277f91f3acb6e" - integrity sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw== - dependencies: - "@babel/runtime" "^7.21.0" - "@mui/lab@^5.0.0-alpha.72": version "5.0.0-alpha.134" resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.134.tgz#e48c108fce91fbb89446dcf86ca35e7e761bc078" @@ -2789,25 +2729,7 @@ react-is "^18.2.0" react-transition-group "^4.4.5" -"@mui/material@^5.11.15": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.13.0.tgz#23c63a9d117dca22b81bb82db6e9c4d761c8fbf1" - integrity sha512-ckS+9tCpAzpdJdaTF+btF0b6mF9wbXg/EVKtnoAWYi0UKXoXBAVvEUMNpLGA5xdpCdf+A6fPbVUEHs9TsfU+Yw== - dependencies: - "@babel/runtime" "^7.21.0" - "@mui/base" "5.0.0-beta.0" - "@mui/core-downloads-tracker" "^5.13.0" - "@mui/system" "^5.12.3" - "@mui/types" "^7.2.4" - "@mui/utils" "^5.12.3" - "@types/react-transition-group" "^4.4.6" - clsx "^1.2.1" - csstype "^3.1.2" - prop-types "^15.8.1" - react-is "^18.2.0" - react-transition-group "^4.4.5" - -"@mui/private-theming@^5.13.1", "@mui/private-theming@^5.13.7": +"@mui/private-theming@^5.13.1": version "5.13.7" resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.13.7.tgz#2f8ef5da066f3c6c6423bd4260d003a28d10b099" integrity sha512-qbSr+udcij5F9dKhGX7fEdx2drXchq7htLNr2Qg2Ma+WJ6q0ERlEqGSBiPiVDJkptcjeVL4DGmcf1wl5+vD4EA== @@ -2863,20 +2785,6 @@ csstype "^3.1.2" prop-types "^15.8.1" -"@mui/system@^5.11.15", "@mui/system@^5.12.3": - version "5.13.7" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.13.7.tgz#b02e6284bbaab4201b142546ebbb2012ec0fa63d" - integrity sha512-7R2KdI6vr8KtnauEfg9e9xQmPk6Gg/1vGNiALYyhSI+cYztxN6WmlSqGX4bjWn/Sygp1TUE1jhFEgs7MWruhkQ== - dependencies: - "@babel/runtime" "^7.22.5" - "@mui/private-theming" "^5.13.7" - "@mui/styled-engine" "^5.13.2" - "@mui/types" "^7.2.4" - "@mui/utils" "^5.13.7" - clsx "^1.2.1" - csstype "^3.1.2" - prop-types "^15.8.1" - "@mui/types@^7.2.4": version "7.2.4" resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328" @@ -2893,7 +2801,7 @@ prop-types "^15.8.1" react-is "^18.2.0" -"@mui/utils@^5.12.3", "@mui/utils@^5.13.7": +"@mui/utils@^5.13.7": version "5.13.7" resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.13.7.tgz#7e6a8336e05eb2642667a5c02eb605351e27ec20" integrity sha512-/3BLptG/q0u36eYED7Nhf4fKXmcKb6LjjT7ZMwhZIZSdSxVqDqSTmATW3a56n3KEPQUXCU9TpxAfCBQhs6brVA== @@ -3211,9 +3119,6 @@ dependencies: nx "15.9.4" -"@nymproject/extension-storage@file:./nym-browser-extension/storage/pkg": - version "0.1.0" - "@nymproject/nym-validator-client@^0.18.0": version "0.18.0" resolved "https://registry.yarnpkg.com/@nymproject/nym-validator-client/-/nym-validator-client-0.18.0.tgz#4dd72bafdf6c72b603242f32c0bb9a1f9e475b98" @@ -3386,7 +3291,7 @@ schema-utils "^3.0.0" source-map "^0.7.3" -"@popperjs/core@^2.11.7", "@popperjs/core@^2.11.8": +"@popperjs/core@^2.11.8": version "2.11.8" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== @@ -3449,11 +3354,6 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.0.tgz#550a8d5760b78efc5d60f62b5829b0f74c1fde81" integrity sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw== -"@remix-run/router@1.7.1": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.1.tgz#fea7ac35ae4014637c130011f59428f618730498" - integrity sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ== - "@rollup/plugin-commonjs@^24.0.1": version "24.1.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-24.1.0.tgz#79e54bd83bb64396761431eee6c44152ef322100" @@ -4548,7 +4448,7 @@ react-docgen-typescript "^2.1.1" tslib "^2.0.0" -"@storybook/react@^6.5.15", "@storybook/react@^6.5.16": +"@storybook/react@^6.5.15": version "6.5.16" resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.5.16.tgz#f7b82ba87f5bb73b4e4e83cce298a98710a88398" integrity sha512-cBtNlOzf/MySpNLBK22lJ8wFU22HnfTB2xJyBk7W7Zi71Lm7Uxkhv1Pz8HdiQndJ0SlsAAQOWjQYsSZsGkZIaA== @@ -4915,20 +4815,6 @@ lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/dom@^9.0.0": - version "9.2.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.2.0.tgz#0e1f45e956f2a16f471559c06edd8827c4832f04" - integrity sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^5.0.1" - aria-query "^5.0.0" - chalk "^4.1.0" - dom-accessibility-api "^0.5.9" - lz-string "^1.5.0" - pretty-format "^27.0.2" - "@testing-library/jest-dom@^5.14.1": version "5.16.5" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" @@ -4953,15 +4839,6 @@ "@testing-library/dom" "^8.0.0" "@types/react-dom" "<18.0.0" -"@testing-library/react@^14.0.0": - version "14.0.0" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.0.0.tgz#59030392a6792450b9ab8e67aea5f3cc18d6347c" - integrity sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg== - dependencies: - "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^9.0.0" - "@types/react-dom" "^18.0.0" - "@testing-library/user-event@^13.2.1": version "13.5.0" resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" @@ -5104,11 +4981,6 @@ dependencies: "@types/node" "*" -"@types/crypto-js@4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d" - integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA== - "@types/d3-array@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.5.tgz#857c1afffd3f51319bbc5b301956aca68acaa7b8" @@ -5475,11 +5347,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.37.tgz#a1f8728e4dc30163deb41e9b7aba65d0c2d4eda1" integrity sha512-ql+4dw4PlPFBP495k8JzUX/oMNRI2Ei4PrMHgj8oT4VhGlYUzF4EYr0qk2fW+XBVGIrq8Zzk13m4cvyXZuv4pA== -"@types/node@^18.16.1": - version "18.16.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.7.tgz#86d0ba2541f808cb78d4dc5d3e40242a349d6db8" - integrity sha512-MFg7ua/bRtnA1hYE3pVyWxGd/r7aMqjNOdHvlSsXV3n8iaeGKkOaPzpJh6/ovf4bEXWcojkeMJpTsq3mzXW4IQ== - "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -5539,13 +5406,6 @@ dependencies: "@types/react" "^17" -"@types/react-dom@^18.0.0": - version "18.2.4" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.4.tgz#13f25bfbf4e404d26f62ac6e406591451acba9e0" - integrity sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw== - dependencies: - "@types/react" "*" - "@types/react-dom@^18.0.10": version "18.2.6" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.6.tgz#ad621fa71a8db29af7c31b41b2ea3d8a6f4144d1" @@ -6133,11 +5993,6 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== -"@webpack-cli/configtest@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.0.1.tgz#a69720f6c9bad6aef54a8fa6ba9c3533e7ef4c7f" - integrity sha512-njsdJXJSiS2iNbQVS0eT8A/KPnmyH4pv1APj2K0d1wrZcBLw+yppxOy4CGqa0OxDJkzfL/XELDhD8rocnIwB5A== - "@webpack-cli/info@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" @@ -6145,21 +6000,11 @@ dependencies: envinfo "^7.7.3" -"@webpack-cli/info@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.1.tgz#eed745799c910d20081e06e5177c2b2569f166c0" - integrity sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA== - "@webpack-cli/serve@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== -"@webpack-cli/serve@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.2.tgz#10aa290e44a182c02e173a89452781b1acbc86d9" - integrity sha512-S9h3GmOmzUseyeFW3tYNnWS7gNUuwxZ3mmMq0JyW78Vx1SGKPSkt5bT4pB0rUnVfHjP0EL9gW2bOzmtiTfQt0A== - "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -8079,11 +7924,6 @@ commander@2, commander@^2.19.0, commander@^2.20.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - commander@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" @@ -8351,18 +8191,6 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== -copy-webpack-plugin@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" - integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== - dependencies: - fast-glob "^3.2.11" - glob-parent "^6.0.1" - globby "^13.1.1" - normalize-path "^3.0.0" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" - core-js-compat@^3.30.1, core-js-compat@^3.30.2, core-js-compat@^3.8.1: version "3.31.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.0.tgz#4030847c0766cc0e803dcdfb30055d7ef2064bf1" @@ -8532,11 +8360,6 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-js@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" - integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== - crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -9422,13 +9245,6 @@ dotenv-webpack@^7.0.3: dependencies: dotenv-defaults "^2.0.2" -dotenv-webpack@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-8.0.1.tgz#6656550460a8076fab20e5ac2eac867e72478645" - integrity sha512-CdrgfhZOnx4uB18SgaoP9XHRN2v48BbjuXQsZY5ixs5A8579NxQkmMxRtI7aTwSiSQcM2ao12Fdu+L3ZS3bG4w== - dependencies: - dotenv-defaults "^2.0.2" - dotenv@^16.0.3: version "16.3.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" @@ -10491,17 +10307,6 @@ fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" -fast-glob@^3.2.11, fast-glob@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0" - integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" @@ -11200,7 +11005,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^6.0.1, glob-parent@^6.0.2: +glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -11331,17 +11136,6 @@ globby@11.1.0, globby@^11.0.1, globby@^11.0.2, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -globby@^13.1.1: - version "13.2.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-13.2.2.tgz#63b90b1bf68619c2135475cbd4e71e66aa090592" - integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== - dependencies: - dir-glob "^3.0.1" - fast-glob "^3.3.0" - ignore "^5.2.4" - merge2 "^1.4.1" - slash "^4.0.0" - globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -11945,7 +11739,7 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4: +ignore@^5.0.4, ignore@^5.2.0: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== @@ -12105,11 +11899,6 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -interpret@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" - integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== - ip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" @@ -12170,7 +11959,7 @@ is-alphanumerical@^2.0.0: is-alphabetical "^2.0.0" is-decimal "^2.0.0" -is-arguments@^1.0.4, is-arguments@^1.1.1: +is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== @@ -12361,13 +12150,6 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-generator-function@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - is-glob@^3.0.0, is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -12594,7 +12376,7 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" -is-typed-array@^1.1.10, is-typed-array@^1.1.3, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.9: version "1.1.10" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== @@ -17577,11 +17359,6 @@ qrcode.react@^1.0.1: prop-types "^15.6.0" qr.js "0.0.0" -qrcode.react@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" - integrity sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q== - qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -17729,13 +17506,6 @@ react-error-boundary@^3.1.3, react-error-boundary@^3.1.4: dependencies: "@babel/runtime" "^7.12.5" -react-error-boundary@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.4.tgz#d2e84505b0a67cec7a6bf33b0146faadfe31597d" - integrity sha512-AbqMFx8bCsob8rCHZvJYQ42MQijK0/034RUvan9qrqyJCpazr8d9vKHrysbxcr6odoHLZvQEcYomFPoIqH9fow== - dependencies: - "@babel/runtime" "^7.12.5" - react-google-charts@^3.0.15: version "3.0.15" resolved "https://registry.yarnpkg.com/react-google-charts/-/react-google-charts-3.0.15.tgz#30759a470f48336e744fd383d054122b039a1ff2" @@ -17743,7 +17513,7 @@ react-google-charts@^3.0.15: dependencies: react-load-script "^0.0.6" -react-hook-form@^7.14.2, react-hook-form@^7.43.9: +react-hook-form@^7.14.2: version "7.45.1" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.45.1.tgz#e352c7f4dbc7540f0756abbb4dcfd1122fecc9bb" integrity sha512-6dWoFJwycbuFfw/iKMcl+RdAOAOHDiF11KWYhNDRN/OkUt+Di5qsZHwA0OwsVnu9y135gkHpTw9DJA+WzCeR9w== @@ -17818,11 +17588,6 @@ react-refresh-typescript@^2.0.2, react-refresh-typescript@^2.0.3: resolved "https://registry.yarnpkg.com/react-refresh-typescript/-/react-refresh-typescript-2.0.9.tgz#f8a86efcb34f8d717100230564b9b57477d74b10" integrity sha512-chAnOO4vpxm/3WkgOVmti+eN8yUtkJzeGkOigV6UA9eDFz12W34e/SsYe2H5+RwYJ3+sfSZkVbiXcG1chEBxlg== -react-refresh-typescript@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/react-refresh-typescript/-/react-refresh-typescript-2.0.8.tgz#84d086f9da742d7de87ad15824301763bf8135ba" - integrity sha512-7zsaM8jIpUZ2hxMdTo9GNZRauTI11AD5YNNaNXVGb8mtYVwdSbc6IQV8YaHevSWYbx+ZpZispezdCfaJuTrh3w== - react-refresh@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" @@ -17833,11 +17598,6 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== -react-refresh@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" - integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== - react-resize-detector@^8.0.4: version "8.1.0" resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-8.1.0.tgz#1c7817db8bc886e2dbd3fbe3b26ea8e56be0524a" @@ -17853,14 +17613,6 @@ react-router-dom@6, react-router-dom@^6.7.0: "@remix-run/router" "1.7.0" react-router "6.14.0" -react-router-dom@^6.9.0: - version "6.14.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.1.tgz#0ad7ba7abdf75baa61169d49f096f0494907a36f" - integrity sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw== - dependencies: - "@remix-run/router" "1.7.1" - react-router "6.14.1" - react-router@6.14.0: version "6.14.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.0.tgz#1c3e8e922d934d43a253fd862c72c82167c0a7f1" @@ -17868,13 +17620,6 @@ react-router@6.14.0: dependencies: "@remix-run/router" "1.7.0" -react-router@6.14.1: - version "6.14.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.1.tgz#5e82bcdabf21add859dc04b1859f91066b3a5810" - integrity sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g== - dependencies: - "@remix-run/router" "1.7.1" - react-simple-maps@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/react-simple-maps/-/react-simple-maps-2.3.0.tgz#7fdb55120335a9b1ef5bf62e0dcb669511705f3e" @@ -18134,13 +17879,6 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -19086,11 +18824,6 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== - slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -19675,11 +19408,6 @@ stylehacks@^5.1.1: browserslist "^4.21.4" postcss-selector-parser "^6.0.4" -stylis@4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7" - integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== - stylis@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" @@ -20855,17 +20583,6 @@ util@^0.11.0: dependencies: inherits "2.0.3" -util@^0.12.5: - version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" - integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - is-typed-array "^1.1.3" - which-typed-array "^1.1.2" - utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" @@ -21165,25 +20882,6 @@ webpack-cli@^4.8.0: rechoir "^0.7.0" webpack-merge "^5.7.3" -webpack-cli@^5.0.1: - version "5.0.2" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.0.2.tgz#2954c10ecb61c5d4dad6f68ee2d77f051741946c" - integrity sha512-4y3W5Dawri5+8dXm3+diW6Mn1Ya+Dei6eEVAdIduAmYNLzv1koKVAqsfgrrc9P2mhrYHQphx5htnGkcNwtubyQ== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^2.0.1" - "@webpack-cli/info" "^2.0.1" - "@webpack-cli/serve" "^2.0.2" - colorette "^2.0.14" - commander "^10.0.1" - cross-spawn "^7.0.3" - envinfo "^7.7.3" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^3.1.1" - rechoir "^0.8.0" - webpack-merge "^5.7.3" - webpack-dev-middleware@^3.7.3: version "3.7.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" @@ -21439,7 +21137,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-typed-array@^1.1.2, which-typed-array@^1.1.9: +which-typed-array@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== @@ -21801,11 +21499,6 @@ yup@^1.2.0: toposort "^2.0.2" type-fest "^2.19.0" -zod@^3.21.4: - version "3.21.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" - integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== - zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" From 026d52a2182cf265be98d135b95dafcff881886a Mon Sep 17 00:00:00 2001 From: pierre Date: Wed, 23 Aug 2023 16:46:07 +0200 Subject: [PATCH 16/44] print location in uppercase --- explorer/src/components/Gateways.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/explorer/src/components/Gateways.ts b/explorer/src/components/Gateways.ts index 39419257d4..4072a33db7 100644 --- a/explorer/src/components/Gateways.ts +++ b/explorer/src/components/Gateways.ts @@ -26,7 +26,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy id: gw.owner, owner: gw.owner, identity_key: gw.gateway.identity_key || '', - location: gw.location?.country_name || '', + location: gw.location?.country_name.toUpperCase() || '', bond: gw.pledge_amount.amount || 0, host: gw.gateway.host || '', version: gw.gateway.version || '', @@ -39,7 +39,7 @@ export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayRe id: gateway.owner, owner: gateway.owner, identity_key: gateway.gateway.identity_key || '', - location: gateway.location?.country_name || '', + location: gateway.location?.country_name.toUpperCase() || '', bond: gateway.pledge_amount.amount || 0, host: gateway.gateway.host || '', version: gateway.gateway.version || '', From 3c9faff4ecd0ff99b83fc1a0a9f55601de29a383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 23 Aug 2023 16:11:27 +0100 Subject: [PATCH 17/44] [hotfix]: don't assign invalid fields when crossing the JS boundary (#3805) * [hotfix]: don't assign invalid fields when crossing the JS boundary * eslint --- nym-wallet/src/components/Bonding/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/components/Bonding/utils.ts b/nym-wallet/src/components/Bonding/utils.ts index d3285215f6..6e7b05a651 100644 --- a/nym-wallet/src/components/Bonding/utils.ts +++ b/nym-wallet/src/components/Bonding/utils.ts @@ -4,12 +4,13 @@ import { toPercentFloatString } from '../../utils'; export function mixnodeToTauri(data: MixnodeData): MixNode { return { - ...data, mix_port: data.mixPort, http_api_port: data.httpApiPort, verloc_port: data.verlocPort, sphinx_key: data.sphinxKey, identity_key: data.identityKey, + version: data.version, + host: data.host, }; } @@ -25,7 +26,6 @@ export function costParamsToTauri(data: MixnodeAmount): MixNodeCostParams { export function gatewayToTauri(data: GatewayData): Gateway { return { - ...data, host: data.host, version: data.version, mix_port: data.mixPort, From 2f624d4f10712fcfcf8f92526f043d4d5e0331c8 Mon Sep 17 00:00:00 2001 From: pierre Date: Wed, 23 Aug 2023 17:20:36 +0200 Subject: [PATCH 18/44] doc(nc-android): add notes for AAB build --- nym-connect/native/android/README.md | 10 ++++++++-- nym-connect/native/android/app/build.gradle.kts | 12 ++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/nym-connect/native/android/README.md b/nym-connect/native/android/README.md index b2a92f2454..c0092aedd5 100644 --- a/nym-connect/native/android/README.md +++ b/nym-connect/native/android/README.md @@ -37,7 +37,7 @@ RELEASE=true ./build-android.sh aarch64 x86_64 The shared library for each ABIs will be automatically moved into `app/src/main/jniLibs/*` directories. -### APK build (from terminal) +### APK/AAB build (from terminal) This project is setup with multiple [product flavors](app/build.gradle) to build for specific architectures.\ @@ -48,12 +48,18 @@ Supported archs: - x86_64 - x86 -For example to build for _arm64_ in _release_ mode use +For example to build an APK for _arm64_ in _release_ mode use ```shell ./gradlew :app:assembleArm64Release ``` +Instead of building an APK, to build an app bundle (`.aab`) run + +```shell +./gradlew :app:bundleArm64Release +``` + **NOTE**: you likely want _arch64_ (`arm64` & `x86_64`) for APK distribution To build a _universal_ APK which includes all ABI use diff --git a/nym-connect/native/android/app/build.gradle.kts b/nym-connect/native/android/app/build.gradle.kts index decadfc6dc..b1c912729f 100644 --- a/nym-connect/native/android/app/build.gradle.kts +++ b/nym-connect/native/android/app/build.gradle.kts @@ -22,6 +22,18 @@ android { } } + bundle { + language { + enableSplit = true + } + density { + enableSplit = true + } + abi { + enableSplit = true + } + } + buildTypes { release { isMinifyEnabled = true From 776a9d508a6db865eea31bc66b5e431b350274c7 Mon Sep 17 00:00:00 2001 From: pierre Date: Wed, 23 Aug 2023 17:44:53 +0200 Subject: [PATCH 19/44] fix wrong helper text --- .../src/components/Bonding/forms/gatewayValidationSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts index b36ff2d095..6935b6e623 100644 --- a/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts +++ b/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts @@ -28,7 +28,7 @@ export const gatewayValidationSchema = Yup.object().shape({ location: Yup.string() .required('A location is required') - .test('valid-location', 'A valid version is required', (locationValueTest) => + .test('valid-location', 'A valid location is required', (locationValueTest) => locationValueTest ? validateLocation(locationValueTest) : false, ), From f1b9cf4d68a273970e08e605f46cb4b995358077 Mon Sep 17 00:00:00 2001 From: mfahampshire Date: Wed, 23 Aug 2023 20:56:43 +0200 Subject: [PATCH 20/44] update minibolt pp --- .../images/profile_picture/minibolt_pp.png | Bin 13877 -> 23061 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png b/documentation/dev-portal/src/images/profile_picture/minibolt_pp.png index ed059e6055840dcd148f5760efc06b2eb5e331d9..455243cc221e6350d5575ccc8cd8c9461cc672f4 100644 GIT binary patch literal 23061 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 literal 13877 zcmV-5HpL{T8P z03<-{jo#49+uys(IcNU3_jNx24UhyHASv+_y889&#(npm@A>VUu-5Wpv-tYm={k2si~io&o;&+W=pG>U;=z7_;<7e;eS}xT+bTk6Z_~d%f174CpUpGW>0TFGH=L z%7CrBMfHMu2K4X0S*ZHW0Ps%WH-KN|$7_8id#*PWX!!=S;zxm#KZf9vJ=4kT8LK~O z06&tQ?7zp4|N48`vp({Zz@iUi^64&qGS{I@&v^F7k%E5%IQSC`F4?mm%=Y5PhJt_6 zsrC(}=Ic$tpCa}8ybbQg)ckn1M{h}h9|eBzEo{@?h*in<=+U
  • {J-bRK*gzt^>% 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 DYWw7D From f0a888d59c82f2a059c36ef65e238d03f469c7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 15:34:49 +0200 Subject: [PATCH 21/44] Add EXPLORER_API configurable url --- .../topology_control/geo_aware_provider.rs | 17 +++++++++++++++-- common/network-defaults/src/lib.rs | 10 ++++++++++ common/network-defaults/src/mainnet.rs | 4 ++++ common/network-defaults/src/var_names.rs | 1 + envs/mainnet.env | 1 + envs/sandbox.env | 3 ++- 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index 268f6612e0..62085796c6 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, fmt}; use log::{debug, error, info}; use nym_explorer_api_requests::PrettyDetailedMixNodeBond; +use nym_network_defaults::var_names::EXPLORER_API; use nym_topology::{ nym_topology_from_detailed, provider_trait::{async_trait, TopologyProvider}, @@ -13,11 +14,23 @@ use serde::{Deserialize, Serialize}; use url::Url; const MIN_NODES_PER_LAYER: usize = 1; -const EXPLORER_API_MIXNODES_URL: &str = "https://explorer.nymtech.net/api/v1/mix-nodes"; // TODO: create a explorer-api-client async fn fetch_mixnodes_from_explorer_api() -> Option> { - reqwest::get(EXPLORER_API_MIXNODES_URL) + let explorer_api_url = std::env::var(EXPLORER_API).ok()?; + let explorer_api_url = Url::parse(&explorer_api_url) + .ok()? + .join("v1/mix-nodes") + .ok()?; + + debug!("Fetching: {}", explorer_api_url); + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .build() + .ok()?; + client + .get(explorer_api_url) + .send() .await .ok()? .json::>() diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 8de0ed465c..1704608043 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -43,6 +43,7 @@ pub struct NymNetworkDetails { pub chain_details: ChainDetails, pub endpoints: Vec, pub contracts: NymContracts, + pub explorer_api: Option, } // by default we assume the same defaults as mainnet, i.e. same prefixes and denoms @@ -71,6 +72,7 @@ impl NymNetworkDetails { }, endpoints: Default::default(), contracts: Default::default(), + explorer_api: Default::default(), } } @@ -136,6 +138,7 @@ impl NymNetworkDetails { var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS, )) .with_name_service_contract(get_optional_env(var_names::NAME_SERVICE_CONTRACT_ADDRESS)) + .with_explorer_api(get_optional_env(var_names::EXPLORER_API)) } pub fn new_mainnet() -> Self { @@ -167,6 +170,7 @@ impl NymNetworkDetails { service_provider_directory_contract_address: None, name_service_contract_address: None, }, + explorer_api: parse_optional_str(mainnet::EXPLORER_API), } } @@ -278,6 +282,12 @@ impl NymNetworkDetails { self.contracts.name_service_contract_address = contract.map(Into::into); self } + + #[must_use] + pub fn with_explorer_api>(mut self, endpoint: Option) -> Self { + self.explorer_api = endpoint.map(Into::into); + self + } } #[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)] diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index fbc25c4b00..bfb3f42531 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -26,6 +26,8 @@ pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hh pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/"; pub const NYXD_URL: &str = "https://rpc.nymtech.net"; pub const NYM_API: &str = "https://validator.nymtech.net/api/"; +pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/"; + pub(crate) fn validators() -> Vec { vec![ValidatorDetails::new(NYXD_URL, Some(NYM_API))] } @@ -99,6 +101,7 @@ pub fn export_to_env() { ); set_var_to_default(var_names::NYXD, NYXD_URL); set_var_to_default(var_names::NYM_API, NYM_API); + set_var_to_default(var_names::EXPLORER_API, EXPLORER_API); } pub fn export_to_env_if_not_set() { @@ -145,4 +148,5 @@ pub fn export_to_env_if_not_set() { ); set_var_conditionally_to_default(var_names::NYXD, NYXD_URL); set_var_conditionally_to_default(var_names::NYM_API, NYM_API); + set_var_conditionally_to_default(var_names::EXPLORER_API, EXPLORER_API); } diff --git a/common/network-defaults/src/var_names.rs b/common/network-defaults/src/var_names.rs index 26b65b7f95..d082045a03 100644 --- a/common/network-defaults/src/var_names.rs +++ b/common/network-defaults/src/var_names.rs @@ -26,6 +26,7 @@ pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str = pub const NAME_SERVICE_CONTRACT_ADDRESS: &str = "NAME_SERVICE_CONTRACT_ADDRESS"; pub const NYXD: &str = "NYXD"; pub const NYM_API: &str = "NYM_API"; +pub const EXPLORER_API: &str = "EXPLORER_API"; pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION"; diff --git a/envs/mainnet.env b/envs/mainnet.env index 4bf406bd64..9dc5b5272d 100644 --- a/envs/mainnet.env +++ b/envs/mainnet.env @@ -22,5 +22,6 @@ REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090" NYXD="https://rpc.nymtech.net" NYM_API="https://validator.nymtech.net/api/" +EXPLORER_API="https://explorer.nymtech.net/api/" DKG_TIME_CONFIGURATION="259200,300,300,60,60,1209600" diff --git a/envs/sandbox.env b/envs/sandbox.env index 95b0b4d49a..b4a66a70d1 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -23,4 +23,5 @@ EPHEMERA_CONTRACT_ADDRESS="nymt1gwk6muhmzeuxje7df7rjvqwl2vex0kj4t2hwuzmyx5k62kfu STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" NYXD="https://sandbox-validator1.nymtech.net" -NYM_API="https://sandbox-nym-api1.nymtech.net/api/" \ No newline at end of file +NYM_API="https://sandbox-nym-api1.nymtech.net/api/" +EXPLORER_API="https://sandbox-explorer.nymtech.net/api/" From 8a38c61065ba28014fa763b82b0b7dd7bb9c2177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 15:43:48 +0200 Subject: [PATCH 22/44] Fix wallet build --- nym-wallet/nym-wallet-types/src/network/qa.rs | 3 +++ nym-wallet/nym-wallet-types/src/network/sandbox.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/nym-wallet/nym-wallet-types/src/network/qa.rs b/nym-wallet/nym-wallet-types/src/network/qa.rs index be282384f5..605cc63fdd 100644 --- a/nym-wallet/nym-wallet-types/src/network/qa.rs +++ b/nym-wallet/nym-wallet-types/src/network/qa.rs @@ -39,6 +39,8 @@ pub(crate) fn validators() -> Vec { )] } +pub(crate) const EXPLORER_API: &str = "https://qa-explorer.qa.nymte.ch/api/"; + pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { nym_network_defaults::NymNetworkDetails { network_name: NETWORK_NAME.into(), @@ -63,5 +65,6 @@ pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { ), name_service_contract_address: parse_optional_str(NAME_SERVICE_CONTRACT_ADDRESS), }, + explorer_api: parse_optional_str(EXPLORER_API), } } diff --git a/nym-wallet/nym-wallet-types/src/network/sandbox.rs b/nym-wallet/nym-wallet-types/src/network/sandbox.rs index 0499fa5420..ace2ff7663 100644 --- a/nym-wallet/nym-wallet-types/src/network/sandbox.rs +++ b/nym-wallet/nym-wallet-types/src/network/sandbox.rs @@ -35,6 +35,8 @@ pub(crate) fn validators() -> Vec { )] } +pub(crate) const EXPLORER_API: &str = "https://sandbox-explorer.nymtech.net/api/"; + pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { nym_network_defaults::NymNetworkDetails { network_name: NETWORK_NAME.into(), @@ -57,5 +59,6 @@ pub(crate) fn network_details() -> nym_network_defaults::NymNetworkDetails { service_provider_directory_contract_address: None, name_service_contract_address: None, }, + explorer_api: parse_optional_str(EXPLORER_API), } } From 6a327b0bd68ad7c94d3334ab78a6018874e5fb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 16:01:47 +0200 Subject: [PATCH 23/44] No timeout for wasm --- Cargo.lock | 1 + clients/webassembly/Cargo.lock | 1 + common/client-core/Cargo.toml | 1 + .../topology_control/geo_aware_provider.rs | 17 +++++++++++++---- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9024fa7ad6..98a64c657a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5891,6 +5891,7 @@ version = "1.1.15" dependencies = [ "async-trait", "base64 0.21.2", + "cfg-if", "dashmap 5.5.0", "dirs 4.0.0", "futures", diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index 8449b45ff6..529b241500 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -2503,6 +2503,7 @@ version = "1.1.15" dependencies = [ "async-trait", "base64 0.21.2", + "cfg-if 1.0.0", "dashmap", "dirs 4.0.0", "futures", diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index bcb21899fe..56733b5ab8 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -10,6 +10,7 @@ rust-version = "1.66" [dependencies] async-trait = { workspace = true } base64 = "0.21.2" +cfg-if = "1.0.0" dashmap = "5.4.0" dirs = "4.0" futures = { workspace = true } diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index 62085796c6..ece6e967a2 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -24,10 +24,19 @@ async fn fetch_mixnodes_from_explorer_api() -> Option Date: Thu, 24 Aug 2023 17:46:16 +0200 Subject: [PATCH 24/44] Upgrade ts-rs to 7.0.0 --- Cargo.lock | 10 +++++----- Cargo.toml | 1 + common/client-libs/validator-client/Cargo.toml | 2 +- .../mixnet-contract/Cargo.toml | 2 +- .../vesting-contract/Cargo.toml | 2 +- common/types/Cargo.toml | 2 +- explorer-api/explorer-api-requests/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-api/nym-api-requests/Cargo.toml | 2 +- nym-connect/desktop/src-tauri/Cargo.toml | 2 +- nym-wallet/Cargo.lock | 10 +++++----- nym-wallet/nym-wallet-types/Cargo.toml | 2 +- nym-wallet/src-tauri/Cargo.toml | 2 +- tools/ts-rs-cli/Cargo.toml | 2 +- 14 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9024fa7ad6..078ad10a0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10562,9 +10562,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ts-rs" -version = "6.2.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4added4070a4fdf9df03457206cd2e4b12417c8560a2954d91ffcbe60177a56a" +checksum = "e1ff1f8c90369bc172200013ac17ae86e7b5def580687df4e6127883454ff2b0" dependencies = [ "thiserror", "ts-rs-macros", @@ -10587,14 +10587,14 @@ dependencies = [ [[package]] name = "ts-rs-macros" -version = "6.2.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" +checksum = "a6f41cc0aeb7a4a55730188e147d3795a7349b501f8334697fd37629b896cdc2" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", "termcolor", ] diff --git a/Cargo.toml b/Cargo.toml index 696f1bd9f1..ac0178a41e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,6 +150,7 @@ tap = "1.0.1" tendermint-rpc = "0.32" # same version as used by cosmrs thiserror = "1.0.38" tokio = "1.24.1" +ts-rs = "7.0.0" url = "2.4" zeroize = "1.6.0" diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index f3d2716143..0ee5c8b3af 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -65,7 +65,7 @@ features = ["tokio"] [dev-dependencies] bip39 = { workspace = true } cosmrs = { workspace = true, features = ["bip32"] } -ts-rs = "6.1.2" +ts-rs = { workspace = true } [[example]] name = "offline_signing" diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index 47397d7b88..e82be77e29 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -26,7 +26,7 @@ humantime-serde = "1.1.1" # TO CHECK WHETHER STILL NEEDED: log = { workspace = true } time = { version = "0.3.6", features = ["parsing", "formatting"] } -ts-rs = { version = "6.1.2", optional = true } +ts-rs = { workspace = true, optional = true } [dev-dependencies] rand_chacha = "0.3" diff --git a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml index f5fa35df5f..3fd7e8a377 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml @@ -15,7 +15,7 @@ mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-co contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" } serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" -ts-rs = {version = "6.1.2", optional = true} +ts-rs = { workspace = true, optional = true} [features] schema = ["cw2"] diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index 5515ba5248..4df4087661 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -17,7 +17,7 @@ serde_json = { workspace = true } strum = { version = "0.23", features = ["derive"] } thiserror = { workspace = true } url = { workspace = true } -ts-rs = "6.1.2" +ts-rs = { workspace = true } cosmwasm-std = { workspace = true } cosmrs = { workspace = true } diff --git a/explorer-api/explorer-api-requests/Cargo.toml b/explorer-api/explorer-api-requests/Cargo.toml index 7d8dc2f803..d7139b9f78 100644 --- a/explorer-api/explorer-api-requests/Cargo.toml +++ b/explorer-api/explorer-api-requests/Cargo.toml @@ -9,4 +9,4 @@ nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mix nym-api-requests = { path = "../../nym-api/nym-api-requests" } schemars = { version = "0.8", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } -ts-rs = { version = "6.1.2", optional = true } +ts-rs = { workspace = true, optional = true } diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index c9a0b8e8e4..506c0dda9a 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -47,7 +47,7 @@ tokio = { version = "1.24.1", features = [ tokio-stream = "0.1.11" url = { workspace = true } -ts-rs = {version = "6.1", optional = true} +ts-rs = { workspace = true, optional = true} anyhow = "1.0" getset = "0.1.1" diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index 37aded5efa..e890aa8e44 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -12,7 +12,7 @@ cosmwasm-std = { workspace = true, default-features = false } getset = "0.1.1" schemars = { version = "0.8", features = ["preserve_order"] } serde = { version = "1.0", features = ["derive"] } -ts-rs = { version = "6.1.2", optional = true } +ts-rs = { workspace = true, optional = true } nym-coconut-interface = { path = "../../common/coconut-interface" } nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" } diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index 1844688af9..630adca45b 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -61,7 +61,7 @@ nym-task = { path = "../../../common/task" } nym-validator-client = { path = "../../../common/client-libs/validator-client" } [dev-dependencies] -ts-rs = "6.1.2" +ts-rs = "7.0.0" tempfile = "3.3.0" [features] diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index fbe8b1d3f4..3cb32da98f 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -5864,9 +5864,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ts-rs" -version = "6.2.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4added4070a4fdf9df03457206cd2e4b12417c8560a2954d91ffcbe60177a56a" +checksum = "e1ff1f8c90369bc172200013ac17ae86e7b5def580687df4e6127883454ff2b0" dependencies = [ "thiserror", "ts-rs-macros", @@ -5874,14 +5874,14 @@ dependencies = [ [[package]] name = "ts-rs-macros" -version = "6.2.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" +checksum = "a6f41cc0aeb7a4a55730188e147d3795a7349b501f8334697fd37629b896cdc2" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", "termcolor", ] diff --git a/nym-wallet/nym-wallet-types/Cargo.toml b/nym-wallet/nym-wallet-types/Cargo.toml index 670739ad5f..ca422a9832 100644 --- a/nym-wallet/nym-wallet-types/Cargo.toml +++ b/nym-wallet/nym-wallet-types/Cargo.toml @@ -9,7 +9,7 @@ hex-literal = "0.3.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" strum = { version = "0.23", features = ["derive"] } -ts-rs = "6.1.2" +ts-rs = "7.0.0" cosmwasm-std = "1.3.0" cosmrs = "=0.14.0" diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 2297a1ac90..d6b9c1392a 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -66,7 +66,7 @@ nym-store-cipher = { path = "../../common/store-cipher", features = ["json"] } nym-crypto = { path = "../../common/crypto", features = ["rand"] } rand_chacha = "0.2" tempfile = "3.3.0" -ts-rs = "6.1.2" +ts-rs = "7.0.0" [features] default = ["custom-protocol"] diff --git a/tools/ts-rs-cli/Cargo.toml b/tools/ts-rs-cli/Cargo.toml index 41f9ead98d..5e315a761c 100644 --- a/tools/ts-rs-cli/Cargo.toml +++ b/tools/ts-rs-cli/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] anyhow = "1" -ts-rs = "6.1.2" +ts-rs = { workspace = true } walkdir = "2" nym-validator-client = { path = "../../common/client-libs/validator-client", features = [ From 5d7fd66cfce1457213a8d93fb4ed8060e528a270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 17:13:49 +0200 Subject: [PATCH 25/44] rustfmt --- clients/webassembly/src/client/helpers.rs | 4 ++-- clients/webassembly/src/tester/ephemeral_receiver.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/webassembly/src/client/helpers.rs b/clients/webassembly/src/client/helpers.rs index c3f456a891..7d5373853b 100644 --- a/clients/webassembly/src/client/helpers.rs +++ b/clients/webassembly/src/client/helpers.rs @@ -114,7 +114,7 @@ impl WasmTopologyExt for Arc { let this = Arc::clone(self); future_to_promise(async move { let Some(current_topology) = this.topology_accessor.current_topology().await else { - return Err(WasmClientError::UnavailableNetworkTopology.into()) + return Err(WasmClientError::UnavailableNetworkTopology.into()); }; match current_topology.find_mix_by_identity(&mixnode_identity) { @@ -135,7 +135,7 @@ impl WasmTopologyExt for Arc { let this = Arc::clone(self); future_to_promise(async move { let Some(current_topology) = this.topology_accessor.current_topology().await else { - return Err(WasmClientError::UnavailableNetworkTopology.into()) + return Err(WasmClientError::UnavailableNetworkTopology.into()); }; let Some(mix) = current_topology.find_mix_by_identity(&mixnode_identity) else { diff --git a/clients/webassembly/src/tester/ephemeral_receiver.rs b/clients/webassembly/src/tester/ephemeral_receiver.rs index 826bfa07b2..2d3117addd 100644 --- a/clients/webassembly/src/tester/ephemeral_receiver.rs +++ b/clients/webassembly/src/tester/ephemeral_receiver.rs @@ -58,7 +58,7 @@ impl<'a> EphemeralTestReceiver<'a> { let Some(received_packet) = packet else { // can't do anything more... console_error!("packet receiver has stopped processing results!"); - return true + return true; }; match received_packet { Received::Message(msg) => { From 377c9be790827e721a6c06d93bf9bca39022da9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 17:43:57 +0200 Subject: [PATCH 26/44] clippy --fix --- .../native/websocket-requests/src/requests.rs | 30 +++++++++---------- .../websocket-requests/src/responses.rs | 12 ++++---- .../webassembly/src/encoded_payload_helper.rs | 2 +- .../src/signing/direct_wallet.rs | 8 ++--- common/coconut-interface/src/lib.rs | 2 +- common/nymcoconut/src/proofs/mod.rs | 4 +-- .../acknowledgements/src/identifier.rs | 2 +- .../acknowledgements/src/surb_ack.rs | 2 +- .../anonymous-replies/src/reply_surb.rs | 2 +- .../anonymous-replies/src/requests.rs | 4 +-- common/nymsphinx/chunking/src/fragment.rs | 2 +- common/nymsphinx/cover/src/lib.rs | 2 +- common/nymsphinx/forwarding/src/packet.rs | 4 +-- common/nymsphinx/src/preparer/payload.rs | 9 ++---- common/socks5/requests/src/lib.rs | 14 ++++----- common/socks5/requests/src/request.rs | 24 ++++++--------- common/socks5/requests/src/response.rs | 10 +++---- common/topology/src/lib.rs | 2 +- .../src/registration/handshake/gateway.rs | 2 +- .../src/registration/handshake/shared_key.rs | 5 +--- .../common/src/interface/control.rs | 2 +- .../common/src/interface/request.rs | 4 +-- .../common/src/interface/response.rs | 4 +-- 23 files changed, 67 insertions(+), 85 deletions(-) diff --git a/clients/native/websocket-requests/src/requests.rs b/clients/native/websocket-requests/src/requests.rs index 3c4c1a618b..645f2edf9c 100644 --- a/clients/native/websocket-requests/src/requests.rs +++ b/clients/native/websocket-requests/src/requests.rs @@ -105,10 +105,10 @@ impl ClientRequest { let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes(); std::iter::once(ClientRequestTag::Send as u8) - .chain(recipient.to_bytes().into_iter()) // will not be length prefixed because the length is constant - .chain(conn_id_bytes.into_iter()) - .chain(data_len_bytes.into_iter()) - .chain(data.into_iter()) + .chain(recipient.to_bytes()) // will not be length prefixed because the length is constant + .chain(conn_id_bytes) + .chain(data_len_bytes) + .chain(data) .collect() } @@ -180,11 +180,11 @@ impl ClientRequest { let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes(); std::iter::once(ClientRequestTag::SendAnonymous as u8) - .chain(reply_surbs.to_be_bytes().into_iter()) - .chain(recipient.to_bytes().into_iter()) // will not be length prefixed because the length is constant - .chain(conn_id_bytes.into_iter()) - .chain(data_len_bytes.into_iter()) - .chain(data.into_iter()) + .chain(reply_surbs.to_be_bytes()) + .chain(recipient.to_bytes()) // will not be length prefixed because the length is constant + .chain(conn_id_bytes) + .chain(data_len_bytes) + .chain(data) .collect() } @@ -258,10 +258,10 @@ impl ClientRequest { let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes(); std::iter::once(ClientRequestTag::Reply as u8) - .chain(sender_tag.to_bytes().into_iter()) - .chain(conn_id_bytes.into_iter()) - .chain(message_len_bytes.into_iter()) - .chain(message.into_iter()) + .chain(sender_tag.to_bytes()) + .chain(conn_id_bytes) + .chain(message_len_bytes) + .chain(message) .collect() } @@ -332,7 +332,7 @@ impl ClientRequest { fn serialize_closed_connection(connection_id: u64) -> Vec { let conn_id_bytes = connection_id.to_be_bytes(); std::iter::once(ClientRequestTag::ClosedConnection as u8) - .chain(conn_id_bytes.into_iter()) + .chain(conn_id_bytes) .collect() } @@ -359,7 +359,7 @@ impl ClientRequest { fn serialize_get_lane_queue_lengths(connection_id: u64) -> Vec { let conn_id_bytes = connection_id.to_be_bytes(); std::iter::once(ClientRequestTag::GetLaneQueueLength as u8) - .chain(conn_id_bytes.into_iter()) + .chain(conn_id_bytes) .collect() } diff --git a/clients/native/websocket-requests/src/responses.rs b/clients/native/websocket-requests/src/responses.rs index d326388570..713054ecf3 100644 --- a/clients/native/websocket-requests/src/responses.rs +++ b/clients/native/websocket-requests/src/responses.rs @@ -67,15 +67,15 @@ impl ServerResponse { if let Some(sender_tag) = reconstructed_message.sender_tag { std::iter::once(ServerResponseTag::Received as u8) .chain(std::iter::once(true as u8)) - .chain(sender_tag.to_bytes().into_iter()) + .chain(sender_tag.to_bytes()) .chain(message_len_bytes.iter().cloned()) - .chain(reconstructed_message.message.into_iter()) + .chain(reconstructed_message.message) .collect() } else { std::iter::once(ServerResponseTag::Received as u8) .chain(std::iter::once(false as u8)) .chain(message_len_bytes.iter().cloned()) - .chain(reconstructed_message.message.into_iter()) + .chain(reconstructed_message.message) .collect() } } @@ -149,7 +149,7 @@ impl ServerResponse { // SELF_ADDRESS_RESPONSE_TAG || self_address fn serialize_self_address(address: Recipient) -> Vec { std::iter::once(ServerResponseTag::SelfAddress as u8) - .chain(address.to_bytes().into_iter()) + .chain(address.to_bytes()) .collect() } @@ -211,8 +211,8 @@ impl ServerResponse { let message_len_bytes = (error.message.len() as u64).to_be_bytes(); std::iter::once(ServerResponseTag::Error as u8) .chain(std::iter::once(error.kind as u8)) - .chain(message_len_bytes.into_iter()) - .chain(error.message.into_bytes().into_iter()) + .chain(message_len_bytes) + .chain(error.message.into_bytes()) .collect() } diff --git a/clients/webassembly/src/encoded_payload_helper.rs b/clients/webassembly/src/encoded_payload_helper.rs index 84f463ec58..17ae92fbf8 100644 --- a/clients/webassembly/src/encoded_payload_helper.rs +++ b/clients/webassembly/src/encoded_payload_helper.rs @@ -52,7 +52,7 @@ pub fn encode_payload_with_headers( Ok(metadata) => { let metadata = metadata.as_bytes().to_vec(); let size = (metadata.len() as u64).to_be_bytes().to_vec(); - Ok(vec![size, metadata, payload].concat()) + Ok([size, metadata, payload].concat()) } Err(e) => Err(JsValue::from(JsError::new( format!("Could not encode message: {}", e).as_str(), diff --git a/common/client-libs/validator-client/src/signing/direct_wallet.rs b/common/client-libs/validator-client/src/signing/direct_wallet.rs index b26ac39ada..3de09502ae 100644 --- a/common/client-libs/validator-client/src/signing/direct_wallet.rs +++ b/common/client-libs/validator-client/src/signing/direct_wallet.rs @@ -199,16 +199,14 @@ mod tests { #[test] fn generating_account_addresses() { // test vectors produced from our js wallet - let mnemonics = vec![ - "crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", + let mnemonics = ["crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", - "step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball" - ]; + "step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"]; let prefix = NymNetworkDetails::new_mainnet() .chain_details .bech32_account_prefix; - let addrs = vec![ + let addrs = [ "n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf", "n1h5hgn94nsq4kh99rjj794hr5h5q6yfm2lr52es", "n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf", diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 4f69c2c9fa..1b96fe062f 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -57,7 +57,7 @@ impl Credential { pub fn verify(&self, verification_key: &VerificationKey) -> bool { let params = Parameters::new(self.n_params).unwrap(); - let public_attributes = vec![ + let public_attributes = [ self.voucher_value.to_string().as_bytes(), self.voucher_info.as_bytes(), ] diff --git a/common/nymcoconut/src/proofs/mod.rs b/common/nymcoconut/src/proofs/mod.rs index 84c7211aa2..42f923466d 100644 --- a/common/nymcoconut/src/proofs/mod.rs +++ b/common/nymcoconut/src/proofs/mod.rs @@ -354,7 +354,7 @@ impl ProofKappaZeta { let witness_blinder = params.random_scalar(); let witness_serial_number = params.random_scalar(); let witness_binding_number = params.random_scalar(); - let witness_attributes = vec![witness_serial_number, witness_binding_number]; + let witness_attributes = [witness_serial_number, witness_binding_number]; let beta_bytes = verification_key .beta_g2 @@ -417,7 +417,7 @@ impl ProofKappaZeta { .map(|beta_i| beta_i.to_bytes()) .collect::>(); - let response_attributes = vec![self.response_serial_number, self.response_binding_number]; + let response_attributes = [self.response_serial_number, self.response_binding_number]; // re-compute witnesses commitments // Aw = (c * kappa) + (rt * g2) + ((1 - c) * alpha) + (rm[0] * beta[0]) + ... + (rm[i] * beta[i]) let commitment_kappa = kappa * self.challenge diff --git a/common/nymsphinx/acknowledgements/src/identifier.rs b/common/nymsphinx/acknowledgements/src/identifier.rs index aab5a96146..abe1d58368 100644 --- a/common/nymsphinx/acknowledgements/src/identifier.rs +++ b/common/nymsphinx/acknowledgements/src/identifier.rs @@ -17,7 +17,7 @@ pub fn prepare_identifier( let id_ciphertext = encrypt::(key.inner(), &iv, &serialized_id); // IV || ID_CIPHERTEXT - iv.into_iter().chain(id_ciphertext.into_iter()).collect() + iv.into_iter().chain(id_ciphertext).collect() } pub fn recover_identifier( diff --git a/common/nymsphinx/acknowledgements/src/surb_ack.rs b/common/nymsphinx/acknowledgements/src/surb_ack.rs index e623d4e738..732f84b9ba 100644 --- a/common/nymsphinx/acknowledgements/src/surb_ack.rs +++ b/common/nymsphinx/acknowledgements/src/surb_ack.rs @@ -122,7 +122,7 @@ impl SurbAck { .first_hop_address .as_zero_padded_bytes(MAX_NODE_ADDRESS_UNPADDED_LEN) .into_iter() - .chain(self.surb_ack_packet.to_bytes()?.into_iter()) + .chain(self.surb_ack_packet.to_bytes()?) .collect(); Ok((self.expected_total_delay, surb_bytes)) } diff --git a/common/nymsphinx/anonymous-replies/src/reply_surb.rs b/common/nymsphinx/anonymous-replies/src/reply_surb.rs index 2bf5799912..d38b16fc13 100644 --- a/common/nymsphinx/anonymous-replies/src/reply_surb.rs +++ b/common/nymsphinx/anonymous-replies/src/reply_surb.rs @@ -130,7 +130,7 @@ impl ReplySurb { self.encryption_key .to_bytes() .into_iter() - .chain(self.surb.to_bytes().into_iter()) + .chain(self.surb.to_bytes()) .collect() } diff --git a/common/nymsphinx/anonymous-replies/src/requests.rs b/common/nymsphinx/anonymous-replies/src/requests.rs index f4effbf7d6..9dd4c84dc0 100644 --- a/common/nymsphinx/anonymous-replies/src/requests.rs +++ b/common/nymsphinx/anonymous-replies/src/requests.rs @@ -275,7 +275,7 @@ impl RepliableMessageContent { .to_be_bytes() .into_iter() .chain(reply_surbs.into_iter().flat_map(|s| s.to_bytes())) - .chain(message.into_iter()) + .chain(message) .collect() } RepliableMessageContent::AdditionalSurbs { reply_surbs } => { @@ -465,7 +465,7 @@ impl ReplyMessageContent { ReplyMessageContent::SurbRequest { recipient, amount } => recipient .to_bytes() .into_iter() - .chain(amount.to_be_bytes().into_iter()) + .chain(amount.to_be_bytes()) .collect(), } } diff --git a/common/nymsphinx/chunking/src/fragment.rs b/common/nymsphinx/chunking/src/fragment.rs index 74996f29ab..6dccfa053a 100644 --- a/common/nymsphinx/chunking/src/fragment.rs +++ b/common/nymsphinx/chunking/src/fragment.rs @@ -204,7 +204,7 @@ impl Fragment { self.header .to_bytes() .into_iter() - .chain(self.payload.into_iter()) + .chain(self.payload) .collect() } diff --git a/common/nymsphinx/cover/src/lib.rs b/common/nymsphinx/cover/src/lib.rs index 05f4cffe33..66142a4cee 100644 --- a/common/nymsphinx/cover/src/lib.rs +++ b/common/nymsphinx/cover/src/lib.rs @@ -115,7 +115,7 @@ where let packet_payload: Vec<_> = ack_bytes .into_iter() .chain(ephemeral_keypair.public_key().to_bytes().iter().cloned()) - .chain(cover_content.into_iter()) + .chain(cover_content) .collect(); let route = diff --git a/common/nymsphinx/forwarding/src/packet.rs b/common/nymsphinx/forwarding/src/packet.rs index 2276e9a5cd..13026a3aaf 100644 --- a/common/nymsphinx/forwarding/src/packet.rs +++ b/common/nymsphinx/forwarding/src/packet.rs @@ -106,8 +106,8 @@ impl MixPacket { pub fn into_bytes(self) -> Result, MixPacketFormattingError> { Ok(std::iter::once(self.packet_type as u8) - .chain(self.next_hop.as_bytes().into_iter()) - .chain(self.packet.to_bytes()?.into_iter()) + .chain(self.next_hop.as_bytes()) + .chain(self.packet.to_bytes()?) .collect()) } } diff --git a/common/nymsphinx/src/preparer/payload.rs b/common/nymsphinx/src/preparer/payload.rs index 5999026252..bfd1715d57 100644 --- a/common/nymsphinx/src/preparer/payload.rs +++ b/common/nymsphinx/src/preparer/payload.rs @@ -50,8 +50,8 @@ impl NymPayloadBuilder { Ok(NymPayload( surb_ack_bytes .into_iter() - .chain(variant_data.into_iter()) - .chain(fragment_data.into_iter()) + .chain(variant_data) + .chain(fragment_data) .collect(), )) } @@ -61,10 +61,7 @@ impl NymPayloadBuilder { packet_encryption_key: &SurbEncryptionKey, ) -> Result { let key_digest = packet_encryption_key.compute_digest(); - self.build::( - packet_encryption_key.inner(), - key_digest.into_iter(), - ) + self.build::(packet_encryption_key.inner(), key_digest) } pub fn build_regular( diff --git a/common/socks5/requests/src/lib.rs b/common/socks5/requests/src/lib.rs index 58c23dcd9d..c131f132a0 100644 --- a/common/socks5/requests/src/lib.rs +++ b/common/socks5/requests/src/lib.rs @@ -73,7 +73,7 @@ impl SocketDataHeader { .to_be_bytes() .into_iter() .chain(std::iter::once(self.local_socket_closed as u8)) - .chain(self.seq.to_be_bytes().into_iter()) + .chain(self.seq.to_be_bytes()) } pub fn try_from_response_bytes( @@ -107,8 +107,8 @@ impl SocketDataHeader { pub fn into_response_bytes_iter(self) -> impl Iterator { std::iter::once(self.local_socket_closed as u8) - .chain(self.connection_id.to_be_bytes().into_iter()) - .chain(self.seq.to_be_bytes().into_iter()) + .chain(self.connection_id.to_be_bytes()) + .chain(self.seq.to_be_bytes()) } } @@ -170,9 +170,7 @@ impl SocketData { } pub fn into_request_bytes_iter(self) -> impl Iterator { - self.header - .into_request_bytes_iter() - .chain(self.data.into_iter()) + self.header.into_request_bytes_iter().chain(self.data) } pub fn try_from_response_bytes(b: &[u8]) -> Result { @@ -190,9 +188,7 @@ impl SocketData { } pub fn into_response_bytes_iter(self) -> impl Iterator { - self.header - .into_response_bytes_iter() - .chain(self.data.into_iter()) + self.header.into_response_bytes_iter().chain(self.data) } } diff --git a/common/socks5/requests/src/request.rs b/common/socks5/requests/src/request.rs index 0e29fb49d9..530d27e11f 100644 --- a/common/socks5/requests/src/request.rs +++ b/common/socks5/requests/src/request.rs @@ -113,7 +113,7 @@ impl Serializable for Socks5Request { fn into_bytes(self) -> Vec { if let Some(version) = self.protocol_version.as_u8() { std::iter::once(version) - .chain(self.content.into_bytes().into_iter()) + .chain(self.content.into_bytes()) .collect() } else { std::iter::once(Self::LEGACY_TYPE_TAG) @@ -335,12 +335,12 @@ impl Socks5RequestContent { let remote_address_bytes_len = remote_address_bytes.len() as u16; let iter = std::iter::once(RequestFlag::Connect as u8) - .chain(req.conn_id.to_be_bytes().into_iter()) - .chain(remote_address_bytes_len.to_be_bytes().into_iter()) - .chain(remote_address_bytes.into_iter()); + .chain(req.conn_id.to_be_bytes()) + .chain(remote_address_bytes_len.to_be_bytes()) + .chain(remote_address_bytes); if let Some(return_address) = req.return_address { - iter.chain(return_address.to_bytes().into_iter()).collect() + iter.chain(return_address.to_bytes()).collect() } else { iter.collect() } @@ -358,7 +358,7 @@ impl Socks5RequestContent { }) .unwrap_or_default(); std::iter::once(RequestFlag::Query as u8) - .chain(query_bytes.into_iter()) + .chain(query_bytes) .collect() } } @@ -495,7 +495,7 @@ mod request_deserialization_tests { let request_bytes: Vec<_> = request_bytes_prefix .iter() .cloned() - .chain(recipient_bytes.into_iter()) + .chain(recipient_bytes) .collect(); assert!(Socks5RequestContent::try_from_bytes(&request_bytes) .unwrap_err() @@ -530,10 +530,7 @@ mod request_deserialization_tests { let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap(); let recipient_bytes = recipient.to_bytes(); - let request_bytes: Vec<_> = request_bytes - .into_iter() - .chain(recipient_bytes.into_iter()) - .collect(); + let request_bytes: Vec<_> = request_bytes.into_iter().chain(recipient_bytes).collect(); let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap(); match request { @@ -577,10 +574,7 @@ mod request_deserialization_tests { let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap(); let recipient_bytes = recipient.to_bytes(); - let request_bytes: Vec<_> = request_bytes - .into_iter() - .chain(recipient_bytes.into_iter()) - .collect(); + let request_bytes: Vec<_> = request_bytes.into_iter().chain(recipient_bytes).collect(); let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap(); match request { diff --git a/common/socks5/requests/src/response.rs b/common/socks5/requests/src/response.rs index c0ff60fa39..0415035ac5 100644 --- a/common/socks5/requests/src/response.rs +++ b/common/socks5/requests/src/response.rs @@ -84,7 +84,7 @@ impl Serializable for Socks5Response { fn into_bytes(self) -> Vec { if let Some(version) = self.protocol_version.as_u8() { std::iter::once(version) - .chain(self.content.into_bytes().into_iter()) + .chain(self.content.into_bytes()) .collect() } else { self.content.into_bytes() @@ -192,7 +192,7 @@ impl Socks5ResponseContent { } Socks5ResponseContent::ConnectionError(res) => { std::iter::once(ResponseFlag::ConnectionError as u8) - .chain(res.into_bytes().into_iter()) + .chain(res.into_bytes()) .collect() } Socks5ResponseContent::Query(query) => { @@ -204,7 +204,7 @@ impl Socks5ResponseContent { }) .unwrap_or_default(); std::iter::once(ResponseFlag::Query as u8) - .chain(query_bytes.into_iter()) + .chain(query_bytes) .collect() } } @@ -290,7 +290,7 @@ impl ConnectionError { .to_be_bytes() .iter() .copied() - .chain(self.network_requester_error.into_bytes().into_iter()) + .chain(self.network_requester_error.into_bytes()) .collect() } } @@ -339,7 +339,7 @@ mod tests { let bytes: Vec = 42u64 .to_be_bytes() .into_iter() - .chain([0, 159, 146, 150].into_iter()) + .chain([0, 159, 146, 150]) .collect(); let err = ConnectionError::try_from_bytes(&bytes).err().unwrap(); assert!(matches!( diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index 45eb2d9d7d..428efd1056 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -131,7 +131,7 @@ impl NymTopology { } pub fn mixes_in_layer(&self, layer: MixLayer) -> Vec { - assert!(vec![1, 2, 3].contains(&layer)); + assert!([1, 2, 3].contains(&layer)); self.mixes.get(&layer).unwrap().to_owned() } diff --git a/gateway/gateway-requests/src/registration/handshake/gateway.rs b/gateway/gateway-requests/src/registration/handshake/gateway.rs index 2afebbc115..69381dc5d4 100644 --- a/gateway/gateway-requests/src/registration/handshake/gateway.rs +++ b/gateway/gateway-requests/src/registration/handshake/gateway.rs @@ -94,7 +94,7 @@ impl<'a> GatewayHandshake<'a> { .to_bytes() .iter() .cloned() - .chain(material.into_iter()) + .chain(material) .collect() } diff --git a/gateway/gateway-requests/src/registration/handshake/shared_key.rs b/gateway/gateway-requests/src/registration/handshake/shared_key.rs index abe1754e31..d3101017a4 100644 --- a/gateway/gateway-requests/src/registration/handshake/shared_key.rs +++ b/gateway/gateway-requests/src/registration/handshake/shared_key.rs @@ -85,10 +85,7 @@ impl SharedKeys { let mac = compute_keyed_hmac::(self.mac_key(), &encrypted_data); - mac.into_bytes() - .into_iter() - .chain(encrypted_data.into_iter()) - .collect() + mac.into_bytes().into_iter().chain(encrypted_data).collect() } pub fn decrypt_tagged( diff --git a/service-providers/common/src/interface/control.rs b/service-providers/common/src/interface/control.rs index 8e808d6fdd..d6a0a28a68 100644 --- a/service-providers/common/src/interface/control.rs +++ b/service-providers/common/src/interface/control.rs @@ -130,7 +130,7 @@ impl Serializable for ControlResponse { fn into_bytes(self) -> Vec { std::iter::once(self.tag() as u8) - .chain(self.serialize_inner().into_iter()) + .chain(self.serialize_inner()) .collect() } diff --git a/service-providers/common/src/interface/request.rs b/service-providers/common/src/interface/request.rs index fefa1ffae6..12a884d79e 100644 --- a/service-providers/common/src/interface/request.rs +++ b/service-providers/common/src/interface/request.rs @@ -81,7 +81,7 @@ where pub fn into_bytes(self) -> Vec { if let Some(version) = self.interface_version.as_u8() { std::iter::once(version) - .chain(self.content.into_bytes(self.interface_version).into_iter()) + .chain(self.content.into_bytes(self.interface_version)) .collect() } else { self.content.into_bytes(self.interface_version) @@ -137,7 +137,7 @@ where } } else { std::iter::once(self.tag() as u8) - .chain(self.serialize_inner().into_iter()) + .chain(self.serialize_inner()) .collect() } } diff --git a/service-providers/common/src/interface/response.rs b/service-providers/common/src/interface/response.rs index 5aff60c5ad..ad502e8b30 100644 --- a/service-providers/common/src/interface/response.rs +++ b/service-providers/common/src/interface/response.rs @@ -71,7 +71,7 @@ where pub fn into_bytes(self) -> Vec { if let Some(version) = self.interface_version.as_u8() { std::iter::once(version) - .chain(self.content.into_bytes(self.interface_version).into_iter()) + .chain(self.content.into_bytes(self.interface_version)) .collect() } else { self.content.into_bytes(self.interface_version) @@ -127,7 +127,7 @@ where } } else { std::iter::once(self.tag() as u8) - .chain(self.serialize_inner().into_iter()) + .chain(self.serialize_inner()) .collect() } } From b050ae72de668f56b1d922cb0e2fe719a44cf6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 07:33:32 +0200 Subject: [PATCH 27/44] Replace Arc with Rc on clippy's suggestion --- clients/webassembly/src/storage/mod.rs | 7 ++++--- clients/webassembly/src/tester/mod.rs | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/clients/webassembly/src/storage/mod.rs b/clients/webassembly/src/storage/mod.rs index f4ab912657..febbd9ecd5 100644 --- a/clients/webassembly/src/storage/mod.rs +++ b/clients/webassembly/src/storage/mod.rs @@ -1,6 +1,8 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::rc::Rc; + use crate::client::config::Config; use crate::storage::error::ClientStorageError; use js_sys::Promise; @@ -8,7 +10,6 @@ use nym_client_core::client::base_client::storage::gateway_details::PersistedGat use nym_crypto::asymmetric::{encryption, identity}; use nym_gateway_client::SharedKeys; use nym_sphinx::acknowledgements::AckKey; -use std::sync::Arc; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage}; @@ -43,7 +44,7 @@ mod v1 { pub struct ClientStorage { #[allow(dead_code)] pub(crate) name: String, - pub(crate) inner: Arc, + pub(crate) inner: Rc, } #[wasm_bindgen] @@ -88,7 +89,7 @@ impl ClientStorage { .await?; Ok(ClientStorage { - inner: Arc::new(inner), + inner: Rc::new(inner), name, }) } diff --git a/clients/webassembly/src/tester/mod.rs b/clients/webassembly/src/tester/mod.rs index ba70062f3b..6433c029c7 100644 --- a/clients/webassembly/src/tester/mod.rs +++ b/clients/webassembly/src/tester/mod.rs @@ -29,6 +29,7 @@ use nym_validator_client::client::IdentityKey; use nym_validator_client::QueryReqwestRpcNyxdClient; use rand::rngs::OsRng; use std::collections::HashSet; +use std::rc::Rc; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Arc, Mutex as SyncMutex}; use std::time::Duration; @@ -42,7 +43,7 @@ pub(crate) mod helpers; pub type NodeTestMessage = TestMessage; type LockedGatewayClient = - Arc>>; + Rc>>; pub(crate) const DEFAULT_TEST_TIMEOUT: Duration = Duration::from_secs(10); pub(crate) const DEFAULT_TEST_PACKETS: u32 = 20; @@ -210,7 +211,7 @@ impl NymNodeTesterBuilder { test_in_progress: Arc::new(AtomicBool::new(false)), current_test_nonce: Default::default(), tester: Arc::new(SyncMutex::new(tester)), - gateway_client: Arc::new(AsyncMutex::new(gateway_client)), + gateway_client: Rc::new(AsyncMutex::new(gateway_client)), processed_receiver: ReceivedReceiverWrapper::new(processed_receiver), _task_manager: task_manager, }) @@ -343,7 +344,7 @@ impl NymNodeTester { )); let processed_receiver_clone = self.processed_receiver.clone(); - let gateway_client_clone = Arc::clone(&self.gateway_client); + let gateway_client_clone = Rc::clone(&self.gateway_client); let tester_marker = TestMarker::new(Arc::clone(&self.test_in_progress)); // start doing async things (send packets and watch for anything coming back) From 48023eab41a38df16f56d193f260a4c2a5e6e123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 07:33:49 +0200 Subject: [PATCH 28/44] Remove shadowing import --- common/wasm-utils/src/storage/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/common/wasm-utils/src/storage/mod.rs b/common/wasm-utils/src/storage/mod.rs index 1a5b601ce6..eb17949849 100644 --- a/common/wasm-utils/src/storage/mod.rs +++ b/common/wasm-utils/src/storage/mod.rs @@ -5,7 +5,6 @@ use crate::console_log; use crate::storage::cipher_export::StoredExportedStoreCipher; use crate::storage::error::StorageError; use futures::TryFutureExt; -use indexed_db_futures::IdbDatabase; use nym_store_cipher::{ Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned, Version, From 8503def37fb1d3570d12fbe0ed143a4bf1c163c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 07:37:29 +0200 Subject: [PATCH 29/44] Remove nym-connect mobile from Makefile --- Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Makefile b/Makefile index 2576489be9..6fc92e55f8 100644 --- a/Makefile +++ b/Makefile @@ -77,9 +77,6 @@ $(eval $(call add_cargo_workspace,contracts,contracts,--lib --target wasm32-unkn $(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown)) $(eval $(call add_cargo_workspace,wallet,nym-wallet,)) $(eval $(call add_cargo_workspace,connect,nym-connect/desktop)) -ifdef NYM_MOBILE -$(eval $(call add_cargo_workspace,connect-mobile,nym-connect/mobile/src-tauri)) -endif # ----------------------------------------------------------------------------- # Convenience targets for crates that are already part of the main workspace From 8104761914c92207112ace1706a5208367aba869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 07:37:45 +0200 Subject: [PATCH 30/44] Update nym-connect Cargo.lock --- nym-connect/desktop/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 612a9d8149..df5097ad1c 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -7557,9 +7557,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "ts-rs" -version = "6.2.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4added4070a4fdf9df03457206cd2e4b12417c8560a2954d91ffcbe60177a56a" +checksum = "e1ff1f8c90369bc172200013ac17ae86e7b5def580687df4e6127883454ff2b0" dependencies = [ "thiserror", "ts-rs-macros", @@ -7567,14 +7567,14 @@ dependencies = [ [[package]] name = "ts-rs-macros" -version = "6.2.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f807fdb3151fee75df7485b901a89624358cd07a67a8fb1a5831bf5a07681ff" +checksum = "a6f41cc0aeb7a4a55730188e147d3795a7349b501f8334697fd37629b896cdc2" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", "termcolor", ] From decae2b54dba147e4102406b3c602ffe0437d793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 09:01:27 +0200 Subject: [PATCH 31/44] Clippy pub use shadowing warning --- common/coconut-interface/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 1b96fe062f..f0f38f5cd4 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -8,7 +8,14 @@ use serde::{Deserialize, Serialize}; use error::CoconutInterfaceError; -pub use nym_coconut::*; +// We list these explicity instead of glob export due to shadowing warnings with the pub tests +// module. +pub use nym_coconut::{ + aggregate_signature_shares, aggregate_verification_keys, hash_to_scalar, prepare_blind_sign, + prove_bandwidth_credential, Attribute, Base58, BlindSignRequest, BlindedSignature, Bytable, + CoconutError, KeyPair, Parameters, PrivateAttribute, PublicAttribute, Signature, + SignatureShare, Theta, VerificationKey, blind_sign +}; #[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)] pub struct Credential { @@ -138,6 +145,8 @@ impl Base58 for Credential {} #[cfg(test)] mod tests { + use nym_coconut::{prove_bandwidth_credential, Signature}; + use super::*; #[test] From 188650ea05dad6b79f79882bdccf2e2eef28bbaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 09:09:05 +0200 Subject: [PATCH 32/44] Stay on 1.71.0 for nym-wallet until the clippy crash is resolved --- .github/workflows/wallet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wallet.yml b/.github/workflows/wallet.yml index d77fb9d6fb..34f202d25f 100644 --- a/.github/workflows/wallet.yml +++ b/.github/workflows/wallet.yml @@ -31,7 +31,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: stable + toolchain: 1.71.0 override: true components: rustfmt, clippy From ee67f3d0f040b9546d5d4721d2ccf136530d15a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 09:10:10 +0200 Subject: [PATCH 33/44] rustfmt --- common/coconut-interface/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index f0f38f5cd4..2322bc3978 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -11,10 +11,10 @@ use error::CoconutInterfaceError; // We list these explicity instead of glob export due to shadowing warnings with the pub tests // module. pub use nym_coconut::{ - aggregate_signature_shares, aggregate_verification_keys, hash_to_scalar, prepare_blind_sign, - prove_bandwidth_credential, Attribute, Base58, BlindSignRequest, BlindedSignature, Bytable, - CoconutError, KeyPair, Parameters, PrivateAttribute, PublicAttribute, Signature, - SignatureShare, Theta, VerificationKey, blind_sign + aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, + prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest, + BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute, + PublicAttribute, Signature, SignatureShare, Theta, VerificationKey, }; #[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)] From ec75a06c9d73d74f962119766c191b4a60b6716f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 14:47:03 +0200 Subject: [PATCH 34/44] nym-connect: filter gateways on compatible version --- .../src-tauri/src/operations/directory/mod.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs index dc001dac2e..3842720161 100644 --- a/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/desktop/src-tauri/src/operations/directory/mod.rs @@ -8,6 +8,7 @@ use crate::{ }; use itertools::Itertools; use nym_api_requests::models::GatewayBondAnnotated; +use nym_bin_common::version_checker::is_minor_version_compatible; use nym_config::defaults::var_names::{NETWORK_NAME, NYM_API}; use nym_contracts_common::types::Percent; use nym_validator_client::nym_api::Client as ApiClient; @@ -120,7 +121,17 @@ fn filter_out_inactive_services( async fn fetch_gateways() -> Result> { let api_client = ApiClient::new(Url::from_str(&std::env::var(NYM_API)?)?); - Ok(api_client.get_gateways_detailed().await?) + let gateways = api_client.get_gateways_detailed().await?; + let our_version = env!("CARGO_PKG_VERSION"); + log::debug!( + "Our version that we use to filter compatible gateways: {}", + our_version + ); + let gateways = gateways + .into_iter() + .filter(|g| is_minor_version_compatible(&g.gateway_bond.gateway.version, our_version)) + .collect(); + Ok(gateways) } #[tauri::command] From 2fbb901deb3d97ec36933d99a707c08029db6d5a Mon Sep 17 00:00:00 2001 From: Lorexia Date: Fri, 25 Aug 2023 11:42:36 +0200 Subject: [PATCH 35/44] Update mixnode setup to return IPv4 --- documentation/operators/src/nodes/mix-node-setup.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index a50c5cac13..b168329487 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -63,16 +63,16 @@ To check available configuration options for initializing your node use: ``` ~~~ -Initalise your mix node with the following command, replacing the value of `--id` with the moniker you wish to give your mix node. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. +Initalise your mix node with the following command, replacing the value of `--id` with the moniker you wish to give your mix node. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl -4 https://ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. ``` -./nym-mixnode init --id --host $(curl ifconfig.me) +./nym-mixnode init --id --host $(curl -4 https://ifconfig.me) ``` ~~~admonish example collapsible=true title="Console output" ``` -.nym-mixnode init --id --host $(curl ifconfig.me) --wallet-address +.nym-mixnode init --id --host $(curl -4 https://ifconfig.me) --wallet-address Initialising mixnode ... From 069226125b05699e267c2b021ec651457b42078e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 24 Aug 2023 16:36:06 +0200 Subject: [PATCH 36/44] Separate cfg wasm function --- .../topology_control/geo_aware_provider.rs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index ece6e967a2..36010e5e3f 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -15,6 +15,19 @@ use url::Url; const MIN_NODES_PER_LAYER: usize = 1; +#[cfg(target_arch = "wasm32")] +fn reqwest_client() -> Option { + reqwest::Client::builder().build().ok() +} + +#[cfg(not(target_arch = "wasm32"))] +fn reqwest_client() -> Option { + reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .build() + .ok() +} + // TODO: create a explorer-api-client async fn fetch_mixnodes_from_explorer_api() -> Option> { let explorer_api_url = std::env::var(EXPLORER_API).ok()?; @@ -24,20 +37,7 @@ async fn fetch_mixnodes_from_explorer_api() -> Option Date: Fri, 25 Aug 2023 12:44:17 +0200 Subject: [PATCH 37/44] Also filter gateways on location --- .../topology_control/geo_aware_provider.rs | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index 36010e5e3f..88a8bdada4 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -1,14 +1,14 @@ use std::{collections::HashMap, fmt}; use log::{debug, error, info}; -use nym_explorer_api_requests::PrettyDetailedMixNodeBond; +use nym_explorer_api_requests::{PrettyDetailedMixNodeBond, PrettyDetailedGatewayBond}; use nym_network_defaults::var_names::EXPLORER_API; use nym_topology::{ nym_topology_from_detailed, provider_trait::{async_trait, TopologyProvider}, NymTopology, }; -use nym_validator_client::client::MixId; +use nym_validator_client::client::{MixId, IdentityKey}; use rand::{prelude::SliceRandom, thread_rng}; use serde::{Deserialize, Serialize}; use url::Url; @@ -47,6 +47,25 @@ async fn fetch_mixnodes_from_explorer_api() -> Option Option> { + let explorer_api_url = std::env::var(EXPLORER_API).ok()?; + let explorer_api_url = Url::parse(&explorer_api_url) + .ok()? + .join("v1/gateways") + .ok()?; + + debug!("Fetching: {}", explorer_api_url); + reqwest_client()? + .get(explorer_api_url) + .send() + .await + .ok()? + .json::>() + .await + .ok() +} + #[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)] pub enum CountryGroup { Europe, @@ -213,6 +232,22 @@ fn group_mixnodes_by_country_code( }) } +fn group_gateways_by_country_code( + gateways: Vec, +) -> HashMap> { + gateways + .into_iter() + .fold(HashMap::>::new(), |mut acc, g| { + if let Some(ref location) = g.location { + let country_code = location.two_letter_iso_country_code.clone(); + let group_code = CountryGroup::new(country_code.as_str()); + let gateways = acc.entry(group_code).or_insert_with(Vec::new); + gateways.push(g.gateway.identity_key) + } + acc + }) +} + fn log_mixnode_distribution(mixnodes: &HashMap>) { let mixnode_distribution = mixnodes .iter() @@ -222,6 +257,15 @@ fn log_mixnode_distribution(mixnodes: &HashMap>) { debug!("Mixnode distribution - {}", mixnode_distribution); } +fn log_gateway_distribution(gateways: &HashMap>) { + let gateway_distribution = gateways + .iter() + .map(|(k, v)| format!("{}: {}", k, v.len())) + .collect::>() + .join(", "); + debug!("Gateway distribution - {}", gateway_distribution); +} + fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> { let mixes = topology.mixes(); if mixes.keys().len() < 3 { @@ -294,6 +338,12 @@ impl GeoAwareTopologyProvider { return None; }; + debug!("Fetching gateways from explorer-api..."); + let Some(gateways_from_explorer_api) = fetch_gateways_from_explorer_api().await else { + error!("failed to get mixnodes from explorer-api"); + return None; + }; + // Partition mixnodes_from_explorer_api according to the value of // two_letter_iso_country_code. // NOTE: we construct the full distribution here, but only use the one we're interested in. @@ -302,16 +352,29 @@ impl GeoAwareTopologyProvider { let mixnode_distribution = group_mixnodes_by_country_code(mixnodes_from_explorer_api); log_mixnode_distribution(&mixnode_distribution); + let gateway_distribution = group_gateways_by_country_code(gateways_from_explorer_api); + log_gateway_distribution(&gateway_distribution); + let Some(filtered_mixnode_ids) = mixnode_distribution.get(&self.filter_on) else { error!("no mixnodes found for: {}", self.filter_on); return None; }; + let Some(filtered_gateway_ids) = gateway_distribution.get(&self.filter_on) else { + error!("no gateways found for: {}", self.filter_on); + return None; + }; + let mixnodes = mixnodes .into_iter() .filter(|m| filtered_mixnode_ids.contains(&m.mix_id())) .collect::>(); + let gateways = gateways + .into_iter() + .filter(|g| filtered_gateway_ids.contains(g.identity())) + .collect::>(); + let topology = nym_topology_from_detailed(mixnodes, gateways) .filter_system_version(&self.client_version); From 66f04d17b73d7a1d34a84394a616d077e7a31083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 13:41:21 +0200 Subject: [PATCH 38/44] Select country group by the exit gateway --- clients/socks5/src/commands/mod.rs | 16 ++++-- .../client-core/src/client/base_client/mod.rs | 12 +++-- .../topology_control/geo_aware_provider.rs | 54 ++++++++++++++----- common/client-core/src/config/mod.rs | 13 ++++- nym-connect/desktop/Cargo.lock | 1 + nym-connect/desktop/src-tauri/src/tasks.rs | 13 +++-- 6 files changed, 82 insertions(+), 27 deletions(-) diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 393429246d..d2b37b5a84 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -17,7 +17,7 @@ use nym_client_core::client::base_client::storage::gateway_details::{ }; use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup; -use nym_client_core::config::{GatewayEndpointConfig, TopologyStructure}; +use nym_client_core::config::{GatewayEndpointConfig, GroupBy, TopologyStructure}; use nym_client_core::error::ClientCoreError; use nym_config::OptionalSet; use nym_sphinx::params::{PacketSize, PacketType}; @@ -101,9 +101,17 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { let secondary_packet_size = args.medium_toggle.then_some(PacketSize::ExtendedPacket16); let no_per_hop_delays = args.medium_toggle; - let topology_structure = if args.medium_toggle || args.geo_routing.is_some() { - // TODO: rethink the default group. I just picked one for now. - TopologyStructure::GeoAware(args.geo_routing.unwrap_or(CountryGroup::Europe)) + let topology_structure = if args.medium_toggle { + // Use the location of the network-requester + let address = config + .core + .socks5 + .provider_mix_address + .parse() + .expect("failed to parse provider mix address"); + TopologyStructure::GeoAware(GroupBy::NymAddress(address)) + } else if let Some(code) = args.geo_routing { + TopologyStructure::GeoAware(GroupBy::CountryGroup(code)) } else { TopologyStructure::default() }; diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index 2f2e7d0072..02615f2b95 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -354,11 +354,13 @@ where nym_api_urls, env!("CARGO_PKG_VERSION").to_string(), )), - config::TopologyStructure::GeoAware(group) => Box::new(GeoAwareTopologyProvider::new( - nym_api_urls, - env!("CARGO_PKG_VERSION").to_string(), - group, - )), + config::TopologyStructure::GeoAware(group_by) => { + Box::new(GeoAwareTopologyProvider::new( + nym_api_urls, + env!("CARGO_PKG_VERSION").to_string(), + group_by, + )) + } }) } diff --git a/common/client-core/src/client/topology_control/geo_aware_provider.rs b/common/client-core/src/client/topology_control/geo_aware_provider.rs index 88a8bdada4..dabece05e5 100644 --- a/common/client-core/src/client/topology_control/geo_aware_provider.rs +++ b/common/client-core/src/client/topology_control/geo_aware_provider.rs @@ -1,18 +1,21 @@ use std::{collections::HashMap, fmt}; use log::{debug, error, info}; -use nym_explorer_api_requests::{PrettyDetailedMixNodeBond, PrettyDetailedGatewayBond}; +use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond}; use nym_network_defaults::var_names::EXPLORER_API; use nym_topology::{ nym_topology_from_detailed, provider_trait::{async_trait, TopologyProvider}, NymTopology, }; -use nym_validator_client::client::{MixId, IdentityKey}; +use nym_validator_client::client::{IdentityKey, MixId}; use rand::{prelude::SliceRandom, thread_rng}; use serde::{Deserialize, Serialize}; +use tap::TapOptional; use url::Url; +use crate::config::GroupBy; + const MIN_NODES_PER_LAYER: usize = 1; #[cfg(target_arch = "wasm32")] @@ -235,9 +238,9 @@ fn group_mixnodes_by_country_code( fn group_gateways_by_country_code( gateways: Vec, ) -> HashMap> { - gateways - .into_iter() - .fold(HashMap::>::new(), |mut acc, g| { + gateways.into_iter().fold( + HashMap::>::new(), + |mut acc, g| { if let Some(ref location) = g.location { let country_code = location.two_letter_iso_country_code.clone(); let group_code = CountryGroup::new(country_code.as_str()); @@ -245,7 +248,8 @@ fn group_gateways_by_country_code( gateways.push(g.gateway.identity_key) } acc - }) + }, + ) } fn log_mixnode_distribution(mixnodes: &HashMap>) { @@ -288,7 +292,7 @@ fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> { pub struct GeoAwareTopologyProvider { validator_client: nym_validator_client::client::NymApiClient, - filter_on: CountryGroup, + filter_on: GroupBy, client_version: String, } @@ -296,7 +300,7 @@ impl GeoAwareTopologyProvider { pub fn new( mut nym_api_urls: Vec, client_version: String, - filter_on: CountryGroup, + filter_on: GroupBy, ) -> GeoAwareTopologyProvider { log::info!( "Creating geo-aware topology provider with filter on {:?}", @@ -344,6 +348,32 @@ impl GeoAwareTopologyProvider { return None; }; + // Determine what we should filter around + let filter_on = match self.filter_on { + GroupBy::CountryGroup(group) => group, + GroupBy::NymAddress(recipient) => { + // Convert recipient into a country group by extracting out the gateway part and + // using that as the country code. + let gateway = recipient.gateway().to_base58_string(); + + // Lookup the location of this gateway by using the location data from the + // explorer-api + let gateway_location = gateways_from_explorer_api + .iter() + .find(|g| g.gateway.identity_key == gateway) + .and_then(|g| g.location.clone()) + .map(|location| location.two_letter_iso_country_code) + .tap_none(|| error!("No location found for the gateway: {}", gateway))?; + debug!( + "Filtering on nym-address: {}, with location: {}", + recipient, gateway_location + ); + + CountryGroup::new(&gateway_location) + } + }; + debug!("Filter group: {}", filter_on); + // Partition mixnodes_from_explorer_api according to the value of // two_letter_iso_country_code. // NOTE: we construct the full distribution here, but only use the one we're interested in. @@ -355,13 +385,13 @@ impl GeoAwareTopologyProvider { let gateway_distribution = group_gateways_by_country_code(gateways_from_explorer_api); log_gateway_distribution(&gateway_distribution); - let Some(filtered_mixnode_ids) = mixnode_distribution.get(&self.filter_on) else { - error!("no mixnodes found for: {}", self.filter_on); + let Some(filtered_mixnode_ids) = mixnode_distribution.get(&filter_on) else { + error!("no mixnodes found for: {}", filter_on); return None; }; - let Some(filtered_gateway_ids) = gateway_distribution.get(&self.filter_on) else { - error!("no gateways found for: {}", self.filter_on); + let Some(filtered_gateway_ids) = gateway_distribution.get(&filter_on) else { + error!("no gateways found for: {}", filter_on); return None; }; diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index 465c227e19..3442210d37 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -3,7 +3,10 @@ use nym_config::defaults::NymNetworkDetails; use nym_crypto::asymmetric::identity; -use nym_sphinx::params::{PacketSize, PacketType}; +use nym_sphinx::{ + addressing::clients::Recipient, + params::{PacketSize, PacketType}, +}; use serde::{Deserialize, Serialize}; use std::time::Duration; use url::Url; @@ -484,7 +487,13 @@ pub struct Topology { pub enum TopologyStructure { #[default] NymApi, - GeoAware(CountryGroup), + GeoAware(GroupBy), +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum GroupBy { + CountryGroup(CountryGroup), + NymAddress(Recipient), } impl Default for Topology { diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index df5097ad1c..e915498a01 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3884,6 +3884,7 @@ version = "1.1.15" dependencies = [ "async-trait", "base64 0.21.2", + "cfg-if", "dashmap", "dirs 4.0.0", "futures", diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index 06b0809c2f..6b0d5cadc8 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -6,7 +6,7 @@ use nym_client_core::{ }, topology_control::geo_aware_provider::CountryGroup, }, - config::{GatewayEndpointConfig, TopologyStructure}, + config::{GatewayEndpointConfig, GroupdBy, TopologyStructure}, error::ClientCoreStatusMessage, }; use nym_socks5_client_core::{NymClient as Socks5NymClient, Socks5ControlMessageSender}; @@ -54,12 +54,17 @@ fn override_config_from_env(config: &mut Config, privacy_level: &PrivacyLevel) { config.core.base.set_no_per_hop_delays(); // TODO: selectable in the UI - let default_country_group = CountryGroup::Europe; - log::warn!("Using geo-aware mixnode selection: {default_country_group}"); + let address = config + .core + .socks5 + .provider_mix_address + .parse() + .expect("failed to parse provider mix address"); + log::warn!("Using geo-aware mixnode selection baseon the location of: {address}"); config .core .base - .set_topology_structure(TopologyStructure::GeoAware(default_country_group)); + .set_topology_structure(TopologyStructure::GeoAware(GroupBy::NymAddress(address))); } } From 6c67cff235a087c0417d1dc6d146e69b3850d600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 16:02:13 +0200 Subject: [PATCH 39/44] Allow clippy for now --- common/client-core/src/config/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index 3442210d37..f3cee8200f 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -483,6 +483,7 @@ pub struct Topology { pub topology_structure: TopologyStructure, } +#[allow(clippy::large_enum_variant)] #[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum TopologyStructure { #[default] @@ -490,6 +491,7 @@ pub enum TopologyStructure { GeoAware(GroupBy), } +#[allow(clippy::large_enum_variant)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum GroupBy { CountryGroup(CountryGroup), From d530e492ec3f66e7e2cc2a9570d64f01f6ec5f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 25 Aug 2023 16:13:41 +0200 Subject: [PATCH 40/44] Fix compilation --- nym-connect/desktop/src-tauri/src/tasks.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/nym-connect/desktop/src-tauri/src/tasks.rs b/nym-connect/desktop/src-tauri/src/tasks.rs index 6b0d5cadc8..4af366ca48 100644 --- a/nym-connect/desktop/src-tauri/src/tasks.rs +++ b/nym-connect/desktop/src-tauri/src/tasks.rs @@ -1,12 +1,9 @@ use futures::{channel::mpsc, StreamExt}; use nym_client_core::{ - client::{ - base_client::storage::{ - gateway_details::GatewayDetailsStore, MixnetClientStorage, OnDiskPersistent, - }, - topology_control::geo_aware_provider::CountryGroup, + client::base_client::storage::{ + gateway_details::GatewayDetailsStore, MixnetClientStorage, OnDiskPersistent, }, - config::{GatewayEndpointConfig, GroupdBy, TopologyStructure}, + config::{GatewayEndpointConfig, GroupBy, TopologyStructure}, error::ClientCoreStatusMessage, }; use nym_socks5_client_core::{NymClient as Socks5NymClient, Socks5ControlMessageSender}; From 2f6617daace6a6517142948add8f087d49192729 Mon Sep 17 00:00:00 2001 From: Fran Arbanas Date: Fri, 25 Aug 2023 17:13:52 +0200 Subject: [PATCH 41/44] Feature/issue credentials (#3691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move the functionality to issue credentials from the credential binary and connect it with nym-cli * finished CLI part, trying to fit SDK part * finished Rust SDK * fix: cleanup * linting * linting * linting * remove one layer of coconut in nym-cli * linting * Fixes based on PR comments * formatting * fixes based on PR comments * formatting * fixing clippy errors * fixed post-rebasing issues and converted the lib into shared dep for other binaries * removed credentials client in favour of moving the functionality to nym-cli * removed redundant 'issue_credential' example (it did the same thing as 'bandwdith') * removed credentials client from build server * made the coconut cli also accept nym-api configs * fixed support for socks5 and NR --------- Co-authored-by: Jędrzej Stuczyński --- .../build-and-upload-binaries-ci.yml | 1 - Cargo.lock | 45 +++--- Cargo.toml | 2 +- clients/credential/README.md | 32 ---- clients/credential/src/client.rs | 55 ------- clients/credential/src/commands.rs | 82 ---------- clients/credential/src/error.rs | 36 ----- clients/credential/src/main.rs | 131 --------------- clients/credential/src/recovery_storage.rs | 56 ------- clients/webassembly/Cargo.lock | 1 + .../examples/query_name_service.rs | 2 +- .../query_service_provider_directory.rs | 2 +- .../validator-client/src/client.rs | 8 + common/commands/Cargo.toml | 7 + .../commands/src/coconut/issue_credentials.rs | 51 ++++++ common/commands/src/coconut/mod.rs | 18 ++- .../src/coconut/recover_credentials.rs | 48 ++++++ common/commands/src/utils.rs | 153 +++++++++++++++++- .../credential-utils}/Cargo.toml | 19 +-- common/credential-utils/src/errors.rs | 31 ++++ common/credential-utils/src/lib.rs | 5 + .../credential-utils/src/recovery_storage.rs | 64 ++++++++ common/credential-utils/src/utils.rs | 139 ++++++++++++++++ common/credentials/Cargo.toml | 3 +- common/credentials/src/coconut/utils.rs | 29 +++- common/network-defaults/src/lib.rs | 4 +- explorer-api/src/main.rs | 2 +- nym-connect/desktop/Cargo.lock | 1 + sdk/lib/socks5-listener/src/lib.rs | 2 +- sdk/rust/nym-sdk/Cargo.toml | 4 +- sdk/rust/nym-sdk/examples/bandwidth.rs | 29 ++-- sdk/rust/nym-sdk/src/mixnet/paths.rs | 3 +- tools/nym-cli/src/coconut/mod.rs | 26 +++ tools/nym-cli/src/main.rs | 4 + 34 files changed, 633 insertions(+), 462 deletions(-) delete mode 100644 clients/credential/README.md delete mode 100644 clients/credential/src/client.rs delete mode 100644 clients/credential/src/commands.rs delete mode 100644 clients/credential/src/error.rs delete mode 100644 clients/credential/src/main.rs delete mode 100644 clients/credential/src/recovery_storage.rs create mode 100644 common/commands/src/coconut/issue_credentials.rs create mode 100644 common/commands/src/coconut/recover_credentials.rs rename {clients/credential => common/credential-utils}/Cargo.toml (54%) create mode 100644 common/credential-utils/src/errors.rs create mode 100644 common/credential-utils/src/lib.rs create mode 100644 common/credential-utils/src/recovery_storage.rs create mode 100644 common/credential-utils/src/utils.rs create mode 100644 tools/nym-cli/src/coconut/mod.rs diff --git a/.github/workflows/build-and-upload-binaries-ci.yml b/.github/workflows/build-and-upload-binaries-ci.yml index f317f90110..57b897d9c2 100644 --- a/.github/workflows/build-and-upload-binaries-ci.yml +++ b/.github/workflows/build-and-upload-binaries-ci.yml @@ -98,7 +98,6 @@ jobs: cp target/release/nym-network-requester $OUTPUT_DIR cp target/release/nym-network-statistics $OUTPUT_DIR cp target/release/nym-cli $OUTPUT_DIR - cp target/release/nym-credential-client $OUTPUT_DIR cp target/release/explorer-api $OUTPUT_DIR cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR diff --git a/Cargo.lock b/Cargo.lock index e31c5a9702..1232af2f51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5811,6 +5811,7 @@ dependencies = [ name = "nym-cli-commands" version = "1.0.0" dependencies = [ + "anyhow", "base64 0.13.1", "bip39", "bs58 0.4.0", @@ -5824,10 +5825,16 @@ dependencies = [ "humantime-serde", "k256 0.13.1", "log", + "nym-bandwidth-controller", "nym-bin-common", + "nym-client-core", "nym-coconut-bandwidth-contract-common", "nym-coconut-dkg-common", + "nym-config", "nym-contracts-common", + "nym-credential-storage", + "nym-credential-utils", + "nym-credentials", "nym-crypto", "nym-mixnet-contract-common", "nym-multisig-contract-common", @@ -6024,26 +6031,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "nym-credential-client" -version = "0.1.0" -dependencies = [ - "clap 4.3.21", - "log", - "nym-bandwidth-controller", - "nym-bin-common", - "nym-client-core", - "nym-config", - "nym-credential-storage", - "nym-credentials", - "nym-network-defaults", - "nym-pemstore", - "nym-validator-client", - "serde", - "thiserror", - "tokio", -] - [[package]] name = "nym-credential-storage" version = "0.1.0" @@ -6055,12 +6042,28 @@ dependencies = [ "tokio", ] +[[package]] +name = "nym-credential-utils" +version = "0.1.0" +dependencies = [ + "log", + "nym-bandwidth-controller", + "nym-client-core", + "nym-config", + "nym-credential-storage", + "nym-credentials", + "nym-validator-client", + "thiserror", + "tokio", +] + [[package]] name = "nym-credentials" version = "0.1.0" dependencies = [ "bls12_381 0.5.0", "cosmrs", + "log", "nym-api-requests", "nym-coconut-interface", "nym-crypto", @@ -6568,6 +6571,7 @@ dependencies = [ name = "nym-sdk" version = "0.1.0" dependencies = [ + "anyhow", "async-trait", "bip39", "dotenvy", @@ -6579,6 +6583,7 @@ dependencies = [ "nym-bin-common", "nym-client-core", "nym-credential-storage", + "nym-credential-utils", "nym-credentials", "nym-crypto", "nym-gateway-requests", diff --git a/Cargo.toml b/Cargo.toml index ac0178a41e..2c505c4bfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ opt-level = 3 resolver = "2" members = [ - "clients/credential", "clients/native", "clients/native/websocket-requests", "clients/socks5", @@ -43,6 +42,7 @@ members = [ "common/cosmwasm-smart-contracts/vesting-contract", "common/credential-storage", "common/credentials", + "common/credential-utils", "common/crypto", "common/dkg", "common/execute", diff --git a/clients/credential/README.md b/clients/credential/README.md deleted file mode 100644 index 9d82b4b839..0000000000 --- a/clients/credential/README.md +++ /dev/null @@ -1,32 +0,0 @@ - - -## Credential binary - -The credential binary is used to acquire coconut bandwidth credentials in exchange for nym tokens. Those credentials are stored in the client's `data` directory, so that they can be used as the client sees fit. - -### Warning - -The credential binary is still experimental software. The infrastructure for using it is not yet deployed to mainnet and it's still in the process of being deployed to sandbox. - -### Building - -From the project's root directory, run: -``` -cargo build -p nym-credential-client -``` -which generates the `nym-credential-client` binary in `target/debug/nym-credential-client`. - - -### Running - -For example, you can get a credential worth 3 nym (3000000 unym) in a socks5 client that was already initialized like so: - -``` -./target/debug/nym-credential-client --config-env-file envs/sandbox.env --client-home-directory ~/.nym/socks5-clients/cred_client --nyxd-url https://sandbox-validator1.nymtech.net --mnemonic $MNEMONIC --recovery-dir /tmp/recovery --amount 3000000 -``` - -More information regarding how to run the binary can be found by running it with the `--help` argument. - diff --git a/clients/credential/src/client.rs b/clients/credential/src/client.rs deleted file mode 100644 index 9eb0a7da75..0000000000 --- a/clients/credential/src/client.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::error::Result; -use bip39::Mnemonic; -use nym_network_defaults::{NymNetworkDetails, VOUCHER_INFO}; -use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient; -use nym_validator_client::nyxd::{self, DirectSigningHttpRpcNyxdClient}; -use nym_validator_client::nyxd::{Coin, Fee, NyxdClient}; -use std::str::FromStr; -use url::Url; - -pub(crate) struct Client { - nyxd_client: DirectSigningHttpRpcNyxdClient, - mix_denom_base: String, -} - -impl Client { - pub fn new(nyxd_url: &str, mnemonic: &str) -> Self { - let nyxd_url = Url::from_str(nyxd_url).unwrap(); - let mnemonic = Mnemonic::from_str(mnemonic).unwrap(); - let network_details = NymNetworkDetails::new_from_env(); - let config = nyxd::Config::try_from_nym_network_details(&network_details) - .expect("failed to construct valid validator client config with the provided network"); - let nyxd_client = - NyxdClient::connect_with_mnemonic(config, nyxd_url.as_ref(), mnemonic, None).unwrap(); - - Client { - nyxd_client, - mix_denom_base: network_details.chain_details.mix_denom.base, - } - } - - pub async fn deposit( - &self, - amount: u64, - verification_key: String, - encryption_key: String, - fee: Option, - ) -> Result { - let amount = Coin::new(amount as u128, self.mix_denom_base.clone()); - Ok(self - .nyxd_client - .deposit( - amount, - String::from(VOUCHER_INFO), - verification_key, - encryption_key, - fee, - ) - .await? - .transaction_hash - .to_string()) - } -} diff --git a/clients/credential/src/commands.rs b/clients/credential/src/commands.rs deleted file mode 100644 index da603eb84b..0000000000 --- a/clients/credential/src/commands.rs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use clap::{ArgGroup, Args, Subcommand}; -use log::*; -use nym_bandwidth_controller::acquire::state::State; -use nym_bin_common::completions::ArgShell; -use nym_credential_storage::persistent_storage::PersistentStorage; -use nym_validator_client::nyxd::contract_traits::DkgQueryClient; - -use crate::error::Result; -use crate::recovery_storage::RecoveryStorage; - -#[derive(Subcommand)] -pub(crate) enum Command { - /// Run the binary to obtain a credential - Run(Run), - - /// Generate shell completions - Completions(ArgShell), - - /// Generate Fig specification - GenerateFigSpec, -} - -#[derive(Args)] -#[clap(group( -ArgGroup::new("recov") -.required(true) -.args(&["amount", "recovery_mode"]), -))] -pub(crate) struct Run { - /// Home directory of the client that is supposed to use the credential. - #[clap(long)] - pub(crate) client_home_directory: std::path::PathBuf, - - /// A mnemonic for the account that buys the credential - #[clap(long)] - pub(crate) mnemonic: String, - - /// The amount of utokens the credential will hold. If recovery mode is enabled, this value - /// is not needed - #[clap(long, default_value = "0")] - pub(crate) amount: u64, - - /// Path to a directory used to store recovery files for unconsumed deposits - #[clap(long)] - pub(crate) recovery_dir: std::path::PathBuf, - - /// Recovery mode, when enabled, tries to recover any deposit data dumped in recovery_dir - #[clap(long)] - pub(crate) recovery_mode: bool, -} - -pub(crate) async fn recover_credentials( - client: &C, - recovery_storage: &RecoveryStorage, - shared_storage: &PersistentStorage, -) -> Result<()> { - for voucher in recovery_storage.unconsumed_vouchers()? { - let state = State::new(voucher); - if let Err(e) = - nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await - { - error!( - "Could not recover deposit {} due to {:?}, try again later", - state.voucher.tx_hash(), - e - ) - } else { - info!( - "Converted deposit {} to a credential, removing recovery data for it", - state.voucher.tx_hash() - ); - if let Err(e) = recovery_storage.remove_voucher(state.voucher.tx_hash().to_string()) { - warn!("Could not remove recovery data - {:?}", e); - } - } - } - - Ok(()) -} diff --git a/clients/credential/src/error.rs b/clients/credential/src/error.rs deleted file mode 100644 index 1c6ac1521b..0000000000 --- a/clients/credential/src/error.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use std::time::SystemTimeError; -use thiserror::Error; - -use nym_credential_storage::error::StorageError; -use nym_credentials::error::Error as CredentialError; -use nym_validator_client::nyxd::error::NyxdError; -use nym_validator_client::ValidatorClientError; - -pub type Result = std::result::Result; - -#[derive(Error, Debug)] -pub enum CredentialClientError { - #[error("IO error: {0}")] - IOError(#[from] std::io::Error), - - #[error("Bandwidth controller error: {0}")] - BandwidthControllerError(#[from] nym_bandwidth_controller::error::BandwidthControllerError), - - #[error("Nyxd error: {0}")] - Nyxd(#[from] NyxdError), - - #[error("Validator client error: {0}")] - ValidatorClientError(#[from] ValidatorClientError), - - #[error("Credential error: {0}")] - Credential(#[from] CredentialError), - - #[error("Could not use shared storage")] - SharedStorageError(#[from] StorageError), - - #[error("Could not get system time")] - SysTimeError(#[from] SystemTimeError), -} diff --git a/clients/credential/src/main.rs b/clients/credential/src/main.rs deleted file mode 100644 index 982ef02823..0000000000 --- a/clients/credential/src/main.rs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -mod commands; -mod error; -mod recovery_storage; - -use commands::*; -use error::Result; -use log::*; -use nym_bin_common::completions::fig_generate; -use nym_config::DEFAULT_DATA_DIR; -use nym_network_defaults::{setup_env, NymNetworkDetails}; -use std::process::exit; -use std::time::{Duration, SystemTime}; - -use clap::{CommandFactory, Parser}; -use nym_bin_common::logging::setup_logging; -use nym_client_core::config::disk_persistence::CommonClientPaths; -use nym_validator_client::nyxd::contract_traits::DkgQueryClient; -use nym_validator_client::nyxd::{Coin, Config}; -use nym_validator_client::DirectSigningHttpRpcNyxdClient; - -const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute - -#[derive(Parser)] -#[clap(author = "Nymtech", version, about)] -struct Cli { - /// Path pointing to an env file that configures the client. - #[clap(short, long)] - pub(crate) config_env_file: Option, - - #[clap(subcommand)] - pub(crate) command: Command, -} - -async fn block_until_coconut_is_available( - client: &C, -) -> Result<()> { - loop { - let epoch = client.get_current_epoch().await?; - let current_timestamp_secs = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH)? - .as_secs(); - if epoch.state.is_final() { - if current_timestamp_secs + SAFETY_BUFFER_SECS >= epoch.finish_timestamp.seconds() { - info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60); - exit(0); - } - - break; - } else { - // Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries - let secs_until_final = epoch - .final_timestamp_secs() - .saturating_sub(current_timestamp_secs) - + 1; - info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final); - std::thread::sleep(Duration::from_secs(secs_until_final)); - } - } - - Ok(()) -} - -#[tokio::main] -async fn main() -> Result<()> { - let args = Cli::parse(); - setup_logging(); - setup_env(args.config_env_file.as_ref()); - let bin_name = "nym-credential-client"; - - match args.command { - Command::Run(r) => { - // we assume the structure of /data - let data_dir = r.client_home_directory.join(DEFAULT_DATA_DIR); - let paths = CommonClientPaths::new_default(data_dir); - let db_path = paths.credentials_database; - - let shared_storage = - nym_credential_storage::initialise_persistent_storage(db_path).await; - let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?; - - let network_details = NymNetworkDetails::new_from_env(); - let config = Config::try_from_nym_network_details(&network_details).expect( - "failed to construct valid validator client config with the provided network", - ); - let amount = Coin::new( - r.amount as u128, - network_details.chain_details.mix_denom.base, - ); - let endpoint = network_details.endpoints[0].nyxd_url.as_str(); - let client = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic( - config, - endpoint, - r.mnemonic.parse().unwrap(), - )?; - - block_until_coconut_is_available(&client).await?; - info!("Starting depositing funds, don't kill the process"); - - if !r.recovery_mode { - let state = nym_bandwidth_controller::acquire::deposit(&client, amount).await?; - if nym_bandwidth_controller::acquire::get_credential( - &state, - &client, - &shared_storage, - ) - .await - .is_err() - { - warn!("Failed to obtain credential. Dumping recovery data.",); - match recovery_storage.insert_voucher(&state.voucher) { - Ok(file_path) => { - warn!("Dumped recovery data to {:?}. Try using recovery mode to convert it to a credential", file_path); - } - Err(e) => { - error!("Could not dump recovery data to file system due to {:?}, the deposit will be lost!", e) - } - } - } - } else { - recover_credentials(&client, &recovery_storage, &shared_storage).await?; - } - } - Command::Completions(c) => c.generate(&mut Cli::command(), bin_name), - Command::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), - } - - Ok(()) -} diff --git a/clients/credential/src/recovery_storage.rs b/clients/credential/src/recovery_storage.rs deleted file mode 100644 index db81d2ff6a..0000000000 --- a/clients/credential/src/recovery_storage.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use nym_credentials::coconut::bandwidth::BandwidthVoucher; -use std::fs::{create_dir_all, read_dir, File}; -use std::io::{Read, Write}; -use std::path::PathBuf; - -pub struct RecoveryStorage { - recovery_dir: PathBuf, -} - -impl RecoveryStorage { - pub fn new(recovery_dir: PathBuf) -> std::io::Result { - create_dir_all(&recovery_dir)?; - Ok(Self { recovery_dir }) - } - - pub fn unconsumed_vouchers(&self) -> std::io::Result> { - Ok(read_dir(&self.recovery_dir)? - .filter_map(|entry| entry.ok()) - .filter_map(|entry| { - let path = entry.path(); - if path.is_file() { - Some(path) - } else { - None - } - }) - .filter_map(|path| File::open(path).ok()) - .filter_map(|mut f| { - let mut buff = Vec::new(); - if f.read_to_end(&mut buff).is_ok() { - Some(buff) - } else { - None - } - }) - .filter_map(|buff| BandwidthVoucher::try_from_bytes(&buff).ok())) - } - - pub fn insert_voucher(&self, voucher: &BandwidthVoucher) -> std::io::Result { - let file_name = voucher.tx_hash().to_string(); - let file_path = self.recovery_dir.join(file_name); - let mut file = File::create(&file_path)?; - let buff = voucher.to_bytes(); - file.write_all(&buff)?; - - Ok(file_path) - } - - pub fn remove_voucher(&self, file_name: String) -> std::io::Result<()> { - let file_path = self.recovery_dir.join(file_name); - std::fs::remove_file(file_path) - } -} diff --git a/clients/webassembly/Cargo.lock b/clients/webassembly/Cargo.lock index 529b241500..271ea93ea8 100644 --- a/clients/webassembly/Cargo.lock +++ b/clients/webassembly/Cargo.lock @@ -2677,6 +2677,7 @@ version = "0.1.0" dependencies = [ "bls12_381 0.5.0", "cosmrs", + "log", "nym-api-requests", "nym-coconut-interface", "nym-crypto", diff --git a/common/client-libs/validator-client/examples/query_name_service.rs b/common/client-libs/validator-client/examples/query_name_service.rs index 68e4da4cf2..f705cb3564 100644 --- a/common/client-libs/validator-client/examples/query_name_service.rs +++ b/common/client-libs/validator-client/examples/query_name_service.rs @@ -9,7 +9,7 @@ use nym_validator_client::nyxd::contract_traits::{ #[tokio::main] async fn main() { - setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap())); + setup_env(Some("../../../envs/qa-qwerty.env")); let network_details = NymNetworkDetails::new_from_env(); let config = nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap(); diff --git a/common/client-libs/validator-client/examples/query_service_provider_directory.rs b/common/client-libs/validator-client/examples/query_service_provider_directory.rs index d0f6f7ce46..e489564e94 100644 --- a/common/client-libs/validator-client/examples/query_service_provider_directory.rs +++ b/common/client-libs/validator-client/examples/query_service_provider_directory.rs @@ -9,7 +9,7 @@ use nym_validator_client::nyxd::contract_traits::{ #[tokio::main] async fn main() { - setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap())); + setup_env(Some("../../../envs/qa-qwerty.env")); let network_details = NymNetworkDetails::new_from_env(); let config = nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap(); diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 6352dabff0..c5ae83a6bf 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -172,6 +172,10 @@ impl Client { // validator-api wrappers impl Client { + pub fn api_url(&self) -> &Url { + self.nym_api.current_url() + } + pub fn change_nym_api(&mut self, new_endpoint: Url) { self.nym_api.change_url(new_endpoint) } @@ -242,6 +246,10 @@ impl NymApiClient { NymApiClient { nym_api } } + pub fn api_url(&self) -> &Url { + self.nym_api.current_url() + } + pub fn change_nym_api(&mut self, new_endpoint: Url) { self.nym_api.change_url(new_endpoint); } diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index 45c9139fe3..df6860641e 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -5,6 +5,7 @@ authors.workspace = true edition = "2021" [dependencies] +anyhow = { workspace = true } base64 = "0.13.0" bip39 = { workspace = true } bs58 = "0.4" @@ -33,6 +34,7 @@ nym-bin-common = { path = "../../common/bin-common", features = ["output_format" nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } nym-network-defaults = { path = "../network-defaults" } nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" } +nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-contract" } nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" } @@ -41,6 +43,11 @@ nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-co nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" } nym-name-service-common = { path = "../cosmwasm-smart-contracts/name-service" } nym-sphinx = { path = "../../common/nymsphinx" } +nym-client-core = { path = "../../common/client-core" } +nym-config = { path = "../../common/config" } +nym-credentials = { path = "../../common/credentials" } +nym-credential-storage = { path = "../../common/credential-storage" } +nym-credential-utils = { path = "../../common/credential-utils" } nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" } nym-types = { path = "../../common/types" } diff --git a/common/commands/src/coconut/issue_credentials.rs b/common/commands/src/coconut/issue_credentials.rs new file mode 100644 index 0000000000..cd8a9b5f36 --- /dev/null +++ b/common/commands/src/coconut/issue_credentials.rs @@ -0,0 +1,51 @@ +// Copyright 2022-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::context::SigningClient; +use crate::utils::CommonConfigsWrapper; +use anyhow::bail; +use clap::Parser; +use nym_credential_storage::initialise_persistent_storage; +use nym_credential_utils::utils; +use nym_validator_client::nyxd::Coin; +use std::path::PathBuf; + +#[derive(Debug, Parser)] +pub struct Args { + /// Config file of the client that is supposed to use the credential. + #[clap(long)] + pub(crate) client_config: PathBuf, + + /// The amount of utokens the credential will hold. + #[clap(long, default_value = "0")] + pub(crate) amount: u64, + + /// Path to a directory used to store recovery files for unconsumed deposits + #[clap(long)] + pub(crate) recovery_dir: PathBuf, +} + +pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> { + let loaded = CommonConfigsWrapper::try_load(args.client_config)?; + + if let Ok(id) = loaded.try_get_id() { + println!("loaded config file for client '{id}'"); + } + + let Ok(credentials_store) = loaded.try_get_credentials_store() else { + bail!("the loaded config does not have a credentials store information") + }; + + println!( + "using credentials store at '{}'", + credentials_store.display() + ); + + let denom = &client.current_chain_details().mix_denom.base; + let coin = Coin::new(args.amount as u128, denom); + + let persistent_storage = initialise_persistent_storage(credentials_store).await; + utils::issue_credential(&client, coin, &persistent_storage, args.recovery_dir).await?; + + Ok(()) +} diff --git a/common/commands/src/coconut/mod.rs b/common/commands/src/coconut/mod.rs index 6cfc921228..531a316125 100644 --- a/common/commands/src/coconut/mod.rs +++ b/common/commands/src/coconut/mod.rs @@ -1,4 +1,20 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -// TODO: add coconut commands here +use clap::{Args, Subcommand}; + +pub mod issue_credentials; +pub mod recover_credentials; + +#[derive(Debug, Args)] +#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] +pub struct Coconut { + #[clap(subcommand)] + pub command: CoconutCommands, +} + +#[derive(Debug, Subcommand)] +pub enum CoconutCommands { + IssueCredentials(issue_credentials::Args), + RecoverCredentials(recover_credentials::Args), +} diff --git a/common/commands/src/coconut/recover_credentials.rs b/common/commands/src/coconut/recover_credentials.rs new file mode 100644 index 0000000000..025ea68c2c --- /dev/null +++ b/common/commands/src/coconut/recover_credentials.rs @@ -0,0 +1,48 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::context::QueryClient; +use crate::utils::CommonConfigsWrapper; +use anyhow::bail; +use clap::Parser; +use nym_credential_storage::initialise_persistent_storage; +use nym_credential_utils::{recovery_storage, utils}; +use std::path::PathBuf; + +#[derive(Debug, Parser)] +pub struct Args { + /// Config file of the client that is supposed to use the credential. + #[clap(long)] + pub(crate) client_config: PathBuf, + + /// Path to a directory used to store recovery files for unconsumed deposits + #[clap(long)] + pub(crate) recovery_dir: PathBuf, +} + +pub async fn execute(args: Args, client: QueryClient) -> anyhow::Result<()> { + let loaded = CommonConfigsWrapper::try_load(args.client_config)?; + + if let Ok(id) = loaded.try_get_id() { + println!("loaded config file for client '{id}'"); + } + + let Ok(credentials_store) = loaded.try_get_credentials_store() else { + bail!("the loaded config does not have a credentials store information") + }; + + println!( + "using credentials store at '{}'", + credentials_store.display() + ); + + let persistent_storage = initialise_persistent_storage(credentials_store).await; + let recovery_storage = recovery_storage::RecoveryStorage::new(args.recovery_dir)?; + + let recovered = + utils::recover_credentials(&client, &recovery_storage, &persistent_storage).await?; + + // TODO: denom? + println!("recovered {recovered} worth of credentials"); + Ok(()) +} diff --git a/common/commands/src/utils.rs b/common/commands/src/utils.rs index 63f8b6a085..25797c71e9 100644 --- a/common/commands/src/utils.rs +++ b/common/commands/src/utils.rs @@ -1,13 +1,17 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use anyhow::{anyhow, bail}; use cosmrs::AccountId; use cosmwasm_std::{Addr, Coin as CosmWasmCoin, Decimal}; use log::error; +use nym_client_core::config::disk_persistence::CommonClientPaths; use nym_validator_client::nyxd::Coin; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt::{Display, Formatter}; +use std::fs; +use std::path::{Path, PathBuf}; // TODO: perhaps it should be moved to some global common crate? pub fn account_id_to_cw_addr(account_id: &AccountId) -> Addr { @@ -75,3 +79,150 @@ impl DataWrapper { DataWrapper { data } } } + +fn find_toml_value<'a>(root: &'a toml::Value, key: &str) -> Option<&'a toml::Value> { + if let toml::Value::Table(table) = root { + for (k, v) in table { + if k == key { + return Some(v); + } + if v.is_table() { + if let Some(res) = find_toml_value(v, key) { + return Some(res); + } + } + } + } + None +} + +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub(crate) enum CommonConfigsWrapper { + // native, socks5, NR, etc. clients + NymClients(Box), + + // nym-api + NymApi(NymApiConfigLight), + + // anything else that might get get introduced + Unknown(UnknownConfigWrapper), +} + +impl CommonConfigsWrapper { + pub(crate) fn try_load>(path: P) -> anyhow::Result { + let content = fs::read_to_string(path)?; + Ok(toml::from_str(&content)?) + } + + pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> { + match self { + CommonConfigsWrapper::NymClients(cfg) => cfg.try_get_id(), + CommonConfigsWrapper::NymApi(cfg) => Ok(&cfg.base.id), + CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_id(), + } + } + + pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result { + match self { + CommonConfigsWrapper::NymClients(cfg) => { + Ok(cfg.storage_paths.inner.credentials_database.clone()) + } + CommonConfigsWrapper::NymApi(cfg) => Ok(cfg + .network_monitor + .storage_paths + .credentials_database_path + .clone()), + CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_credentials_store(), + } + } +} + +// ideally we would have just imported the full nym-api config structure, but that'd have been an overkill, +// because we'd have to import the whole crate +#[derive(Deserialize, Debug)] +pub(crate) struct NymApiConfigLight { + base: NymApiConfigBaseLight, + network_monitor: NymApiConfigNetworkMonitorLight, +} + +#[derive(Deserialize, Debug)] +struct NymApiConfigBaseLight { + id: String, +} + +#[derive(Deserialize, Debug)] +struct NymApiConfigNetworkMonitorLight { + storage_paths: NetworkMonitorPaths, +} + +#[derive(Deserialize, Debug)] +struct NetworkMonitorPaths { + credentials_database_path: PathBuf, +} + +// a hacky way of reading common data from client configs (native, socks5, etc.) +// it works because all clients follow the same structure for storage paths +// (or so I thought) +#[derive(Deserialize, Debug)] +pub(crate) struct ClientConfigCommonWrapper { + storage_paths: StoragePathsWrapper, + + // ... but they have different structure for `nym_client_core::config::Client` + // native client has it on the top layer, whilsts socks5 has it under 'core' table + #[serde(flatten)] + other: toml::Value, +} + +// wrapper to allow for any additional entries besides the common paths, like allow list for NR +#[derive(Deserialize, Debug)] +struct StoragePathsWrapper { + #[serde(flatten)] + inner: CommonClientPaths, +} + +impl ClientConfigCommonWrapper { + pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> { + let id_val = find_toml_value(&self.other, "id") + .ok_or_else(|| anyhow!("no id field present in the config"))?; + if let toml::Value::String(id) = id_val { + Ok(id) + } else { + bail!("no id field present in the config") + } + } +} + +#[derive(Deserialize, Debug)] +pub(crate) struct UnknownConfigWrapper { + #[serde(flatten)] + inner: toml::Value, +} + +impl UnknownConfigWrapper { + fn find_value(&self, key: &str) -> Option<&toml::Value> { + find_toml_value(&self.inner, key) + } + + pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> { + let id_val = self + .find_value("id") + .ok_or_else(|| anyhow!("no id field present in the config"))?; + if let toml::Value::String(id) = id_val { + Ok(id) + } else { + bail!("no id field present in the config") + } + } + + pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result { + let id_val = self + .find_value("credentials_database_path") + .ok_or_else(|| anyhow!("no 'credentials_database_path' field present in the config"))?; + if let toml::Value::String(credentials_store) = id_val { + Ok(credentials_store.parse()?) + } else { + bail!("no 'credentials_database_path' field present in the config") + } + } +} diff --git a/clients/credential/Cargo.toml b/common/credential-utils/Cargo.toml similarity index 54% rename from clients/credential/Cargo.toml rename to common/credential-utils/Cargo.toml index 8ceb14bc98..1949107036 100644 --- a/clients/credential/Cargo.toml +++ b/common/credential-utils/Cargo.toml @@ -1,25 +1,18 @@ [package] -name = "nym-credential-client" +name = "nym-credential-utils" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = { version = "4.0", features = ["cargo", "derive"] } -log = "0.4" -serde = { workspace = true, features = ["derive"] } -thiserror = "1.0" -tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime - +log = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } -nym-client-core = { path = "../../common/client-core" } -nym-config = { path = "../../common/config" } nym-credentials = { path = "../../common/credentials" } nym-credential-storage = { path = "../../common/credential-storage" } -nym-bin-common = { path = "../../common/bin-common"} -nym-network-defaults = { path = "../../common/network-defaults" } -nym-pemstore = { path = "../../common/pemstore" } nym-validator-client = { path = "../../common/client-libs/validator-client" } - +nym-config = { path = "../../common/config" } +nym-client-core = { path = "../../common/client-core" } diff --git a/common/credential-utils/src/errors.rs b/common/credential-utils/src/errors.rs new file mode 100644 index 0000000000..6407929a99 --- /dev/null +++ b/common/credential-utils/src/errors.rs @@ -0,0 +1,31 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_credential_storage::error::StorageError; +use nym_credentials::error::Error as CredentialError; +use nym_validator_client::nyxd::error::NyxdError; +use std::num::ParseIntError; +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + IOError(#[from] std::io::Error), + + #[error(transparent)] + BandwidthControllerError(#[from] nym_bandwidth_controller::error::BandwidthControllerError), + + #[error(transparent)] + Nyxd(#[from] NyxdError), + + #[error(transparent)] + Credential(#[from] CredentialError), + + #[error("Could not use shared storage: {0}")] + SharedStorageError(#[from] StorageError), + + #[error("failed to parse credential value: {0}")] + MalformedCredentialValue(#[from] ParseIntError), +} diff --git a/common/credential-utils/src/lib.rs b/common/credential-utils/src/lib.rs new file mode 100644 index 0000000000..6e5fbc3158 --- /dev/null +++ b/common/credential-utils/src/lib.rs @@ -0,0 +1,5 @@ +pub mod errors; +pub mod recovery_storage; +pub mod utils; + +pub use errors::{Error, Result}; diff --git a/common/credential-utils/src/recovery_storage.rs b/common/credential-utils/src/recovery_storage.rs new file mode 100644 index 0000000000..1793553c62 --- /dev/null +++ b/common/credential-utils/src/recovery_storage.rs @@ -0,0 +1,64 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::errors::Result; +use log::error; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use std::fs::{create_dir_all, read_dir, File}; +use std::io::{Read, Write}; +use std::path::PathBuf; + +pub struct RecoveryStorage { + recovery_dir: PathBuf, +} + +impl RecoveryStorage { + pub fn new(recovery_dir: PathBuf) -> Result { + create_dir_all(&recovery_dir)?; + Ok(Self { recovery_dir }) + } + + pub fn unconsumed_vouchers(&self) -> Result> { + let entries = read_dir(&self.recovery_dir)?; + + let mut paths = vec![]; + for entry in entries.flatten() { + let path = entry.path(); + if path.is_file() { + paths.push(path) + } + } + + let mut vouchers = vec![]; + for path in paths { + if let Ok(mut file) = File::open(&path) { + let mut buff = Vec::new(); + if file.read_to_end(&mut buff).is_ok() { + match BandwidthVoucher::try_from_bytes(&buff) { + Ok(voucher) => vouchers.push(voucher), + Err(err) => { + error!("failed to parse the voucher at {}: {err}", path.display()) + } + } + } + } + } + + Ok(vouchers) + } + + pub fn insert_voucher(&self, voucher: &BandwidthVoucher) -> Result { + let file_name = voucher.tx_hash().to_string(); + let file_path = self.recovery_dir.join(file_name); + let mut file = File::create(&file_path)?; + let buff = voucher.to_bytes(); + file.write_all(&buff)?; + + Ok(file_path) + } + + pub fn remove_voucher(&self, file_name: String) -> Result<()> { + let file_path = self.recovery_dir.join(file_name); + Ok(std::fs::remove_file(file_path)?) + } +} diff --git a/common/credential-utils/src/utils.rs b/common/credential-utils/src/utils.rs new file mode 100644 index 0000000000..2f92b53c90 --- /dev/null +++ b/common/credential-utils/src/utils.rs @@ -0,0 +1,139 @@ +use crate::errors::{Error, Result}; +use crate::recovery_storage::RecoveryStorage; +use log::*; +use nym_bandwidth_controller::acquire::state::State; +use nym_client_core::config::disk_persistence::CommonClientPaths; +use nym_config::DEFAULT_DATA_DIR; +use nym_credential_storage::persistent_storage::PersistentStorage; +use nym_validator_client::nyxd::contract_traits::{CoconutBandwidthSigningClient, DkgQueryClient}; +use nym_validator_client::nyxd::Coin; +use std::path::PathBuf; +use std::process::exit; +use std::time::{Duration, SystemTime}; + +const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute + +pub async fn issue_credential( + client: &C, + amount: Coin, + persistent_storage: &PersistentStorage, + recovery_storage_path: PathBuf, +) -> Result<()> +where + C: DkgQueryClient + CoconutBandwidthSigningClient + Send + Sync, +{ + let recovery_storage = setup_recovery_storage(recovery_storage_path).await; + + block_until_coconut_is_available(client).await?; + info!("Starting to deposit funds, don't kill the process"); + + if let Ok(recovered_amount) = + recover_credentials(client, &recovery_storage, persistent_storage).await + { + if recovered_amount != 0 { + info!( + "Recovered credentials in the amount of {}", + recovered_amount + ); + return Ok(()); + } + }; + + let state = nym_bandwidth_controller::acquire::deposit(client, amount.clone()).await?; + + if nym_bandwidth_controller::acquire::get_credential(&state, client, persistent_storage) + .await + .is_err() + { + warn!("Failed to obtain credential. Dumping recovery data.",); + match recovery_storage.insert_voucher(&state.voucher) { + Ok(file_path) => { + warn!("Dumped recovery data to {}. Try using recovery mode to convert it to a credential", file_path.to_str().unwrap()); + } + Err(e) => { + error!("Could not dump recovery data to file system due to {:?}, the deposit will be lost!", e) + } + } + + return Err(Error::Credential( + nym_credentials::error::Error::BandwidthCredentialError, + )); + } + + info!("Succeeded adding a credential with amount {amount}"); + + Ok(()) +} + +pub async fn setup_recovery_storage(recovery_dir: PathBuf) -> RecoveryStorage { + RecoveryStorage::new(recovery_dir).expect("") +} + +pub async fn setup_persistent_storage(client_home_directory: PathBuf) -> PersistentStorage { + let data_dir = client_home_directory.join(DEFAULT_DATA_DIR); + let paths = CommonClientPaths::new_default(data_dir); + let db_path = paths.credentials_database; + + nym_credential_storage::initialise_persistent_storage(db_path).await +} + +pub async fn block_until_coconut_is_available(client: &C) -> Result<()> +where + C: DkgQueryClient + Send + Sync, +{ + loop { + let epoch = client.get_current_epoch().await?; + let current_timestamp_secs = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("the system clock is set to 01/01/1970 (or earlier)") + .as_secs(); + if epoch.state.is_final() { + if current_timestamp_secs + SAFETY_BUFFER_SECS >= epoch.finish_timestamp.seconds() { + info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60); + exit(0); + } + + break; + } else { + // Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries + let secs_until_final = epoch + .final_timestamp_secs() + .saturating_sub(current_timestamp_secs) + + 1; + info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final); + tokio::time::sleep(Duration::from_secs(secs_until_final)).await; + } + } + + Ok(()) +} + +pub async fn recover_credentials( + client: &C, + recovery_storage: &RecoveryStorage, + shared_storage: &PersistentStorage, +) -> Result +where + C: DkgQueryClient + Send + Sync, +{ + let mut recovered_amount: u128 = 0; + for voucher in recovery_storage.unconsumed_vouchers()? { + let voucher_value = voucher.get_voucher_value(); + recovered_amount += voucher_value.parse::()?; + + let state = State::new(voucher); + let voucher = state.voucher.tx_hash(); + if let Err(e) = + nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await + { + error!("Could not recover deposit {voucher} due to {e}, try again later",) + } else { + info!("Converted deposit {voucher} to a credential, removing recovery data for it",); + if let Err(e) = recovery_storage.remove_voucher(voucher.to_string()) { + warn!("Could not remove recovery data: {e}"); + } + } + } + + Ok(recovered_amount) +} diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index 1f4c444b31..aff9cf01ab 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -8,7 +8,8 @@ edition = "2021" [dependencies] bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] } cosmrs = { workspace = true } -thiserror = "1.0" +thiserror = { workspace = true } +log = { workspace = true } # I guess temporarily until we get serde support in coconut up and running nym-coconut-interface = { path = "../coconut-interface" } diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index 025297f167..b7fda42f73 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -1,6 +1,10 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; +use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm}; +use crate::error::Error; +use log::{debug, warn}; use nym_api_requests::coconut::BlindSignRequestBody; use nym_coconut_interface::{ aggregate_signature_shares, aggregate_verification_keys, prove_bandwidth_credential, Attribute, @@ -11,10 +15,6 @@ use nym_crypto::shared_key::recompute_shared_key; use nym_crypto::symmetric::stream_cipher; use nym_validator_client::client::CoconutApiClient; -use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; -use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm}; -use crate::error::Error; - pub async fn obtain_aggregate_verification_key( api_clients: &[CoconutApiClient], ) -> Result { @@ -107,7 +107,12 @@ pub async fn obtain_aggregate_signature( aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?; for coconut_api_client in coconut_api_clients.iter() { - if let Ok(signature) = obtain_partial_credential( + debug!( + "attempting to obtain partial credential from {}", + coconut_api_client.api_client.api_url() + ); + + match obtain_partial_credential( params, attributes, &coconut_api_client.api_client, @@ -115,9 +120,17 @@ pub async fn obtain_aggregate_signature( ) .await { - let share = SignatureShare::new(signature, coconut_api_client.node_id); - shares.push(share) - } + Ok(signature) => { + let share = SignatureShare::new(signature, coconut_api_client.node_id); + shares.push(share) + } + Err(err) => { + warn!( + "failed to obtain partial credential from {}: {err}", + coconut_api_client.api_client.api_url() + ); + } + }; } if shares.len() < threshold as usize { return Err(Error::NotEnoughShares); diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 1704608043..fe353b16ae 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -4,11 +4,11 @@ use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::path::Path; use std::{ env::{var, VarError}, ffi::OsStr, ops::Not, - path::PathBuf, }; use url::Url; @@ -388,7 +388,7 @@ fn fix_deprecated_environmental_variables() { } } -pub fn setup_env(config_env_file: Option<&PathBuf>) { +pub fn setup_env>(config_env_file: Option

    ) { match std::env::var(var_names::CONFIGURED) { // if the configuration is not already set in the env vars Err(std::env::VarError::NotPresent) => { diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 0df77aeaf9..1c00c1548f 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -36,7 +36,7 @@ async fn main() { dotenv().ok(); setup_logging(); let args = commands::Cli::parse(); - setup_env(args.config_env_file.as_ref()); + setup_env(args.config_env_file); let mut explorer_api = ExplorerApi::new(); explorer_api.run().await; } diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index e915498a01..ecfe3bf851 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4068,6 +4068,7 @@ version = "0.1.0" dependencies = [ "bls12_381 0.5.0", "cosmrs", + "log", "nym-api-requests", "nym-coconut-interface", "nym-crypto", diff --git a/sdk/lib/socks5-listener/src/lib.rs b/sdk/lib/socks5-listener/src/lib.rs index 1a3e22ca6c..98dac2755d 100644 --- a/sdk/lib/socks5-listener/src/lib.rs +++ b/sdk/lib/socks5-listener/src/lib.rs @@ -58,7 +58,7 @@ async fn is_shutdown_handle_set() -> bool { fn set_default_env() { if !ENV_SET.swap(true, Ordering::SeqCst) { - setup_env(None); + setup_env::<&str>(None); } } diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index f3d1d47aae..6986e0ef15 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -14,6 +14,7 @@ nym-gateway-requests = { path = "../../../gateway/gateway-requests" } nym-bandwidth-controller = { path = "../../../common/bandwidth-controller" } nym-credentials = { path = "../../../common/credentials" } nym-credential-storage = { path = "../../../common/credential-storage" } +nym-credential-utils = { path = "../../../common/credential-utils" } nym-network-defaults = { path = "../../../common/network-defaults" } nym-sphinx = { path = "../../../common/nymsphinx" } nym-task = { path = "../../../common/task" } @@ -30,11 +31,12 @@ url = { workspace = true } toml = "0.5.10" [dev-dependencies] +anyhow = { workspace = true } dotenvy = { workspace = true } pretty_env_logger = "0.4.0" reqwest = { workspace = true, features = ["json", "socks"] } thiserror = { workspace = true } -tokio = { version = "1", features = ["full"] } +tokio = { workspace = true, features = ["full"] } nym-bin-common = { path = "../../../common/bin-common" } # extra dependencies for libp2p examples diff --git a/sdk/rust/nym-sdk/examples/bandwidth.rs b/sdk/rust/nym-sdk/examples/bandwidth.rs index b7f452fa92..377dd48527 100644 --- a/sdk/rust/nym-sdk/examples/bandwidth.rs +++ b/sdk/rust/nym-sdk/examples/bandwidth.rs @@ -1,46 +1,43 @@ +use futures::StreamExt; +use nym_network_defaults::setup_env; use nym_sdk::mixnet; use nym_sdk::mixnet::MixnetMessageSender; #[tokio::main] -async fn main() { +async fn main() -> anyhow::Result<()> { nym_bin_common::logging::setup_logging(); // right now, only sandbox has coconut setup // this should be run from the `sdk/rust/nym-sdk` directory - dotenvy::from_path("../../../envs/sandbox.env").unwrap(); + setup_env(Some("../../../envs/sandbox.env")); let sandbox_network = mixnet::NymNetworkDetails::new_from_env(); + let mnemonic = String::from("my super secret mnemonic"); let mixnet_client = mixnet::MixnetClientBuilder::new_ephemeral() .network_details(sandbox_network) .enable_credentials_mode() .build() - .await - .unwrap(); + .await?; - let bandwidth_client = mixnet_client - .create_bandwidth_client(String::from("very secret mnemonic")) - .unwrap(); + let bandwidth_client = mixnet_client.create_bandwidth_client(mnemonic)?; // Get a bandwidth credential worth 1000000 unym for the mixnet_client - bandwidth_client.acquire(1000000).await.unwrap(); + bandwidth_client.acquire(1000000).await?; // Connect using paid bandwidth credential - let mut client = mixnet_client.connect_to_mixnet().await.unwrap(); + let mut client = mixnet_client.connect_to_mixnet().await?; let our_address = client.nym_address(); // Send a message throughout the mixnet to ourselves client .send_plain_message(*our_address, "hello there") - .await - .unwrap(); + .await?; println!("Waiting for message"); - if let Some(received) = client.wait_for_messages().await { - for r in received { - println!("Received: {}", String::from_utf8_lossy(&r.message)); - } - } + let received = client.next().await.unwrap(); + println!("Received: {}", String::from_utf8_lossy(&received.message)); client.disconnect().await; + Ok(()) } diff --git a/sdk/rust/nym-sdk/src/mixnet/paths.rs b/sdk/rust/nym-sdk/src/mixnet/paths.rs index 5c09752280..288218f5c5 100644 --- a/sdk/rust/nym-sdk/src/mixnet/paths.rs +++ b/sdk/rust/nym-sdk/src/mixnet/paths.rs @@ -51,7 +51,8 @@ impl StoragePaths { /// /// This function will return an error if it is passed a path to an existing file instead of a /// directory. - pub fn new_from_dir(dir: &Path) -> Result { + pub fn new_from_dir>(dir: P) -> Result { + let dir = dir.as_ref(); if dir.is_file() { return Err(Error::ExpectedDirectory(dir.to_owned())); } diff --git a/tools/nym-cli/src/coconut/mod.rs b/tools/nym-cli/src/coconut/mod.rs new file mode 100644 index 0000000000..ea4fd45381 --- /dev/null +++ b/tools/nym-cli/src/coconut/mod.rs @@ -0,0 +1,26 @@ +use nym_cli_commands::context::{create_query_client, create_signing_client, ClientArgs}; +use nym_network_defaults::NymNetworkDetails; + +pub(crate) async fn execute( + global_args: ClientArgs, + coconut: nym_cli_commands::coconut::Coconut, + network_details: &NymNetworkDetails, +) -> anyhow::Result<()> { + match coconut.command { + nym_cli_commands::coconut::CoconutCommands::IssueCredentials(args) => { + nym_cli_commands::coconut::issue_credentials::execute( + args, + create_signing_client(global_args, network_details)?, + ) + .await? + } + nym_cli_commands::coconut::CoconutCommands::RecoverCredentials(args) => { + nym_cli_commands::coconut::recover_credentials::execute( + args, + create_query_client(network_details)?, + ) + .await? + } + } + Ok(()) +} diff --git a/tools/nym-cli/src/main.rs b/tools/nym-cli/src/main.rs index a5acae77b9..16b771d33e 100644 --- a/tools/nym-cli/src/main.rs +++ b/tools/nym-cli/src/main.rs @@ -7,6 +7,7 @@ use nym_bin_common::logging::setup_logging; use nym_cli_commands::context::{get_network_details, ClientArgs}; use nym_validator_client::nyxd::AccountId; +mod coconut; mod completion; mod validator; @@ -61,6 +62,8 @@ pub(crate) enum Commands { Account(nym_cli_commands::validator::account::Account), /// Sign and verify messages Signature(nym_cli_commands::validator::signature::Signature), + /// Coconut related stuff + Coconut(nym_cli_commands::coconut::Coconut), /// Query chain blocks Block(nym_cli_commands::validator::block::Block), /// Manage and execute WASM smart contracts @@ -101,6 +104,7 @@ async fn execute(cli: Cli) -> anyhow::Result<()> { Commands::Signature(signature) => { validator::signature::execute(signature, &network_details, mnemonic).await? } + Commands::Coconut(coconut) => coconut::execute(args, coconut, &network_details).await?, Commands::Block(block) => validator::block::execute(block, &network_details).await?, Commands::Cosmwasm(cosmwasm) => { validator::cosmwasm::execute(args, cosmwasm, &network_details).await? From b57df35f8ce561c9f567f29515eb8c1578ce2b74 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 29 Aug 2023 10:37:22 +0200 Subject: [PATCH 42/44] update changelog and bump versions --- CHANGELOG.md | 20 +++++++++++++++++++ Cargo.lock | 18 ++++++++--------- clients/native/Cargo.toml | 2 +- clients/socks5/Cargo.toml | 2 +- explorer-api/Cargo.toml | 2 +- gateway/Cargo.toml | 2 +- mixnode/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-connect/desktop/CHANGELOG.md | 6 ++++++ nym-connect/desktop/package.json | 2 +- nym-connect/desktop/src-tauri/Cargo.toml | 2 +- nym-connect/desktop/src-tauri/tauri.conf.json | 2 +- .../network-requester/Cargo.toml | 2 +- .../network-statistics/Cargo.toml | 2 +- tools/nym-cli/Cargo.toml | 2 +- 15 files changed, 47 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e728e00506..3df8920b43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [v1.1.29-snickers] (2023-08-29) + +- Add EXPLORER_API configurable url ([#3810]) +- Bugfix/use correct tendermint dialect ([#3802]) +- Explorer - look up gateways based on geo-location ([#3776]) +- Speedy mode - select the mixnodes based on the location of the NR ([#3775]) +- NR - reduce response time by removing poisson delay ([#3774]) +- [demo] libp2p example with nym-sdk ([#3763]) +- introduced /network/details endpoint to nym-api to return used network information ([#3758]) +- Feature/issue credentials ([#3691]) + +[#3810]: https://github.com/nymtech/nym/pull/3810 +[#3802]: https://github.com/nymtech/nym/pull/3802 +[#3776]: https://github.com/nymtech/nym/issues/3776 +[#3775]: https://github.com/nymtech/nym/issues/3775 +[#3774]: https://github.com/nymtech/nym/issues/3774 +[#3763]: https://github.com/nymtech/nym/pull/3763 +[#3758]: https://github.com/nymtech/nym/pull/3758 +[#3691]: https://github.com/nymtech/nym/pull/3691 + ## [1.1.28] (2023-08-22) - [final step3]: add [rust] support to nyxd client in wasm ([#3743]) diff --git a/Cargo.lock b/Cargo.lock index 1232af2f51..6a09ca15d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2768,7 +2768,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "explorer-api" -version = "1.1.26" +version = "1.1.27" dependencies = [ "chrono", "clap 4.3.21", @@ -5638,7 +5638,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.27" +version = "1.1.28" dependencies = [ "actix-web", "anyhow", @@ -5785,7 +5785,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.26" +version = "1.1.27" dependencies = [ "anyhow", "base64 0.13.1", @@ -5858,7 +5858,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.26" +version = "1.1.27" dependencies = [ "clap 4.3.21", "dirs 4.0.0", @@ -6153,7 +6153,7 @@ dependencies = [ [[package]] name = "nym-gateway" -version = "1.1.26" +version = "1.1.27" dependencies = [ "anyhow", "async-trait", @@ -6308,7 +6308,7 @@ dependencies = [ [[package]] name = "nym-mixnode" -version = "1.1.27" +version = "1.1.28" dependencies = [ "anyhow", "bs58 0.4.0", @@ -6426,7 +6426,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.26" +version = "1.1.27" dependencies = [ "anyhow", "async-file-watcher", @@ -6473,7 +6473,7 @@ dependencies = [ [[package]] name = "nym-network-statistics" -version = "1.1.26" +version = "1.1.27" dependencies = [ "dirs 4.0.0", "log", @@ -6638,7 +6638,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.26" +version = "1.1.27" dependencies = [ "clap 4.3.21", "lazy_static", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index b94b1e7b76..351f8fb295 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.26" +version = "1.1.27" 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 8532f9d2b7..a52422b5d0 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.26" +version = "1.1.27" 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 a39e3cb52b..10ca849b3d 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.26" +version = "1.1.27" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 2b71aa1542..8e907a9eee 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-gateway" -version = "1.1.26" +version = "1.1.27" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index ec0b3f5f94..0a9edd82b9 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-mixnode" -version = "1.1.27" +version = "1.1.28" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 506c0dda9a..29be65e7bb 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-api" -version = "1.1.27" +version = "1.1.28" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-connect/desktop/CHANGELOG.md b/nym-connect/desktop/CHANGELOG.md index 20534c13ef..c7c62a72fc 100644 --- a/nym-connect/desktop/CHANGELOG.md +++ b/nym-connect/desktop/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +## [v1.1.29-snickers] (2023-08-29) + +- NymConnect sometimes fails to connect because the gateway it fetches from the validator-api to use is running an old version (of the gateway binary) ([#3788]) + +[#3788]: https://github.com/nymtech/nym/issues/3788 + ## [1.1.18] (2023-08-22) - refactor(nc-desktop): use userdata storage to save user gateway&sp ([#3723]) diff --git a/nym-connect/desktop/package.json b/nym-connect/desktop/package.json index a3a74ab218..a69bc51486 100644 --- a/nym-connect/desktop/package.json +++ b/nym-connect/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@nym/nym-connect", - "version": "1.1.18", + "version": "1.1.19", "main": "index.js", "license": "MIT", "scripts": { diff --git a/nym-connect/desktop/src-tauri/Cargo.toml b/nym-connect/desktop/src-tauri/Cargo.toml index 630adca45b..ae940cd32d 100644 --- a/nym-connect/desktop/src-tauri/Cargo.toml +++ b/nym-connect/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-connect" -version = "1.1.18" +version = "1.1.19" description = "nym-connect" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-connect/desktop/src-tauri/tauri.conf.json b/nym-connect/desktop/src-tauri/tauri.conf.json index bfbceb21be..555daaa1b7 100644 --- a/nym-connect/desktop/src-tauri/tauri.conf.json +++ b/nym-connect/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-connect", - "version": "1.1.18" + "version": "1.1.19" }, "build": { "distDir": "../dist", diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 9f1fc07c97..cf66ae325e 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-network-requester" -version = "1.1.26" +version = "1.1.27" authors.workspace = true edition.workspace = true rust-version = "1.65" diff --git a/service-providers/network-statistics/Cargo.toml b/service-providers/network-statistics/Cargo.toml index 7ff628cdc6..197ee6e49a 100644 --- a/service-providers/network-statistics/Cargo.toml +++ b/service-providers/network-statistics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-network-statistics" -version = "1.1.26" +version = "1.1.27" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index e63dcc8225..d571f87d7f 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.26" +version = "1.1.27" authors.workspace = true edition = "2021" From a1482a28874b0c2b44025a7543e356824c9398e3 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 29 Aug 2023 10:37:51 +0200 Subject: [PATCH 43/44] update changelog and bump versions --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3df8920b43..2df4f7e732 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#3758]: https://github.com/nymtech/nym/pull/3758 [#3691]: https://github.com/nymtech/nym/pull/3691 -## [1.1.28] (2023-08-22) +## [v1.1.28] (2023-08-22) - [final step3]: add [rust] support to nyxd client in wasm ([#3743]) - Feature/ephemera upgrade ([#3791]) @@ -39,7 +39,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// [#3767]: https://github.com/nymtech/nym/pull/3767 -## [1.1.27] (2023-08-16) +## [v1.1.27] (2023-08-16) - fix serialisation of contract types ([#3752]) - Investigate spending credentials from the main API (coconut enabled to a gateway) from feature/ephemera branch ([#3741]) From 2df42e222c254ae8256916b9b0860b14337b2aa1 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Tue, 29 Aug 2023 14:02:41 +0200 Subject: [PATCH 44/44] version change NC --- nym-connect/desktop/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-connect/desktop/CHANGELOG.md b/nym-connect/desktop/CHANGELOG.md index c7c62a72fc..ab9c1da100 100644 --- a/nym-connect/desktop/CHANGELOG.md +++ b/nym-connect/desktop/CHANGELOG.md @@ -2,7 +2,7 @@ ## [Unreleased] -## [v1.1.29-snickers] (2023-08-29) +## [v1.1.19-snickers] (2023-08-29) - NymConnect sometimes fails to connect because the gateway it fetches from the validator-api to use is running an old version (of the gateway binary) ([#3788])