Compare commits

...

91 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacșu 89228ae1e1 Catch and emit event in backend for URL opening
The JS part of listening to that event is still not working, probably
due to some window init race. Needs to be investigated more.
2023-09-05 22:16:03 +03:00
Bogdan-Ștefan Neacșu c0cb98fee0 Better error message on invalid payment req 2023-09-04 14:29:33 +03:00
Bogdan-Ștefan Neacșu 14a9991fa4 Use clap arguments 2023-09-04 14:29:33 +03:00
Bogdan-Ștefan Neacșu a7c71ab3d4 Introduce nyxd client 2023-09-04 14:29:33 +03:00
Bogdan-Ștefan Neacșu f26aad9880 Add payment manager 2023-09-04 14:29:33 +03:00
Jon Häggblad 581cba9365 Merge pull request #3830 from nymtech/jon/nc-directory-error-handling
nym-connect directory error handling
2023-09-01 16:14:37 +02:00
Jon Häggblad c7d99bb951 Update error enum name 2023-09-01 15:58:51 +02:00
mx ee938e6a0c Merge pull request #3736 from nymtech/feature/rust-sdk-tutorial-chain-querier
Feature/rust sdk tutorial chain querier
2023-09-01 13:52:34 +00:00
Jon Häggblad 00501f7073 rustfmt 2023-09-01 14:53:25 +02:00
Jon Häggblad db80666271 Append gateway_id to to gateway client error type 2023-09-01 14:38:34 +02:00
mfahampshire 3f9fdac9ec removed ref to send_str for send_message 2023-09-01 14:00:48 +02:00
mfahampshire 3257315676 tweaks 2023-09-01 13:24:01 +02:00
Jon Häggblad 2f874a66de Split directory mod into gateways and services 2023-09-01 13:14:05 +02:00
Jon Häggblad defbcea227 Fallback on last hour performance for gateways 2023-09-01 13:14:05 +02:00
Jon Häggblad 58f79a972c More extensive error handling in NC directory 2023-09-01 13:14:05 +02:00
Jon Häggblad de531d41ed Merge pull request #3826 from nymtech/jon/fix-gateways-in-geoaware-routing
geo_aware_provider: fix too much filtering of gateways
2023-09-01 09:17:21 +02:00
Mark Sinclair 15df2cfbe5 Create release-calculate-hash.yml 2023-08-31 17:34:30 +01:00
Jon Häggblad 987401c320 Add one more gateway check when fetching in nym-connect 2023-08-31 15:35:39 +02:00
Jon Häggblad 81cbc48521 Remove unused 2023-08-31 15:18:43 +02:00
Jon Häggblad 8935eb125b geo_aware_provider: fix too much filtering of gateways 2023-08-31 15:05:17 +02:00
Tommy Verrall ca2aad778b Merge pull request #3768 from nymtech/chore/enable-versioning
Chore/enable versioning
2023-08-31 11:53:22 +02:00
Tommy Verrall 584c902f93 Merge pull request #3809 from nymtech/feature/ncandroid-state
fix(nc-android): state sync cross ffi
2023-08-31 11:52:18 +02:00
Tommy Verrall 2b15d53f45 Merge pull request #3824 from nymtech/jon/explorer-client
Create explorer-client and use in geo aware provider
2023-08-31 10:55:20 +02:00
Tommy Verrall ce98ce72d8 Merge pull request #3799 from nymtech/jon/network-requester-add-description-in-config
network-requester: add description to config
2023-08-30 12:33:42 +02:00
mfahampshire 5a8bad4503 Merge branch 'feature/rust-sdk-tutorial-chain-querier' of github.com:nymtech/nym into feature/rust-sdk-tutorial-chain-querier 2023-08-30 11:04:51 +02:00
mfahampshire 0e37084f34 updated client send method 2023-08-30 10:59:18 +02:00
Jon Häggblad 8af83ceac6 Add note about country to continent mapping 2023-08-30 09:57:56 +02:00
Jon Häggblad ee6b6ecc7e Create explorer-client and use in geo aware provider 2023-08-30 09:40:27 +02:00
mfahampshire 27a9557c7b tweak 2023-08-29 17:22:59 +02:00
mfahampshire c143fef912 tweaks to tutorial 2023-08-29 17:22:59 +02:00
mfahampshire e7e48f0e53 added ide config to gitignore 2023-08-29 17:22:59 +02:00
mfahampshire c62b344349 finished first pass at tutorial 2023-08-29 17:22:59 +02:00
mfahampshire 441fbf8255 added new pages for client and service /src/ files 2023-08-29 17:22:59 +02:00
mfahampshire b5cde68e62 continued working on tutorial; finished bin/service 2023-08-29 17:22:59 +02:00
mfahampshire c3d38fb904 continued working on dev portal tutorial 2023-08-29 17:22:59 +02:00
mfahampshire 2922306e25 added tree output for created client with storage example 2023-08-29 17:22:59 +02:00
mfahampshire 204b2e1101 working lib setup for tutorial 2023-08-29 17:22:54 +02:00
mfahampshire c022486e63 cont. with first pass at tutorial 2023-08-29 17:21:51 +02:00
mfahampshire 2a7a681b7d * started on cosmos tutorial
* edited summary accordingly
* edited links in other pages for new ts tutorial structure
* removed ipfs coming soon page
2023-08-29 17:21:51 +02:00
mx a50b4ad211 Merge pull request #3786 from nymtech/dev-portal/telegram
initiated telegram tutorial
2023-08-29 15:18:36 +00:00
mfahampshire ca613ad3aa change shebang to nixos-compatible one 2023-08-29 17:07:52 +02:00
mfahampshire f518c8377b version bump 2023-08-29 17:07:34 +02:00
mx e1a30ea01a Merge pull request #3790 from nymtech/feature/documentation-scripts
Feature/documentation scripts
2023-08-29 15:00:48 +00:00
Tommy Verrall 9f5a0a7ca6 Merge pull request #3818 from nymtech/jon/nc-latency-based-gateway-selection
nym-connect: select gateway based on latency in medium mode
2023-08-29 16:33:07 +02:00
Tommy Verrall c7de97d6dd Merge pull request #3819 from nymtech/feature/nc-privacy-switch
feat(nc): disable privacy switch when active connection
2023-08-29 16:32:38 +02:00
Tommy Verrall 85a7ec9f02 Merge pull request #3821 from nymtech/release/v1.1.29-snickers
Release/v1.1.29 snickers
2023-08-29 16:31:32 +02:00
Jon Häggblad 8d105cf4dd Add note about mixnode config entry 2023-08-29 14:57:43 +02:00
Jon Häggblad 6131d000e6 Add some logging statements 2023-08-29 14:35:08 +02:00
Jon Häggblad 972a220209 Add nr_description entry to NR config file 2023-08-29 14:28:31 +02:00
Jon Häggblad b7cf7e06d2 Upgrade clap macro calls 2023-08-29 14:26:42 +02:00
benedettadavico 2df42e222c version change NC 2023-08-29 14:02:41 +02:00
pierre 83a0a6455f remove useless override 2023-08-29 11:23:17 +02:00
pierre f53e5c42c3 fix fmt 2023-08-29 11:20:41 +02:00
Jon Häggblad b537a7c2c7 Add some minor comments 2023-08-29 11:13:36 +02:00
pierre 9fb8b1d7c0 clean code 2023-08-29 11:06:39 +02:00
benedettadavico a1482a2887 update changelog and bump versions 2023-08-29 10:37:51 +02:00
benedettadavico b57df35f8c update changelog and bump versions 2023-08-29 10:37:22 +02:00
Jon Häggblad 7f0a02f6ec Also filter on performance 2023-08-29 08:25:59 +02:00
Jon Häggblad cfc86ba9f5 nym-connect: select gateway based on latency in medium mode 2023-08-29 08:25:59 +02:00
pierre 9c68de64a0 fix lint 2023-08-28 22:17:56 +02:00
pierre 2fdd09deee fix lint 2023-08-28 22:14:58 +02:00
pierre 5f9a514bc7 fix state 2023-08-28 21:14:07 +02:00
pierre 0812a0f599 disable privacy switch on active connection 2023-08-28 14:37:32 +02:00
pierre 7741a3fea1 wip 2023-08-25 17:16:36 +02:00
pierre 017d536d35 wip 2023-08-25 16:30:05 +02:00
pierre eb444f73ce dont uwnrap blocking_run_client result 2023-08-24 16:10:14 +02:00
pierre d108919cf6 wip implement pingClient call 2023-08-24 13:06:16 +02:00
mfahampshire 5d454f2efc fixed issue with already existing directory during mv 2023-08-22 10:03:08 +02:00
mfahampshire ffac0a1f92 updated readme w info re: scripts 2023-08-21 18:01:04 +02:00
mfahampshire 8004d54d5e added exit to conditional failure 2023-08-21 16:43:52 +02:00
mfahampshire b6febc51a3 added check for # of args 2023-08-21 16:42:34 +02:00
mfahampshire 5a0255fd01 added some checks 2023-08-21 16:30:16 +02:00
mfahampshire 5b86646bd8 added optional arg for updating minimum rust version 2023-08-21 15:55:38 +02:00
mfahampshire 067a501d98 added docs 2023-08-21 15:40:14 +02:00
mfahampshire fa9908413b minimal first version bump script 2023-08-21 15:40:03 +02:00
serinko d6369ea784 added telegram to SUMMARY.md 2023-08-18 15:31:11 +02:00
serinko e49c8588c6 initiated telegram tutorial 2023-08-18 14:03:43 +02:00
Jędrzej Stuczyński e504def9e4 using stricter version requirements for mdbook and mdbook-variables 2023-08-16 15:31:55 +02:00
Jędrzej Stuczyński 3ea4e0bf7c using versioned socks5 SP protocol 2023-08-15 09:57:11 +01:00
Jędrzej Stuczyński 4681c0b275 using versioned mix packet framing 2023-08-15 09:41:05 +01:00
mfahampshire 50acec575f tweak 2023-08-08 11:49:51 +02:00
mfahampshire ffb9ab9019 tweaks to tutorial 2023-08-08 11:00:11 +02:00
mfahampshire 1a195d151d added ide config to gitignore 2023-08-08 10:59:42 +02:00
mfahampshire dd4f4c44c3 finished first pass at tutorial 2023-08-03 10:53:23 +02:00
mfahampshire c04993e49c added new pages for client and service /src/ files 2023-08-02 23:29:56 +02:00
mfahampshire 464984a83c continued working on tutorial; finished bin/service 2023-08-02 23:29:33 +02:00
mfahampshire 40ffb6b65d continued working on dev portal tutorial 2023-08-02 08:59:37 +02:00
mfahampshire bb263f8c5e added tree output for created client with storage example 2023-07-31 15:38:49 +02:00
mfahampshire 82872ae02a working lib setup for tutorial 2023-07-31 15:38:19 +02:00
mfahampshire 6db396e877 cont. with first pass at tutorial 2023-07-28 16:31:08 +02:00
mfahampshire d8925fe234 * started on cosmos tutorial
* edited summary accordingly
* edited links in other pages for new ts tutorial structure
* removed ipfs coming soon page
2023-07-28 14:30:01 +02:00
105 changed files with 2828 additions and 600 deletions
@@ -0,0 +1,18 @@
name: Releases - calculate file hashes
on:
workflow_dispatch:
jobs:
build:
name: Calculate hash
runs-on: custom-runner-linux
steps:
- uses: MCJack123/ghaction-generate-release-hashes@v3
with:
hash-type: sha256
file-name: hashes.txt
- uses: actions/upload-artifact@v2
with:
name: Asset Hashes
path: hashes.txt
+22 -2
View File
@@ -4,7 +4,27 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [1.1.28] (2023-08-22)
## [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
## [v1.1.28] (2023-08-22)
- [final step3]: add [rust] support to nyxd client in wasm ([#3743])
- Feature/ephemera upgrade ([#3791])
@@ -19,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])
Generated
+55 -10
View File
@@ -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",
@@ -5909,7 +5909,7 @@ dependencies = [
"nym-config",
"nym-credential-storage",
"nym-crypto",
"nym-explorer-api-requests",
"nym-explorer-client",
"nym-gateway-client",
"nym-gateway-requests",
"nym-network-defaults",
@@ -6151,9 +6151,22 @@ dependencies = [
"ts-rs",
]
[[package]]
name = "nym-explorer-client"
version = "0.1.0"
dependencies = [
"log",
"nym-explorer-api-requests",
"reqwest",
"serde",
"thiserror",
"tokio",
"url",
]
[[package]]
name = "nym-gateway"
version = "1.1.26"
version = "1.1.27"
dependencies = [
"anyhow",
"async-trait",
@@ -6308,7 +6321,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.27"
version = "1.1.28"
dependencies = [
"anyhow",
"bs58 0.4.0",
@@ -6426,7 +6439,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.26"
version = "1.1.27"
dependencies = [
"anyhow",
"async-file-watcher",
@@ -6473,7 +6486,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "1.1.26"
version = "1.1.27"
dependencies = [
"dirs 4.0.0",
"log",
@@ -6560,6 +6573,38 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nym-payment-manager"
version = "0.1.0"
dependencies = [
"anyhow",
"bip39",
"clap 4.3.21",
"lazy_static",
"log",
"nym-bin-common",
"nym-network-defaults",
"nym-payment-manager-common",
"nym-task",
"nym-validator-client",
"rocket",
"rocket_cors",
"rocket_okapi",
"schemars",
"serde",
"sqlx 0.6.3",
"thiserror",
"tokio",
]
[[package]]
name = "nym-payment-manager-common"
version = "0.1.0"
dependencies = [
"schemars",
"serde",
]
[[package]]
name = "nym-pemstore"
version = "0.3.0"
@@ -6638,7 +6683,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.26"
version = "1.1.27"
dependencies = [
"clap 4.3.21",
"lazy_static",
+3
View File
@@ -64,6 +64,7 @@ members = [
"common/nymsphinx/params",
"common/nymsphinx/routing",
"common/nymsphinx/types",
"common/payment-manager",
"common/pemstore",
"common/socks5-client-core",
"common/socks5/proxy-helpers",
@@ -76,6 +77,7 @@ members = [
"common/wasm-utils",
"explorer-api",
"explorer-api/explorer-api-requests",
"explorer-api/explorer-client",
"gateway",
"gateway/gateway-requests",
"integrations/bity",
@@ -87,6 +89,7 @@ members = [
"service-providers/network-statistics",
"nym-api",
"nym-api/nym-api-requests",
"nym-connect/payment-manager",
"nym-outfox",
"tools/nym-cli",
"tools/nym-nr-query",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.26"
version = "1.1.27"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.26"
version = "1.1.27"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+13 -1
View File
@@ -2514,7 +2514,7 @@ dependencies = [
"nym-config",
"nym-credential-storage",
"nym-crypto",
"nym-explorer-api-requests",
"nym-explorer-client",
"nym-gateway-client",
"nym-gateway-requests",
"nym-network-defaults",
@@ -2752,6 +2752,18 @@ dependencies = [
"serde",
]
[[package]]
name = "nym-explorer-client"
version = "0.1.0"
dependencies = [
"log",
"nym-explorer-api-requests",
"reqwest",
"serde",
"thiserror",
"url",
]
[[package]]
name = "nym-gateway-client"
version = "0.1.0"
+1 -1
View File
@@ -33,7 +33,7 @@ zeroize = { workspace = true }
nym-bandwidth-controller = { path = "../bandwidth-controller" }
nym-config = { path = "../config" }
nym-crypto = { path = "../crypto" }
nym-explorer-api-requests = { path = "../../explorer-api/explorer-api-requests" }
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
nym-gateway-client = { path = "../client-libs/gateway-client" }
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
nym-gateway-requests = { path = "../../gateway/gateway-requests" }
@@ -46,7 +46,6 @@ use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use std::sync::Arc;
use tap::TapFallible;
use url::Url;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
@@ -329,13 +328,18 @@ where
)
};
let gateway_id = gateway_client.gateway_identity();
gateway_client.set_disabled_credentials_mode(config.client.disabled_credentials_mode);
let shared_key = gateway_client
.authenticate_and_start()
.await
.tap_err(|err| {
log::error!("Could not authenticate and start up the gateway connection - {err}")
.map_err(|err| {
log::error!("Could not authenticate and start up the gateway connection - {err}");
ClientCoreError::GatewayClientError {
gateway_id: gateway_id.to_base58_string(),
source: err,
}
})?;
managed_keys.ensure_gateway_key(shared_key);
@@ -1,14 +1,14 @@
use std::{collections::HashMap, fmt};
use log::{debug, error, info};
use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
use nym_explorer_client::{ExplorerClient, 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::{IdentityKey, MixId};
use nym_validator_client::client::MixId;
use rand::{prelude::SliceRandom, thread_rng};
use serde::{Deserialize, Serialize};
use tap::TapOptional;
@@ -18,55 +18,24 @@ use crate::config::GroupBy;
const MIN_NODES_PER_LAYER: usize = 1;
#[cfg(target_arch = "wasm32")]
fn reqwest_client() -> Option<reqwest::Client> {
reqwest::Client::builder().build().ok()
}
fn create_explorer_client() -> Option<ExplorerClient> {
let Ok(explorer_api_url) = std::env::var(EXPLORER_API) else {
error!("Missing EXPLORER_API");
return None;
};
#[cfg(not(target_arch = "wasm32"))]
fn reqwest_client() -> Option<reqwest::Client> {
reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.ok()
}
let Ok(explorer_api_url) = explorer_api_url.parse() else {
error!("Failed to parse EXPLORER_API");
return None;
};
// TODO: create a explorer-api-client
async fn fetch_mixnodes_from_explorer_api() -> Option<Vec<PrettyDetailedMixNodeBond>> {
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()?;
log::debug!("Using explorer-api url: {}", explorer_api_url);
let Ok(client) = nym_explorer_client::ExplorerClient::new(explorer_api_url) else {
error!("Failed to create explorer-api client");
return None;
};
debug!("Fetching: {}", explorer_api_url);
reqwest_client()?
.get(explorer_api_url)
.send()
.await
.ok()?
.json::<Vec<PrettyDetailedMixNodeBond>>()
.await
.ok()
}
// TODO: create a explorer-api-client
async fn fetch_gateways_from_explorer_api() -> Option<Vec<PrettyDetailedGatewayBond>> {
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::<Vec<PrettyDetailedGatewayBond>>()
.await
.ok()
Some(client)
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
@@ -83,6 +52,8 @@ pub enum CountryGroup {
impl CountryGroup {
// We map contry codes into group, which initially are continent codes to a first approximation,
// but we do it manually to reserve the right to tweak this distribution for our purposes.
// NOTE: I did this quickly and it's not a complete list of all countries, but only those that
// were present in the network at the time. Please add more as needed.
fn new(country_code: &str) -> Self {
let country_code = country_code.to_uppercase();
use CountryGroup::*;
@@ -235,23 +206,6 @@ fn group_mixnodes_by_country_code(
})
}
fn group_gateways_by_country_code(
gateways: Vec<PrettyDetailedGatewayBond>,
) -> HashMap<CountryGroup, Vec<IdentityKey>> {
gateways.into_iter().fold(
HashMap::<CountryGroup, Vec<IdentityKey>>::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<CountryGroup, Vec<MixId>>) {
let mixnode_distribution = mixnodes
.iter()
@@ -261,15 +215,6 @@ fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<MixId>>) {
debug!("Mixnode distribution - {}", mixnode_distribution);
}
fn log_gateway_distribution(gateways: &HashMap<CountryGroup, Vec<IdentityKey>>) {
let gateway_distribution = gateways
.iter()
.map(|(k, v)| format!("{}: {}", k, v.len()))
.collect::<Vec<_>>()
.join(", ");
debug!("Gateway distribution - {}", gateway_distribution);
}
fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
let mixes = topology.mixes();
if mixes.keys().len() < 3 {
@@ -303,7 +248,7 @@ impl GeoAwareTopologyProvider {
filter_on: GroupBy,
) -> GeoAwareTopologyProvider {
log::info!(
"Creating geo-aware topology provider with filter on {:?}",
"Creating geo-aware topology provider with filter on {}",
filter_on
);
nym_api_urls.shuffle(&mut thread_rng());
@@ -337,13 +282,14 @@ impl GeoAwareTopologyProvider {
// Also fetch mixnodes cached by explorer-api, with the purpose of getting their
// geolocation.
debug!("Fetching mixnodes from explorer-api...");
let Some(mixnodes_from_explorer_api) = fetch_mixnodes_from_explorer_api().await else {
let explorer_client = create_explorer_client()?;
let Ok(mixnodes_from_explorer_api) = explorer_client.get_mixnodes().await else {
error!("failed to get mixnodes from explorer-api");
return None;
};
debug!("Fetching gateways from explorer-api...");
let Some(gateways_from_explorer_api) = fetch_gateways_from_explorer_api().await else {
let Ok(gateways_from_explorer_api) = explorer_client.get_gateways().await else {
error!("failed to get mixnodes from explorer-api");
return None;
};
@@ -382,29 +328,16 @@ 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(&filter_on) else {
error!("no mixnodes found for: {}", filter_on);
return None;
};
let Some(filtered_gateway_ids) = gateway_distribution.get(&filter_on) else {
error!("no gateways found for: {}", filter_on);
return None;
};
let mixnodes = mixnodes
.into_iter()
.filter(|m| filtered_mixnode_ids.contains(&m.mix_id()))
.collect::<Vec<_>>();
let gateways = gateways
.into_iter()
.filter(|g| filtered_gateway_ids.contains(g.identity()))
.collect::<Vec<_>>();
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
+9
View File
@@ -498,6 +498,15 @@ pub enum GroupBy {
NymAddress(Recipient),
}
impl std::fmt::Display for GroupBy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GroupBy::CountryGroup(group) => write!(f, "group: {}", group),
GroupBy::NymAddress(address) => write!(f, "address: {}", address),
}
}
}
impl Default for Topology {
fn default() -> Self {
Topology {
+5 -2
View File
@@ -13,8 +13,11 @@ pub enum ClientCoreError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error: {0}")]
GatewayClientError(#[from] GatewayClientError),
#[error("Gateway client error ({gateway_id}): {source}")]
GatewayClientError {
gateway_id: String,
source: GatewayClientError,
},
#[error("Ed25519 error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
+18 -7
View File
@@ -11,7 +11,6 @@ use nym_gateway_client::GatewayClient;
use nym_topology::{filter::VersionFilterable, gateway};
use rand::{seq::SliceRandom, Rng};
use std::{sync::Arc, time::Duration};
use tap::TapFallible;
use tungstenite::Message;
use url::Url;
@@ -149,7 +148,7 @@ async fn measure_latency(gateway: &gateway::Node) -> Result<GatewayWithLatency,
Ok(GatewayWithLatency::new(gateway, avg))
}
pub(super) async fn choose_gateway_by_latency<R: Rng>(
pub async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R,
gateways: &[gateway::Node],
) -> Result<gateway::Node, ClientCoreError> {
@@ -209,14 +208,26 @@ pub(super) async fn register_with_gateway(
our_identity.clone(),
timeout,
);
gateway_client
.establish_connection()
.await
.tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?;
gateway_client.establish_connection().await.map_err(|err| {
log::warn!("Failed to establish connection with gateway!");
ClientCoreError::GatewayClientError {
gateway_id: gateway.gateway_id.clone(),
source: err,
}
})?;
let shared_keys = gateway_client
.perform_initial_authentication()
.await
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
.map_err(|err| {
log::warn!(
"Failed to register with the gateway {}!",
gateway.gateway_id
);
ClientCoreError::GatewayClientError {
gateway_id: gateway.gateway_id.clone(),
source: err,
}
})?;
Ok(RegistrationResult {
shared_keys,
authenticated_ephemeral_client: Some(gateway_client),
@@ -14,15 +14,15 @@ use std::path::PathBuf;
pub struct Args {
/// Config file of the client that is supposed to use the credential.
#[clap(long)]
pub(crate) client_config: PathBuf,
pub client_config: PathBuf,
/// The amount of utokens the credential will hold.
#[clap(long, default_value = "0")]
pub(crate) amount: u64,
pub amount: u64,
/// Path to a directory used to store recovery files for unconsumed deposits
#[clap(long)]
pub(crate) recovery_dir: PathBuf,
pub recovery_dir: PathBuf,
}
pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "nym-payment-manager-common"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
schemars = { version = "0.8", features = ["preserve_order"] }
serde = { workspace = true }
+9
View File
@@ -0,0 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PaymentResponse {
pub unyms_bought: u64,
}
+3 -3
View File
@@ -110,10 +110,10 @@ pub struct Socks5 {
/// The version of the 'service provider' this client is going to use in its communication with the
/// specified socks5 provider.
// if in doubt, use the legacy version as initially nobody will be using the updated binaries
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
#[serde(default)]
pub provider_interface_version: ProviderInterfaceVersion,
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
#[serde(default)]
pub socks5_protocol_version: Socks5ProtocolVersion,
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
@@ -147,7 +147,7 @@ impl Socks5 {
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
#[serde(default, deny_unknown_fields)]
pub struct Socks5Debug {
/// Number of reply SURBs attached to each `Request::Connect` message.
pub connection_start_surbs: u32,
+7 -1
View File
@@ -1,7 +1,13 @@
# Documentation
## Doc projects
Each directory contains a readme with more information about running and contributing to the projects. Each is built with [`mdbook`](https://rust-lang.github.io/mdBook/index.html) - use `mdbook serve` to build and serve them (defaults to `localhost:3000`).
* `docs` contains technical documentation hosted at [https://nymtech.net/docs](https://nymtech.net/docs)
* `dev-portal` contains developer documentation hosted at [https://nymtech.net/developers](https://nymtech.net/developers)
* `operators` contains node setup and maintenance guides hosted at [https://nymtech.net/operators](https://nymtech.net/operators)
Each directory contains a readme with more information about running and contributing to the projects. Each is built with [`mdbook`](https://rust-lang.github.io/mdBook/index.html) - use `mdbook serve` to build and serve them (defaults to `localhost:3000`).
## Scripts
* `bump_versions.sh` allows you to update the `platform_release_version` and `wallet_release_version` variables in the `book.toml` of each mdbook project at once. You can also optionally update the `minimum_rust_version` as well. Helpful for lazy-updating when cutting a new version of the docs.
* `build_all_to_dist.sh` is used by the `ci-dev.yml` and `cd-dev.yml` scripts for building all mdbook projects and moving the rendered html to `../dist/` to be rsynced with various servers.
+40 -32
View File
@@ -1,37 +1,45 @@
#!/bin/bash
# commands assume you run script from `nym/documentation/`
# this is a script called by the github CI and CD workflows to build all 3 docs projects
# and move them to /dist/ in the root of the monorepo. They are rsynced to various servers
# from there by subsequent workflow tasks.
# array of project dirs
declare -a projects=("docs" "dev-portal" "operators")
## now loop through the above array
for i in "${projects[@]}"
do
# cd to project dir
cd "./$i" &&
# little sanity checks
echo $(pwd) && echo $(mdbook --version) &&
# clean old book
echo "cleaning old book"
rm -rf ./book/
# build book
mdbook build
# check for destination, if ! then mkdir & check again else echo thumbs up
if [ ! -d ../../dist/docs/$i ]; then
echo "dest doesn't exist: creating dir"
mkdir -p ../../dist/docs/$i
fi
if [ -d ../../dist/docs/$i ]; then
echo "cp destination exists, all good"
fi
# clean old dist/$i
rm -rf ../../dist/docs/$i
# move newly rendered book/ to dist
rsync -r ./book/html/ ../../dist/docs/$i
# sanity check
ls -laF ../../dist/docs/
# cd back to ../documentation/
cd ../
done
mv ../dist/docs/dev-portal ../dist/docs/developers
# check you're calling from the right place
if [ $(pwd | awk -F/ '{print $NF}') != "documentation" ]
then
echo "failure: please run script from documentation/"
else
for i in "${projects[@]}"
do
# cd to project dir
cd "./$i" &&
# little sanity checks
echo $(pwd) && echo $(mdbook --version) &&
# clean old book
echo "cleaning old book"
rm -rf ./book/
# build book
mdbook build
# check for destination, if ! then mkdir & check again else echo thumbs up
if [ ! -d ../../dist/docs/$i ]; then
echo "dest doesn't exist: creating dir"
mkdir -p ../../dist/docs/$i
fi
if [ -d ../../dist/docs/$i ]; then
echo "cp destination exists, all good"
fi
# clean old dist/$i
rm -rf ../../dist/docs/$i
# move newly rendered book/ to dist
rsync -r ./book/html/ ../../dist/docs/$i
# sanity check
ls -laF ../../dist/docs/
# cd back to ../documentation/
cd ../
done
# rename for server paths
rm -rf ../dist/docs/developers
mv ../dist/docs/dev-portal ../dist/docs/developers
fi
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# this takes two args: platform release version and wallet release version.
# it then uses sed to bump them in the three book.toml files.
#
# e.g if the upcoming platform release was v1.1.29 and the release version 1.2.9 you'd run this as:
# `./bump_versions.sh "1.1.29" "1.2.9"`
#
# you can also set the minumum rust version by passing an optional 3rd argument:
# `./bump_versions.sh "1.1.29" "1.2.9" "1.67"`
# array of project dirs
declare -a projects=("docs" "dev-portal" "operators")
# check number of args passed
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ];
then
echo "failure: please pass at least 2 and at most 3 args: "
echo "./bump_version.sh <new platform_release_version> <new wallet_release_version> [OPTIONAL]<new minimum_rust_version>"
exit 0
fi
# check you're calling from the right place
if [ $(pwd | awk -F/ '{print $NF}') != "documentation" ]
then
echo "failure: please run script from documentation/"
exit 0
else
## now loop through the above array sed-ing the variable values in the book.toml files
for i in "${projects[@]}"
do
# sed the vars in the book.toml file for each project
echo "setting platform and wallet versions in $i/"
sed -i 's/platform_release_version =.*/platform_release_version = "'$1'"/' "$i"/book.toml
sed -i 's/wallet_release_version =.*/wallet_release_version = "'$2'"/' "$i"/book.toml
if [ "$3" ]
then
echo "setting minimum rust version in $i/"
sed -i 's/minimum_rust_version = .*/minimum_rust_version = "'$3'"/' "$i"/book.toml
fi
done
fi
+3 -1
View File
@@ -17,4 +17,6 @@ book
theme/
theme
theme/*
theme/*
.idea
+3 -2
View File
@@ -40,6 +40,7 @@ section-line-height = "1.5em"
# if true, never read and touch the files in theme dir
turn-off = false
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
@@ -48,8 +49,8 @@ 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"
platform_release_version = "v1.1.28"
wallet_release_version = "v1.2.7"
platform_release_version = "1.1.29"
wallet_release_version = "1.2.8"
[preprocessor.last-changed]
command = "mdbook-last-changed"
+20 -8
View File
@@ -20,6 +20,7 @@
- [NymConnect Monero](tutorials/monero.md)
- [NymConnect Matrix](tutorials/matrix.md)
- [NymConnect Telegram](tutorials/telegram.md)
# Integrations
@@ -30,14 +31,25 @@
# Tutorials
- [Simple Service Provider](tutorials/simple-service-provider.md)
- [Tutorial Overview](tutorials/simple-service-provider/overview.md)
- [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md)
- [Building Your User Client](tutorials/simple-service-provider/user-client.md)
- [Preparing Your Service Provider Environment](tutorials/simple-service-provider/preparating-env2.md)
- [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md)
- [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md)
- [IPFS Service Provider (coming soon)](tutorials/ipfs-service-provider.md)
- [Rust SDK](tutorials/rust-sdk.md)
- [Blockchain Service pt1](tutorials/cosmos-service/intro.md)
- [Tutorial Overview](tutorials/cosmos-service/overview.md)
- [Preparing Your Environment](tutorials/cosmos-service/preparing-env.md)
- [Preparing Your Lib](tutorials/cosmos-service/lib.md)
- [Preparing Your Client](tutorials/cosmos-service/client.md)
- [Preparing Your Client pt2](tutorials/cosmos-service/client-src.md)
- [Preparing Your Service](tutorials/cosmos-service/service.md)
- [Preparing Your Service pt2](tutorials/cosmos-service/service-src.md)
- [Querying the Chain](tutorials/cosmos-service/querying.md)
- [Typescript](tutorials/typescript.md)
- [Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md)
- [Tutorial Overview](tutorials/simple-service-provider/overview.md)
- [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md)
- [Building Your User Client](tutorials/simple-service-provider/user-client.md)
- [Preparing Your Service Provider Environment](tutorials/simple-service-provider/preparating-env2.md)
- [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md)
- [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md)
# Community Resources
@@ -36,12 +36,11 @@ For example, a Service Provider could receive a request to check a mail server a
Maybe you would like to concentrate on building a application that uses the mixnet:
* Explore the Tutorials section of the Developer Portal. Our in-depth tutorial on [Building a Simple Service Provider](../tutorials/simple-service-provider.md) give a good understanding of building User Clients and Service Providers in TypeScript, and how to configure Nym Websocket Clients for seamless communication with the mixnet.
* Explore the Tutorials section of the Developer Portal. Our in-depth tutorial on [Building a Simple Service Provider](../tutorials/simple-service-provider/simple-service-provider.md) give a good understanding of building User Clients and Service Providers in TypeScript, and how to configure Nym Websocket Clients for seamless communication with the mixnet.
* Get started with using the Nym Mixnet quickly and easily by exploring the [Quickstart](../quickstart/overview.md) options, such a NymConnect, proxying traffic through the Nym Socks5 client, or dive into integrating Nym into your existing application with the [Integrations](../integrations/integration-options.md) section.
Or perhaps you a developer that would like to run a infrastructure node such as a Gateway, Mix node or Network Requestor:
* Check out the [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) docs page.
Or perhaps you a developer that would like to run a infrastructure node such as a Gateway, Mix node or Network Requestor:
* Check out the [Network Overview](https://nymtech.net/docs/architecture/network-overview.html) docs page.
* Take a look at our [Node Setup Guide](https://nymtech.net/docs/nodes/setup-guides.html) with our Nym Docs, containing setup guides for setting up you own infrastructure node.
@@ -1,19 +1,19 @@
# Integrating with Nym for network privacy
If you are wanting to integrate Nym by using the Mixnet as a transport layer for application traffic, you will have to run one of the three Nym clients in order to connect to the Mixnet.
If you are wanting to integrate Nym by using the Mixnet as a transport layer for application traffic, you will have to run one of the three Nym clients in order to connect to the Mixnet.
## Connecting applications to the mixnet
### SDK support
### SDK support
If your app is written in Typescript or Rust, then you can use the [Typescript](https://nymtech.net/docs/sdk/typescript.html) or [Rust](https://nymtech.net/docs/sdk/rust.html) SDKs. These SDKs abstract away much of the messaging logic from your app, and allow you to run a Nym client as part of your application process, instead of having to run them seperately.
### Choosing a client
In order to connect your application to the mixnet, you need to select one of three clients to use. These clients do the majority of the heavy-lifting with regards to cryptographic operations and routing under the hood, and all do basically the same thing: create a connection to a gateway, encrypt and decrypt packets sent to and received from the mixnet, and send cover traffic to hide the flow of actual app traffic from observers.
In order to connect your application to the mixnet, you need to select one of three clients to use. These clients do the majority of the heavy-lifting with regards to cryptographic operations and routing under the hood, and all do basically the same thing: create a connection to a gateway, encrypt and decrypt packets sent to and received from the mixnet, and send cover traffic to hide the flow of actual app traffic from observers.
As outlined in the [clients overview documentation](https://nymtech.net/docs/clients/overview.html) there are three clients availiable to developers to use when connecting applications to the mixnet:
As outlined in the [clients overview documentation](https://nymtech.net/docs/clients/overview.html) there are three clients availiable to developers to use when connecting applications to the mixnet:
#### Websocket client
Your first option is the native websocket client. This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It runs as a persistent process on a desktop or server machine. You can connect to it with any language that supports websockets.
Your first option is the native websocket client. This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It runs as a persistent process on a desktop or server machine. You can connect to it with any language that supports websockets.
You can see an example of how to connect to and manage interactions with this client in the [Simple Service Provider tutorial](../tutorials/simple-service-provider.md).
You can see an example of how to connect to and manage interactions with this client in the [Simple Service Provider tutorial](../tutorials/simple-service-provider/simple-service-provider.md).
#### Webassembly client
If youre working in JavaScript or Typescript in the browser, or building an edge computing app, youll likely want to choose the webassembly client.
@@ -27,16 +27,16 @@ You can find example code in the [examples section](https://github.com/nymtech/n
#### SOCKS client
This client is useful for allowing existing applications to use the Nym mixnet without any code changes. All thats necessary is that they can use one of the SOCKS5, SOCKS4a, or SOCKS4 proxy protocols (which many applications can - crypto wallets, browsers, chat applications etc).
Its less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes.
Its less flexible as a way of writing custom applications than the other clients, but able to be used to proxy application traffic through the mixnet without having to make any code changes.
You can find examples of how to utilise this client in the [Quickstart](../quickstart/socks-proxy.md) section, and the [SOCKS5 documentation](https://nymtech.net/docs/clients/socks5-client.html).
You can find examples of how to utilise this client in the [Quickstart](../quickstart/socks-proxy.md) section, and the [SOCKS5 documentation](https://nymtech.net/docs/clients/socks5-client.html).
## Recommended infrastructure setup
In order to ensure uptime and reliability, it is recommended that you run some pieces of mixnet infrastructure. What infrastructure is necessary to run depends on the architecture of your application, and the endpoints that it needs to hit!
## Recommended infrastructure setup
In order to ensure uptime and reliability, it is recommended that you run some pieces of mixnet infrastructure. What infrastructure is necessary to run depends on the architecture of your application, and the endpoints that it needs to hit!
* If you're running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet.
* If you're wanting to place the mixnet between your users' application instances and a server-based backend, you can use the [network requester](https://nymtech.net/docs/nodes/network-requester-setup.html) service provider binary to proxy these requests to your application backend, with the mixnet 'between' the user and your service, in order to prevent metadata leakage being broadcast to the internet.
* If you're wanting to route RPC requests through the mixnet to a blockchain, you will need to look into setting up some sort of service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications](../community-resources/community-applications-and-guides.md) page.
* If you're running a purely P2P application, then just integrating clients and having some method of sharing addresses should be enough to route your traffic through the mixnet.
* If you're wanting to place the mixnet between your users' application instances and a server-based backend, you can use the [network requester](https://nymtech.net/docs/nodes/network-requester-setup.html) service provider binary to proxy these requests to your application backend, with the mixnet 'between' the user and your service, in order to prevent metadata leakage being broadcast to the internet.
* If you're wanting to route RPC requests through the mixnet to a blockchain, you will need to look into setting up some sort of service that does the transaction broadcasting for you. You can find examples of such projects on the [community applications](../community-resources/community-applications-and-guides.md) page.
## Example application traffic flow
### Initialization
@@ -44,36 +44,36 @@ First, we need to initalise an app and connect it to Nym.
```
+-----------+
| Gateway |
+-----------+
^
|
|
|
|
|
|
+-------------------+
| +---------------+ |
| | Nym client | |
| +---------------+ |
| ^ |
| | |
| | |
| | |
| v |
| +---------------+ |
| | Your app code | |
| +---------------+ |
+-------------------+
Your Local Machine
| Gateway |
+-----------+
^
|
|
|
|
|
|
+-------------------+
| +---------------+ |
| | Nym client | |
| +---------------+ |
| ^ |
| | |
| | |
| | |
| v |
| +---------------+ |
| | Your app code | |
| +---------------+ |
+-------------------+
Your Local Machine
```
At the bottom we have an app. It consists of two parts:
* your application specific logic
* your Nym client - either running as a standalone process, or as part of the process of your app code if you're using an SDK
* your application specific logic
* your Nym client - either running as a standalone process, or as part of the process of your app code if you're using an SDK
Nym apps have a stable, potentially long-lasting relation to a gateway node. A client will register itself with a gateway, and get back an authentication token that it can then use to retrieve messages from the gateway later on.
@@ -89,40 +89,40 @@ The Nym client part of the app accepts messages from your code and automatically
The app has now connected to the Gateway, but we haven't sent a message to ourselves yet. Let's do that now.
```
+----------+ +----------+ +----------+
| Mix Node |<-----------> | Mix Node |<----------->| Mix Node |
| Layer 1 | | Layer 2 | | Layer 3 |
+----------+ +----------+ +----------+
^ ^
| |
+----------+ +----------+ +----------+
| Mix Node |<-----------> | Mix Node |<----------->| Mix Node |
| Layer 1 | | Layer 2 | | Layer 3 |
+----------+ +----------+ +----------+
^ ^
| |
|<--------------------------------------------------+
|
v
+--------------+
| Your gateway |
+--------------+
^
|
|
v
+-------------------+
| +---------------+ |
| | Nym client | |
| +---------------+ |
| ^ |
| | |
| | |
| v |
| +---------------+ |
| | Your app code | |
| +---------------+ |
+-------------------+
Your Local Machine**
|
v
+--------------+
| Your gateway |
+--------------+
^
|
|
v
+-------------------+
| +---------------+ |
| | Nym client | |
| +---------------+ |
| ^ |
| | |
| | |
| v |
| +---------------+ |
| | Your app code | |
| +---------------+ |
+-------------------+
Your Local Machine**
** note that depending on the technical setup, the Nym client running on this machine may
be either a seperate process or embedded in the same process as the app code via one of our SDKs.
be either a seperate process or embedded in the same process as the app code via one of our SDKs.
```
Let's say your code code pokes a message `hello world` into the Nym client. The Nym client automatically wraps that message up into a layer encrypted Sphinx packet, adds some routing information and encryption, and sends it to its own gateway. The gateway strips the first layer of encryption, ending up with the address of the first mixnode it should forward to, and a Sphinx packet.
@@ -139,38 +139,37 @@ Messages are end-to-end encrypted. Although the gateway knows our app's IP when
The process for sending messages to other apps is exactly the same, you simply specify a different recipient address. Address discovery happens outside the Nym system: in the case of a Service Provider app, the service provider has presumably advertised its own address. If you're sending to a friend of yours, you'll need to get a hold of their address out of band, maybe through a private messaging app such as Signal.
```
+----------+ +----------+ +----------+
| Mix Node |<-----------> | Mix Node |<----------->| Mix Node |
| Layer 1 | | Layer 2 | | Layer 3 |
+----------+ +----------+ +----------+
^ ^
| |
| |
v v
+--------------+ +-----------------+
| Your gateway | | Service gateway |
+--------------+ +-----------------+
^ ^
| |
| |
v v
+-------------------+ +-------------------+
| +---------------+ | | +---------------+ |
| | Nym client | | | | Nym Client | |
| +---------------+ | | +---------------+ |
| ^ | | ^ |
| | | | | |
| | | | | |
| v | | v |
| +---------------+ | | +---------------+ |
| | Your app code | | | | Service Code | |
| +---------------+ | | +---------------+ |
+-------------------+ +-------------------+
Your Local Machine** Service Provider Machine**
+----------+ +----------+ +----------+
| Mix Node |<-----------> | Mix Node |<----------->| Mix Node |
| Layer 1 | | Layer 2 | | Layer 3 |
+----------+ +----------+ +----------+
^ ^
| |
| |
v v
+--------------+ +-----------------+
| Your gateway | | Service gateway |
+--------------+ +-----------------+
^ ^
| |
| |
v v
+-------------------+ +-------------------+
| +---------------+ | | +---------------+ |
| | Nym client | | | | Nym Client | |
| +---------------+ | | +---------------+ |
| ^ | | ^ |
| | | | | |
| | | | | |
| v | | v |
| +---------------+ | | +---------------+ |
| | Your app code | | | | Service Code | |
| +---------------+ | | +---------------+ |
+-------------------+ +-------------------+
Your Local Machine** Service Provider Machine**
** note that depending on the technical setup, the Nym client running on these machines may
be either a seperate process or embedded in the same process as the app code via one of our SDKs.
be either a seperate process or embedded in the same process as the app code via one of our SDKs.
```
@@ -0,0 +1,55 @@
# Preparing Your Client pt2
Open `src/client.rs`. This is where the logic of the command from the `match` statement in `bin/client.rs` is defined.
# Dependencies
```rust
use crate::{handle_response, wait_for_non_empty_message, RequestTypes, DEFAULT_VALIDATOR_RPC};
use cosmrs::AccountId;
use nym_sdk::mixnet::MixnetClient;
use nym_sphinx_addressing::clients::Recipient;
use nym_validator_client::nyxd::Coin;
```
As well as importing message-handling functionality, request types, and the default RPC endpoint, this file relies on the `AccountId` type to construct blockchain addresses, the `MixnetClient` for interacting with the mixnet, the `Recipient` type to construct mixnet recipient addresses, and the `Coin` type for properly handling the returned balance of the account that will be queried.
# Querying via the Mixnet
The following is used to construct a `BalanceRequest`, send this to the supplied `service` address, and then handle the response, matching it to a `ResponseType` (in this case the only expected response, a `BalanceResponse`).
The actual sending of the request is performed by `client.send_message`: sending the serialised `BalanceRequest` to the supplied Nym address (the `Recipient` imported from the `nym_sphinx_addressing` crate). It is sending the default number of SURBs along with the message as the third argument, defined [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/src/mixnet/client.rs#L34).
```rust
pub async fn query_balance(
account: AccountId,
client: &mut MixnetClient,
sp_address: Recipient,
) -> anyhow::Result<Coin> {
// construct balance request
let message = RequestTypes::Balance(crate::BalanceRequest {
validator: DEFAULT_VALIDATOR_RPC.to_owned(), // rpc endpoint for broadcaster to use
account,
});
// send serialised request to service via mixnet
let _ = client
.send_message(sp_address, message.serialize(), Default::default())
.await;
let received = wait_for_non_empty_message(client).await?;
// listen for response from service
let sp_response = handle_response(received)?;
// match JSON -> ResponseType
let res = match sp_response {
crate::ResponseTypes::Balance(response) => {
println!("{:#?}", response);
response.balance
}
};
Ok(res)
}
```
That is all the client code written: now to move on to the `service` that will be interacting with the blockchain on behalf of the `client`.
@@ -0,0 +1,79 @@
# Preparing Your Client
Start by creating the startup logic of your `client` in `bin/client.rs` - creating a Nym client and connecting to the mixnet (or just connecting if your client has been started before and config already exists for it), and defining and running commands.
## Dependencies
Import the following dependencies:
```rust
use clap::{Args, Parser, Subcommand};
use chain_query::{client::query_balance, create_client};
use nym_sdk::mixnet::Recipient;
use nym_validator_client::nyxd::AccountId;
use nym_bin_common::logging::setup_logging;
```
`clap` is used so different commands can be passed to the `client` (even though we're only defining one function in this first part of the tutorial, more will be added in subsequent chapters). `nym_sdk::mixnet::Recipient` is the type used to define the recipient of a mixnet message, `nym_bin_common::logging::setup_logging` is the logging setup for `client`'s Nym client, and `chain_query` imports the `create_client` and `query_balance` functions created on the previous page.
## CLI Command with Clap
The following simply defines the commands that the client can perform. For the moment, there is only one: the `query_balance` function created in the previous section.
As with the data structures, this structure is being used for ease of adding future commands in subsequent tutorials.
```rust
#[derive(Debug, Parser)]
#[clap(name = "rust sdk demo - chain query service")]
#[clap(about = "query the sandbox testnet blockchain via the mixnet... part 2 coming soon")]
struct Cli {
#[clap(subcommand)]
command: Option<Commands>,
}
#[derive(Debug, Subcommand)]
enum Commands {
QueryBalance(QueryBalance),
}
#[derive(Debug, Args)]
struct QueryBalance {
/// the account we want to query
account: AccountId,
/// the address of the broadcaster service - this submits txs and queries the chain on our behalf
sp_address: String,
}
```
## `main()`
This is the root logic of the `client`. Using `[tokio](https://tokio.rs/)` for the async runtime, this function performs the following functions:
* If not already existing, create a Nym client with config at `/tmp/client`. Otherwise load the already existing client from this config.
* Matche the command from the CLI - in this instance, the `QueryBalance` function which will be defined in the next section. This creates a `BalanceRequest` and sends this to the `service`, before returning the response back to the main thread and print this to the console.
* Perform a proper shutdown of the Nym client.
```rust
#[tokio::main]
async fn main() -> anyhow::Result<()> {
setup_logging();
let cli = Cli::parse();
let mut client = create_client("/tmp/client2".into()).await;
let our_address = client.nym_address();
println!("\nclient's nym address: {our_address}");
match cli.command {
Some(Commands::QueryBalance(QueryBalance {
account,
sp_address,
})) => {
println!("\nsending bank balance request to service via mixnet");
let sp_address = Recipient::try_from_base58_string(sp_address).unwrap();
let returned_balance = query_balance(account, &mut client, sp_address).await?;
println!("\nreturned balance is: {}", returned_balance);
}
None => {
println!("\nno command specified - nothing to do")
}
}
println!("\ndisconnecting client");
client.disconnect().await;
println!("client disconnected");
Ok(())
}
```
@@ -0,0 +1,7 @@
# Interacting with a Cosmos SDK Blockchain via the Mixnet with the Rust SDK
This tutorial is for Rust developers wanting to interact with the Rust SDK and take a first step at building a service with which to interact with a Cosmos SDK blockchain.
The key here is to think of the service as a proxy: it interacts with the blockchain _on the client's behalf_, shielding the client from the Validator it interacts with, whilst also being shielded from the client by the mixnet.
> This service also nicely highlights the limitations of the mixnet - even though with this code your metadata is shielded from the Validator, and even the service does not know your Nym address, application-level information such as a blockchain address is not made private, in virtue of the fact that using the mixnet provides solely network-level privacy. For information on what application-level privacy Nym offers, check out the [coconut credential SDK example](https://nymtech.net/docs/sdk/rust.html#coconut-credential-generation).
@@ -0,0 +1,165 @@
# Preparing Your Lib
Now move on to preparing shared data structures and functions in `src/lib.rs`.
These include the request and response types the client and the service will be passing through the mixnet, as well as shared functions such as client creation, and message parsing.
## Dependencies
The dependencies for the shared `lib` file are the following:
```rust
use anyhow::bail;
use cosmrs::AccountId;
use nym_sdk::mixnet::{
AnonymousSenderTag, MixnetClient, MixnetClientBuilder, ReconstructedMessage, StoragePaths,
};
use nym_validator_client::nyxd::Coin;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub mod client;
pub mod service;
```
Since this is the file where client creation and message parsing are handled, the various `nym_sdk` imports, as well as `serde`'s (de)serialisation functionality, are required. `PathBuf` is for reading filepaths, `cosmrs` types are required for defining Nyx blockchain accounts, and the `Coin` type from the `nyxd_validator_client` is for our Coin balance request and response. `anyhow` is for easy error handing.
## Constants
Below this are the chain-related `const` variables. These have been hardcoded for this demo.
```rust
pub const DEFAULT_VALIDATOR_RPC: &str = "https://sandbox-validator1.nymtech.net";
pub const DEFAULT_DENOM: &str = "unym";
pub const DEFAULT_PREFIX: &str = "n";
```
These define the RPC endpoint your service will use to interact with the blockchain - in this case the Sandbox testnet - as well as the expected coin denomination, and Bech32-prefix of addresses.
## Shared Data Structures
Define the following structs for our different request and responses that will be serialised and sent through the mixnet between your client and service binaries:
```rust
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct BalanceRequest {
pub validator: String,
pub account: AccountId,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct BalanceResponse {
pub balance: Coin,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub enum RequestTypes {
Balance(BalanceRequest),
}
impl RequestTypes {
pub fn serialize(&self) -> Vec<u8> {
serde_json::to_vec(self).expect("serde failure")
}
pub fn try_deserialize<M: AsRef<[u8]>>(raw: M) -> anyhow::Result<Self> {
serde_json::from_slice(raw.as_ref()).map_err(Into::into)
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub enum ResponseTypes {
Balance(BalanceResponse),
}
impl ResponseTypes {
pub fn serialize(&self) -> Vec<u8> {
serde_json::to_vec(self).expect("serde failure")
}
pub fn try_deserialize<M: AsRef<[u8]>>(raw: M) -> anyhow::Result<Self> {
serde_json::from_slice(raw.as_ref()).map_err(Into::into)
}
}
```
The above data types are pretty straightforward. Even though there are only one instance of a request type (sent from `client` -> mixnet -> `service`) and one of a response type (`service` -> mixnet -> `client`) so far, a pair of enums has been defined to contain additional response and request types that will be added in part 2 of this tutorial, when adding credential functionality.
`BalanceRequest` will be used when requesting the service to query the token balance of the supplied address on the client's behalf. You can see the information that will be returned from the chain to the service, and from the service to the client, in `BalanceResponse`.
Custom serialistion and deserialisation have been implemented for each enum for ease of future modification and testing.
## Shared Functions
Now to define functions shared by the `client` and `service` binaries.
### Client Creation
The following function is called on startup by each binary, with the `config_path` being a filepath for storing client config:
```rust
// create our client with specified path for key storage
pub async fn create_client(config_path: PathBuf) -> MixnetClient {
let config_dir = config_path;
let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap();
let client = MixnetClientBuilder::new_with_default_storage(storage_paths)
.await
.unwrap()
.build()
.await
.unwrap();
client.connect_to_mixnet().await.unwrap()
}
```
If no config files exist at the location designated by `config_path` (in this case `/tmp/service`) then the following files are generated:
```sh
service
├── ack_key.pem
├── db.sqlite
├── db.sqlite-shm
├── db.sqlite-wal
├── gateway_details.json
├── gateway_shared.pem
├── persistent_reply_store.sqlite
├── private_encryption.pem
├── private_identity.pem
├── public_encryption.pem
└── public_identity.pem
1 directory, 11 files
```
> If keys and config already exist at this location, re-running this function **will not** overwrite them.
### Listening for & Parsing Incoming messages
Next to define two functions: one for listening _for_ messages from the mixnet (used by `service`), and one for handling a _response_ to a request (used by `client`).
Both functions attempt to deserialise the vec of `ReconstructedMessages` that are reconstructed by the client from delivered Sphinx packets after decryption.
`handle_request` performs one additional function - parsing the `sender_tag` from the incoming reconstructed message. This is the randomised alphanumeric string used to identify a bucket of _SURBs_ (Single Use Reply Blocks) that are sent along with any outgoing message by default. More information about them can be found [here](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs) but all that is necessary to know for now is that these are pre-addressed packets that clients send out with their messages. Any reply to their message that is to be sent back to them back be written to the payload of these packets, but without the replying party being able to see the destination that the reply is being sent to. This allows for services to **anonymously reply to clients without being able to doxx them by knowing their Nym address**.
```rust
pub fn handle_response(message: ReconstructedMessage) -> anyhow::Result<ResponseTypes> {
ResponseTypes::try_deserialize(message.message)
}
pub fn handle_request(
message: ReconstructedMessage,
) -> anyhow::Result<(RequestTypes, Option<AnonymousSenderTag>)> {
let request = RequestTypes::try_deserialize(message.message)?;
Ok((request, message.sender_tag))
}
```
Before moving on to the `client` and `service` code, one more function is needed. This allows for both binaries to parse empty incoming messages that they might receive. This is necessary as incoming SURBs, as well as requests for more SURBs, contain empty data fields.
```rust
pub async fn wait_for_non_empty_message(
client: &mut MixnetClient,
) -> anyhow::Result<ReconstructedMessage> {
while let Some(mut new_message) = client.wait_for_messages().await {
if !new_message.is_empty() {
return Ok(new_message.pop().unwrap());
}
}
bail!("did not receive any non-empty message")
}
```
@@ -0,0 +1,29 @@
# Tutorial Overview
This tutorial involves writing two pieces of code in Rust:
- A client side binary used to construct a blockchain query and send this query to a service, which will query the Cosmos SDK blockchain and then pass the response back to the client (bear in mind this principle works for all blockchains - we're just utilising the `cosmrs` library to interact with the Sandbox testnet blockchain in this tutorial). This query will be to query the balance of an account, in preparation for spending these tokens on a [bandwidth credential](https://nymtech.net/docs/bandwidth-credentials.html) in a subsequent tutorial.
- A service which will listen out for requests from the mixnet, act on those requests, and anonymously reply to the client sending the requests.
You will learn how to do the following with the Rust SDK:
- Create clients with manual storage settings.
- Parse incoming traffic from the mixnet and reply anonymously using [SURBs](https://nymtech.net/docs/architecture/traffic-flow.html#private-replies-using-surbs).
> Services usually run on remote servers to assure reliable uptime and to unlink sender and receiver metadata. For demonstration purposes however, you will run both components on your local machine, looping messages through the mixnet to yourself.
You can find the code for these components [here](https://github.com/nymtech/developer-tutorials). You can use it as a reference while building or simply download it and follow along as you progress through the tutorial.
Notice that this tutorial attempts to use very few external libraries. This tutorial is not showing you how to build production-grade code, but **to understand how to connect and send messages to, as well as receive messages from, the mixnet.**
```admonish note title="Sidenote: What is a Service / Service Provider?"
'Service' or 'Service Provider' are catchall names used to refer to any type of app that can communicate with the mixnet via a Nym client - in this case, one embedded in its app process via the Rust SDK.
The first SP to have been released is the [Network Requester](https://nymtech.net/docs/nodes/network-requester-setup.html) - a binary that receives a network request from the mixnet, performs that request (e.g. authenticating with a message server and receiving new messages for a user) and then passes the response back to the user who requested it anonymously, shielding their metadata from the message server.
The SP you will build in this tutorial is far more simple than this, showing you how to approach building something that can:
* connect to the mixnet,
* listen for messages, and
* perform some action with them - in this case, query a Cosmos SDK blockchain.
However, once you see how easy it is to integrate with the mixnet for traffic transport, you will be able to build apps with real-world uses easily.
```
@@ -0,0 +1,62 @@
# Preparing Your Environment
## Prerequisites
* `Rust` & `cargo`
## Creating your Project Structure
* Make a new cargo project:
```
cargo new nym-cosmos-service
```
* Create the following directory structure and files:
```
.
├── Cargo.toml
├── bin
│   ├── client.rs
│   └── service.rs
└── src
├── client.rs
├── lib.rs
└── service.rs
3 directories, 6 files
```
* Add the following dependencies to your `Cargo.toml` file:
```
[dependencies]
clap = { version = "4.0", features = ["derive"] }
cosmrs = "=0.14.0"
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
serde = "1.0.152"
serde_json = "1.0.91"
anyhow = "1.0.72"
```
These are non Nym-specific dependencies for the project. `clap` is for setting up the CLI commands, `cosmrs` for cosmos-specific types and functionality, `tokio` for the async/await environment, and `serde` for (de)serialisation. `anyhow` is for catch-all error handling.
* Next add Nym-specific dependencies. Since these libraries are not yet on [crates io](https://crates.io) then you need to import them from the Nym monorepo:
```
nym-sdk = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }
nym-sphinx-addressing = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }
nym-validator-client = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }
nym-bin-common = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }
nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", rev = "85a7ec9f02ca8262d47eebb6c3b19d832341b55d" }
```
The `sphinx` dependencies are for packet- and address-related functionality, the `validator-client` for Nyx blockchain specific configs, `common` for client logging, and the `sdk` for SDK functionality: creating and managing client storage and connections, and sending and receiving messages to and from the mixnet.
* Finally add the following underneath your `[dependencies]`:
```
[[bin]]
name = "client"
path = "bin/client.rs"
[[bin]]
name = "service"
path = "bin/service.rs"
```
This defines multiple binaries to run in a single cargo project, as outlined [here](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries).
@@ -0,0 +1,43 @@
# Querying the Chain
Now that all the code has been written, time to query the blockchain.
To test against the account operating one of the mix nodes on the testnet, use `n1lcutqz94k739s39u26rvexql40ehf42zd27fwe` in the following instructions:
```sh
# build the binaries
cargo build --release
# open two console windows. In one run the following
./target/release/service
# copy the printed Nym address from the console - this is used by the client when running the chain query
# in the other run
./target/release/client query-balance n1lcutqz94k739s39u26rvexql40ehf42zd27fwe <SERVICE_ADDRESS_FROM_CLIPBOARD>
```
The following happens:
* `client` and `service` both start their Nym clients and log the address. `service` is listening for incoming messages from the mixnet.
* `client` sends a request to `service` using the supplied `n1...` address as the Nyx account to query the balance of, and the supplied Nym address to communicate with this instance of `service`.
* `service` queries the Sandbox testnet blockchain using the `broadcaster` http client. It then serialises the response and returns it using SURBs to the `client`.
All in all, quite simple. By using the service as a proxy, the client never interacts with the blockchain, thus is not revealing metadata to the operator of the Validator nor any entities monitoring its incoming and outgoing traffic. Furthermore, the client doesn't even need to share its Nym address with the service, as the service is able to reply via SURBs.
## Creating Sandbox Account
If you wish to create an account on Sandbox to use instead of the supplied account above, the easiest way is by building the `nym-cli` tool and using that to create one:
```sh
# start from the root of the nym monorepo
cd tools/nym-cli
# build
cargo build --release
# create account using Sandbox testnet environment
../../target/release/nym-cli --config-env-file ../../envs/sandbox.env account create
```
However, since this account is fresh, it won't have any tokens. Querying the balance will still work obviously, it will just return `0`.
## Get Tokens
We're working on getting the faucet up and running again in preparation for part 2 of this tutorial: using the tokens you have privately checked the balance of to generate a [bandwidth credential](https://nymtech.net/docs/bandwidth-credentials.html).
If you wish to get testnet tokens already then feel free to ask in the [Dev channel](https://matrix.to/#/#dev:nymtech.chat) on Matrix.
@@ -0,0 +1,45 @@
# Preparing Your Service pt2
Now to define the logic of creating the `broadcaster` for interacting with the blockchain, and querying it in `src/service.rs`.
## Dependencies
The following dependencies are for creating a client to interact with the blockchain, and deal with the returned `Coin` type in `BalanceResponse`.
```rust
use crate::{BalanceResponse, DEFAULT_DENOM, DEFAULT_VALIDATOR_RPC};
use cosmrs::rpc::HttpClient;
use cosmrs::AccountId;
use nym_validator_client::nyxd::{Coin, CosmWasmClient};
```
## Creating a `broadcaster`
The `broadcaster` is an `HttpClient` taken from `cosmrs`, created with the `DEFAULT_VALIDATOR_RPC` as its default endpoint. The service will use this to query the Sandbox testnet chain.
```rust
pub async fn create_broadcaster() -> anyhow::Result<HttpClient> {
let broadcaster: HttpClient = HttpClient::new(DEFAULT_VALIDATOR_RPC)?;
Ok(broadcaster)
}
```
## Querying the Chain
Now to write the logic for querying the chain, using the `broadcaster` created in the previous step to query for the balance of the `account` that the client sent via the mixnet in the `BalanceRequest`. This function returns a `BalanceResponse` containing a `Coin` type, denoting the `balance` and `denom` returned by `get_balance()`.
```rust
pub async fn get_balance(
broadcaster: HttpClient,
account: AccountId,
) -> anyhow::Result<BalanceResponse> {
let balance = broadcaster
.get_balance(&account, DEFAULT_DENOM.to_string())
.await
.unwrap()
.unwrap();
Ok(BalanceResponse {
balance: Coin {
amount: balance.amount,
denom: balance.denom,
},
})
}
```
@@ -0,0 +1,72 @@
# Preparing Your Service
In `bin/src.rs` define the startup and response logic of the `service`. Client connection / config reading happens as it does in `bin/client.rs`.
## Dependencies
```rust
use chain_query::{
create_client, handle_request,
service::{create_broadcaster, get_balance},
BalanceResponse, RequestTypes, ResponseTypes,
};
use nym_sphinx_anonymous_replies::{self, requests::AnonymousSenderTag};
use nym_bin_common::logging::setup_logging;
use nym_sdk::mixnet::MixnetMessageSender;
```
The imports from `chain_query` are most of the data types and functions defined in the previous sections of this tutorial.
The `AnonymousSenderTag` type is used for SURBs.
## main()
Also using tokio for the async runtime, `main` does the following:
* Create a Nym client with config at `/tmp/service`, or load the existing client from this config directory.
* Create a `broadcaster` - this is used by the service to interact with the blockchain, using the consts defined in `src/lib.rs` as chain config.
* Listen out for incoming messages, and in much the same way as the `client`, handle and match the incoming request.
* Using the `sender_tag`, anonymously reply to the `client` with the response from the blockchain **without having to know the client's Nym address**.
```rust
#[tokio::main]
async fn main() -> anyhow::Result<()> {
setup_logging();
let mut client = create_client("/tmp/service".into()).await;
let our_address = client.nym_address();
println!("\nservice's nym address: {our_address}");
// the httpclient we will use to broadcast our query to the blockchain
let broadcaster = create_broadcaster().await?;
println!("listening for messages, press CTRL-C to exit");
while let Some(received) = client.wait_for_messages().await {
for msg in received {
let request = match handle_request(msg) {
Ok(request) => request,
Err(err) => {
eprintln!("failed to handle received request: {err}");
continue;
}
};
let return_recipient: AnonymousSenderTag = request.1.expect("no sender tag received");
match request.0 {
RequestTypes::Balance(request) => {
println!("\nincoming balance request for: {}\n", request.account);
let balance: BalanceResponse =
get_balance(broadcaster.clone(), request.account).await?;
let response = ResponseTypes::Balance(balance);
println!("response from chain: {:#?}", response);
println!("\nreturn recipient surb bucket: {}", &return_recipient);
println!("\nsending response to {}", &return_recipient);
// send response back to anon requesting client via mixnet
let _ = client
.send_reply(return_recipient, &serde_json::to_string(&response)?)
.await;
}
}
}
}
Ok(())
```
@@ -1 +0,0 @@
# Building a Image Upload Service Provider with IPFS (coming soon)
@@ -0,0 +1,5 @@
# Rust SDK
The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a seperate process on their machine.
[Read the docs](https://nymtech.net/docs/sdk/rust.html).
@@ -0,0 +1 @@
# Rust SDK
@@ -0,0 +1,41 @@
# Telegram NymConnect Integration
*This is a shortened version of a [Nym Community post](https://blog.nymtech.net/how-to-use-telegram-in-iraq-with-nymconnect-106a3b8dd050) written by Saliveja.*
The purpose of the following manual is not to promote Telegram but so people can use it with the Nym mixnet if they wish to, should a situation ask for that. This privacy-enhances Telegram at the network level and allows users to access the application from locations like where the application was banned.
See also: [Element (Matrix) over the Nym mixnet](./matrix.md): private, decentralised and secure messaging.
## Setup & Run
Heres how to configure Telegram with NymConnect:
1. Download and install NymConnect ().**
For more releases, check out [Github](https://github.com/nymtech/nym/tags). NymConnect is available for Linux, Windows, and MacOS.
On Linux make sure NymConnect is executable. Opening a terminal in the same directory and run:
```sh
chmod +x ./<YOUR-NYM-CONNECT-VERSION>.AppImage
```
2. **Start NymConnect**
Telegram is added to NymConnect by default.
3. **Click connect - the host and port will now be displayed.**
4. **Click on host or port to copy** the value to the clipboard.
5. **Open the Telegram proxy settings.**
Linux: Telegram -> Settings -> Advanced -> Connection type -> Use custom proxy
MacOS: Telegram -> Settings -> Advanced -> Data & Storage -> Connection Type -> Use custom Proxy
Windows: Telegram -> Settings -> Data and Storage -> Use proxy
6. **Add a proxy** with the Add proxy button.
7. **Select SOCKS5** and make sure the port details are the same as those generated by NymConnect. Alternatively, follow this link: https://t.me/socks?server=127.0.0.1&port=1080
8. **Save the proxy settings** in Telegram.
9. **Telegram is now running through the Nym Mixnet and is privacy-enhanced!**
This allows you to connect from regions which blocked Telegram.
10. Note if you remain idle on Telegram for a while you might lose connectivity and your messages might not get through via SOCKS5 proxy. If that happens reconnect your NymConnect and reset the proxy again.
Follow this [video](https://youtu.be/quj8H2qeOwY?t=97) to see the steps on Telegram setup.
<!---It's set to particular time as the NC setup part is outdated --->
**Now your Telegram runs over NymConnect.**
NymConnect is currently available for several applications and service providers. Support for more apps is on the way. For any bug reports or feedback please reach out to us on Telegram or our [Discord](http://discord.gg/nym) server.
@@ -0,0 +1,3 @@
# Typescript
Tutorial code in this section is built to interact with a standalone Nym client. You can read about interacting with standalone clients [here](https://nymtech.net/docs/clients/websocket-client.html#connecting-to-the-local-websocket), although it is usually preferable to use the [Typescript SDK](https://nymtech.net/docs/sdk/typescript.html).
+2 -2
View File
@@ -48,8 +48,8 @@ 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"
platform_release_version = "v1.1.28"
wallet_release_version = "v1.2.7"
platform_release_version = "1.1.29"
wallet_release_version = "1.2.8"
[preprocessor.last-changed]
command = "mdbook-last-changed"
+21 -1
View File
@@ -52,6 +52,26 @@ The example above involves ephemeral keys - if we want to create and then mainta
As seen in the example above, the `mixnet::MixnetClientBuilder::new()` function handles checking for keys in a storage location, loading them if present, or creating them and storing them if not, making client key management very simple.
Assuming our client config is stored in `/tmp/mixnet-client`, the following files are generated:
```
$ tree /tmp/mixnet-client
mixnet-client
├── ack_key.pem
├── db.sqlite
├── db.sqlite-shm
├── db.sqlite-wal
├── gateway_details.json
├── gateway_shared.pem
├── persistent_reply_store.sqlite
├── private_encryption.pem
├── private_identity.pem
├── public_encryption.pem
└── public_identity.pem
1 directory, 11 files
```
### Manually handling storage
If you're integrating mixnet functionality into an existing app and want to integrate saving client configs and keys into your existing storage logic, you can manually perform the actions taken automatically above (`examples/manually_handle_keys_and_config.rs`)
@@ -70,7 +90,7 @@ The number of SURBs is set [here](https://github.com/nymtech/nym/blob/release/{{
You can read more about how SURBs function under the hood [here](../architecture/traffic-flow.md#private-replies-using-surbs).
In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to:
In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to:
```rust,noplayground
{{#include ../../../../sdk/rust/nym-sdk/examples/surb-reply.rs}}
+2 -2
View File
@@ -48,8 +48,8 @@ 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"
platform_release_version = "v1.1.28"
wallet_release_version = "v1.2.7"
platform_release_version = "1.1.29"
wallet_release_version = "1.2.8"
[preprocessor.last-changed]
command = "mdbook-last-changed"
+1 -1
View File
@@ -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
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "nym-explorer-client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log.workspace = true
nym-explorer-api-requests = { path = "../explorer-api-requests" }
reqwest = { workspace = true, features = ["json"] }
serde.workspace = true
thiserror.workspace = true
url.workspace = true
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
+96
View File
@@ -0,0 +1,96 @@
use std::time::Duration;
use reqwest::StatusCode;
use thiserror::Error;
use url::Url;
// Re-export request types
pub use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
// Paths
const API_VERSION: &str = "v1";
const MIXNODES: &str = "mix-nodes";
const GATEWAYS: &str = "gateways";
#[cfg(not(target_arch = "wasm32"))]
const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Debug, Error)]
pub enum ExplorerApiError {
#[error("REST request error: {0}")]
ReqwestError(#[from] reqwest::Error),
#[error("URL parse error: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("not found")]
NotFound,
#[error("request failure: {0}")]
RequestFailure(String),
}
pub struct ExplorerClient {
url: Url,
client: reqwest::Client,
}
impl ExplorerClient {
#[cfg(not(target_arch = "wasm32"))]
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
let client = reqwest::Client::builder()
.timeout(REQUEST_TIMEOUT)
.build()?;
Ok(Self { client, url })
}
#[cfg(target_arch = "wasm32")]
pub fn new(url: url::Url) -> Result<Self, ExplorerApiError> {
let client = reqwest::Client::builder().build()?;
Ok(Self { client, url })
}
async fn send_get_request(
&self,
paths: &[&str],
) -> Result<reqwest::Response, ExplorerApiError> {
let url = combine_url(self.url.clone(), paths)?;
log::trace!("Sending GET request {url:?}");
Ok(self.client.get(url).send().await?)
}
async fn query_explorer_api<T>(&self, paths: &[&str]) -> Result<T, ExplorerApiError>
where
T: std::fmt::Debug,
T: for<'a> serde::Deserialize<'a>,
{
let response = self.send_get_request(paths).await?;
if response.status().is_success() {
let res = response.json::<T>().await?;
log::trace!("Got response: {res:?}");
Ok(res)
} else if response.status() == StatusCode::NOT_FOUND {
Err(ExplorerApiError::NotFound)
} else {
Err(ExplorerApiError::RequestFailure(response.text().await?))
}
}
pub async fn get_mixnodes(&self) -> Result<Vec<PrettyDetailedMixNodeBond>, ExplorerApiError> {
self.query_explorer_api(&[API_VERSION, MIXNODES]).await
}
pub async fn get_gateways(&self) -> Result<Vec<PrettyDetailedGatewayBond>, ExplorerApiError> {
self.query_explorer_api(&[API_VERSION, GATEWAYS]).await
}
}
fn combine_url(mut base_url: Url, paths: &[&str]) -> Result<Url, ExplorerApiError> {
{
let mut segments = base_url.path_segments_mut().expect("failed to parse url");
for path in paths {
segments.push(path);
}
}
Ok(base_url)
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.1.26"
version = "1.1.27"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -2
View File
@@ -292,8 +292,7 @@ impl Default for Debug {
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
// TODO: remember to change it in one of future releases!!
use_legacy_framed_packet_version: true,
use_legacy_framed_packet_version: false,
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.1.27"
version = "1.1.28"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -2
View File
@@ -282,8 +282,7 @@ impl Default for Debug {
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
// TODO: remember to change it in one of future releases!!
use_legacy_framed_packet_version: true,
use_legacy_framed_packet_version: false,
}
}
}
+4
View File
@@ -24,6 +24,10 @@ impl MixNodePaths {
pub fn new_default<P: AsRef<Path>>(id: P) -> Self {
MixNodePaths {
keys: KeysPaths::new_default(id.as_ref()),
// TODO: next time there is a breaking change in the mixnode config, change this to
// `default_base_directory`.
// I'd rather not change this willy-nilly since it means a `mixnode init` will break
// the existing configurated description.
node_description: default_config_directory(id).join(DEFAULT_DESCRIPTION_FILENAME),
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-api"
version = "1.1.27"
version = "1.1.28"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+6
View File
@@ -2,6 +2,12 @@
## [Unreleased]
## [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])
[#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])
+425 -17
View File
@@ -242,7 +242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
dependencies = [
"async-lock",
"autocfg",
"autocfg 1.1.0",
"blocking",
"futures-lite",
]
@@ -254,7 +254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
dependencies = [
"async-lock",
"autocfg",
"autocfg 1.1.0",
"cfg-if",
"concurrent-queue",
"futures-lite",
@@ -284,7 +284,7 @@ checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9"
dependencies = [
"async-io",
"async-lock",
"autocfg",
"autocfg 1.1.0",
"blocking",
"cfg-if",
"event-listener",
@@ -381,6 +381,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "autocfg"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -894,6 +903,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "cocoa"
version = "0.24.1"
@@ -969,6 +987,18 @@ dependencies = [
"memchr",
]
[[package]]
name = "comfy-table"
version = "6.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e959d788268e3bf9d35ace83e81b124190378e4c91c9067524675e33394b8ba"
dependencies = [
"crossterm",
"strum 0.24.1",
"strum_macros 0.24.3",
"unicode-width",
]
[[package]]
name = "concurrent-queue"
version = "2.2.0"
@@ -1209,7 +1239,7 @@ version = "0.9.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
dependencies = [
"autocfg",
"autocfg 1.1.0",
"cfg-if",
"crossbeam-utils",
"memoffset 0.9.0",
@@ -1235,6 +1265,31 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossterm"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
"bitflags 1.3.2",
"crossterm_winapi",
"libc",
"mio",
"parking_lot 0.12.1",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "crypto-bigint"
version = "0.4.9"
@@ -2166,6 +2221,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "funty"
version = "2.0.0"
@@ -3143,7 +3204,7 @@ version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"autocfg 1.1.0",
"hashbrown 0.12.3",
"serde",
]
@@ -3195,6 +3256,19 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "interprocess"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb"
dependencies = [
"cfg-if",
"libc",
"rustc_version",
"to_method",
"winapi",
]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
@@ -3493,7 +3567,7 @@ version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
dependencies = [
"autocfg",
"autocfg 1.1.0",
"scopeguard",
]
@@ -3596,7 +3670,7 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
"autocfg 1.1.0",
]
[[package]]
@@ -3605,7 +3679,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg",
"autocfg 1.1.0",
]
[[package]]
@@ -3643,6 +3717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
]
@@ -3768,7 +3843,7 @@ version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"autocfg 1.1.0",
"num-traits",
]
@@ -3778,7 +3853,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"autocfg 1.1.0",
"num-integer",
"num-traits",
]
@@ -3789,7 +3864,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
"autocfg 1.1.0",
"libm",
]
@@ -3875,9 +3950,59 @@ dependencies = [
"pretty_env_logger",
"semver 0.11.0",
"serde",
"serde_json",
"vergen",
]
[[package]]
name = "nym-cli-commands"
version = "1.0.0"
dependencies = [
"anyhow",
"base64 0.13.1",
"bip39",
"bs58 0.4.0",
"cfg-if",
"clap",
"comfy-table",
"cosmrs",
"cosmwasm-std",
"cw-utils",
"handlebars",
"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",
"nym-name-service-common",
"nym-network-defaults",
"nym-pemstore",
"nym-service-provider-directory-common",
"nym-sphinx",
"nym-types",
"nym-validator-client",
"nym-vesting-contract-common",
"rand 0.6.5",
"serde",
"serde_json",
"tap",
"thiserror",
"time",
"toml 0.5.11",
"url",
]
[[package]]
name = "nym-client-core"
version = "1.1.15"
@@ -3895,7 +4020,7 @@ dependencies = [
"nym-config",
"nym-credential-storage",
"nym-crypto",
"nym-explorer-api-requests",
"nym-explorer-client",
"nym-gateway-client",
"nym-gateway-requests",
"nym-network-defaults",
@@ -3992,10 +4117,11 @@ dependencies = [
[[package]]
name = "nym-connect"
version = "1.1.18"
version = "1.1.19"
dependencies = [
"anyhow",
"bip39",
"bs58 0.4.0",
"dirs 4.0.0",
"dotenvy",
"eyre",
@@ -4006,6 +4132,7 @@ dependencies = [
"log",
"nym-api-requests",
"nym-bin-common",
"nym-cli-commands",
"nym-client-core",
"nym-config",
"nym-contracts-common",
@@ -4014,8 +4141,10 @@ dependencies = [
"nym-socks5-client-core",
"nym-sphinx",
"nym-task",
"nym-topology",
"nym-validator-client",
"pretty_env_logger",
"rand 0.7.3",
"rand 0.8.5",
"reqwest",
"rust-embed",
@@ -4029,6 +4158,7 @@ dependencies = [
"tauri-build",
"tauri-codegen",
"tauri-macros",
"tauri-plugin-deep-link",
"tempfile",
"thiserror",
"time",
@@ -4062,6 +4192,21 @@ 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"
@@ -4141,6 +4286,18 @@ dependencies = [
"serde",
]
[[package]]
name = "nym-explorer-client"
version = "0.1.0"
dependencies = [
"log",
"nym-explorer-api-requests",
"reqwest",
"serde",
"thiserror",
"url",
]
[[package]]
name = "nym-gateway-client"
version = "0.1.0"
@@ -4564,6 +4721,30 @@ dependencies = [
"thiserror",
]
[[package]]
name = "nym-types"
version = "1.0.0"
dependencies = [
"cosmrs",
"cosmwasm-std",
"eyre",
"itertools",
"log",
"nym-coconut-interface",
"nym-config",
"nym-mixnet-contract-common",
"nym-validator-client",
"nym-vesting-contract-common",
"reqwest",
"schemars",
"serde",
"serde_json",
"strum 0.23.0",
"thiserror",
"ts-rs",
"url",
]
[[package]]
name = "nym-validator-client"
version = "0.1.0"
@@ -4645,6 +4826,28 @@ dependencies = [
"objc_id",
]
[[package]]
name = "objc-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e1d07c6eab1ce8b6382b8e3c7246fe117ff3f8b34be065f5ebace6749fe845"
[[package]]
name = "objc2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d"
dependencies = [
"objc-sys",
"objc2-encode",
]
[[package]]
name = "objc2-encode"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666"
[[package]]
name = "objc_exception"
version = "0.1.2"
@@ -5183,7 +5386,7 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg",
"autocfg 1.1.0",
"bitflags 1.3.2",
"cfg-if",
"concurrent-queue",
@@ -5352,6 +5555,25 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.8",
"libc",
"rand_chacha 0.1.1",
"rand_core 0.4.2",
"rand_hc 0.1.0",
"rand_isaac",
"rand_jitter",
"rand_os",
"rand_pcg 0.1.2",
"rand_xorshift",
"winapi",
]
[[package]]
name = "rand"
version = "0.7.3"
@@ -5362,8 +5584,8 @@ dependencies = [
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc",
"rand_pcg",
"rand_hc 0.2.0",
"rand_pcg 0.2.1",
]
[[package]]
@@ -5377,6 +5599,16 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
@@ -5397,6 +5629,21 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
@@ -5425,6 +5672,15 @@ dependencies = [
"rand 0.7.3",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
@@ -5434,6 +5690,50 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
"libc",
"rand_core 0.4.2",
"winapi",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
"rdrand",
"winapi",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.4.2",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
@@ -5443,6 +5743,15 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "raw-window-handle"
version = "0.5.2"
@@ -5471,6 +5780,15 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -6317,6 +6635,17 @@ dependencies = [
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
@@ -6364,7 +6693,7 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg",
"autocfg 1.1.0",
]
[[package]]
@@ -6745,6 +7074,47 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strum"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb"
dependencies = [
"strum_macros 0.23.1",
]
[[package]]
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]]
name = "strum_macros"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38"
dependencies = [
"heck 0.3.3",
"proc-macro2",
"quote",
"rustversion",
"syn 1.0.109",
]
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn 1.0.109",
]
[[package]]
name = "subtle"
version = "1.0.0"
@@ -7015,6 +7385,22 @@ dependencies = [
"tauri-utils",
]
[[package]]
name = "tauri-plugin-deep-link"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4536f5f6602e8fdfaa7b3b185076c2a0704f8eb7015f4e58461eb483ec3ed1f8"
dependencies = [
"dirs 5.0.1",
"interprocess",
"log",
"objc2",
"once_cell",
"tauri-utils",
"windows-sys 0.48.0",
"winreg 0.50.0",
]
[[package]]
name = "tauri-runtime"
version = "0.14.0"
@@ -7318,6 +7704,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "to_method"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8"
[[package]]
name = "tokio"
version = "1.31.0"
@@ -7658,6 +8050,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unicode_categories"
version = "0.1.1"
@@ -8407,6 +8805,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "wry"
version = "0.24.3"
+1
View File
@@ -1,2 +1,3 @@
[workspace]
members = ["src-tauri"]
resolver = "2"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nym/nym-connect",
"version": "1.1.18",
"version": "1.1.19",
"main": "index.js",
"license": "MIT",
"scripts": {
+6 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-connect"
version = "1.1.18"
version = "1.1.19"
description = "nym-connect"
authors = ["Nym Technologies SA"]
license = ""
@@ -21,6 +21,7 @@ tauri-macros = "^1.2.1"
[dependencies]
anyhow = "1.0"
bip39 = { version = "2.0.0", features = ["zeroize"] }
bs58 = "0.4"
dirs = "4.0"
eyre = "0.6.5"
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
@@ -30,6 +31,7 @@ itertools = "0.10.5"
log = { version = "0.4", features = ["serde"] }
pretty_env_logger = "0.4.0"
rand = "0.8"
rand-07 = { package = "rand", version = "0.7.3" }
reqwest = { version = "0.11.18", features = ["json", "socks"] }
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
serde = { version = "1.0", features = ["derive"] }
@@ -37,6 +39,7 @@ serde_json = "1.0"
serde_repr = "0.1"
tap = "1.0.1"
tauri = { version = "^1.2.2", features = ["clipboard-write-text", "macos-private-api", "notification-all", "shell-open", "system-tray", "updater", "window-close", "window-minimize", "window-start-dragging"] }
tauri-plugin-deep-link = "0.1.1"
#tendermint-rpc = "0.23.0"
thiserror = "1.0"
time = { version = "0.3.17", features = ["local-offset"] }
@@ -50,6 +53,7 @@ dotenvy = "0.15.7"
nym-client-core = { path = "../../../common/client-core" }
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
nym-cli-commands = { path = "../../../common/commands" }
nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common"}
nym-config = { path = "../../../common/config" }
nym-crypto = { path = "../../../common/crypto" }
@@ -58,6 +62,7 @@ nym-bin-common = { path = "../../../common/bin-common"}
nym-socks5-client-core = { path = "../../../common/socks5-client-core" }
nym-sphinx = { path = "../../../common/nymsphinx" }
nym-task = { path = "../../../common/task" }
nym-topology = { path = "../../../common/topology" }
nym-validator-client = { path = "../../../common/client-libs/validator-client" }
[dev-dependencies]
+20
View File
@@ -0,0 +1,20 @@
<!-- Add this file next to your tauri.conf.json file -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<!-- Obviously needs to be replaced with your app's bundle identifier -->
<string>net.nymtech.connect</string>
<key>CFBundleURLSchemes</key>
<array>
<!-- register the myapp:// and myscheme:// schemes -->
<string>nym-connect</string>
</array>
</dict>
</array>
</dict>
</plist>
@@ -0,0 +1,39 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::{BackendError, Result};
use bip39::Mnemonic;
use nym_cli_commands::coconut::issue_credentials::Args as CoconutArgs;
use nym_cli_commands::context::{create_signing_client, get_network_details, ClientArgs};
use std::path::PathBuf;
use url::Url;
pub async fn handle_url(url: &str) -> Result<()> {
let url = Url::parse(url)?;
if url.scheme() != env!("CARGO_PKG_NAME") {
return Err(BackendError::InvalidURLScheme {
scheme: url.scheme().to_string(),
});
}
let bytes = bs58::decode(url.path()).into_vec()?;
let mnemonic = Mnemonic::from_entropy(&bytes)?;
let args = ClientArgs {
config_env_file: None,
nyxd_url: None,
nym_api_url: None,
mnemonic: Some(mnemonic),
mixnet_contract_address: None,
vesting_contract_address: None,
};
let network_details = get_network_details(&args)?;
let client = create_signing_client(args, &network_details)?;
let coconut_args = CoconutArgs {
client_config: Default::default(),
amount: 10,
recovery_dir: PathBuf::default(),
};
nym_cli_commands::coconut::issue_credentials::execute(coconut_args, client).await?;
Ok(())
}
@@ -81,6 +81,19 @@ pub enum BackendError {
CouldNotGetConfigFilename,
#[error("could not load existing gateway configuration")]
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
#[error("no gateways found in directory")]
NoGatewaysFoundInDirectory,
#[error("no gateways found with compatible version: {0}")]
NoVersionCompatibleGatewaysFound(String),
#[error("no gateways found with acceptable performance")]
NoGatewaysWithAcceptablePerformanceFound,
#[error("no network-requesters found in directory")]
NoServicesFoundInDirectory,
#[error("no active network-requesters found in directory")]
NoActiveServicesFound,
#[error("unable to open a new window")]
NewWindowError,
#[error("unable to parse the specified gateway")]
@@ -98,6 +111,27 @@ pub enum BackendError {
url: reqwest::Url,
status_code: reqwest::StatusCode,
},
#[error("the URL provided has an unrecognized scheme: {scheme}")]
InvalidURLScheme { scheme: String },
#[error("{source}")]
BS58DecodeError {
#[from]
source: bs58::decode::Error,
},
#[error("{source}")]
Bip39Error {
#[from]
source: bip39::Error,
},
#[error("{source}")]
ContextError {
#[from]
source: nym_cli_commands::context::errors::ContextError,
},
}
impl Serialize for BackendError {
+27 -3
View File
@@ -18,6 +18,7 @@ use crate::window::window_toggle;
mod config;
mod constants;
// mod deep_links;
mod error;
mod events;
mod logging;
@@ -30,6 +31,7 @@ mod tasks;
mod window;
fn main() {
tauri_plugin_deep_link::prepare("nym-connect");
dotenvy::dotenv().ok();
setup_env(env::args().nth(1).map(PathBuf::from).as_ref());
@@ -86,8 +88,9 @@ fn main() {
crate::operations::connection::status::get_connection_status,
crate::operations::connection::status::get_gateway_connection_status,
crate::operations::connection::status::start_connection_health_check_task,
crate::operations::directory::get_services,
crate::operations::directory::get_gateways,
crate::operations::directory::gateways::get_gateways,
crate::operations::directory::gateways::select_gateway_with_low_latency_from_list,
crate::operations::directory::services::get_services,
crate::operations::export::export_keys,
crate::operations::window::hide_window,
crate::operations::growth::test_and_earn::growth_tne_get_client_id,
@@ -111,7 +114,28 @@ fn main() {
);
}
})
.setup(move |app| Ok(crate::logging::setup_logging(app.app_handle(), monitoring)?))
.setup(move |app| {
let handle = app.handle();
tauri_plugin_deep_link::register(env!("CARGO_PKG_NAME"), move |request| {
let window = handle.windows();
log::info!("Windows: {:?}", window.keys());
if let Err(err) = window.emit("scheme-request-received", request) {
log::error!("Failed to emit tauri event: {err}");
}
})
.unwrap();
// If you also need the url when the primary instance was started by the custom scheme, you currently have to read it yourself
#[cfg(not(target_os = "macos"))]
// on macos the plugin handles this (macos doesn't use cli args for the url)
if let Some(arg) = env::args().nth(1) {
if let Err(e) = deep_links::handle_url(&arg) {
log::error!("Could not handle URL {}: {:?}", request, e);
}
}
Ok(crate::logging::setup_logging(app.app_handle(), monitoring)?)
})
.system_tray(create_tray_menu())
.on_system_tray_event(tray_menu_event_handler)
.run(context)
@@ -1,4 +1,4 @@
use core::fmt;
use std::fmt;
use serde::{Deserialize, Serialize};
@@ -115,3 +115,9 @@ pub struct PagedResult<T> {
pub struct Gateway {
pub identity: String,
}
impl fmt::Display for Gateway {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Gateway({})", self.identity)
}
}
@@ -8,10 +8,11 @@ use tokio::sync::RwLock;
#[tauri::command]
pub async fn start_connecting(
coconut_enabled: bool,
state: tauri::State<'_, Arc<RwLock<State>>>,
window: tauri::Window<tauri::Wry>,
) -> Result<ConnectResult> {
log::trace!("Start connecting");
log::info!("Start connecting with coconut enabled: {}", coconut_enabled);
let (msg_receiver, exit_status_receiver) = {
let mut state_w = state.write().await;
@@ -0,0 +1,138 @@
use crate::{
error::{BackendError, Result},
models::Gateway,
};
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::NYM_API;
use nym_contracts_common::types::Percent;
use nym_topology::gateway;
use nym_validator_client::nym_api::Client as ApiClient;
use std::str::FromStr;
use url::Url;
// Only use gateways with a performnnce score above this
const GATEWAY_PERFORMANCE_SCORE_THRESHOLD: u64 = 90;
async fn fetch_all_gateways() -> Result<Vec<GatewayBondAnnotated>> {
let api_client = ApiClient::new(Url::from_str(&std::env::var(NYM_API)?)?);
let gateways = api_client.get_gateways_detailed().await?;
if gateways.is_empty() {
Err(BackendError::NoGatewaysFoundInDirectory)
} else {
Ok(gateways)
}
}
async fn fetch_only_compatible_gateways() -> Result<Vec<GatewayBondAnnotated>> {
let gateways = fetch_all_gateways().await?;
let our_version = env!("CARGO_PKG_VERSION");
log::debug!(
"Our version that we use to filter compatible gateways: {}",
our_version
);
let gateways: Vec<_> = gateways
.into_iter()
.filter(|g| is_minor_version_compatible(&g.gateway_bond.gateway.version, our_version))
.collect();
if gateways.is_empty() {
Err(BackendError::NoVersionCompatibleGatewaysFound(
our_version.to_string(),
))
} else {
Ok(gateways)
}
}
fn filter_out_low_performance_gateways(
gateways: Vec<GatewayBondAnnotated>,
) -> Result<Vec<GatewayBondAnnotated>> {
let mut filtered_gateways: Vec<_> = gateways
.iter()
.filter(|g| {
g.node_performance.most_recent
> Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap()
})
.cloned()
.collect();
// Sometimes the most_recent is zero for all gateways (bug in nym-api?)
if filtered_gateways.is_empty() {
log::warn!(
"No gateways with recent performance score above threshold found! Using \
last hour performance scores instead as fallback"
);
filtered_gateways = gateways
.into_iter()
.filter(|g| {
g.node_performance.last_hour
> Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap()
})
.collect();
}
if filtered_gateways.is_empty() {
log::error!("No gateways found! (with high enough performance score)");
Err(BackendError::NoGatewaysWithAcceptablePerformanceFound)
} else {
Ok(filtered_gateways)
}
}
async fn select_gateway_by_latency(gateways: Vec<GatewayBondAnnotated>) -> Result<gateway::Node> {
let gateways_as_nodes: Vec<gateway::Node> = gateways
.into_iter()
.filter_map(|g| g.gateway_bond.try_into().ok())
.collect();
let mut rng = rand_07::rngs::OsRng;
let selected_gateway =
nym_client_core::init::helpers::choose_gateway_by_latency(&mut rng, &gateways_as_nodes)
.await?;
Ok(selected_gateway)
}
// Get all gateways satisfying the performance threshold.
#[tauri::command]
pub async fn get_gateways() -> Result<Vec<Gateway>> {
log::trace!("Fetching gateways");
let all_gateways = fetch_only_compatible_gateways().await?;
log::debug!("Received {} gateways", all_gateways.len());
log::trace!("Received: {:#?}", all_gateways);
let gateways_filtered = filter_out_low_performance_gateways(all_gateways.clone())?
.into_iter()
.map(|g| Gateway {
identity: g.identity().clone(),
})
.collect_vec();
log::debug!(
"After filtering out low-performance gateways: {}",
gateways_filtered.len()
);
log::trace!(
"Filtered: [\n\t{}\n]",
gateways_filtered.iter().join(",\n\t")
);
Ok(gateways_filtered)
}
// From a given list of gateways, select the one with low latency.
#[tauri::command]
pub async fn select_gateway_with_low_latency_from_list(gateways: Vec<Gateway>) -> Result<Gateway> {
log::debug!("Selecting a gateway with low latency");
let gateways = gateways.into_iter().map(|g| g.identity).collect_vec();
let all_gateways = fetch_only_compatible_gateways().await?;
let gateways_union_set: Vec<GatewayBondAnnotated> = all_gateways
.into_iter()
.filter(|g| gateways.contains(g.identity()))
.collect();
let gateways_filtered = filter_out_low_performance_gateways(gateways_union_set)?;
let selected_gateway = select_gateway_by_latency(gateways_filtered).await?;
log::debug!("Selected gateway: {}", selected_gateway);
Ok(Gateway {
identity: selected_gateway.identity().to_base58_string(),
})
}
@@ -1,166 +1,4 @@
use crate::{
config::PrivacyLevel,
error::Result,
models::{
DirectoryService, DirectoryServiceProvider, Gateway, HarbourMasterService, PagedResult,
},
state::State,
};
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;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::RwLock;
use url::Url;
pub mod gateways;
pub mod services;
pub(crate) static WELLKNOWN_DIR: &str = "https://nymtech.net/.wellknown";
static SERVICE_PROVIDER_URL_PATH: &str = "connect/service-providers.json";
// List of network-requesters running with medium toggle enabled, for testing
static SERVICE_PROVIDER_MEDIUM_URL_PATH: &str = "connect/service-providers-medium.json";
// Harbour master is used to periodically keep track of which network-requesters are online
static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100";
// We only consider network requesters with a routing score above this threshold
const SERVICE_ROUTING_SCORE_THRESHOLD: f32 = 0.9;
// Only use gateways with a performnnce score above this
const GATEWAY_PERFORMANCE_SCORE_THRESHOLD: u64 = 90;
#[tauri::command]
pub async fn get_services(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<DirectoryServiceProvider>> {
let guard = state.read().await;
let privacy_level = guard.get_user_data().privacy_level.unwrap_or_default();
log::trace!("Fetching services");
let all_services_with_category = fetch_services(&privacy_level).await?;
log::trace!("Received: {:#?}", all_services_with_category);
// Flatten all services into a single vector (get rid of categories)
// We currently don't care about categories, but we might in the future...
let all_services = all_services_with_category
.into_iter()
.flat_map(|sp| sp.items)
.collect_vec();
// Early return if we're running with medium toggle enabled
if let PrivacyLevel::Medium = privacy_level {
return Ok(all_services);
}
// TODO: get paged
log::trace!("Fetching active services");
let active_services = fetch_active_services().await?;
log::trace!("Active: {:#?}", active_services);
if active_services.items.is_empty() {
log::warn!("No active services found! Using all services instead as fallback");
return Ok(all_services);
}
log::trace!("Filter out inactive");
let filtered_services = filter_out_inactive_services(&all_services, active_services);
log::trace!("After filtering: {:#?}", filtered_services);
if filtered_services.is_empty() {
log::warn!(
"After filtering, no active services found! Using all services instead as fallback"
);
return Ok(all_services);
}
Ok(filtered_services)
}
async fn fetch_services(privacy_level: &PrivacyLevel) -> Result<Vec<DirectoryService>> {
let services_url = match privacy_level {
PrivacyLevel::Medium => SERVICE_PROVIDER_MEDIUM_URL_PATH,
_ => SERVICE_PROVIDER_URL_PATH,
};
let network_name = std::env::var(NETWORK_NAME)?;
let url = format!("{}/{}/{}", WELLKNOWN_DIR, network_name, services_url);
let services_res = reqwest::get(url)
.await?
.json::<Vec<DirectoryService>>()
.await?;
Ok(services_res)
}
async fn fetch_active_services() -> Result<PagedResult<HarbourMasterService>> {
let active_services = reqwest::get(HARBOUR_MASTER_URL)
.await?
.json::<PagedResult<HarbourMasterService>>()
.await?;
Ok(active_services)
}
fn filter_out_inactive_services(
all_services: &[DirectoryServiceProvider],
active_services: PagedResult<HarbourMasterService>,
) -> Vec<DirectoryServiceProvider> {
all_services
.iter()
.filter(|sp| {
active_services.items.iter().any(|active| {
active.service_provider_client_id == sp.address
&& active.routing_score > SERVICE_ROUTING_SCORE_THRESHOLD
})
})
.cloned()
.collect()
}
async fn fetch_gateways() -> Result<Vec<GatewayBondAnnotated>> {
let api_client = ApiClient::new(Url::from_str(&std::env::var(NYM_API)?)?);
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]
pub async fn get_gateways() -> Result<Vec<Gateway>> {
log::trace!("Fetching gateways");
let all_gateways = fetch_gateways().await?;
log::trace!("Received: {:#?}", all_gateways);
let filtered_gateways = all_gateways
.iter()
.filter(|g| {
g.node_performance.most_recent
> Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap()
})
.map(|g| Gateway {
identity: g.identity().clone(),
})
.collect_vec();
log::trace!("Filtered: {:#?}", filtered_gateways);
if filtered_gateways.is_empty() {
log::warn!("No gateways with high enough performance score found! Using all gateways instead as fallback");
return Ok(all_gateways
.iter()
.map(|g| Gateway {
identity: g.identity().clone(),
})
.collect_vec());
}
Ok(filtered_gateways)
}
@@ -0,0 +1,140 @@
use crate::{
config::PrivacyLevel,
error::{BackendError, Result},
models::{DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult},
state::State,
};
use itertools::Itertools;
use nym_config::defaults::var_names::NETWORK_NAME;
use std::sync::Arc;
use tap::TapFallible;
use tokio::sync::RwLock;
use super::WELLKNOWN_DIR;
static SERVICE_PROVIDER_URL_PATH: &str = "connect/service-providers.json";
// List of network-requesters running with medium toggle enabled, for testing
static SERVICE_PROVIDER_MEDIUM_URL_PATH: &str = "connect/service-providers-medium.json";
// Harbour master is used to periodically keep track of which network-requesters are online
static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100";
// We only consider network requesters with a routing score above this threshold
const SERVICE_ROUTING_SCORE_THRESHOLD: f32 = 0.9;
// Fetch all the services from the directory (currently hardcoded, but in the future it could be a
// contract).
async fn fetch_services(privacy_level: &PrivacyLevel) -> Result<Vec<DirectoryService>> {
let services_url = match privacy_level {
PrivacyLevel::Medium => SERVICE_PROVIDER_MEDIUM_URL_PATH,
_ => SERVICE_PROVIDER_URL_PATH,
};
let network_name = std::env::var(NETWORK_NAME)?;
let url = format!("{}/{}/{}", WELLKNOWN_DIR, network_name, services_url);
let services_res = reqwest::get(url)
.await?
.json::<Vec<DirectoryService>>()
.await?;
if services_res.is_empty() {
log::error!("No services found in directory!");
Err(BackendError::NoServicesFoundInDirectory)
} else {
Ok(services_res)
}
}
// Fetch all the active services from harbour master
async fn query_active_services() -> Result<PagedResult<HarbourMasterService>> {
let active_services = reqwest::get(HARBOUR_MASTER_URL)
.await?
.json::<PagedResult<HarbourMasterService>>()
.await?;
if active_services.items.is_empty() {
log::error!("No active services found!");
Err(BackendError::NoActiveServicesFound)
} else {
Ok(active_services)
}
}
fn filter_out_inactive_services(
all_services: &[DirectoryServiceProvider],
active_services: PagedResult<HarbourMasterService>,
) -> Result<Vec<DirectoryServiceProvider>> {
let services: Vec<_> = all_services
.iter()
.filter(|sp| {
active_services.items.iter().any(|active| {
active.service_provider_client_id == sp.address
&& active.routing_score > SERVICE_ROUTING_SCORE_THRESHOLD
})
})
.cloned()
.collect();
if services.is_empty() {
Err(BackendError::NoServicesFoundInDirectory)
} else {
Ok(services)
}
}
#[tauri::command]
pub async fn get_services(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<DirectoryServiceProvider>> {
let guard = state.read().await;
let privacy_level = guard.get_user_data().privacy_level.unwrap_or_default();
log::trace!("Fetching services");
let all_services_with_category = fetch_services(&privacy_level).await?;
// Flatten all services into a single vector (get rid of categories)
// We currently don't care about categories, but we might in the future...
let all_services = all_services_with_category
.into_iter()
.flat_map(|sp| sp.items)
.collect_vec();
log::debug!("Received {} services", all_services.len());
log::trace!("Received: {:#?}", all_services);
// Early return if we're running with medium toggle enabled
if let PrivacyLevel::Medium = privacy_level {
return Ok(all_services);
}
// If there is a failure getting the active services, just return all of them
// TODO: get paged
log::trace!("Fetching active services");
let Ok(active_services) = query_active_services().await else {
log::warn!("Using all services instead as fallback");
return Ok(all_services);
};
log::debug!(
"Received {} active services from harbourmaster",
active_services.items.len()
);
log::trace!("Active: {:#?}", active_services);
// From the list of all services, filter out the ones that are inactive
log::trace!("Filter out inactive and low performance");
let filtered_services = filter_out_inactive_services(&all_services, active_services);
// If there is a failure filtering out inactive services, just return all of them
filtered_services
.tap_ok(|services| {
log::debug!(
"After filtering out inactive and low performance: {}",
services.len()
);
log::trace!("After filtering: {:#?}", services);
})
.or_else(|_| {
// If for some reason harbourmaster is done, we want things to still sort of work.
log::warn!(
"After filtering, no active services found! Using all services instead as fallback"
);
Ok(all_services)
})
}
+1 -1
View File
@@ -57,7 +57,7 @@ fn override_config_from_env(config: &mut Config, privacy_level: &PrivacyLevel) {
.provider_mix_address
.parse()
.expect("failed to parse provider mix address");
log::warn!("Using geo-aware mixnode selection baseon the location of: {address}");
log::warn!("Using geo-aware mixnode selection based on the location of: {address}");
config
.core
.base
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-connect",
"version": "1.1.18"
"version": "1.1.19"
},
"build": {
"distDir": "../dist",
+28 -13
View File
@@ -38,7 +38,7 @@ export type TClientContext = {
setShowInfoModal: (show: boolean) => void;
setServiceProvider: () => void;
setGateway: () => void;
startConnecting: () => Promise<void>;
startConnecting: (coconut_enabled: boolean) => Promise<void>;
startDisconnecting: () => Promise<void>;
setUserDefinedGateway: React.Dispatch<React.SetStateAction<UserDefinedGateway>>;
setUserDefinedSPAddress: React.Dispatch<React.SetStateAction<UserDefinedSPAddress>>;
@@ -139,15 +139,6 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
setGateways(fetchedGateways);
};
useEvents({
onError: (e) => {
setError(e);
Sentry.captureException(e);
},
onGatewayPerformanceChange: (performance) => setGatewayPerformance(performance),
onStatusChange: (status) => setConnectionStatus(status),
});
useEffect(() => {
initialiseApp();
}, []);
@@ -160,9 +151,9 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
})();
}, []);
const startConnecting = useCallback(async () => {
const startConnecting = useCallback(async (value: boolean) => {
try {
await invoke('start_connecting');
await invoke('start_connecting', { coconutEnabled: value });
} catch (e) {
setError({ title: 'Could not connect', message: e as string });
console.log(e);
@@ -214,7 +205,12 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
const setGateway = async () => {
if (gateways) {
const randomGateway = getRandomFromList(gateways);
let randomGateway;
if (userData?.privacy_level === 'Medium') {
randomGateway = await invoke<Gateway>('select_gateway_with_low_latency_from_list', { gateways });
} else {
randomGateway = getRandomFromList(gateways);
}
const withUserDefinitions = await buildGateway(randomGateway);
await invoke('set_gateway', {
gateway: shouldUseUserGateway ? userDefinedGateway.address : withUserDefinitions.identity,
@@ -238,6 +234,25 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
await getUserData();
};
const handleSchemeRequest = async () => {
if (connectionStatus === 'disconnected') {
await setServiceProvider();
await setGateway();
await startConnecting(true);
setConnectedSince(DateTime.now());
}
};
useEvents({
onError: (e) => {
setError(e);
Sentry.captureException(e);
},
onGatewayPerformanceChange: (performance) => setGatewayPerformance(performance),
onStatusChange: (status) => setConnectionStatus(status),
onSchemeRequest: handleSchemeRequest,
});
const contextValue = useMemo(
() => ({
mode,
+9
View File
@@ -11,10 +11,12 @@ export const useEvents = ({
onError,
onStatusChange,
onGatewayPerformanceChange,
onSchemeRequest,
}: {
onError: (error: Error) => void;
onStatusChange: (status: ConnectionStatusKind) => void;
onGatewayPerformanceChange: (status: GatewayPerformance) => void;
onSchemeRequest: () => Promise<void>;
}) => {
const timerId = useRef<NodeJS.Timeout>();
@@ -65,6 +67,13 @@ export const useEvents = ({
unlisten.push(result);
});
listen('scheme-request-received', (e: TauriEvent) => {
console.log(e);
onSchemeRequest();
}).then((result) => {
unlisten.push(result);
});
return () => {
unlisten.forEach((unsubscribe) => unsubscribe());
};
@@ -19,7 +19,7 @@ export const ConnectionPage = () => {
Sentry.captureMessage('start connect', 'info');
await context.setServiceProvider();
await context.setGateway();
await context.startConnecting();
await context.startConnecting(false);
context.setConnectedSince(DateTime.now());
context.setShowInfoModal(true);
break;
@@ -2,9 +2,10 @@ import React, { ChangeEvent, useState } from 'react';
import * as Sentry from '@sentry/react';
import { Box, FormControl, FormControlLabel, FormHelperText, Stack, Switch, Typography } from '@mui/material';
import { useClientContext } from '../../context/main';
import { ConnectionStatusKind } from '../../types';
export const PrivacyLevelSettings = () => {
const { userData, setPrivacyLevel } = useClientContext();
const { userData, setPrivacyLevel, connectionStatus } = useClientContext();
const [speedBoost, setSpeedBoost] = useState(userData?.privacy_level !== 'High');
const [loading, setLoading] = useState(false);
@@ -29,7 +30,7 @@ export const PrivacyLevelSettings = () => {
<Switch
checked={speedBoost}
onChange={handleChange}
disabled={loading}
disabled={loading || connectionStatus === ConnectionStatusKind.connected}
size="small"
sx={{ ml: 1, mr: 1 }}
/>
@@ -7,12 +7,12 @@ plugins {
android {
namespace = "net.nymtech.nyms5"
compileSdk = 33
compileSdk = 34
defaultConfig {
applicationId = "net.nymtech.nyms5"
minSdk = 24
targetSdk = 33
targetSdk = 34
versionCode = 1
versionName = "1.0"
@@ -16,11 +16,16 @@ import kotlinx.coroutines.launch
import net.nymtech.nyms5.ui.theme.NymTheme
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.map
import net.nymtech.nyms5.ui.composables.HomeScreen
@OptIn(DelicateCoroutinesApi::class)
class MainActivity : ComponentActivity() {
private val tag = "MainActivity"
private val nymProxy = App.nymProxy
private val viewModel: MainViewModel by viewModels {
MainViewModelFactory(
@@ -45,27 +50,53 @@ class MainActivity : ComponentActivity() {
Log.d(tag, "____onCreate")
Log.i(tag, "device SDK [${Build.VERSION.SDK_INT}]")
val workManager = WorkManager.getInstance(applicationContext)
checkPermission()
// observe proxy work progress
WorkManager.getInstance(applicationContext)
.getWorkInfoByIdLiveData(ProxyWorker.workId)
workManager.getWorkInfoByIdLiveData(ProxyWorker.workId)
// this observer is tied to the activity lifecycle
.observe(this) { workInfo ->
if (workInfo != null && workInfo.state == WorkInfo.State.RUNNING) {
val progress =
workInfo.progress.getString(ProxyWorker.State)
when (progress) {
ProxyWorker.Work.Status.CONNECTED.name -> {
Log.i(tag, "Nym proxy $progress")
viewModel.setConnected()
}
else -> Log.i(tag, "Nym proxy $progress")
lifecycleScope.launch(Dispatchers.IO) {
val proxyState = App.nymProxy.getState()
val workState = workInfo.state
val workProgress = workInfo.progress.getString(ProxyWorker.State)
Log.i(
tag,
"proxy state: $proxyState, work state: $workState, work progress: $workProgress"
)
if (proxyState == NymProxy.Companion.State.CONNECTED &&
workState == WorkInfo.State.RUNNING
) {
viewModel.setUiConnected()
}
}
}
// The work can be cancelled by the user from the dedicated notification
// by tapping the "Stop" action. When that happens the underlying proxy
// process keeps running in the background
// We have to manually call `stopClient` to kill it
workManager.getWorkInfoByIdLiveData(ProxyWorker.workId)
// watch "forever", ie. even when this viewModel has been cleared
.observeForever { workInfo ->
if (workInfo?.state == WorkInfo.State.CANCELLED || workInfo?.state == WorkInfo.State.FAILED) {
// ⚠ here one could be tempted to call `viewModel.cancelProxyWork`
// but it uses viewModelScope which is cancelled when
// the app goes to background so use `GlobalScope` instead
GlobalScope.launch(Dispatchers.IO) {
// if the proxy process is still running kill it
if (nymProxy.getState() == NymProxy.Companion.State.CONNECTED) {
Log.i(tag, "⚠ work has been cancelled")
nymProxy.stop()
}
}
// update the UI
viewModel.setUiDisconnected()
}
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
Log.d(tag, "____UI recompose")
@@ -100,11 +131,6 @@ class MainActivity : ComponentActivity() {
}
}
override fun onStart() {
super.onStart()
viewModel.checkStateSync()
}
private fun checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// POST_NOTIFICATIONS is needed for the notification displayed
@@ -10,11 +10,8 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkInfo
import androidx.work.WorkManager
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -22,7 +19,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@OptIn(DelicateCoroutinesApi::class)
class MainViewModel(
private val workManager: WorkManager,
private val nymProxy: NymProxy
@@ -40,33 +36,7 @@ class MainViewModel(
.setId(ProxyWorker.workId)
.build()
init {
Log.d(tag, "____init")
// When the work is cancelled "externally" ie. when the user tap the
// "Stop" action on the notification, the underlying proxy process
// keeps running in background
// We have to manually call `stopClient` to kill it
workManager.getWorkInfoByIdLiveData(ProxyWorker.workId)
// watch "forever", ie. even when this viewModel has been cleared
.observeForever { workInfo ->
if (workInfo?.state == WorkInfo.State.CANCELLED || workInfo?.state == WorkInfo.State.FAILED) {
// ⚠ here one could be tempted to call cancelProxyWork
// but it uses viewModelScope which is cancelled when
// this viewModel instance is cleared
// use GlobalScope instead
GlobalScope.launch(Dispatchers.IO) {
// if the proxy process is still running ie. connected kill it
if (nymProxy.getState() == NymProxy.Companion.State.CONNECTED) {
Log.d(tag, "stopping proxy")
nymProxy.stop()
Log.i(tag, "proxy work cancelled")
}
}
setDisconnected()
}
}
}
data class ProxyState(
val connected: Boolean = false,
@@ -77,7 +47,8 @@ class MainViewModel(
private val _uiState = MutableStateFlow(ProxyState())
val uiState: StateFlow<ProxyState> = _uiState.asStateFlow()
fun setConnected() {
fun setUiConnected() {
Log.d(tag, "____setUiConnected")
_uiState.update { currentState ->
currentState.copy(
connected = true,
@@ -86,7 +57,8 @@ class MainViewModel(
}
}
private fun setDisconnected() {
fun setUiDisconnected() {
Log.d(tag, "____setUiDisconnected")
_uiState.update { currentState ->
currentState.copy(
connected = false,
@@ -127,30 +99,7 @@ class MainViewModel(
// wait a bit to be sure the proxy client has enough time to
// close connection
delay(2000)
setDisconnected()
}
}
fun checkStateSync() {
Log.d(tag, "check state sync")
viewModelScope.launch(Dispatchers.IO) {
val proxyState = nymProxy.getState()
val workInfo = workManager.getWorkInfoById(ProxyWorker.workId).get()
Log.d(tag, "proxy state $proxyState, work state ${workInfo?.state}")
if (proxyState == NymProxy.Companion.State.CONNECTED &&
workInfo?.state != WorkInfo.State.RUNNING
) {
Log.w(tag, "⚠ state desync")
Log.i(tag, "stopping proxy")
cancelProxyWork()
}
if (proxyState == NymProxy.Companion.State.DISCONNECTED &&
workInfo?.state == WorkInfo.State.RUNNING
) {
Log.w(tag, "⚠ state desync")
Log.i(tag, "stopping worker")
workManager.cancelAllWorkByTag(ProxyWorker.workTag)
}
setUiDisconnected()
}
}
}
@@ -22,6 +22,8 @@ class NymProxy {
Log.i(tag, "loaded native library $nymNativeLib")
}
// this is a blocking call as `startClient` is blocking and will releases when
// the socks5 connection has been terminated
fun start(serviceProvider: String, onStartCbObj: Any, onStopCbObj: Any) {
Log.d(tag, "calling $nymNativeLib:startClient")
try {
@@ -42,6 +44,17 @@ class NymProxy {
}
}
fun ping(): Boolean {
Log.d(tag, "calling $nymNativeLib:pingClient")
try {
return pingClient()
} catch (e: Throwable) {
Log.e(tag, "$nymNativeLib:pingClient internal error: $e")
Sentry.captureException(e)
}
return false
}
fun getState(): State {
Log.d(tag, "calling $nymNativeLib:getClientState")
try {
@@ -66,4 +79,5 @@ class NymProxy {
private external fun stopClient()
private external fun getClientState(): Int
private external fun pingClient(): Boolean
}
@@ -17,6 +17,10 @@ import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.github.kittinunf.fuel.Fuel
import io.sentry.Sentry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@@ -32,7 +36,9 @@ class ProxyWorker(
companion object Work {
const val name = "nymS5ProxyWorker"
const val workTag = "nymProxy"
val workId: UUID = UUID.randomUUID()
// it is very important to use a static UUID in order to allow WorkManager
// handling the proxy work as a unique work
val workId: UUID = UUID.fromString("cc785aa4-5775-4bf0-b870-39645e35e660")
const val State = "State"
@@ -45,6 +51,8 @@ class ProxyWorker(
private val tag = "proxyWorker"
private val pingRate = 1000L
private val spUrl = context.getString(R.string.sp_url)
private val defaultSp = context.getString(R.string.default_sp)
@@ -135,7 +143,26 @@ class ProxyWorker(
Log.w(tag, "using a default service provider $defaultSp")
}
nymProxy.start(serviceProvider ?: defaultSp, onStartCb, onStopCb)
withContext(Dispatchers.IO) {
val pingJob = launch {
// this job will get automatically killed by the WorkManager once
// the job has been terminated, so it's safe to use `while (true)`
while (true) {
nymProxy.ping()
delay(pingRate)
}
}
val proxyJob = launch {
nymProxy.start(serviceProvider ?: defaultSp, onStartCb, onStopCb)
}
// wait for the underlying call to `startClient` to be released which means
// the connection has been terminated (`startClient` is a blocking call)
proxyJob.join()
// stop pinging
pingJob.cancel()
}
// the state should be already set to DISCONNECTED at this point
// but for the sake of it, reset it
+2 -2
View File
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.1.0" apply false
id("com.android.library") version "8.1.0" apply false
id("com.android.application") version "8.1.1" apply false
id("com.android.library") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.8.20" apply false
}
+46
View File
@@ -0,0 +1,46 @@
[package]
name = "nym-payment-manager"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0"
bip39 = { workspace = true }
clap = { version = "4.0", features = ["cargo", "derive"] }
lazy_static = "1.4.0"
log = { workspace = true }
schemars = { version = "0.8", features = ["preserve_order"] }
rocket = { version = "0.5.0-rc.2", features = ["json"] }
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "dfd3662c49e2f6fc37df35091cb94d82f7fb5915" }
rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] }
serde = { workspace = true }
sqlx = { version = "0.6.2", features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
] }
thiserror = "1.0"
tokio = { version = "1.24.1", features = [
"rt-multi-thread",
"macros",
"signal",
"time",
] }
nym-bin-common = { path = "../../common/bin-common" }
nym-network-defaults = { path = "../../common/network-defaults" }
nym-payment-manager-common = { path = "../../common/payment-manager" }
nym-task = { path = "../../common/task" }
nym-validator-client = { path = "../../common/client-libs/validator-client" }
[build-dependencies]
sqlx = { version = "0.6.2", features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
] }
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use sqlx::{Connection, SqliteConnection};
use std::env;
#[tokio::main]
async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{}/nym-payment-manager-example.sqlite", out_dir);
let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path))
.await
.expect("Failed to create SQLx database connection");
sqlx::migrate!("./migrations")
.run(&mut conn)
.await
.expect("Failed to perform SQLx migrations");
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
#[cfg(target_family = "windows")]
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
}
@@ -0,0 +1,7 @@
CREATE TABLE payments
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
serial_number VARCHAR NOT NULL UNIQUE,
unyms_bought INTEGER NOT NULL,
paid BOOLEAN NOT NULL
);
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use lazy_static::lazy_static;
use nym_bin_common::bin_info;
lazy_static! {
pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print();
}
// Helper for passing LONG_VERSION to clap
fn pretty_build_info_static() -> &'static str {
&PRETTY_BUILD_INFORMATION
}
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
pub(crate) struct CliArgs {
/// Path pointing to an env file that configures the Payment Manager.
#[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
/// Mnemonic of the Nym account holding the funds to be converted after successful payments
#[clap(short, long)]
pub(crate) mnemonic: bip39::Mnemonic,
/// Path pointing to the SQLite database containing payment data.
#[clap(short, long)]
pub(crate) db_path: std::path::PathBuf,
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Error;
use nym_network_defaults::NymNetworkDetails;
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
use std::sync::Arc;
use tokio::sync::RwLock;
pub(crate) struct Client(pub(crate) Arc<RwLock<DirectSigningHttpRpcNyxdClient>>);
impl Clone for Client {
fn clone(&self) -> Self {
Client(Arc::clone(&self.0))
}
}
impl Client {
pub(crate) fn new(
details: &NymNetworkDetails,
mnemonic: &bip39::Mnemonic,
) -> Result<Self, Error> {
let client_config = nyxd::Config::try_from_nym_network_details(details)?;
let endpoint = details
.endpoints
.first()
.ok_or(Error::EmptyValidatorList)?
.nyxd_url
.as_str();
let inner = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic(
client_config,
endpoint,
mnemonic.clone(),
)?;
Ok(Client(Arc::new(RwLock::new(inner))))
}
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use rocket::http::{ContentType, Status};
use rocket::response::Responder;
use rocket::{response, Request, Response};
use rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::okapi::openapi3::Responses;
use rocket_okapi::response::OpenApiResponderInner;
use rocket_okapi::util::ensure_status_code_exists;
use std::io::Cursor;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("Database experienced an internal error - {0}")]
InternalDatabaseError(#[from] sqlx::Error),
#[error("SQL migrate error - {0}")]
DatabaseMigrateError(#[from] sqlx::migrate::MigrateError),
#[error("NyxdError - {0}")]
NyxdError(#[from] nym_validator_client::nyxd::error::NyxdError),
#[error("Invalid payment requested")]
InvalidPaymentRequest,
#[error("Bad deposit address")]
BadAddress,
#[error("Empty list of validators")]
EmptyValidatorList,
}
impl<'r, 'o: 'r> Responder<'r, 'o> for Error {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
let err_msg = self.to_string();
Response::build()
.header(ContentType::Plain)
.sized_body(err_msg.len(), Cursor::new(err_msg))
.status(Status::BadRequest)
.ok()
}
}
impl OpenApiResponderInner for Error {
fn responses(_gen: &mut OpenApiGenerator) -> rocket_okapi::Result<Responses> {
let mut responses = Responses::default();
ensure_status_code_exists(&mut responses, 404);
Ok(responses)
}
}
@@ -0,0 +1,65 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::cli::CliArgs;
use crate::client::Client;
use crate::state::{Config, State};
use crate::storage::Storage;
use anyhow::Result;
use nym_network_defaults::NymNetworkDetails;
use rocket::http::Method;
use rocket::{Ignite, Rocket, Route};
use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::settings::OpenApiSettings;
use rocket_okapi::swagger_ui::make_swagger_ui;
use rocket_okapi::{mount_endpoints_and_merged_docs, openapi_get_routes_spec};
pub(crate) mod openapi;
pub(crate) mod routes;
pub(crate) fn routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
settings: routes::claim_payment,
]
}
fn setup_cors() -> Result<Cors> {
let allowed_origins = AllowedOrigins::all();
// You can also deserialize this
let cors = rocket_cors::CorsOptions {
allowed_origins,
allowed_methods: vec![Method::Post].into_iter().map(From::from).collect(),
allowed_headers: AllowedHeaders::all(),
allow_credentials: true,
..Default::default()
}
.to_cors()?;
Ok(cors)
}
pub(crate) async fn setup_rocket(args: &CliArgs) -> Result<Rocket<Ignite>> {
let openapi_settings = rocket_okapi::settings::OpenApiSettings::default();
let mut rocket = rocket::build();
let storage = Storage::init(&args.db_path).await?;
mount_endpoints_and_merged_docs! {
rocket,
"/v1".to_owned(),
openapi_settings,
"/" => (vec![], openapi::custom_openapi_spec()),
"" => routes(&openapi_settings),
}
let details = NymNetworkDetails::new_from_env();
let client = Client::new(&details, &args.mnemonic)?;
let config = Config::new(details.chain_details.mix_denom.base.clone());
let rocket = rocket
.manage(State::new(storage, client, config))
.mount("/swagger", make_swagger_ui(&openapi::get_docs()))
.attach(setup_cors()?);
Ok(rocket.ignite().await?)
}
@@ -0,0 +1,41 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::swagger_ui::SwaggerUIConfig;
pub fn custom_openapi_spec() -> OpenApi {
use rocket_okapi::okapi::openapi3::*;
OpenApi {
openapi: OpenApi::default_version(),
info: Info {
title: "Nym Payment Manager".to_owned(),
description: None,
terms_of_service: None,
contact: None,
license: None,
version: env!("CARGO_PKG_VERSION").to_owned(),
..Default::default()
},
servers: get_servers(),
..Default::default()
}
}
fn get_servers() -> Vec<rocket_okapi::okapi::openapi3::Server> {
if std::env::var_os("CARGO").is_some() {
return vec![];
}
vec![rocket_okapi::okapi::openapi3::Server {
url: std::env::var("OPEN_API_BASE").unwrap_or_else(|_| "/v1/".to_owned()),
description: Some("Payment Manager".to_owned()),
..Default::default()
}]
}
pub(crate) fn get_docs() -> SwaggerUIConfig {
SwaggerUIConfig {
url: "../v1/openapi.json".to_owned(),
..SwaggerUIConfig::default()
}
}
@@ -0,0 +1,55 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Error;
use crate::state::State;
use nym_payment_manager_common::PaymentResponse;
use nym_validator_client::nyxd::{AccountId, Coin};
use rocket::serde::json::Json;
use rocket::{post, State as RocketState};
use rocket_okapi::okapi::schemars;
use rocket_okapi::openapi;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
// All strings are base58 encoded representations of structs
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ClaimPaymentRequestBody {
pub serial_number: String,
pub deposit_address: String,
}
#[openapi(tag = "payment")]
#[post("/claim_payment", data = "<claim_payment_request_body>")]
pub async fn claim_payment(
claim_payment_request_body: Json<ClaimPaymentRequestBody>,
state: &RocketState<State>,
) -> Result<Json<PaymentResponse>, Error> {
let payment = state
.storage
.manager
.get_payment(&claim_payment_request_body.serial_number)
.await?
.ok_or(Error::InvalidPaymentRequest)?;
let recipient = AccountId::from_str(&claim_payment_request_body.deposit_address)
.map_err(|_| Error::BadAddress)?;
let amount = vec![Coin::new(
payment.unyms_bought as u128,
state.config.denom(),
)];
state
.client
.0
.read()
.await
.send(&recipient, amount, "deposit via payment", None)
.await?;
state.storage.manager.update_payment(payment.id).await?;
Ok(Json(PaymentResponse {
unyms_bought: payment.unyms_bought as u64,
}))
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use nym_bin_common::logging::setup_logging;
use nym_network_defaults::setup_env;
use nym_task::TaskManager;
use std::error::Error;
mod cli;
mod client;
mod error;
mod http;
mod state;
mod storage;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
setup_logging();
let args = cli::CliArgs::parse();
setup_env(args.config_env_file.as_ref());
// let's build our rocket!
let rocket = http::setup_rocket(&args).await?;
// setup shutdowns
let shutdown = TaskManager::new(10);
let rocket_shutdown_handle = rocket.shutdown();
// launch rocket
tokio::spawn(rocket.launch());
// wait for termination
shutdown.catch_interrupt().await.ok();
rocket_shutdown_handle.notify();
Ok(())
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::Client;
use crate::storage::Storage;
pub struct Config {
denom: String,
}
impl Config {
pub fn new(denom: String) -> Self {
Config { denom }
}
pub fn denom(&self) -> &str {
&self.denom
}
}
pub struct State {
pub(crate) storage: Storage,
pub(crate) client: Client,
pub(crate) config: Config,
}
impl State {
pub(crate) fn new(storage: Storage, client: Client, config: Config) -> Self {
State {
storage,
client,
config,
}
}
}
@@ -0,0 +1,44 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::storage::models::Payment;
#[derive(Clone)]
pub(crate) struct StorageManager {
pub(crate) connection_pool: sqlx::SqlitePool,
}
// all SQL goes here
impl StorageManager {
pub(crate) async fn get_payment(
&self,
serial_number: &str,
) -> Result<Option<Payment>, sqlx::Error> {
sqlx::query_as!(
Payment,
r#"
SELECT *
FROM payments
WHERE serial_number = ?
"#,
serial_number
)
.fetch_optional(&self.connection_pool)
.await
}
pub(crate) async fn update_payment(&self, id: i64) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
UPDATE payments
SET paid = true
WHERE id = ?
"#,
id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
}
@@ -0,0 +1,48 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use log::*;
use manager::StorageManager;
use sqlx::ConnectOptions;
use std::path::Path;
mod manager;
mod models;
#[derive(Clone)]
pub(crate) struct Storage {
pub manager: StorageManager,
}
impl Storage {
pub async fn init<P: AsRef<Path>>(database_path: P) -> Result<Self, crate::error::Error> {
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true);
// TODO: do we want auto_vacuum ?
opts.disable_statement_logging();
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
Ok(db) => db,
Err(err) => {
error!("Failed to connect to SQLx database: {err}");
return Err(err.into());
}
};
if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await {
error!("Failed to initialize SQLx database: {err}");
return Err(err.into());
}
info!("Database migration finished!");
let storage = Storage {
manager: StorageManager { connection_pool },
};
Ok(storage)
}
}
@@ -0,0 +1,17 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
#[allow(unused)]
pub(crate) struct Payment {
pub(crate) id: i64,
pub(crate) serial_number: String,
pub(crate) unyms_bought: i64,
pub(crate) paid: bool,
}
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct PaymentResponse {
pub unyms_bought: u64,
}
+6
View File
@@ -89,3 +89,9 @@ pub unsafe extern "C" fn Java_net_nymtech_nyms5_NymProxy_getClientState(
ClientState::Disconnected => 2,
}
}
#[no_mangle]
pub unsafe extern "C" fn Java_net_nymtech_nyms5_NymProxy_pingClient(_env: JNIEnv, _class: JClass) {
log::debug!("pong");
crate::ping_client();
}
+58 -2
View File
@@ -6,7 +6,7 @@ use crate::persistence::MobileClientStorage;
use ::safer_ffi::prelude::*;
use anyhow::{anyhow, Result};
use lazy_static::lazy_static;
use log::{info, warn};
use log::{debug, info, warn};
use nym_bin_common::logging::setup_logging;
use nym_client_core::init::GatewaySetup;
use nym_config_common::defaults::setup_env;
@@ -16,8 +16,10 @@ use std::marker::Send;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::runtime::Runtime;
use tokio::sync::{Mutex, Notify};
use tokio::time::{sleep, Instant};
#[cfg(target_os = "android")]
pub mod android;
@@ -25,11 +27,14 @@ mod config;
mod persistence;
static SOCKS5_CONFIG_ID: &str = "mobile-socks5-test";
const ANDROID_HEALTHCHECK_INTERVAL: Duration = Duration::from_secs(5);
const HEALTHCHECK_TIMEOUT: Duration = Duration::from_secs(10);
// hehe, this is so disgusting : )
lazy_static! {
static ref CLIENT_SHUTDOWN_HANDLE: Mutex<Option<Arc<Notify>>> = Mutex::new(None);
static ref RUNTIME: Runtime = Runtime::new().unwrap();
static ref LAST_HEALTHCHECK_PING: Mutex<Option<Instant>> = Mutex::new(None);
}
static ENV_SET: AtomicBool = AtomicBool::new(false);
@@ -136,6 +141,50 @@ pub fn stop_client() {
RUNTIME.block_on(async move { stop_and_reset_shutdown_handle().await });
}
#[ffi_export]
pub fn ping_client() {
RUNTIME.spawn(async {
let mut guard = LAST_HEALTHCHECK_PING.lock().await;
*guard = Some(Instant::now());
});
}
// Continusouly poll that we are being pinged from the outside. If the pings stop that means
// that the higher layer somehow terminated without telling us.
pub async fn health_check() {
// init the ping to now
let mut guard = LAST_HEALTHCHECK_PING.lock().await;
*guard = Some(Instant::now());
// release the mutex
drop(guard);
loop {
sleep(ANDROID_HEALTHCHECK_INTERVAL).await;
if !is_shutdown_handle_set().await {
debug!("client has been shutdown, cancelling healthcheck");
break;
}
let mut guard = LAST_HEALTHCHECK_PING.lock().await;
let Some(last_ping) = *guard else {
warn!("client has not been pinged yet - shutting down");
*guard = None;
stop_and_reset_shutdown_handle().await;
break;
};
if last_ping.elapsed() > HEALTHCHECK_TIMEOUT {
warn!(
"client has not been pinged for more than {} seconds - shutting down",
HEALTHCHECK_TIMEOUT.as_secs()
);
*guard = None;
stop_and_reset_shutdown_handle().await;
break;
}
debug!("✓ android app healthy");
}
}
pub fn blocking_run_client<'cb, F, S>(
storage_directory: Option<char_p::Ref<'_>>,
service_provider: Option<char_p::Ref<'_>>,
@@ -150,6 +199,10 @@ pub fn blocking_run_client<'cb, F, S>(
return;
}
// Spawn a task that monitors that we are continuously receiving pings from the outside,
// to make sure we don't end up with a runaway process
RUNTIME.spawn(async { health_check().await });
let storage_dir = storage_directory.map(|s| s.to_string());
let service_provider = service_provider.map(|s| s.to_string());
RUNTIME
@@ -163,7 +216,10 @@ pub fn blocking_run_client<'cb, F, S>(
)
.await
})
.unwrap();
.map_err(|err| {
warn!("failed to run client: {}", err);
})
.ok();
}
#[ffi_export]
@@ -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"
@@ -7,7 +7,7 @@ use nym_bin_common::output_format::OutputFormat;
#[derive(Args)]
pub(crate) struct BuildInfo {
#[clap(short, long, default_value_t = OutputFormat::default())]
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -24,38 +24,38 @@ use tap::TapFallible;
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
#[clap(long)]
#[arg(long)]
id: String,
/// Id of the gateway we are going to connect to.
#[clap(long)]
#[arg(long)]
gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
#[arg(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
#[arg(long)]
force_register_gateway: bool,
/// Comma separated list of rest endpoints of the nyxd validators
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
#[arg(long, alias = "nymd_validators", value_delimiter = ',')]
nyxd_urls: Option<Vec<url::Url>>,
/// Comma separated list of rest endpoints of the API validators
#[clap(long, alias = "api_validators", value_delimiter = ',')]
#[arg(long, alias = "api_validators", value_delimiter = ',')]
// the alias here is included for backwards compatibility (1.1.4 and before)
nym_apis: Option<Vec<url::Url>>,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[clap(long)]
#[arg(long)]
enabled_credentials_mode: Option<bool>,
#[clap(short, long, default_value_t = OutputFormat::default())]
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -9,7 +9,7 @@ use crate::{
error::NetworkRequesterError,
};
use clap::{CommandFactory, Parser, Subcommand};
use log::{error, info};
use log::{error, info, trace};
use nym_bin_common::bin_info;
use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_bin_common::version_checker;
@@ -36,17 +36,17 @@ fn pretty_build_info_static() -> &'static str {
}
#[derive(Parser)]
#[clap(author = "Nymtech", version, about, long_version = pretty_build_info_static())]
#[command(author = "Nymtech", version, about, long_version = pretty_build_info_static())]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(short, long)]
#[arg(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
/// Flag used for disabling the printed banner in tty.
#[clap(long)]
#[arg(long)]
pub(crate) no_banner: bool,
#[clap(subcommand)]
#[command(subcommand)]
command: Commands,
}
@@ -165,6 +165,7 @@ fn persist_gateway_details(
}
fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, NetworkRequesterError> {
trace!("Trying to load as v1.1.13 config");
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
@@ -186,6 +187,7 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, NetworkRequesterError> {
}
fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, NetworkRequesterError> {
trace!("Trying to load as v1.1.20 config");
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
@@ -207,6 +209,8 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, NetworkRequesterError> {
}
fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, NetworkRequesterError> {
trace!("Trying to load as v1.1.20_2 config");
// explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20_2::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
@@ -224,6 +228,7 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, NetworkRequesterError>
}
fn try_upgrade_config(id: &str) -> Result<(), NetworkRequesterError> {
trace!("Attempting to upgrade config");
if try_upgrade_v1_1_13_config(id)? {
return Ok(());
}
@@ -16,39 +16,39 @@ const ENABLE_STATISTICS: &str = "enable-statistics";
#[derive(Args, Clone)]
pub(crate) struct Run {
/// Id of the nym-mixnet-client we want to run.
#[clap(long)]
#[arg(long)]
id: String,
/// Specifies whether this network requester should run in 'open-proxy' mode
#[clap(long)]
#[arg(long)]
open_proxy: bool,
/// Enable service anonymized statistics that get sent to a statistics aggregator server
#[clap(long)]
#[arg(long)]
enable_statistics: bool,
/// Mixnet client address where a statistics aggregator is running. The default value is a Nym
/// aggregator client
#[clap(long)]
#[arg(long)]
statistics_recipient: Option<String>,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[clap(long)]
#[arg(long)]
enabled_credentials_mode: Option<bool>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hide = true)]
#[arg(long, hide = true)]
fastmode: bool,
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
#[clap(long, hide = true)]
#[arg(long, hide = true)]
no_cover: bool,
/// Enable medium mixnet traffic, for experiments only.
/// This includes things like disabling cover traffic, no per hop delays, etc.
#[clap(long, hide = true)]
#[arg(long, hide = true)]
medium_toggle: bool,
}
@@ -13,14 +13,14 @@ use nym_types::helpers::ConsoleSigningOutput;
#[derive(Args, Clone)]
pub(crate) struct Sign {
/// The id of the mixnode you want to sign with
#[clap(long)]
#[arg(long)]
id: String,
/// Signs a transaction-specific payload, that is going to be sent to the smart contract, with your identity key
#[clap(long)]
#[arg(long)]
contract_msg: String,
#[clap(short, long, default_value_t = OutputFormat::default())]
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}

Some files were not shown because too many files have changed in this diff Show More