Compare commits

...

46 Commits

Author SHA1 Message Date
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
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
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 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
58 changed files with 663 additions and 243 deletions
+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
+23 -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",
@@ -6638,7 +6651,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.26"
version = "1.1.27"
dependencies = [
"clap 4.3.21",
"lazy_static",
+1
View File
@@ -76,6 +76,7 @@ members = [
"common/wasm-utils",
"explorer-api",
"explorer-api/explorer-api-requests",
"explorer-api/explorer-client",
"gateway",
"gateway/gateway-requests",
"integrations/bity",
+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" }
@@ -1,7 +1,7 @@
use std::{collections::HashMap, fmt};
use log::{debug, error, info};
use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
use nym_explorer_client::{ExplorerClient, PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
use nym_network_defaults::var_names::EXPLORER_API;
use nym_topology::{
nym_topology_from_detailed,
@@ -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::*;
@@ -303,7 +274,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 +308,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;
};
+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 {
+1 -1
View File
@@ -149,7 +149,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> {
+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
+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
View File
@@ -20,6 +20,7 @@
- [NymConnect Monero](tutorials/monero.md)
- [NymConnect Matrix](tutorials/matrix.md)
- [NymConnect Telegram](tutorials/telegram.md)
# Integrations
@@ -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.
+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"
+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])
+2
View File
@@ -4014,8 +4014,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",
+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": {
+3 -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 = ""
@@ -30,6 +30,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"] }
@@ -58,6 +59,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]
+3 -1
View File
@@ -86,8 +86,10 @@ 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_gateway_with_low_latency,
crate::operations::directory::get_gateways,
crate::operations::directory::get_services,
crate::operations::directory::select_gateway_with_low_latency_from_list,
crate::operations::export::export_keys,
crate::operations::window::hide_window,
crate::operations::growth::test_and_earn::growth_tne_get_client_id,
@@ -11,6 +11,7 @@ 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_topology::gateway;
use nym_validator_client::nym_api::Client as ApiClient;
use std::str::FromStr;
use std::sync::Arc;
@@ -134,25 +135,47 @@ async fn fetch_gateways() -> Result<Vec<GatewayBondAnnotated>> {
Ok(gateways)
}
fn filter_out_low_performance_gateways(
gateways: Vec<GatewayBondAnnotated>,
) -> Vec<GatewayBondAnnotated> {
gateways
.into_iter()
.filter(|g| {
g.node_performance.most_recent
> Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap()
})
.collect()
}
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_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()
})
let gateways_filtered = filter_out_low_performance_gateways(all_gateways.clone())
.into_iter()
.map(|g| Gateway {
identity: g.identity().clone(),
})
.collect_vec();
log::trace!("Filtered: {:#?}", filtered_gateways);
log::trace!("Filtered: {:#?}", gateways_filtered);
if filtered_gateways.is_empty() {
if gateways_filtered.is_empty() {
log::warn!("No gateways with high enough performance score found! Using all gateways instead as fallback");
return Ok(all_gateways
.iter()
@@ -162,5 +185,38 @@ pub async fn get_gateways() -> Result<Vec<Gateway>> {
.collect_vec());
}
Ok(filtered_gateways)
Ok(gateways_filtered)
}
// Lookup and select a single gateway with low latency.
#[tauri::command]
pub async fn get_gateway_with_low_latency() -> Result<Gateway> {
log::trace!("Fetching gateways");
let all_gateways = fetch_gateways().await?;
log::trace!("Received: {:#?}", all_gateways);
let gateways_filtered = filter_out_low_performance_gateways(all_gateways);
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(),
})
}
// 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_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 -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",
+6 -1
View File
@@ -214,7 +214,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,
@@ -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
}
+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,
}
@@ -3,6 +3,7 @@
use crate::config::persistence::NetworkRequesterPaths;
use crate::config::{default_config_filepath, Config, Debug, NetworkRequester};
use log::trace;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseClientConfigV1_1_20_2;
@@ -13,6 +14,8 @@ use std::io;
use std::path::{Path, PathBuf};
use std::time::Duration;
use super::persistence::DEFAULT_DESCRIPTION_FILENAME;
pub const DEFAULT_STANDARD_LIST_UPDATE_INTERVAL: Duration = Duration::from_secs(30 * 60);
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
@@ -56,13 +59,23 @@ impl ConfigV1_1_20_2 {
// in this upgrade, gateway endpoint configuration was moved out of the config file,
// so its returned to be stored elsewhere.
pub fn upgrade(self) -> (Config, GatewayEndpointConfig) {
trace!("Upgrading from v1.1.20_2");
let gateway_details = self.base.client.gateway_endpoint.clone().into();
let nr_description = self
.storage_paths
.common_paths
.keys
.ack_key_file
.parent()
.expect("config paths upgrade failure")
.join(DEFAULT_DESCRIPTION_FILENAME);
let config = Config {
base: self.base.into(),
storage_paths: NetworkRequesterPaths {
common_paths: self.storage_paths.common_paths.upgrade_default(),
allowed_list_location: self.storage_paths.allowed_list_location,
unknown_list_location: self.storage_paths.unknown_list_location,
nr_description,
},
network_requester_debug: self.network_requester_debug.into(),
logging: self.logging,
@@ -7,6 +7,7 @@ use std::path::{Path, PathBuf};
pub const DEFAULT_ALLOWED_LIST_FILENAME: &str = "allowed.list";
pub const DEFAULT_UNKNOWN_LIST_FILENAME: &str = "unknown.list";
pub const DEFAULT_DESCRIPTION_FILENAME: &str = "description.toml";
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct NetworkRequesterPaths {
@@ -18,6 +19,11 @@ pub struct NetworkRequesterPaths {
/// Location of the file containing our unknown.list
pub unknown_list_location: PathBuf,
/// Location of the file containing our description
// For upgrade use default if missing. On next config upgrade iteration, remove the serde(default)
#[serde(default)]
pub nr_description: PathBuf,
}
impl NetworkRequesterPaths {
@@ -28,6 +34,7 @@ impl NetworkRequesterPaths {
common_paths: CommonClientPaths::new_default(base_dir),
allowed_list_location: base_dir.join(DEFAULT_ALLOWED_LIST_FILENAME),
unknown_list_location: base_dir.join(DEFAULT_UNKNOWN_LIST_FILENAME),
nr_description: base_dir.join(DEFAULT_DESCRIPTION_FILENAME),
}
}
}
@@ -75,6 +75,9 @@ allowed_list_location = '{{ storage_paths.allowed_list_location }}'
# Location of the file containing our unknown.list
unknown_list_location = '{{ storage_paths.unknown_list_location }}'
# Path to file containing description of this network-requester.
nr_description = '{{ storage_paths.nr_description }}'
[network_requester]
@@ -1,6 +1,6 @@
[package]
name = "nym-network-statistics"
version = "1.1.26"
version = "1.1.27"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-cli"
version = "1.1.26"
version = "1.1.27"
authors.workspace = true
edition = "2021"