Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a66b2ae006 | |||
| 48045f79d7 | |||
| 4188c9fce4 | |||
| 7c104173b0 | |||
| 32fc3c1e70 | |||
| cd61e7c903 | |||
| cc4dcffc42 | |||
| 162b74293e | |||
| 7e2e122725 | |||
| cc28575176 | |||
| c6675bb207 | |||
| cb1731494f | |||
| bfa52f7634 | |||
| 690a18953a | |||
| 8f950eece4 | |||
| 1ee8a5a19b | |||
| ddf7c3b860 | |||
| eabad283fa | |||
| 6cf8d79988 | |||
| d21eff3b7f | |||
| 03bec90b83 | |||
| add57b2c14 | |||
| 927ca8970c | |||
| 47d222b13d | |||
| 3b2481e5a5 | |||
| de47982585 | |||
| fafad41230 | |||
| 79df17710d | |||
| e039ea843c | |||
| e898f202b7 | |||
| ca75fec048 | |||
| 87aab4e31e | |||
| 370a4a3a03 | |||
| 9b6b2117dd | |||
| ea90d7b558 | |||
| 52e06a7eb4 | |||
| e6250fa312 | |||
| 6d9e6a0f38 | |||
| c8331f4cad | |||
| d5a2fc7b3a | |||
| 6559fadf7f | |||
| b68f02be6a | |||
| 3f6acbfd66 | |||
| a830881ba5 | |||
| a3a234b41b | |||
| 8730a84a8e | |||
| 5bdda911a9 | |||
| 419e16eb31 | |||
| dcc663891a | |||
| 9c85dc022d | |||
| 5b4e386b21 | |||
| f4e4f262ae | |||
| 75c81b3206 | |||
| b7657e488b | |||
| 546054615a | |||
| 6d4ba18d86 | |||
| 899a2bfc8a | |||
| 57096bd86e | |||
| 3049abf4f1 | |||
| 1dc42df59c | |||
| e2b85c91df | |||
| 796a7fba0a | |||
| fbcf44eeb9 | |||
| f4785099c2 | |||
| b04d3ba376 | |||
| b2dfdda210 | |||
| 41ef3a26f5 | |||
| bae1b488de | |||
| 40cf2c441a | |||
| 34871b14b3 | |||
| c14b010f9e |
@@ -31,5 +31,3 @@ updates:
|
||||
update-types:
|
||||
- "patch"
|
||||
open-pull-requests-limit: 10
|
||||
assignees:
|
||||
- "octol"
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
uses: mikefarah/yq@v4.45.4
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -59,3 +59,6 @@ nym-api/redocly/formatted-openapi.json
|
||||
|
||||
*.sqlite
|
||||
.build
|
||||
|
||||
**/settings.sql
|
||||
**/enter_db.sh
|
||||
|
||||
@@ -4,6 +4,20 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.9-appenzeller] (2025-05-13)
|
||||
|
||||
- build(deps): bump clap from 4.5.36 to 4.5.37 in the patch-updates group ([#5722])
|
||||
- build(deps): bump golang.org/x/net from 0.36.0 to 0.38.0 in /wasm/mix-fetch/go-mix-conn ([#5720])
|
||||
- build(deps-dev): bump http-proxy-middleware from 2.0.6 to 2.0.9 in /wasm/client/internal-dev ([#5719])
|
||||
- Add /account/{address} ([#5673])
|
||||
- Add contains ticketbook data db query ([#5670])
|
||||
|
||||
[#5722]: https://github.com/nymtech/nym/pull/5722
|
||||
[#5720]: https://github.com/nymtech/nym/pull/5720
|
||||
[#5719]: https://github.com/nymtech/nym/pull/5719
|
||||
[#5673]: https://github.com/nymtech/nym/pull/5673
|
||||
[#5670]: https://github.com/nymtech/nym/pull/5670
|
||||
|
||||
## [2025.8-tourist] (2025-04-29)
|
||||
|
||||
- add reserved byte to reply surb serialisation ([#5731])
|
||||
|
||||
Generated
+349
-236
File diff suppressed because it is too large
Load Diff
+18
-20
@@ -33,7 +33,8 @@ members = [
|
||||
"common/commands",
|
||||
"common/config",
|
||||
"common/cosmwasm-smart-contracts/coconut-dkg",
|
||||
"common/cosmwasm-smart-contracts/contracts-common", "common/cosmwasm-smart-contracts/easy_addr",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
"common/cosmwasm-smart-contracts/easy_addr",
|
||||
"common/cosmwasm-smart-contracts/ecash-contract",
|
||||
"common/cosmwasm-smart-contracts/group-contract",
|
||||
"common/cosmwasm-smart-contracts/mixnet-contract",
|
||||
@@ -64,6 +65,8 @@ members = [
|
||||
"common/nym-id",
|
||||
"common/nym-metrics",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nymnoise",
|
||||
"common/nymnoise/keys",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
@@ -123,7 +126,6 @@ members = [
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
"tools/echo-server",
|
||||
"tools/echo-server",
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
"tools/internal/mixnet-connectivity-check",
|
||||
@@ -132,7 +134,8 @@ members = [
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract", "tools/internal/validator-status-check",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/validator-status-check",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-id-cli",
|
||||
"tools/nym-nr-query",
|
||||
@@ -161,12 +164,7 @@ default-members = [
|
||||
"tools/nymvisor",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"explorer",
|
||||
"contracts",
|
||||
"nym-wallet",
|
||||
"cpu-cycles",
|
||||
]
|
||||
exclude = ["explorer", "contracts", "nym-wallet", "cpu-cycles"]
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Nym Technologies SA"]
|
||||
@@ -209,9 +207,9 @@ celes = "2.6.0"
|
||||
cfg-if = "1.0.0"
|
||||
chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.40"
|
||||
chrono = "0.4.41"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.36"
|
||||
clap = "4.5.38"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.2"
|
||||
@@ -286,7 +284,6 @@ pem = "0.8"
|
||||
petgraph = "0.6.5"
|
||||
pin-project = "1.1"
|
||||
pin-project-lite = "0.2.16"
|
||||
pretty_env_logger = "0.4.0"
|
||||
publicsuffix = "2.3.0"
|
||||
quote = "1"
|
||||
rand = "0.8.5"
|
||||
@@ -310,8 +307,9 @@ serde_json_path = "0.7.2"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.8"
|
||||
sha2 = "0.10.9"
|
||||
si-scale = "0.2.3"
|
||||
snow = "0.9.6"
|
||||
sphinx-packet = "=0.6.0"
|
||||
sqlx = "0.7.4"
|
||||
strum = "0.26"
|
||||
@@ -330,8 +328,8 @@ tokio-stream = "0.1.17"
|
||||
tokio-test = "0.4.4"
|
||||
tokio-tun = "0.11.5"
|
||||
tokio-tungstenite = { version = "0.20.1" }
|
||||
tokio-util = "0.7.14"
|
||||
toml = "0.8.20"
|
||||
tokio-util = "0.7.15"
|
||||
toml = "0.8.22"
|
||||
tower = "0.5.2"
|
||||
tower-http = "0.5.2"
|
||||
tracing = "0.1.41"
|
||||
@@ -342,7 +340,7 @@ tracing-tree = "0.2.2"
|
||||
tracing-indicatif = "0.3.9"
|
||||
ts-rs = "10.1.0"
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
uniffi = "0.29.1"
|
||||
uniffi = "0.29.2"
|
||||
uniffi_build = "0.29.0"
|
||||
url = "2.5"
|
||||
utoipa = "5.2"
|
||||
@@ -355,7 +353,7 @@ wasm-bindgen-test = "0.3.49"
|
||||
x25519-dalek = "2.0.0"
|
||||
zeroize = "1.7.0"
|
||||
|
||||
prometheus = { version = "0.13.0" }
|
||||
prometheus = { version = "0.14.0" }
|
||||
|
||||
# coconut/DKG related
|
||||
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
|
||||
@@ -385,15 +383,15 @@ bip32 = { version = "0.5.3", default-features = false }
|
||||
|
||||
|
||||
cosmrs = { version = "0.21.1" }
|
||||
tendermint = "0.40.3"
|
||||
tendermint-rpc = "0.40.3"
|
||||
tendermint = "0.40.4"
|
||||
tendermint-rpc = "0.40.4"
|
||||
prost = { version = "0.13", default-features = false }
|
||||
|
||||
# wasm-related dependencies
|
||||
gloo-utils = "0.2.0"
|
||||
gloo-net = "0.6.0"
|
||||
|
||||
indexed_db_futures = "0.6.1"
|
||||
indexed_db_futures = "0.6.4"
|
||||
js-sys = "0.3.76"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
tsify = "0.4.5"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.54"
|
||||
version = "1.1.55"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
@@ -46,6 +46,7 @@ nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = [
|
||||
"output_format",
|
||||
"clap",
|
||||
"basic_tracing",
|
||||
] }
|
||||
nym-client-core = { path = "../../common/client-core", features = [
|
||||
"fs-credentials-storage",
|
||||
|
||||
@@ -25,6 +25,7 @@ pub mod old_config_v1_1_13;
|
||||
pub mod old_config_v1_1_20;
|
||||
pub mod old_config_v1_1_20_2;
|
||||
pub mod old_config_v1_1_33;
|
||||
pub mod old_config_v1_1_54;
|
||||
mod persistence;
|
||||
mod template;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::persistence::ClientPaths;
|
||||
use crate::client::config::{default_config_filepath, Config, Socket, SocketType};
|
||||
use crate::client::config::{default_config_filepath, Socket, SocketType};
|
||||
use crate::error::ClientError;
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
@@ -14,6 +14,8 @@ use std::io;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::path::Path;
|
||||
|
||||
use super::old_config_v1_1_54::ConfigV1_1_54;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||
pub struct ClientPathsV1_1_33 {
|
||||
#[serde(flatten)]
|
||||
@@ -33,6 +35,21 @@ pub struct ConfigV1_1_33 {
|
||||
pub logging: LoggingSettings,
|
||||
}
|
||||
|
||||
impl TryFrom<ConfigV1_1_33> for ConfigV1_1_54 {
|
||||
type Error = ClientError;
|
||||
|
||||
fn try_from(value: ConfigV1_1_33) -> Result<Self, Self::Error> {
|
||||
Ok(ConfigV1_1_54 {
|
||||
base: value.base.into(),
|
||||
socket: value.socket.into(),
|
||||
storage_paths: ClientPaths {
|
||||
common_paths: value.storage_paths.common_paths.upgrade_default()?,
|
||||
},
|
||||
logging: value.logging,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigV1_1_33 {
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
@@ -41,17 +58,6 @@ impl ConfigV1_1_33 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn try_upgrade(self) -> Result<Config, ClientError> {
|
||||
Ok(Config {
|
||||
base: self.base.into(),
|
||||
socket: self.socket.into(),
|
||||
storage_paths: ClientPaths {
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default()?,
|
||||
},
|
||||
logging: self.logging,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use std::{io, path::Path};
|
||||
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::old_config_v1_1_54::ConfigV1_1_54 as BaseConfigV1_1_54;
|
||||
use nym_config::read_config_from_toml_file;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::ClientError;
|
||||
|
||||
use super::{default_config_filepath, persistence::ClientPaths, Config, Socket};
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
|
||||
pub struct ConfigV1_1_54 {
|
||||
#[serde(flatten)]
|
||||
pub base: BaseConfigV1_1_54,
|
||||
|
||||
pub socket: Socket,
|
||||
|
||||
pub storage_paths: ClientPaths,
|
||||
|
||||
pub logging: LoggingSettings,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_54 {
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn try_upgrade(self) -> Result<Config, ClientError> {
|
||||
Ok(Config {
|
||||
base: self.base.into(),
|
||||
socket: self.socket,
|
||||
storage_paths: self.storage_paths,
|
||||
logging: self.logging,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -92,10 +92,6 @@ host = '{{ socket.host }}'
|
||||
|
||||
[debug]
|
||||
|
||||
[debug.traffic]
|
||||
average_packet_delay = '{{ debug.traffic.average_packet_delay }}'
|
||||
message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}'
|
||||
|
||||
[debug.acknowledgements]
|
||||
average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
|
||||
use crate::client::config::old_config_v1_1_20::ConfigV1_1_20;
|
||||
use crate::client::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
|
||||
use crate::client::config::old_config_v1_1_33::ConfigV1_1_33;
|
||||
use crate::client::config::old_config_v1_1_54::ConfigV1_1_54;
|
||||
use crate::client::config::{BaseClientConfig, Config};
|
||||
use crate::commands::ecash::Ecash;
|
||||
use crate::error::ClientError;
|
||||
@@ -177,7 +178,8 @@ async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
|
||||
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
|
||||
let (updated_step3, gateway_config) = updated_step2.upgrade()?;
|
||||
let old_paths = updated_step3.storage_paths.clone();
|
||||
let updated = updated_step3.try_upgrade()?;
|
||||
let updated_step4: ConfigV1_1_54 = updated_step3.try_into()?;
|
||||
let updated = updated_step4.try_upgrade()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
@@ -205,7 +207,8 @@ async fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
|
||||
let updated_step1: ConfigV1_1_20_2 = old_config.into();
|
||||
let (updated_step2, gateway_config) = updated_step1.upgrade()?;
|
||||
let old_paths = updated_step2.storage_paths.clone();
|
||||
let updated = updated_step2.try_upgrade()?;
|
||||
let updated_step3: ConfigV1_1_54 = updated_step2.try_into()?;
|
||||
let updated = updated_step3.try_upgrade()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
@@ -229,7 +232,8 @@ async fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
|
||||
|
||||
let (updated_step1, gateway_config) = old_config.upgrade()?;
|
||||
let old_paths = updated_step1.storage_paths.clone();
|
||||
let updated = updated_step1.try_upgrade()?;
|
||||
let updated_step2: ConfigV1_1_54 = updated_step1.try_into()?;
|
||||
let updated = updated_step2.try_upgrade()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
@@ -252,7 +256,8 @@ async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, ClientError> {
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let old_paths = old_config.storage_paths.clone();
|
||||
let updated = old_config.try_upgrade()?;
|
||||
let updated_step1: ConfigV1_1_54 = old_config.try_into()?;
|
||||
let updated = updated_step1.try_upgrade()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
@@ -265,6 +270,22 @@ async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, ClientError> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn try_upgrade_v1_1_54_config(id: &str) -> Result<bool, ClientError> {
|
||||
// explicitly load it as v1.1.54 (which is incompatible with the current one, i.e. +1.1.55)
|
||||
let Ok(old_config) = ConfigV1_1_54::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.54 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated = old_config.try_upgrade()?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
|
||||
if try_upgrade_v1_1_13_config(id).await? {
|
||||
return Ok(());
|
||||
@@ -278,6 +299,9 @@ async fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
|
||||
if try_upgrade_v1_1_33_config(id).await? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_54_config(id).await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::error::Error;
|
||||
|
||||
use clap::{crate_name, crate_version, Parser};
|
||||
use nym_bin_common::logging::{maybe_print_banner, setup_logging};
|
||||
use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger};
|
||||
use nym_network_defaults::setup_env;
|
||||
|
||||
pub mod client;
|
||||
@@ -20,7 +20,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
if !args.no_banner {
|
||||
maybe_print_banner(crate_name!(), crate_version!());
|
||||
}
|
||||
setup_logging();
|
||||
setup_tracing_logger();
|
||||
|
||||
if let Err(err) = commands::execute(args).await {
|
||||
log::error!("{err}");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.54"
|
||||
version = "1.1.55"
|
||||
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"
|
||||
@@ -27,6 +27,7 @@ zeroize = { workspace = true }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = [
|
||||
"output_format",
|
||||
"clap",
|
||||
"basic_tracing",
|
||||
] }
|
||||
nym-client-core = { path = "../../common/client-core", features = [
|
||||
"fs-credentials-storage",
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::config::old_config_v1_1_20::ConfigV1_1_20;
|
||||
use crate::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
|
||||
use crate::config::old_config_v1_1_30::ConfigV1_1_30;
|
||||
use crate::config::old_config_v1_1_33::ConfigV1_1_33;
|
||||
use crate::config::old_config_v1_1_54::ConfigV1_1_54;
|
||||
use crate::config::{BaseClientConfig, Config};
|
||||
use crate::error::Socks5ClientError;
|
||||
use clap::CommandFactory;
|
||||
@@ -204,15 +205,16 @@ async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError>
|
||||
let old_paths = updated_step3.storage_paths.clone();
|
||||
|
||||
let updated_step4: ConfigV1_1_33 = updated_step3.into();
|
||||
let updated = updated_step4.try_upgrade()?;
|
||||
let updated_step5: ConfigV1_1_54 = updated_step4.try_into()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
&updated.storage_paths.common_paths,
|
||||
&updated_step5.storage_paths.common_paths,
|
||||
Some(gateway_config),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let updated = updated_step5.try_upgrade()?;
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -234,15 +236,16 @@ async fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError>
|
||||
let old_paths = updated_step2.storage_paths.clone();
|
||||
|
||||
let updated_step3: ConfigV1_1_33 = updated_step2.into();
|
||||
let updated = updated_step3.try_upgrade()?;
|
||||
let updated_step4: ConfigV1_1_54 = updated_step3.try_into()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
&updated.storage_paths.common_paths,
|
||||
&updated_step4.storage_paths.common_paths,
|
||||
Some(gateway_config),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let updated = updated_step4.try_upgrade()?;
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -261,15 +264,17 @@ async fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientErro
|
||||
let old_paths = updated_step1.storage_paths.clone();
|
||||
|
||||
let updated_step2: ConfigV1_1_33 = updated_step1.into();
|
||||
let updated = updated_step2.try_upgrade()?;
|
||||
let updated_step3: ConfigV1_1_54 = updated_step2.try_into()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
&updated.storage_paths.common_paths,
|
||||
&updated_step3.storage_paths.common_paths,
|
||||
Some(gateway_config),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let updated = updated_step3.try_upgrade()?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -287,15 +292,16 @@ async fn try_upgrade_v1_1_30_config(id: &str) -> Result<bool, Socks5ClientError>
|
||||
let old_paths = old_config.storage_paths.clone();
|
||||
|
||||
let updated_step1: ConfigV1_1_33 = old_config.into();
|
||||
let updated = updated_step1.try_upgrade()?;
|
||||
let updated_step2: ConfigV1_1_54 = updated_step1.try_into()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
&updated.storage_paths.common_paths,
|
||||
&updated_step2.storage_paths.common_paths,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let updated = updated_step2.try_upgrade()?;
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -312,15 +318,32 @@ async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, Socks5ClientError>
|
||||
|
||||
let old_paths = old_config.storage_paths.clone();
|
||||
|
||||
let updated = old_config.try_upgrade()?;
|
||||
let updated_step1: ConfigV1_1_54 = old_config.try_into()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
&updated.storage_paths.common_paths,
|
||||
&updated_step1.storage_paths.common_paths,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let updated = updated_step1.try_upgrade()?;
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn try_upgrade_v1_1_54_config(id: &str) -> Result<bool, Socks5ClientError> {
|
||||
// explicitly load it as v1.1.54 (which is incompatible with the current one, i.e. +1.1.55)
|
||||
let Ok(old_config) = ConfigV1_1_54::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.54 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated = old_config.try_upgrade()?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -341,6 +364,9 @@ async fn try_upgrade_config(id: &str) -> Result<(), Socks5ClientError> {
|
||||
if try_upgrade_v1_1_33_config(id).await? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_54_config(id).await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ pub mod old_config_v1_1_20;
|
||||
pub mod old_config_v1_1_20_2;
|
||||
pub mod old_config_v1_1_30;
|
||||
pub mod old_config_v1_1_33;
|
||||
pub mod old_config_v1_1_54;
|
||||
mod persistence;
|
||||
mod template;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::{default_config_filepath, Config, SocksClientPaths};
|
||||
use crate::config::{default_config_filepath, SocksClientPaths};
|
||||
use crate::error::Socks5ClientError;
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
@@ -11,6 +11,8 @@ use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use super::old_config_v1_1_54::ConfigV1_1_54;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct SocksClientPathsV1_1_33 {
|
||||
#[serde(flatten)]
|
||||
@@ -28,6 +30,20 @@ pub struct ConfigV1_1_33 {
|
||||
pub logging: LoggingSettings,
|
||||
}
|
||||
|
||||
impl TryFrom<ConfigV1_1_33> for ConfigV1_1_54 {
|
||||
type Error = Socks5ClientError;
|
||||
|
||||
fn try_from(value: ConfigV1_1_33) -> Result<Self, Self::Error> {
|
||||
Ok(ConfigV1_1_54 {
|
||||
core: value.core.into(),
|
||||
storage_paths: SocksClientPaths {
|
||||
common_paths: value.storage_paths.common_paths.upgrade_default()?,
|
||||
},
|
||||
logging: value.logging,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigV1_1_33 {
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
@@ -36,14 +52,4 @@ impl ConfigV1_1_33 {
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn try_upgrade(self) -> Result<Config, Socks5ClientError> {
|
||||
Ok(Config {
|
||||
core: self.core.into(),
|
||||
storage_paths: SocksClientPaths {
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default()?,
|
||||
},
|
||||
logging: self.logging,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
use std::{io, path::Path};
|
||||
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_config::read_config_from_toml_file;
|
||||
use nym_socks5_client_core::config::old_config_v1_1_54::ConfigV1_1_54 as CoreConfigV1_1_54;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::Socks5ClientError;
|
||||
|
||||
use super::{default_config_filepath, SocksClientPaths};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_54 {
|
||||
pub core: CoreConfigV1_1_54,
|
||||
|
||||
pub storage_paths: SocksClientPaths,
|
||||
|
||||
pub logging: LoggingSettings,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_54 {
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn try_upgrade(self) -> Result<Config, Socks5ClientError> {
|
||||
Ok(Config {
|
||||
core: self.core.into(),
|
||||
storage_paths: self.storage_paths,
|
||||
logging: self.logging,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -98,10 +98,6 @@ send_anonymously = {{ core.socks5.send_anonymously }}
|
||||
|
||||
[core.debug]
|
||||
|
||||
[core.debug.traffic]
|
||||
average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}'
|
||||
message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}'
|
||||
|
||||
[core.debug.acknowledgements]
|
||||
average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}'
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::error::Error;
|
||||
|
||||
use clap::{crate_name, crate_version, Parser};
|
||||
use nym_bin_common::logging::{maybe_print_banner, setup_logging};
|
||||
use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger};
|
||||
use nym_network_defaults::setup_env;
|
||||
|
||||
mod commands;
|
||||
@@ -19,7 +19,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
if !args.no_banner {
|
||||
maybe_print_banner(crate_name!(), crate_version!());
|
||||
}
|
||||
setup_logging();
|
||||
setup_tracing_logger();
|
||||
|
||||
if let Err(err) = commands::execute(args).await {
|
||||
log::error!("{err}");
|
||||
|
||||
@@ -13,7 +13,6 @@ clap_complete = { workspace = true, optional = true }
|
||||
clap_complete_fig = { workspace = true, optional = true }
|
||||
const-str = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pretty_env_logger = { workspace = true }
|
||||
schemars = { workspace = true, features = ["preserve_order"], optional = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
|
||||
@@ -21,29 +21,6 @@ pub struct LoggingSettings {
|
||||
// well, we need to implement something here at some point...
|
||||
}
|
||||
|
||||
// I'd argue we should start transitioning from `log` to `tracing`
|
||||
pub fn setup_logging() {
|
||||
let mut log_builder = pretty_env_logger::formatted_timed_builder();
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log_builder.parse_filters(&s);
|
||||
} else {
|
||||
// default to 'Info'
|
||||
log_builder.filter(None, log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
log_builder
|
||||
.filter_module("hyper", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_reactor", log::LevelFilter::Warn)
|
||||
.filter_module("reqwest", log::LevelFilter::Warn)
|
||||
.filter_module("mio", log::LevelFilter::Warn)
|
||||
.filter_module("want", log::LevelFilter::Warn)
|
||||
.filter_module("tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.filter_module("handlebars", log::LevelFilter::Warn)
|
||||
.filter_module("sled", log::LevelFilter::Warn)
|
||||
.init();
|
||||
}
|
||||
|
||||
// don't call init so that we could attach additional layers
|
||||
#[cfg(feature = "basic_tracing")]
|
||||
pub fn build_tracing_logger() -> impl tracing_subscriber::layer::SubscriberExt {
|
||||
|
||||
@@ -44,7 +44,6 @@ nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
nym-topology = { path = "../topology", features = ["persistence"] }
|
||||
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-task = { path = "../task" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
@@ -57,6 +56,9 @@ nym-client-core-surb-storage = { path = "./surb-storage" }
|
||||
nym-client-core-gateways-storage = { path = "./gateways-storage" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
|
||||
|
||||
### For serving prometheus metrics
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.hyper]
|
||||
workspace = true
|
||||
|
||||
@@ -19,6 +19,7 @@ nym-pemstore = { path = "../../pemstore", optional = true }
|
||||
# those are pulling so many deps T.T
|
||||
nym-sphinx-params = { path = "../../nymsphinx/params" }
|
||||
nym-sphinx-addressing = { path = "../../nymsphinx/addressing" }
|
||||
nym-statistics-common = { path = "../../statistics" }
|
||||
|
||||
|
||||
[features]
|
||||
|
||||
@@ -5,6 +5,7 @@ use nym_config::defaults::NymNetworkDetails;
|
||||
use nym_config::serde_helpers::{de_maybe_stringified, ser_maybe_stringified};
|
||||
use nym_sphinx_addressing::Recipient;
|
||||
use nym_sphinx_params::{PacketSize, PacketType};
|
||||
use nym_statistics_common::types::SessionType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
@@ -22,7 +23,7 @@ const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
|
||||
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
|
||||
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15);
|
||||
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
|
||||
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
|
||||
|
||||
@@ -375,14 +376,12 @@ pub struct Traffic {
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub average_packet_delay: Duration,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub message_sending_average_delay: Duration,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
@@ -718,6 +717,9 @@ pub struct DebugConfig {
|
||||
|
||||
/// Defines all configuration options related to the forget me flag.
|
||||
pub forget_me: ForgetMe,
|
||||
|
||||
/// Defines all configuration options related to the remember me flag.
|
||||
pub remember_me: RememberMe,
|
||||
}
|
||||
|
||||
impl DebugConfig {
|
||||
@@ -741,6 +743,7 @@ impl Default for DebugConfig {
|
||||
reply_surbs: Default::default(),
|
||||
stats_reporting: Default::default(),
|
||||
forget_me: Default::default(),
|
||||
remember_me: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -806,3 +809,57 @@ impl ForgetMe {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
|
||||
pub struct RememberMe {
|
||||
/// Signal that this client should be accounted for in the stats
|
||||
stats: bool,
|
||||
|
||||
/// Type of the session to remember, if it should be remembered
|
||||
session_type: SessionType,
|
||||
}
|
||||
|
||||
impl RememberMe {
|
||||
pub fn new_vpn() -> Self {
|
||||
Self {
|
||||
stats: true,
|
||||
session_type: SessionType::Vpn,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mixnet() -> Self {
|
||||
Self {
|
||||
stats: true,
|
||||
session_type: SessionType::Mixnet,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_native() -> Self {
|
||||
Self {
|
||||
stats: true,
|
||||
session_type: SessionType::Native,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(stats: bool, session_type: SessionType) -> Self {
|
||||
Self {
|
||||
stats,
|
||||
session_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_none() -> Self {
|
||||
Self {
|
||||
stats: false,
|
||||
session_type: SessionType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_type(&self) -> SessionType {
|
||||
self.session_type
|
||||
}
|
||||
|
||||
pub fn stats(&self) -> bool {
|
||||
self.stats
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod v2;
|
||||
pub mod v3;
|
||||
pub mod v4;
|
||||
pub mod v5;
|
||||
pub mod v6;
|
||||
|
||||
// aliases for backwards compatibility
|
||||
pub use v1 as old_config_v1_1_13;
|
||||
@@ -13,3 +14,4 @@ pub use v2 as old_config_v1_1_20;
|
||||
pub use v3 as old_config_v1_1_20_2;
|
||||
pub use v4 as old_config_v1_1_30;
|
||||
pub use v5 as old_config_v1_1_33;
|
||||
pub use v6 as old_config_v1_1_54;
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection, ReplySurbs,
|
||||
Topology, Traffic,
|
||||
};
|
||||
use nym_sphinx_addressing::Recipient;
|
||||
use nym_sphinx_params::{PacketSize, PacketType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use super::v6::*;
|
||||
|
||||
// 'DEBUG'
|
||||
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
|
||||
|
||||
@@ -87,18 +85,18 @@ pub struct ConfigV5 {
|
||||
pub debug: DebugConfigV5,
|
||||
}
|
||||
|
||||
impl From<ConfigV5> for Config {
|
||||
impl From<ConfigV5> for ConfigV6 {
|
||||
fn from(value: ConfigV5) -> Self {
|
||||
Config {
|
||||
client: Client {
|
||||
ConfigV6 {
|
||||
client: ClientV6 {
|
||||
version: value.client.version,
|
||||
id: value.client.id,
|
||||
disabled_credentials_mode: value.client.disabled_credentials_mode,
|
||||
nyxd_urls: value.client.nyxd_urls,
|
||||
nym_api_urls: value.client.nym_api_urls,
|
||||
},
|
||||
debug: DebugConfig {
|
||||
traffic: Traffic {
|
||||
debug: DebugConfigV6 {
|
||||
traffic: TrafficV6 {
|
||||
average_packet_delay: value.debug.traffic.average_packet_delay,
|
||||
message_sending_average_delay: value
|
||||
.debug
|
||||
@@ -113,7 +111,7 @@ impl From<ConfigV5> for Config {
|
||||
packet_type: value.debug.traffic.packet_type,
|
||||
..Default::default()
|
||||
},
|
||||
cover_traffic: CoverTraffic {
|
||||
cover_traffic: CoverTrafficV6 {
|
||||
loop_cover_traffic_average_delay: value
|
||||
.debug
|
||||
.cover_traffic
|
||||
@@ -127,18 +125,18 @@ impl From<ConfigV5> for Config {
|
||||
.cover_traffic
|
||||
.disable_loop_cover_traffic_stream,
|
||||
},
|
||||
gateway_connection: GatewayConnection {
|
||||
gateway_connection: GatewayConnectionV6 {
|
||||
gateway_response_timeout: value
|
||||
.debug
|
||||
.gateway_connection
|
||||
.gateway_response_timeout,
|
||||
},
|
||||
acknowledgements: Acknowledgements {
|
||||
acknowledgements: AcknowledgementsV6 {
|
||||
average_ack_delay: value.debug.acknowledgements.average_ack_delay,
|
||||
ack_wait_multiplier: value.debug.acknowledgements.ack_wait_multiplier,
|
||||
ack_wait_addition: value.debug.acknowledgements.ack_wait_addition,
|
||||
},
|
||||
topology: Topology {
|
||||
topology: TopologyV6 {
|
||||
topology_refresh_rate: value.debug.topology.topology_refresh_rate,
|
||||
topology_resolution_timeout: value.debug.topology.topology_resolution_timeout,
|
||||
disable_refreshing: value.debug.topology.disable_refreshing,
|
||||
@@ -148,7 +146,7 @@ impl From<ConfigV5> for Config {
|
||||
.max_startup_gateway_waiting_period,
|
||||
..Default::default()
|
||||
},
|
||||
reply_surbs: ReplySurbs {
|
||||
reply_surbs: ReplySurbsV6 {
|
||||
minimum_reply_surb_storage_threshold: value
|
||||
.debug
|
||||
.reply_surbs
|
||||
|
||||
@@ -0,0 +1,623 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, ForgetMe, GatewayConnection,
|
||||
RememberMe, ReplySurbs, StatsReporting, Topology, Traffic,
|
||||
};
|
||||
use nym_config::serde_helpers::{de_maybe_stringified, ser_maybe_stringified};
|
||||
use nym_sphinx_addressing::Recipient;
|
||||
use nym_sphinx_params::{PacketSize, PacketType};
|
||||
use nym_statistics_common::types::SessionType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
// 'DEBUG'
|
||||
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
|
||||
|
||||
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
|
||||
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15);
|
||||
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
|
||||
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
|
||||
|
||||
// the same values as our current (10.06.24) blacklist
|
||||
const DEFAULT_MIN_MIXNODE_PERFORMANCE: u8 = 50;
|
||||
const DEFAULT_MIN_GATEWAY_PERFORMANCE: u8 = 50;
|
||||
|
||||
const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
|
||||
|
||||
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
|
||||
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
|
||||
// bandwidth bridging protocol, we can come back to a smaller timeout value
|
||||
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||
|
||||
const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
|
||||
|
||||
// reply-surbs related:
|
||||
|
||||
// define when to request
|
||||
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
|
||||
|
||||
// define how much to request at once
|
||||
// clients/client-core/src/client/replies/reply_controller.rs
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 50;
|
||||
|
||||
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
|
||||
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10);
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60);
|
||||
|
||||
// 12 hours
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60);
|
||||
|
||||
// 24 hours
|
||||
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
|
||||
|
||||
// stats reporting related
|
||||
|
||||
/// Time interval between reporting statistics to the given provider if it exists
|
||||
const STATS_REPORT_INTERVAL_SECS: Duration = Duration::from_secs(300);
|
||||
|
||||
// aliases for backwards compatibility
|
||||
pub type ConfigV1_1_54 = ConfigV6;
|
||||
pub type ClientV1_1_54 = ClientV6;
|
||||
pub type DebugConfigV1_1_54 = DebugConfigV6;
|
||||
|
||||
pub type TrafficV1_1_54 = TrafficV6;
|
||||
pub type CoverTrafficV1_1_54 = CoverTrafficV6;
|
||||
pub type GatewayConnectionV1_1_54 = GatewayConnectionV6;
|
||||
pub type AcknowledgementsV1_1_54 = AcknowledgementsV6;
|
||||
pub type TopologyV1_1_54 = TopologyV6;
|
||||
pub type ReplySurbsV1_1_54 = ReplySurbsV6;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV6 {
|
||||
pub client: ClientV6,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugConfigV6,
|
||||
}
|
||||
|
||||
impl From<ConfigV6> for Config {
|
||||
fn from(value: ConfigV6) -> Self {
|
||||
Config {
|
||||
client: Client {
|
||||
version: value.client.version,
|
||||
id: value.client.id,
|
||||
disabled_credentials_mode: value.client.disabled_credentials_mode,
|
||||
nyxd_urls: value.client.nyxd_urls,
|
||||
nym_api_urls: value.client.nym_api_urls,
|
||||
},
|
||||
debug: DebugConfig {
|
||||
traffic: Traffic {
|
||||
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
message_sending_average_delay: value
|
||||
.debug
|
||||
.traffic
|
||||
.message_sending_average_delay,
|
||||
disable_main_poisson_packet_distribution: value
|
||||
.debug
|
||||
.traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
primary_packet_size: value.debug.traffic.primary_packet_size,
|
||||
secondary_packet_size: value.debug.traffic.secondary_packet_size,
|
||||
packet_type: value.debug.traffic.packet_type,
|
||||
deterministic_route_selection: value
|
||||
.debug
|
||||
.traffic
|
||||
.deterministic_route_selection,
|
||||
maximum_number_of_retransmissions: value
|
||||
.debug
|
||||
.traffic
|
||||
.maximum_number_of_retransmissions,
|
||||
use_legacy_sphinx_format: value.debug.traffic.use_legacy_sphinx_format,
|
||||
disable_mix_hops: value.debug.traffic.disable_mix_hops,
|
||||
},
|
||||
cover_traffic: CoverTraffic {
|
||||
loop_cover_traffic_average_delay: value
|
||||
.debug
|
||||
.cover_traffic
|
||||
.loop_cover_traffic_average_delay,
|
||||
cover_traffic_primary_size_ratio: value
|
||||
.debug
|
||||
.cover_traffic
|
||||
.cover_traffic_primary_size_ratio,
|
||||
disable_loop_cover_traffic_stream: value
|
||||
.debug
|
||||
.cover_traffic
|
||||
.disable_loop_cover_traffic_stream,
|
||||
},
|
||||
gateway_connection: GatewayConnection {
|
||||
gateway_response_timeout: value
|
||||
.debug
|
||||
.gateway_connection
|
||||
.gateway_response_timeout,
|
||||
},
|
||||
acknowledgements: Acknowledgements {
|
||||
average_ack_delay: value.debug.acknowledgements.average_ack_delay,
|
||||
ack_wait_multiplier: value.debug.acknowledgements.ack_wait_multiplier,
|
||||
ack_wait_addition: value.debug.acknowledgements.ack_wait_addition,
|
||||
},
|
||||
topology: Topology {
|
||||
topology_refresh_rate: value.debug.topology.topology_refresh_rate,
|
||||
topology_resolution_timeout: value.debug.topology.topology_resolution_timeout,
|
||||
disable_refreshing: value.debug.topology.disable_refreshing,
|
||||
max_startup_gateway_waiting_period: value
|
||||
.debug
|
||||
.topology
|
||||
.max_startup_gateway_waiting_period,
|
||||
minimum_mixnode_performance: value.debug.topology.minimum_mixnode_performance,
|
||||
minimum_gateway_performance: value.debug.topology.minimum_gateway_performance,
|
||||
use_extended_topology: value.debug.topology.use_extended_topology,
|
||||
ignore_egress_epoch_role: value.debug.topology.ignore_egress_epoch_role,
|
||||
ignore_ingress_epoch_role: value.debug.topology.ignore_ingress_epoch_role,
|
||||
},
|
||||
reply_surbs: ReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: value
|
||||
.debug
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: value
|
||||
.debug
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: value
|
||||
.debug
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: value
|
||||
.debug
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: value
|
||||
.debug
|
||||
.reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_rerequest_waiting_period: value
|
||||
.debug
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_rerequest_waiting_period,
|
||||
maximum_reply_surb_drop_waiting_period: value
|
||||
.debug
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_drop_waiting_period,
|
||||
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
|
||||
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
|
||||
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
|
||||
minimum_reply_surb_threshold_buffer: value
|
||||
.debug
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_threshold_buffer,
|
||||
fresh_sender_tags: value.debug.reply_surbs.fresh_sender_tags,
|
||||
},
|
||||
stats_reporting: StatsReporting {
|
||||
enabled: value.debug.stats_reporting.enabled,
|
||||
provider_address: value.debug.stats_reporting.provider_address,
|
||||
reporting_interval: value.debug.stats_reporting.reporting_interval,
|
||||
},
|
||||
forget_me: ForgetMe {
|
||||
client: value.debug.forget_me.client,
|
||||
stats: value.debug.forget_me.stats,
|
||||
},
|
||||
remember_me: RememberMe {
|
||||
stats: value.debug.remember_me.stats,
|
||||
session_type: value.debug.remember_me.session_type.into(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ClientV6 {
|
||||
/// Version of the client for which this configuration was created.
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular client.
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
|
||||
/// to claim bandwidth without presenting bandwidth credentials.
|
||||
// TODO: this should be moved to `debug.gateway_connection`
|
||||
#[serde(default)]
|
||||
pub disabled_credentials_mode: bool,
|
||||
|
||||
/// Addresses to nyxd validators via which the client can communicate with the chain.
|
||||
#[serde(alias = "validator_urls")]
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct TrafficV6 {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub average_packet_delay: Duration,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub message_sending_average_delay: Duration,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Specify whether route selection should be determined by the packet header.
|
||||
pub deterministic_route_selection: bool,
|
||||
|
||||
/// Specify how many times particular packet can be retransmitted
|
||||
/// None - no limit
|
||||
pub maximum_number_of_retransmissions: Option<u32>,
|
||||
|
||||
/// Specifies the packet size used for sent messages.
|
||||
/// Do not override it unless you understand the consequences of that change.
|
||||
pub primary_packet_size: PacketSize,
|
||||
|
||||
/// Specifies the optional auxiliary packet size for optimizing message streams.
|
||||
/// Note that its use decreases overall anonymity.
|
||||
/// Do not set it unless you understand the consequences of that change.
|
||||
pub secondary_packet_size: Option<PacketSize>,
|
||||
|
||||
/// Specify whether any constructed sphinx packets should use the legacy format,
|
||||
/// where the payload keys are explicitly attached rather than using the seeds
|
||||
/// this affects any forward packets, acks and reply surbs
|
||||
/// this flag should remain disabled until sufficient number of nodes on the network has upgraded
|
||||
/// and support updated format.
|
||||
/// in the case of reply surbs, the recipient must also understand the new encoding
|
||||
pub use_legacy_sphinx_format: bool,
|
||||
|
||||
pub packet_type: PacketType,
|
||||
|
||||
/// Indicates whether to mix hops or not. If mix hops are enabled, traffic
|
||||
/// will be routed as usual, to the entry gateway, through three mix nodes, egressing
|
||||
/// through the exit gateway. If mix hops are disabled, traffic will be routed directly
|
||||
/// from the entry gateway to the exit gateway, bypassing the mix nodes.
|
||||
pub disable_mix_hops: bool,
|
||||
}
|
||||
|
||||
impl Default for TrafficV6 {
|
||||
fn default() -> Self {
|
||||
TrafficV6 {
|
||||
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
|
||||
disable_main_poisson_packet_distribution: false,
|
||||
deterministic_route_selection: false,
|
||||
maximum_number_of_retransmissions: None,
|
||||
primary_packet_size: PacketSize::RegularPacket,
|
||||
secondary_packet_size: None,
|
||||
packet_type: PacketType::Mix,
|
||||
|
||||
// we should use the legacy format until sufficient number of nodes understand the
|
||||
// improved encoding
|
||||
use_legacy_sphinx_format: true,
|
||||
disable_mix_hops: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct CoverTrafficV6 {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub loop_cover_traffic_average_delay: Duration,
|
||||
|
||||
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
|
||||
/// Only applicable if `secondary_packet_size` is enabled.
|
||||
pub cover_traffic_primary_size_ratio: f64,
|
||||
|
||||
/// Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
}
|
||||
|
||||
impl Default for CoverTrafficV6 {
|
||||
fn default() -> Self {
|
||||
CoverTrafficV6 {
|
||||
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
|
||||
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
|
||||
disable_loop_cover_traffic_stream: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct GatewayConnectionV6 {
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub gateway_response_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for GatewayConnectionV6 {
|
||||
fn default() -> Self {
|
||||
GatewayConnectionV6 {
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct AcknowledgementsV6 {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub average_ack_delay: Duration,
|
||||
|
||||
/// Value multiplied with the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 1.
|
||||
pub ack_wait_multiplier: f64,
|
||||
|
||||
/// Value added to the expected round trip time of an acknowledgement packet before
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 0.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub ack_wait_addition: Duration,
|
||||
}
|
||||
|
||||
impl Default for AcknowledgementsV6 {
|
||||
fn default() -> Self {
|
||||
AcknowledgementsV6 {
|
||||
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
|
||||
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct TopologyV6 {
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub topology_refresh_rate: Duration,
|
||||
|
||||
/// During topology refresh, test packets are sent through every single possible network
|
||||
/// path. This timeout determines waiting period until it is decided that the packet
|
||||
/// did not reach its destination.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub topology_resolution_timeout: Duration,
|
||||
|
||||
/// Specifies whether the client should not refresh the network topology after obtaining
|
||||
/// the first valid instance.
|
||||
/// Supersedes `topology_refresh_rate_ms`.
|
||||
pub disable_refreshing: bool,
|
||||
|
||||
/// Defines how long the client is going to wait on startup for its gateway to come online,
|
||||
/// before abandoning the procedure.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub max_startup_gateway_waiting_period: Duration,
|
||||
|
||||
/// Specifies a minimum performance of a mixnode that is used on route construction.
|
||||
/// This setting is only applicable when `NymApi` topology is used.
|
||||
pub minimum_mixnode_performance: u8,
|
||||
|
||||
/// Specifies a minimum performance of a gateway that is used on route construction.
|
||||
/// This setting is only applicable when `NymApi` topology is used.
|
||||
pub minimum_gateway_performance: u8,
|
||||
|
||||
/// Specifies whether this client should attempt to retrieve all available network nodes
|
||||
/// as opposed to just active mixnodes/gateways.
|
||||
pub use_extended_topology: bool,
|
||||
|
||||
/// Specifies whether this client should ignore the current epoch role of the target egress node
|
||||
/// when constructing the final hop packets.
|
||||
pub ignore_egress_epoch_role: bool,
|
||||
|
||||
/// Specifies whether this client should ignore the current epoch role of the ingress node
|
||||
/// when attempting to establish new connection
|
||||
pub ignore_ingress_epoch_role: bool,
|
||||
}
|
||||
|
||||
impl Default for TopologyV6 {
|
||||
fn default() -> Self {
|
||||
TopologyV6 {
|
||||
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
disable_refreshing: false,
|
||||
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
|
||||
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
|
||||
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
|
||||
use_extended_topology: false,
|
||||
|
||||
ignore_egress_epoch_role: true,
|
||||
ignore_ingress_epoch_role: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct ReplySurbsV6 {
|
||||
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
|
||||
/// It can only allow to go below that value if its to request additional reply surbs.
|
||||
pub minimum_reply_surb_storage_threshold: usize,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
||||
pub maximum_reply_surb_storage_threshold: usize,
|
||||
|
||||
/// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client
|
||||
/// should proactively request additional reply surbs.
|
||||
pub minimum_reply_surb_threshold_buffer: usize,
|
||||
|
||||
/// Defines the minimum number of reply surbs the client would request.
|
||||
pub minimum_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client would request.
|
||||
pub maximum_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
|
||||
pub maximum_allowed_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
|
||||
/// for more even though in theory they wouldn't need to.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_surb_rerequest_waiting_period: Duration,
|
||||
|
||||
/// Defines maximum amount of time the client is going to wait for reply surbs before
|
||||
/// deciding it's never going to get them and would drop all pending messages
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_surb_drop_waiting_period: Duration,
|
||||
|
||||
/// Defines maximum amount of time given reply surb is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_surb_age: Duration,
|
||||
|
||||
/// Defines maximum amount of time given reply key is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_key_age: Duration,
|
||||
|
||||
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
|
||||
/// the default value is used.
|
||||
pub surb_mix_hops: Option<u8>,
|
||||
|
||||
/// Specifies if we should reset all the sender tags on startup
|
||||
pub fresh_sender_tags: bool,
|
||||
}
|
||||
|
||||
impl Default for ReplySurbsV6 {
|
||||
fn default() -> Self {
|
||||
ReplySurbsV6 {
|
||||
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
minimum_reply_surb_threshold_buffer: DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER,
|
||||
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
|
||||
maximum_reply_surb_rerequest_waiting_period:
|
||||
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
|
||||
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
|
||||
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
|
||||
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
|
||||
surb_mix_hops: None,
|
||||
fresh_sender_tags: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct DebugConfigV6 {
|
||||
/// Defines all configuration options related to traffic streams.
|
||||
pub traffic: TrafficV6,
|
||||
|
||||
/// Defines all configuration options related to cover traffic stream(s).
|
||||
pub cover_traffic: CoverTrafficV6,
|
||||
|
||||
/// Defines all configuration options related to the gateway connection.
|
||||
pub gateway_connection: GatewayConnectionV6,
|
||||
|
||||
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
|
||||
pub acknowledgements: AcknowledgementsV6,
|
||||
|
||||
/// Defines all configuration options related topology, such as refresh rates or timeouts.
|
||||
pub topology: TopologyV6,
|
||||
|
||||
/// Defines all configuration options related to reply SURBs.
|
||||
pub reply_surbs: ReplySurbsV6,
|
||||
|
||||
/// Defines all configuration options related to stats reporting.
|
||||
pub stats_reporting: StatsReportingV6,
|
||||
|
||||
/// Defines all configuration options related to the forget me flag.
|
||||
pub forget_me: ForgetMeV6,
|
||||
|
||||
/// Defines all configuration options related to the remember me flag.
|
||||
pub remember_me: RememberMeV6,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct StatsReportingV6 {
|
||||
/// Is stats reporting enabled
|
||||
pub enabled: bool,
|
||||
|
||||
/// Address of the stats collector. If this is none, no reporting will happen, regardless of `enabled`
|
||||
#[serde(
|
||||
serialize_with = "ser_maybe_stringified",
|
||||
deserialize_with = "de_maybe_stringified"
|
||||
)]
|
||||
pub provider_address: Option<Recipient>,
|
||||
|
||||
/// With what frequence will statistics be sent
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub reporting_interval: Duration,
|
||||
}
|
||||
|
||||
impl Default for StatsReportingV6 {
|
||||
fn default() -> Self {
|
||||
StatsReportingV6 {
|
||||
enabled: true,
|
||||
provider_address: None,
|
||||
reporting_interval: STATS_REPORT_INTERVAL_SECS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
|
||||
pub struct ForgetMeV6 {
|
||||
client: bool,
|
||||
stats: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
|
||||
pub struct RememberMeV6 {
|
||||
/// Signal that this client should be accounted for in the stats
|
||||
stats: bool,
|
||||
|
||||
/// Type of the session to remember, if it should be remembered
|
||||
session_type: SessionTypeV6,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize, Default, Debug)]
|
||||
pub enum SessionTypeV6 {
|
||||
Vpn,
|
||||
Mixnet,
|
||||
Wasm,
|
||||
Native,
|
||||
Socks5,
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<SessionTypeV6> for SessionType {
|
||||
fn from(value: SessionTypeV6) -> Self {
|
||||
match value {
|
||||
SessionTypeV6::Vpn => Self::Vpn,
|
||||
SessionTypeV6::Mixnet => Self::Mixnet,
|
||||
SessionTypeV6::Wasm => Self::Wasm,
|
||||
SessionTypeV6::Native => Self::Native,
|
||||
SessionTypeV6::Socks5 => Self::Socks5,
|
||||
SessionTypeV6::Unknown => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ use crate::{config, spawn_future};
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core_config_types::ForgetMe;
|
||||
use nym_client_core_config_types::{ForgetMe, RememberMe};
|
||||
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
@@ -238,6 +238,12 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_remember_me(mut self, remember_me: &RememberMe) -> Self {
|
||||
self.config.debug.remember_me = *remember_me;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
|
||||
self.setup_method = setup;
|
||||
@@ -930,6 +936,7 @@ where
|
||||
task_handle: shutdown,
|
||||
client_request_sender,
|
||||
forget_me: self.config.debug.forget_me,
|
||||
remember_me: self.config.debug.remember_me,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -944,4 +951,5 @@ pub struct BaseClient {
|
||||
pub client_request_sender: ClientRequestSender,
|
||||
pub task_handle: TaskHandle,
|
||||
pub forget_me: ForgetMe,
|
||||
pub remember_me: RememberMe,
|
||||
}
|
||||
|
||||
@@ -157,6 +157,12 @@ impl TopologyRefresher {
|
||||
let mut interval =
|
||||
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
|
||||
|
||||
// We already have an initial topology, so no need to refresh it immediately.
|
||||
// My understanding is that js setInterval does not fire immediately, so it's not
|
||||
// needed there.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
interval.next().await;
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = interval.next() => {
|
||||
|
||||
@@ -70,6 +70,10 @@ impl NymApiTopologyProvider {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_bincode(&mut self) {
|
||||
self.validator_client.use_bincode = false;
|
||||
}
|
||||
|
||||
fn use_next_nym_api(&mut self) {
|
||||
if self.nym_api_urls.len() == 1 {
|
||||
warn!("There's only a single nym API available - it won't be possible to use a different one");
|
||||
@@ -82,20 +86,13 @@ impl NymApiTopologyProvider {
|
||||
}
|
||||
|
||||
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
|
||||
let rewarded_set = self
|
||||
.validator_client
|
||||
.get_current_rewarded_set()
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to get current rewarded set: {err}"))
|
||||
.ok()?;
|
||||
let rewarded_set_fut = self.validator_client.get_current_rewarded_set();
|
||||
|
||||
let mut topology = NymTopology::new_empty(rewarded_set);
|
||||
let topology = if self.config.use_extended_topology {
|
||||
let all_nodes_fut = self.validator_client.get_all_basic_nodes();
|
||||
|
||||
if self.config.use_extended_topology {
|
||||
let all_nodes = self
|
||||
.validator_client
|
||||
.get_all_basic_nodes()
|
||||
.await
|
||||
// Join rewarded_set_fut and all_nodes_fut concurrently
|
||||
let (rewarded_set, all_nodes) = futures::try_join!(rewarded_set_fut, all_nodes_fut)
|
||||
.inspect_err(|err| error!("failed to get network nodes: {err}"))
|
||||
.ok()?;
|
||||
|
||||
@@ -103,26 +100,28 @@ impl NymApiTopologyProvider {
|
||||
"there are {} nodes on the network (before filtering)",
|
||||
all_nodes.len()
|
||||
);
|
||||
let mut topology = NymTopology::new_empty(rewarded_set);
|
||||
topology.add_additional_nodes(all_nodes.iter().filter(|n| {
|
||||
n.performance.round_to_integer() >= self.config.min_node_performance()
|
||||
}));
|
||||
|
||||
topology
|
||||
} else {
|
||||
// if we're not using extended topology, we're only getting active set mixnodes and gateways
|
||||
|
||||
let mixnodes = self
|
||||
let mixnodes_fut = self
|
||||
.validator_client
|
||||
.get_all_basic_active_mixing_assigned_nodes()
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to get network mixnodes: {err}"))
|
||||
.ok()?;
|
||||
.get_all_basic_active_mixing_assigned_nodes();
|
||||
|
||||
// TODO: we really should be getting ACTIVE gateways only
|
||||
let gateways = self
|
||||
.validator_client
|
||||
.get_all_basic_entry_assigned_nodes()
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to get network gateways: {err}"))
|
||||
.ok()?;
|
||||
let gateways_fut = self.validator_client.get_all_basic_entry_assigned_nodes();
|
||||
|
||||
let (rewarded_set, mixnodes, gateways) =
|
||||
futures::try_join!(rewarded_set_fut, mixnodes_fut, gateways_fut)
|
||||
.inspect_err(|err| {
|
||||
error!("failed to get network nodes: {err}");
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
debug!(
|
||||
"there are {} mixnodes and {} gateways in total (before performance filtering)",
|
||||
@@ -130,12 +129,15 @@ impl NymApiTopologyProvider {
|
||||
gateways.len()
|
||||
);
|
||||
|
||||
let mut topology = NymTopology::new_empty(rewarded_set);
|
||||
topology.add_additional_nodes(mixnodes.iter().filter(|m| {
|
||||
m.performance.round_to_integer() >= self.config.min_mixnode_performance
|
||||
}));
|
||||
topology.add_additional_nodes(gateways.iter().filter(|m| {
|
||||
m.performance.round_to_integer() >= self.config.min_gateway_performance
|
||||
}));
|
||||
|
||||
topology
|
||||
};
|
||||
|
||||
if !topology.is_minimally_routable() {
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
pub use nym_client_core_config_types::disk_persistence;
|
||||
pub use nym_client_core_config_types::old::{
|
||||
old_config_v1_1_13, old_config_v1_1_20, old_config_v1_1_20_2, old_config_v1_1_30,
|
||||
old_config_v1_1_33,
|
||||
old_config_v1_1_33, old_config_v1_1_54,
|
||||
};
|
||||
pub use nym_client_core_config_types::*;
|
||||
|
||||
@@ -10,7 +10,7 @@ use futures::channel::oneshot;
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::{ServerResponse, SimpleGatewayRequestsError};
|
||||
use nym_gateway_requests::{SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError};
|
||||
use nym_task::TaskClient;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
@@ -188,6 +188,34 @@ impl PartiallyDelegatedRouter {
|
||||
}
|
||||
}
|
||||
}
|
||||
ServerResponse::EncryptedResponse { ciphertext, nonce } => {
|
||||
match SensitiveServerResponse::decrypt(
|
||||
&ciphertext,
|
||||
&nonce,
|
||||
self.shared_key.as_ref(),
|
||||
) {
|
||||
Ok(response) => match response {
|
||||
SensitiveServerResponse::ForgetMeAck {} => {
|
||||
info!("received forget me acknowledgement");
|
||||
}
|
||||
SensitiveServerResponse::RememberMeAck {} => {
|
||||
info!("received remember me acknowledgement");
|
||||
}
|
||||
SensitiveServerResponse::KeyUpgradeAck {} => {
|
||||
warn!(
|
||||
"received illegal key upgrade acknowledgement in an authenticated client"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
warn!("received unknown SensitiveServerResponse");
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("failed to handle encrypted response: {e}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
other => {
|
||||
let name = other.name();
|
||||
warn!("received illegal message of type '{name}' in an authenticated client");
|
||||
|
||||
@@ -16,9 +16,14 @@ tokio-util = { workspace = true, features = ["codec"], optional = true }
|
||||
tokio-stream = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-noise = { path = "../../nymnoise" }
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-task = { path = "../../task", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["client"]
|
||||
client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"]
|
||||
client = ["tokio-util", "nym-task", "tokio/net", "tokio/rt"]
|
||||
|
||||
[dev-dependencies]
|
||||
nym-crypto = { path = "../../crypto" }
|
||||
rand = { workspace = true }
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
use dashmap::DashMap;
|
||||
use futures::StreamExt;
|
||||
use nym_noise::config::NoiseConfig;
|
||||
use nym_noise::upgrade_noise_initiator;
|
||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_sphinx::framing::codec::NymCodec;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
@@ -59,6 +61,7 @@ pub trait SendWithoutResponse {
|
||||
|
||||
pub struct Client {
|
||||
active_connections: ActiveConnections,
|
||||
noise_config: NoiseConfig,
|
||||
connections_count: Arc<AtomicUsize>,
|
||||
config: Config,
|
||||
}
|
||||
@@ -104,6 +107,7 @@ impl ConnectionSender {
|
||||
|
||||
struct ManagedConnection {
|
||||
address: SocketAddr,
|
||||
noise_config: NoiseConfig,
|
||||
message_receiver: ReceiverStream<FramedNymPacket>,
|
||||
connection_timeout: Duration,
|
||||
current_reconnection: Arc<AtomicU32>,
|
||||
@@ -112,12 +116,14 @@ struct ManagedConnection {
|
||||
impl ManagedConnection {
|
||||
fn new(
|
||||
address: SocketAddr,
|
||||
noise_config: NoiseConfig,
|
||||
message_receiver: mpsc::Receiver<FramedNymPacket>,
|
||||
connection_timeout: Duration,
|
||||
current_reconnection: Arc<AtomicU32>,
|
||||
) -> Self {
|
||||
ManagedConnection {
|
||||
address,
|
||||
noise_config,
|
||||
message_receiver: ReceiverStream::new(message_receiver),
|
||||
connection_timeout,
|
||||
current_reconnection,
|
||||
@@ -132,9 +138,21 @@ impl ManagedConnection {
|
||||
Ok(stream_res) => match stream_res {
|
||||
Ok(stream) => {
|
||||
debug!("Managed to establish connection to {}", self.address);
|
||||
// if we managed to connect, reset the reconnection count (whatever it might have been)
|
||||
|
||||
let noise_stream =
|
||||
match upgrade_noise_initiator(stream, &self.noise_config).await {
|
||||
Ok(noise_stream) => noise_stream,
|
||||
Err(err) => {
|
||||
error!("Failed to perform Noise handshake with {address} - {err}");
|
||||
// we failed to finish the noise handshake - increase reconnection attempt
|
||||
self.current_reconnection.fetch_add(1, Ordering::SeqCst);
|
||||
return;
|
||||
}
|
||||
};
|
||||
// if we managed to connect AND do the noise handshake, reset the reconnection count (whatever it might have been)
|
||||
self.current_reconnection.store(0, Ordering::Release);
|
||||
Framed::new(stream, NymCodec)
|
||||
debug!("Noise initiator handshake completed for {:?}", address);
|
||||
Framed::new(noise_stream, NymCodec)
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("failed to establish connection to {address} (err: {err})",);
|
||||
@@ -167,9 +185,14 @@ impl ManagedConnection {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(config: Config, connections_count: Arc<AtomicUsize>) -> Client {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
noise_config: NoiseConfig,
|
||||
connections_count: Arc<AtomicUsize>,
|
||||
) -> Client {
|
||||
Client {
|
||||
active_connections: Default::default(),
|
||||
noise_config,
|
||||
connections_count,
|
||||
config,
|
||||
}
|
||||
@@ -224,6 +247,7 @@ impl Client {
|
||||
let initial_connection_timeout = self.config.initial_connection_timeout;
|
||||
|
||||
let connections_count = self.connections_count.clone();
|
||||
let noise_config = self.noise_config.clone();
|
||||
tokio::spawn(async move {
|
||||
// before executing the manager, wait for what was specified, if anything
|
||||
if let Some(backoff) = backoff {
|
||||
@@ -234,6 +258,7 @@ impl Client {
|
||||
connections_count.fetch_add(1, Ordering::SeqCst);
|
||||
ManagedConnection::new(
|
||||
address.into(),
|
||||
noise_config,
|
||||
receiver,
|
||||
initial_connection_timeout,
|
||||
current_reconnection_attempt,
|
||||
@@ -302,8 +327,12 @@ impl SendWithoutResponse for Client {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_noise::config::NoiseNetworkView;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
fn dummy_client() -> Client {
|
||||
let mut rng = OsRng; //for test only, so we don't care if rng source isn't crypto grade
|
||||
Client::new(
|
||||
Config {
|
||||
initial_reconnection_backoff: Duration::from_millis(10_000),
|
||||
@@ -311,6 +340,11 @@ mod tests {
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
},
|
||||
NoiseConfig::new(
|
||||
Arc::new(x25519::KeyPair::new(&mut rng)),
|
||||
NoiseNetworkView::new_empty(),
|
||||
Duration::from_millis(1_500),
|
||||
),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ use nym_api_requests::models::{
|
||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SkimmedNode};
|
||||
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SemiSkimmedNode, SkimmedNode};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
@@ -345,25 +345,47 @@ impl<C, S> Client<C, S> {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NymApiClient {
|
||||
pub use_bincode: bool,
|
||||
pub nym_api: nym_api::Client,
|
||||
// TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
|
||||
// we could re-implement the communication with the REST API on port 1317
|
||||
}
|
||||
|
||||
impl From<nym_api::Client> for NymApiClient {
|
||||
fn from(nym_api: nym_api::Client) -> Self {
|
||||
NymApiClient {
|
||||
use_bincode: false,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
|
||||
#[allow(deprecated)]
|
||||
impl NymApiClient {
|
||||
pub fn new(api_url: Url) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, None);
|
||||
|
||||
NymApiClient { nym_api }
|
||||
NymApiClient {
|
||||
use_bincode: true,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, Some(timeout));
|
||||
|
||||
NymApiClient { nym_api }
|
||||
NymApiClient {
|
||||
use_bincode: true,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_bincode(mut self, use_bincode: bool) -> Self {
|
||||
self.use_bincode = use_bincode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
|
||||
@@ -373,7 +395,10 @@ impl NymApiClient {
|
||||
.build::<ValidatorClientError>()
|
||||
.expect("failed to build nym api client");
|
||||
|
||||
NymApiClient { nym_api }
|
||||
NymApiClient {
|
||||
use_bincode: false,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn api_url(&self) -> &Url {
|
||||
@@ -410,7 +435,7 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_entry_assigned_nodes(false, Some(page), None)
|
||||
.get_basic_entry_assigned_nodes(false, Some(page), None, self.use_bincode)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
@@ -436,7 +461,7 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_active_mixing_assigned_nodes(false, Some(page), None)
|
||||
.get_basic_active_mixing_assigned_nodes(false, Some(page), None, self.use_bincode)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
@@ -462,7 +487,7 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_mixing_capable_nodes(false, Some(page), None)
|
||||
.get_basic_mixing_capable_nodes(false, Some(page), None, self.use_bincode)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
@@ -485,7 +510,32 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_nodes(false, Some(page), None)
|
||||
.get_basic_nodes(false, Some(page), None, self.use_bincode)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
/// retrieve expanded information for all bonded nodes on the network
|
||||
pub async fn get_all_expanded_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<SemiSkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut nodes = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_expanded_nodes(false, Some(page), None)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
|
||||
@@ -19,6 +19,7 @@ use nym_api_requests::models::{
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
|
||||
SemiSkimmedNode,
|
||||
};
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
pub use nym_api_requests::{
|
||||
@@ -318,6 +319,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
@@ -333,7 +335,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
if use_bincode {
|
||||
params.push(("output", "bincode".to_string()))
|
||||
}
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
@@ -355,6 +361,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
@@ -370,7 +377,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
if use_bincode {
|
||||
params.push(("output", "bincode".to_string()))
|
||||
}
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
@@ -392,6 +403,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
@@ -407,7 +419,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
if use_bincode {
|
||||
params.push(("output", "bincode".to_string()))
|
||||
}
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
@@ -427,6 +443,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
use_bincode: bool,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
@@ -442,12 +459,49 @@ pub trait NymApiClientExt: ApiClient {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
if use_bincode {
|
||||
params.push(("output", "bincode".to_string()))
|
||||
}
|
||||
|
||||
self.get_response(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_expanded_nodes(
|
||||
&self,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedCachedNodesResponse<SemiSkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
"semi-skimmed",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
|
||||
@@ -256,6 +256,10 @@ impl PrivateKey {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8; PRIVATE_KEY_SIZE] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
|
||||
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
|
||||
if b.len() != PRIVATE_KEY_SIZE {
|
||||
return Err(KeyRecoveryError::InvalidSizePrivateKey {
|
||||
|
||||
@@ -28,6 +28,7 @@ nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
nym-task = { path = "../task" }
|
||||
|
||||
nym-credentials = { path = "../credentials" }
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::{
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_statistics_common::types::SessionType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use tungstenite::Message;
|
||||
@@ -29,6 +30,9 @@ pub enum ClientRequest {
|
||||
client: bool,
|
||||
stats: bool,
|
||||
},
|
||||
RememberMe {
|
||||
session_type: SessionType,
|
||||
},
|
||||
}
|
||||
|
||||
impl ClientRequest {
|
||||
|
||||
@@ -12,6 +12,7 @@ use tungstenite::Message;
|
||||
pub enum SensitiveServerResponse {
|
||||
KeyUpgradeAck {},
|
||||
ForgetMeAck {},
|
||||
RememberMeAck {},
|
||||
}
|
||||
|
||||
impl SensitiveServerResponse {
|
||||
|
||||
@@ -22,7 +22,6 @@ thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-node-metrics = { path = "../../nym-node/nym-node-metrics" }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
ALTER TABLE sessions_active
|
||||
ADD COLUMN remember INTEGER NOT NULL default 0;
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
use error::StatsStorageError;
|
||||
use models::StoredFinishedSession;
|
||||
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
|
||||
use nym_node_metrics::entry::{ActiveSession, FinishedSession};
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_statistics_common::types::SessionType;
|
||||
use sessions::SessionManager;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
@@ -147,6 +148,16 @@ impl PersistentStatsStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn remember_active_session(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.remember_active_session(client_address.as_base58_string())
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn update_active_session_type(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
|
||||
use nym_node_metrics::entry::{ActiveSession, FinishedSession};
|
||||
use nym_statistics_common::types::SessionType;
|
||||
use sqlx::prelude::FromRow;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub use nym_credentials_interface::TicketType;
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub struct StoredFinishedSession {
|
||||
duration_ms: i64,
|
||||
@@ -22,25 +21,11 @@ impl From<StoredFinishedSession> for FinishedSession {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToSessionType {
|
||||
fn to_session_type(&self) -> SessionType;
|
||||
}
|
||||
|
||||
impl ToSessionType for TicketType {
|
||||
fn to_session_type(&self) -> SessionType {
|
||||
match self {
|
||||
TicketType::V1MixnetEntry => SessionType::Mixnet,
|
||||
TicketType::V1MixnetExit => SessionType::Mixnet,
|
||||
TicketType::V1WireguardEntry => SessionType::Vpn,
|
||||
TicketType::V1WireguardExit => SessionType::Vpn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub(crate) struct StoredActiveSession {
|
||||
start_time: OffsetDateTime,
|
||||
typ: String,
|
||||
remember: u8,
|
||||
}
|
||||
|
||||
impl From<StoredActiveSession> for ActiveSession {
|
||||
@@ -48,6 +33,7 @@ impl From<StoredActiveSession> for ActiveSession {
|
||||
ActiveSession {
|
||||
start: value.start_time,
|
||||
typ: SessionType::from_string(&value.typ),
|
||||
remember: value.remember != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ impl SessionManager {
|
||||
typ: String,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"INSERT INTO sessions_active (client_address, start_time, typ) VALUES (?, ?, ?)",
|
||||
"INSERT INTO sessions_active (client_address, start_time, typ, remember) VALUES (?, ?, ?, 0)",
|
||||
client_address_b58,
|
||||
start_time,
|
||||
typ
|
||||
@@ -117,6 +117,16 @@ impl SessionManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remember_active_session(&self, client_address_b58: String) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"UPDATE sessions_active SET remember = 1 WHERE client_address = ?",
|
||||
client_address_b58,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_active_session_type(
|
||||
&self,
|
||||
client_address_b58: String,
|
||||
@@ -136,14 +146,16 @@ impl SessionManager {
|
||||
&self,
|
||||
client_address_b58: String,
|
||||
) -> Result<Option<StoredActiveSession>> {
|
||||
sqlx::query_as("SELECT start_time, typ FROM sessions_active WHERE client_address = ?")
|
||||
.bind(client_address_b58)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
sqlx::query_as(
|
||||
"SELECT start_time, typ, remember FROM sessions_active WHERE client_address = ?",
|
||||
)
|
||||
.bind(client_address_b58)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_all_active_sessions(&self) -> Result<Vec<StoredActiveSession>> {
|
||||
sqlx::query_as("SELECT start_time, typ FROM sessions_active")
|
||||
sqlx::query_as("SELECT start_time, typ, remember FROM sessions_active")
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "gzip"] }
|
||||
bincode = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "gzip", "deflate", "brotli", "zstd"] }
|
||||
http.workspace = true
|
||||
url = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
@@ -26,7 +27,7 @@ bytes = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
|
||||
|
||||
nym-http-api-common = { path = "../http-api-common", default-features = false }
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
@@ -39,3 +40,4 @@ features = ["tokio"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
|
||||
|
||||
@@ -136,19 +136,23 @@
|
||||
//! ```
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub use reqwest::{IntoUrl, StatusCode};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::{RequestBuilder, Response, StatusCode};
|
||||
use reqwest::{RequestBuilder, Response};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tracing::{instrument, warn};
|
||||
use tracing::{debug, instrument, warn};
|
||||
use url::Url;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use http::HeaderMap;
|
||||
pub use reqwest::IntoUrl;
|
||||
use mime::Mime;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -210,17 +214,36 @@ pub enum HttpClientError<E: Display = String> {
|
||||
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
|
||||
EndpointFailure { status: StatusCode, error: E },
|
||||
|
||||
#[error("failed to decode response body: {source} from {content}")]
|
||||
ResponseDecodeFailure {
|
||||
source: serde_json::Error,
|
||||
content: String,
|
||||
},
|
||||
#[error("failed to decode response body: {message} from {content}")]
|
||||
ResponseDecodeFailure { message: String, content: String },
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("the request has timed out")]
|
||||
RequestTimeout,
|
||||
}
|
||||
|
||||
impl HttpClientError {
|
||||
/// Returns true if the error is a timeout.
|
||||
pub fn is_timeout(&self) -> bool {
|
||||
match self {
|
||||
HttpClientError::ReqwestClientError { source } => source.is_timeout(),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
HttpClientError::RequestTimeout => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the HTTP status code if available.
|
||||
pub fn status_code(&self) -> Option<StatusCode> {
|
||||
match self {
|
||||
HttpClientError::RequestFailure { status } => Some(*status),
|
||||
HttpClientError::EmptyResponse { status } => Some(*status),
|
||||
HttpClientError::EndpointFailure { status, .. } => Some(*status),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `ClientBuilder` can be used to create a [`Client`] with custom configuration applied consistently
|
||||
/// and state tracked across subsequent requests.
|
||||
pub struct ClientBuilder {
|
||||
@@ -265,14 +288,18 @@ impl ClientBuilder {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client_builder = {
|
||||
let r = reqwest::ClientBuilder::new();
|
||||
|
||||
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
|
||||
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
|
||||
// Note: I believe the manual enable calls for the compression methods are extra
|
||||
// as the various compression features for `reqwest` crate should be enabled
|
||||
// just by including the feature which:
|
||||
// `"Enable[s] auto decompression by checking the Content-Encoding response header."`
|
||||
//
|
||||
// I am going to leave it here anyways so that gzip decompression is attempted even if
|
||||
// that feature is removed.
|
||||
r.gzip(true)
|
||||
// I am going to leave these here anyways so that removing a decompression method
|
||||
// from the features list will throw an error if it is not also removed here.
|
||||
reqwest::ClientBuilder::new()
|
||||
.gzip(true)
|
||||
.deflate(true)
|
||||
.brotli(true)
|
||||
.zstd(true)
|
||||
};
|
||||
|
||||
ClientBuilder {
|
||||
@@ -519,6 +546,7 @@ pub trait ApiClientCore {
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl ApiClientCore for Client {
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
fn create_request<B, K, V>(
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
@@ -535,11 +563,6 @@ impl ApiClientCore for Client {
|
||||
|
||||
let mut request = self.reqwest_client.request(method.clone(), url);
|
||||
|
||||
// Indicate that compressed responses are preferred, but if not supported other encodings are fine.
|
||||
// TODO: Down the road we can be more selective about adding this, but it's inclusion here guarantees
|
||||
// that we use compression when available.
|
||||
request = request.header(reqwest::header::ACCEPT_ENCODING, "gzip;q=1.0, *;q=0.5");
|
||||
|
||||
if let Some(body) = json_body {
|
||||
request = request.json(body);
|
||||
}
|
||||
@@ -697,12 +720,30 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// 'get' json data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
|
||||
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
|
||||
/// into the provided type `T`.
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
// TODO: deprecate in favour of get_response that works based on mime type in the response
|
||||
async fn get_json<T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
self.get_response(path, params).await
|
||||
}
|
||||
|
||||
/// 'get' data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
|
||||
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
|
||||
/// into the provided type `T` based on the content type header
|
||||
async fn get_response<T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
@@ -877,14 +918,10 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
url
|
||||
}
|
||||
|
||||
fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
|
||||
fn decode_as_text(bytes: &bytes::Bytes, headers: &HeaderMap) -> String {
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use mime::Mime;
|
||||
|
||||
let content_type = headers
|
||||
.get(http::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<Mime>().ok());
|
||||
let content_type = try_get_mime_type(headers);
|
||||
|
||||
let encoding_name = content_type
|
||||
.as_ref()
|
||||
@@ -897,7 +934,7 @@ fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
|
||||
text.into_owned()
|
||||
}
|
||||
|
||||
/// Attempt to parse a json object from an HTTP response
|
||||
/// Attempt to parse a response object from an HTTP response
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
@@ -919,16 +956,7 @@ where
|
||||
// internally reqwest is first retrieving bytes and then performing parsing via serde_json
|
||||
// (and similarly does the same thing for text())
|
||||
let full = res.bytes().await?;
|
||||
match serde_json::from_slice(&full) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(err) => {
|
||||
let content = decode_as_text(&full, headers);
|
||||
Err(HttpClientError::ResponseDecodeFailure {
|
||||
source: err,
|
||||
content,
|
||||
})
|
||||
}
|
||||
}
|
||||
decode_raw_response(&headers, full)
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(HttpClientError::NotFound)
|
||||
} else {
|
||||
@@ -947,6 +975,71 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_as_json<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
{
|
||||
match serde_json::from_slice(&content) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(err) => {
|
||||
let content = decode_as_text(&content, headers);
|
||||
Err(HttpClientError::ResponseDecodeFailure {
|
||||
message: err.to_string(),
|
||||
content,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_as_bincode<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
{
|
||||
use bincode::Options;
|
||||
|
||||
let opts = nym_http_api_common::make_bincode_serializer();
|
||||
match opts.deserialize(&content) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(err) => {
|
||||
let content = decode_as_text(&content, headers);
|
||||
Err(HttpClientError::ResponseDecodeFailure {
|
||||
message: err.to_string(),
|
||||
content,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_raw_response<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
{
|
||||
// if content type header is missing, fallback to our old default, json
|
||||
let mime = try_get_mime_type(headers).unwrap_or(mime::APPLICATION_JSON);
|
||||
|
||||
debug!("attempting to parse response as {mime}");
|
||||
|
||||
// unfortunately we can't use stronger typing for subtype as "bincode" is not a defined mime type
|
||||
match (mime.type_(), mime.subtype().as_str()) {
|
||||
(mime::APPLICATION, "json") => decode_as_json(headers, content),
|
||||
(mime::APPLICATION, "bincode") => decode_as_bincode(headers, content),
|
||||
(_, _) => {
|
||||
debug!("unrecognised mime type {mime}. falling back to json decoding...");
|
||||
decode_as_json(headers, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_get_mime_type(headers: &HeaderMap) -> Option<Mime> {
|
||||
headers
|
||||
.get(CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<Mime>().ok())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -11,20 +11,44 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
axum-client-ip.workspace = true
|
||||
axum.workspace = true
|
||||
bytes = { workspace = true }
|
||||
colored.workspace = true
|
||||
futures = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
axum = { workspace = true, optional = true }
|
||||
axum-client-ip = { workspace = true, optional = true }
|
||||
bincode = { workspace = true }
|
||||
bytes = { workspace = true, optional = true }
|
||||
colored = { workspace = true, optional = true }
|
||||
futures = { workspace = true, optional = true }
|
||||
mime = { workspace = true, optional = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
serde_yaml = { workspace = true }
|
||||
subtle.workspace = true
|
||||
tower = { workspace = true }
|
||||
serde_yaml = { workspace = true, optional = true }
|
||||
subtle = { workspace = true, optional = true }
|
||||
time = { workspace = true, optional = true, features = ["macros"] }
|
||||
tower = { workspace = true, optional = true }
|
||||
tracing.workspace = true
|
||||
utoipa = { workspace = true, optional = true }
|
||||
zeroize = { workspace = true }
|
||||
zeroize = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
output = [
|
||||
"axum",
|
||||
"bytes",
|
||||
"mime",
|
||||
"serde_yaml",
|
||||
"time",
|
||||
"time/formatting"
|
||||
]
|
||||
|
||||
middleware = [
|
||||
"axum",
|
||||
"axum-client-ip",
|
||||
"colored",
|
||||
"futures",
|
||||
"subtle",
|
||||
"tower",
|
||||
"zeroize"
|
||||
]
|
||||
|
||||
utoipa = ["dep:utoipa"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,92 +1,20 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2023-2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use axum::http::{header, HeaderValue, StatusCode};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::Json;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "middleware")]
|
||||
pub mod middleware;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FormattedResponse<T> {
|
||||
Json(Json<T>),
|
||||
Yaml(Yaml<T>),
|
||||
}
|
||||
#[cfg(feature = "output")]
|
||||
pub mod response;
|
||||
|
||||
impl<T> IntoResponse for FormattedResponse<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
FormattedResponse::Json(json_response) => json_response.into_response(),
|
||||
FormattedResponse::Yaml(yaml_response) => yaml_response.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
// don't break existing imports
|
||||
#[cfg(feature = "output")]
|
||||
pub use response::*;
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Output {
|
||||
#[default]
|
||||
Json,
|
||||
Yaml,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams, utoipa::ToSchema))]
|
||||
#[serde(default)]
|
||||
pub struct OutputParams {
|
||||
pub output: Option<Output>,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn to_response<T: Serialize>(self, data: T) -> FormattedResponse<T> {
|
||||
match self {
|
||||
Output::Json => FormattedResponse::Json(Json(data)),
|
||||
Output::Yaml => FormattedResponse::Yaml(Yaml(data)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
#[must_use]
|
||||
pub struct Yaml<T>(pub T);
|
||||
|
||||
impl<T> From<T> for Yaml<T> {
|
||||
fn from(inner: T) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for Yaml<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
// replicates axum's Json
|
||||
fn into_response(self) -> Response {
|
||||
let mut buf = BytesMut::with_capacity(128).writer();
|
||||
match serde_yaml::to_writer(&mut buf, &self.0) {
|
||||
Ok(()) => (
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/yaml"),
|
||||
)],
|
||||
buf.into_inner().freeze(),
|
||||
)
|
||||
.into_response(),
|
||||
Err(err) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
|
||||
)],
|
||||
err.to_string(),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
// be explicit about those values because bincode uses different defaults in different places
|
||||
pub fn make_bincode_serializer() -> impl ::bincode::Options {
|
||||
use ::bincode::Options;
|
||||
::bincode::DefaultOptions::new()
|
||||
.with_little_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
|
||||
@@ -9,13 +9,35 @@ use axum::response::IntoResponse;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use colored::Colorize;
|
||||
use std::time::Instant;
|
||||
use tracing::info;
|
||||
use tracing::{debug, info};
|
||||
|
||||
enum LogLevel {
|
||||
Debug,
|
||||
Info,
|
||||
}
|
||||
|
||||
pub async fn log_request_info(
|
||||
insecure_client_ip: InsecureClientIp,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> impl IntoResponse {
|
||||
log_request(insecure_client_ip, request, next, LogLevel::Info).await
|
||||
}
|
||||
|
||||
pub async fn log_request_debug(
|
||||
insecure_client_ip: InsecureClientIp,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> impl IntoResponse {
|
||||
log_request(insecure_client_ip, request, next, LogLevel::Debug).await
|
||||
}
|
||||
|
||||
/// Simple logger for requests
|
||||
pub async fn logger(
|
||||
async fn log_request(
|
||||
InsecureClientIp(addr): InsecureClientIp,
|
||||
request: Request,
|
||||
next: Next,
|
||||
level: LogLevel,
|
||||
) -> impl IntoResponse {
|
||||
// TODO dz use `OriginalUri` extractor to get full URI even for nested
|
||||
// routers if routes aren't logged correctly in handlers
|
||||
@@ -58,7 +80,14 @@ pub async fn logger(
|
||||
|
||||
let agent_str = "agent".bold();
|
||||
|
||||
info!("[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}");
|
||||
match level {
|
||||
LogLevel::Debug => debug!(
|
||||
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"
|
||||
),
|
||||
LogLevel::Info => info!(
|
||||
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"
|
||||
),
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::response::{error_response, ResponseWrapper};
|
||||
use axum::http::header::IntoHeaderName;
|
||||
use axum::http::{header, HeaderValue};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[must_use]
|
||||
pub struct Bincode<T>(pub(crate) ResponseWrapper<T>);
|
||||
|
||||
impl<T> From<T> for Bincode<T> {
|
||||
fn from(response: T) -> Self {
|
||||
Bincode(ResponseWrapper::new(response).with_header(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/bincode"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Bincode<T> {
|
||||
pub(crate) fn with_header(
|
||||
mut self,
|
||||
name: impl IntoHeaderName,
|
||||
value: impl Into<HeaderValue>,
|
||||
) -> Self {
|
||||
self.0.headers.insert(name, value.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for Bincode<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
// replicates axum's Json
|
||||
fn into_response(self) -> Response {
|
||||
use bincode::Options;
|
||||
let mut buf = BytesMut::with_capacity(128).writer();
|
||||
|
||||
match crate::make_bincode_serializer().serialize_into(&mut buf, &self.0.data) {
|
||||
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
|
||||
Err(err) => error_response(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::response::{error_response, ResponseWrapper};
|
||||
use axum::http::header::IntoHeaderName;
|
||||
use axum::http::{header, HeaderValue};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use serde::Serialize;
|
||||
use utoipa::gen::serde_json;
|
||||
|
||||
// don't use axum's Json directly as we need to be able to define custom headers
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[must_use]
|
||||
pub struct Json<T>(pub(crate) ResponseWrapper<T>);
|
||||
|
||||
impl<T> From<T> for Json<T> {
|
||||
fn from(response: T) -> Self {
|
||||
Json(ResponseWrapper::new(response).with_header(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Json<T> {
|
||||
pub(crate) fn with_header(
|
||||
mut self,
|
||||
name: impl IntoHeaderName,
|
||||
value: impl Into<HeaderValue>,
|
||||
) -> Self {
|
||||
self.0.headers.insert(name, value.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for Json<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let mut buf = BytesMut::with_capacity(128).writer();
|
||||
|
||||
match serde_json::to_writer(&mut buf, &self.0.data) {
|
||||
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
|
||||
Err(err) => error_response(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use axum::http::header::IntoHeaderName;
|
||||
use axum::http::{header, HeaderMap, HeaderValue, StatusCode};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use time::format_description::BorrowedFormatItem;
|
||||
use time::macros::{format_description, offset};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub mod bincode;
|
||||
pub mod json;
|
||||
pub mod yaml;
|
||||
|
||||
pub use json::Json;
|
||||
pub use yaml::Yaml;
|
||||
|
||||
pub use bincode::Bincode;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct ResponseWrapper<T> {
|
||||
data: T,
|
||||
headers: HeaderMap,
|
||||
}
|
||||
|
||||
impl<T> ResponseWrapper<T> {
|
||||
pub(crate) fn new(response: T) -> ResponseWrapper<T> {
|
||||
ResponseWrapper {
|
||||
data: response,
|
||||
headers: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn with_header(
|
||||
mut self,
|
||||
name: impl IntoHeaderName,
|
||||
value: impl Into<HeaderValue>,
|
||||
) -> Self {
|
||||
self.headers.insert(name, value.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum FormattedResponse<T> {
|
||||
Json(Json<T>),
|
||||
Yaml(Yaml<T>),
|
||||
Bincode(Bincode<T>),
|
||||
}
|
||||
|
||||
impl<T> FormattedResponse<T> {
|
||||
pub fn into_inner(self) -> T {
|
||||
match self {
|
||||
FormattedResponse::Json(inner) => inner.0.data,
|
||||
FormattedResponse::Yaml(inner) => inner.0.data,
|
||||
FormattedResponse::Bincode(inner) => inner.0.data,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_header(
|
||||
self,
|
||||
name: impl IntoHeaderName,
|
||||
value: impl Into<HeaderValue>,
|
||||
) -> FormattedResponse<T> {
|
||||
match self {
|
||||
FormattedResponse::Json(inner) => {
|
||||
FormattedResponse::Json(inner.with_header(name, value))
|
||||
}
|
||||
FormattedResponse::Yaml(inner) => {
|
||||
FormattedResponse::Yaml(inner.with_header(name, value))
|
||||
}
|
||||
FormattedResponse::Bincode(inner) => {
|
||||
FormattedResponse::Bincode(inner.with_header(name, value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the `expires` header on the response to the provided expiration.
|
||||
/// Internally it will perform conversions to make sure the value is set in GMT offset,
|
||||
/// e.g. `Expires: Wed, 21 Oct 2015 07:28:00 GMT`
|
||||
#[must_use]
|
||||
pub fn with_expires_header(self, expiration: OffsetDateTime) -> FormattedResponse<T> {
|
||||
// as per RFC-7234 (section 5.3) EXPIRES header has to use value formatted
|
||||
// as defined in RFC-7231 (section 7.1.1.1)
|
||||
// (preferred format (IMF-fixdate) uses RFC-5322 (section 3.3)
|
||||
let formatted = format_rfc5352(expiration);
|
||||
|
||||
// SAFETY: our formatted datetime doesn't contain forbidden characters
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.with_header(header::EXPIRES, HeaderValue::try_from(formatted).unwrap())
|
||||
}
|
||||
|
||||
/// Work similarly to `with_expires_header`, but rather than setting explicit expiration value,
|
||||
/// it adds the provided time delta to the current time instead.
|
||||
#[must_use]
|
||||
pub fn with_expires_header_delta(self, expires_in: Duration) -> FormattedResponse<T> {
|
||||
self.with_expires_header(OffsetDateTime::now_utc() + expires_in)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for FormattedResponse<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
FormattedResponse::Json(json_response) => json_response.into_response(),
|
||||
FormattedResponse::Yaml(yaml_response) => yaml_response.into_response(),
|
||||
FormattedResponse::Bincode(bincode_response) => bincode_response.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Output {
|
||||
#[default]
|
||||
Json,
|
||||
Yaml,
|
||||
Bincode,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams, utoipa::ToSchema))]
|
||||
#[serde(default)]
|
||||
pub struct OutputParams {
|
||||
pub output: Option<Output>,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn to_response<T: Serialize>(self, data: T) -> FormattedResponse<T> {
|
||||
match self {
|
||||
Output::Json => FormattedResponse::Json(Json::from(data)),
|
||||
Output::Yaml => FormattedResponse::Yaml(Yaml::from(data)),
|
||||
Output::Bincode => FormattedResponse::Bincode(Bincode::from(data)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn error_response<E: ToString>(err: E) -> Response {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
|
||||
)],
|
||||
err.to_string(),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
// SAFETY: this hardcoded datetime formatter is valid
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn format_rfc5352(datetime: OffsetDateTime) -> String {
|
||||
// the time must be using GMT (UTC) offset
|
||||
let normalised = datetime.to_offset(offset!(UTC));
|
||||
normalised.format(&rfc5322()).unwrap()
|
||||
}
|
||||
|
||||
// NOTE: this function is purposely not made public as it cannot guarantee caller
|
||||
// has correctly ensured their date is using correct GMT offset
|
||||
fn rfc5322() -> &'static [BorrowedFormatItem<'static>] {
|
||||
// D, d M Y H:i:s T
|
||||
format_description!(
|
||||
"[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT"
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::response::format_rfc5352;
|
||||
use time::macros::datetime;
|
||||
|
||||
#[test]
|
||||
fn rfc5322_formatting() {
|
||||
let utc_date = datetime!(2021-08-23 12:13:14 UTC);
|
||||
let non_utc_date = datetime!(2021-08-23 12:13:14 -1);
|
||||
|
||||
assert_eq!("Mon, 23 Aug 2021 12:13:14 GMT", format_rfc5352(utc_date));
|
||||
assert_eq!(
|
||||
"Mon, 23 Aug 2021 13:13:14 GMT",
|
||||
format_rfc5352(non_utc_date)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::response::{error_response, ResponseWrapper};
|
||||
use axum::http::header::IntoHeaderName;
|
||||
use axum::http::{header, HeaderValue};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[must_use]
|
||||
pub struct Yaml<T>(pub(crate) ResponseWrapper<T>);
|
||||
|
||||
impl<T> From<T> for Yaml<T> {
|
||||
fn from(response: T) -> Self {
|
||||
Yaml(ResponseWrapper::new(response).with_header(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/yaml"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Yaml<T> {
|
||||
pub(crate) fn with_header(
|
||||
mut self,
|
||||
name: impl IntoHeaderName,
|
||||
value: impl Into<HeaderValue>,
|
||||
) -> Self {
|
||||
self.0.headers.insert(name, value.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for Yaml<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
// replicates axum's Json
|
||||
fn into_response(self) -> Response {
|
||||
let mut buf = BytesMut::with_capacity(128).writer();
|
||||
match serde_yaml::to_writer(&mut buf, &self.0.data) {
|
||||
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
|
||||
Err(err) => error_response(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ fn _aggregate_indices_signatures<B>(
|
||||
validate_shares: bool,
|
||||
) -> Result<Vec<CoinIndexSignature>>
|
||||
where
|
||||
B: Borrow<PartialCoinIndexSignature>,
|
||||
B: Borrow<PartialCoinIndexSignature> + Send + Sync,
|
||||
{
|
||||
// Check if all indices are unique
|
||||
if signatures_shares
|
||||
@@ -271,7 +271,7 @@ pub fn aggregate_indices_signatures<B>(
|
||||
signatures_shares: &[CoinIndexSignatureShare<B>],
|
||||
) -> Result<Vec<CoinIndexSignature>>
|
||||
where
|
||||
B: Borrow<PartialCoinIndexSignature>,
|
||||
B: Borrow<PartialCoinIndexSignature> + Send + Sync,
|
||||
{
|
||||
_aggregate_indices_signatures(params, vk, signatures_shares, true)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ impl From<AnnotatedExpirationDateSignature> for ExpirationDateSignature {
|
||||
|
||||
pub struct ExpirationDateSignatureShare<B = PartialExpirationDateSignature>
|
||||
where
|
||||
B: Borrow<PartialExpirationDateSignature>,
|
||||
B: Borrow<PartialExpirationDateSignature> + Send + Sync,
|
||||
{
|
||||
pub index: SignerIndex,
|
||||
pub key: VerificationKeyAuth,
|
||||
@@ -205,7 +205,7 @@ fn _aggregate_expiration_signatures<B>(
|
||||
validate_shares: bool,
|
||||
) -> Result<Vec<ExpirationDateSignature>>
|
||||
where
|
||||
B: Borrow<ExpirationDateSignature>,
|
||||
B: Borrow<ExpirationDateSignature> + Send + Sync,
|
||||
{
|
||||
// Check if all indices are unique
|
||||
if signatures_shares
|
||||
@@ -304,7 +304,7 @@ pub fn aggregate_expiration_signatures<B>(
|
||||
signatures_shares: &[ExpirationDateSignatureShare<B>],
|
||||
) -> Result<Vec<ExpirationDateSignature>>
|
||||
where
|
||||
B: Borrow<PartialExpirationDateSignature>,
|
||||
B: Borrow<PartialExpirationDateSignature> + Send + Sync,
|
||||
{
|
||||
_aggregate_expiration_signatures(vk, expiration_date, signatures_shares, true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "nym-noise"
|
||||
version = "0.1.0"
|
||||
authors = ["Simon Wicky <simon@nymtech.net>"]
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
arc-swap = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
pin-project = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
snow = { workspace = true }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["net", "io-util", "time"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
|
||||
# internal
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-noise-keys = { path = "keys" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "nym-noise-keys"
|
||||
version = "0.1.0"
|
||||
authors = ["Simon Wicky <simon@nymtech.net>"]
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
utoipa = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(from = "u8", into = "u8")]
|
||||
pub enum NoiseVersion {
|
||||
V1 = 1,
|
||||
Unknown, //Implies a newer version we don't know
|
||||
}
|
||||
|
||||
impl From<u8> for NoiseVersion {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => NoiseVersion::V1,
|
||||
_ => NoiseVersion::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NoiseVersion> for u8 {
|
||||
fn from(version: NoiseVersion) -> Self {
|
||||
version as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, utoipa::ToSchema)]
|
||||
pub struct VersionedNoiseKey {
|
||||
#[schemars(with = "u8")]
|
||||
#[schema(value_type = u8)]
|
||||
pub version: NoiseVersion,
|
||||
|
||||
#[schemars(with = "String")]
|
||||
#[serde(with = "bs58_x25519_pubkey")]
|
||||
#[schema(value_type = String)]
|
||||
pub x25519_pubkey: x25519::PublicKey,
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_noise_keys::{NoiseVersion, VersionedNoiseKey};
|
||||
use snow::params::NoiseParams;
|
||||
|
||||
use strum::EnumIter;
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, EnumIter)]
|
||||
pub enum NoisePattern {
|
||||
#[default]
|
||||
XKpsk3,
|
||||
IKpsk2,
|
||||
}
|
||||
|
||||
impl NoisePattern {
|
||||
pub(crate) fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::XKpsk3 => "Noise_XKpsk3_25519_AESGCM_SHA256",
|
||||
Self::IKpsk2 => "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s", //Wireguard handshake (not exactly though)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: we have tests to ensure that hardcoded pattern are correct
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub(crate) fn psk_position(&self) -> u8 {
|
||||
//automatic parsing, works for correct pattern, more convenient
|
||||
match self.as_str().find("psk") {
|
||||
Some(n) => {
|
||||
let psk_index = n + 3;
|
||||
let psk_char = self.as_str().chars().nth(psk_index).unwrap();
|
||||
psk_char.to_string().parse().unwrap()
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY : we have tests to ensure that hardcoded pattern are correct
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub(crate) fn as_noise_params(&self) -> NoiseParams {
|
||||
self.as_str().parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct SocketAddrToKey {
|
||||
inner: ArcSwap<HashMap<SocketAddr, VersionedNoiseKey>>,
|
||||
}
|
||||
|
||||
// SW NOTE : Only for phased upgrade. To remove once we decide all nodes have to support Noise
|
||||
#[derive(Debug, Default)]
|
||||
struct IpAddrToVersion {
|
||||
inner: ArcSwap<HashMap<IpAddr, NoiseVersion>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NoiseNetworkView {
|
||||
keys: Arc<SocketAddrToKey>,
|
||||
support: Arc<IpAddrToVersion>,
|
||||
}
|
||||
|
||||
impl NoiseNetworkView {
|
||||
pub fn new_empty() -> Self {
|
||||
NoiseNetworkView {
|
||||
keys: Default::default(),
|
||||
support: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap_view(&self, new: HashMap<SocketAddr, VersionedNoiseKey>) {
|
||||
let noise_support = new
|
||||
.iter()
|
||||
.map(|(s_addr, key)| (s_addr.ip(), key.version))
|
||||
.collect::<HashMap<_, _>>();
|
||||
self.keys.inner.store(Arc::new(new));
|
||||
self.support.inner.store(Arc::new(noise_support));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NoiseConfig {
|
||||
network: NoiseNetworkView,
|
||||
|
||||
pub(crate) local_key: Arc<x25519::KeyPair>,
|
||||
pub(crate) pattern: NoisePattern,
|
||||
pub(crate) timeout: Duration,
|
||||
|
||||
pub(crate) unsafe_disabled: bool, // allows for nodes to not attempt to do a noise handshake, VERY UNSAFE, FOR DEBUG PURPOSE ONLY
|
||||
}
|
||||
|
||||
impl NoiseConfig {
|
||||
pub fn new(
|
||||
noise_key: Arc<x25519::KeyPair>,
|
||||
network: NoiseNetworkView,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
NoiseConfig {
|
||||
network,
|
||||
local_key: noise_key,
|
||||
pattern: Default::default(),
|
||||
timeout,
|
||||
unsafe_disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_noise_pattern(mut self, pattern: NoisePattern) -> Self {
|
||||
self.pattern = pattern;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_unsafe_disabled(mut self, disabled: bool) -> Self {
|
||||
self.unsafe_disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn get_noise_key(&self, s_address: &SocketAddr) -> Option<VersionedNoiseKey> {
|
||||
self.network.keys.inner.load().get(s_address).copied()
|
||||
}
|
||||
|
||||
// Only for phased update
|
||||
//SW This can lead to some troubles if two nodes shares the same IP and one support Noise but not the other. This in only for the progressive update though and there is no workaround
|
||||
pub(crate) fn get_noise_support(&self, ip_addr: IpAddr) -> Option<NoiseVersion> {
|
||||
let plain_ip_support = self.network.support.inner.load().get(&ip_addr).copied();
|
||||
|
||||
// SW default bind address being [::]:1789, it can happen that a responder sees the ipv6-mapped address of the initiator, this check for that
|
||||
let canonical_ip = &ip_addr.to_canonical();
|
||||
let canonical_ip_support = self.network.support.inner.load().get(canonical_ip).copied();
|
||||
plain_ip_support.or(canonical_ip_support)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use snow::params::NoiseParams;
|
||||
|
||||
use super::NoisePattern;
|
||||
use std::str::FromStr;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
// The goal of these is to make sure every NoisePatterns are correct and unwrap can be used on them
|
||||
|
||||
#[test]
|
||||
fn noise_patterns_are_valid() {
|
||||
for pattern in NoisePattern::iter() {
|
||||
assert!(NoiseParams::from_str(pattern.as_str()).is_ok())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_patterns_psk_position_is_valid() {
|
||||
for pattern in NoisePattern::iter() {
|
||||
match pattern {
|
||||
NoisePattern::XKpsk3 => assert_eq!(pattern.psk_position(), 3),
|
||||
NoisePattern::IKpsk2 => assert_eq!(pattern.psk_position(), 2),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io;
|
||||
|
||||
use pin_project::pin_project;
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
use crate::stream::NoiseStream;
|
||||
|
||||
//SW once plain TCP support is dropped, this whole enum can be dropped, and we can only propagate NoiseStream
|
||||
#[pin_project(project = ConnectionProj)]
|
||||
pub enum Connection {
|
||||
Tcp(#[pin] TcpStream),
|
||||
Noise(#[pin] NoiseStream),
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn peer_addr(&self) -> Result<std::net::SocketAddr, io::Error> {
|
||||
match self {
|
||||
Self::Noise(stream) => stream.peer_addr(),
|
||||
Self::Tcp(stream) => stream.peer_addr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Connection {
|
||||
fn poll_read(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> std::task::Poll<io::Result<()>> {
|
||||
match self.project() {
|
||||
ConnectionProj::Noise(stream) => stream.poll_read(cx, buf),
|
||||
ConnectionProj::Tcp(stream) => stream.poll_read(cx, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Connection {
|
||||
fn poll_write(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> std::task::Poll<Result<usize, io::Error>> {
|
||||
match self.project() {
|
||||
ConnectionProj::Noise(stream) => stream.poll_write(cx, buf),
|
||||
ConnectionProj::Tcp(stream) => stream.poll_write(cx, buf),
|
||||
}
|
||||
}
|
||||
fn poll_flush(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), io::Error>> {
|
||||
match self.project() {
|
||||
ConnectionProj::Noise(stream) => stream.poll_flush(cx),
|
||||
ConnectionProj::Tcp(stream) => stream.poll_flush(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), io::Error>> {
|
||||
match self.project() {
|
||||
ConnectionProj::Noise(stream) => stream.poll_shutdown(cx),
|
||||
ConnectionProj::Tcp(stream) => stream.poll_shutdown(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use snow::Error;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum NoiseError {
|
||||
#[error("encountered a Noise decryption error")]
|
||||
DecryptionError,
|
||||
|
||||
#[error("encountered a Noise Protocol error: {0}")]
|
||||
ProtocolError(Error),
|
||||
|
||||
#[error("encountered an IO error: {0}")]
|
||||
IoError(#[from] io::Error),
|
||||
|
||||
#[error("Incorrect state")]
|
||||
IncorrectStateError,
|
||||
|
||||
#[error("Handshake did not complete")]
|
||||
HandshakeError,
|
||||
|
||||
#[error("Unknown noise version")]
|
||||
UnknownVersion,
|
||||
|
||||
#[error("Handshake timeout")]
|
||||
HandshakeTimeout(#[from] tokio::time::error::Elapsed),
|
||||
}
|
||||
|
||||
impl From<Error> for NoiseError {
|
||||
fn from(err: Error) -> Self {
|
||||
match err {
|
||||
Error::Decrypt => NoiseError::DecryptionError,
|
||||
err => NoiseError::ProtocolError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::NoiseConfig;
|
||||
use crate::connection::Connection;
|
||||
use crate::error::NoiseError;
|
||||
use crate::stream::NoiseStream;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_noise_keys::NoiseVersion;
|
||||
use sha2::{Digest, Sha256};
|
||||
use snow::{error::Prerequisite, Error};
|
||||
use tokio::net::TcpStream;
|
||||
use tracing::*;
|
||||
|
||||
pub mod config;
|
||||
pub mod connection;
|
||||
pub mod error;
|
||||
pub mod stream;
|
||||
|
||||
const NOISE_PSK_PREFIX: &[u8] = b"NYMTECH_NOISE_dQw4w9WgXcQ";
|
||||
|
||||
pub const LATEST_NOISE_VERSION: NoiseVersion = NoiseVersion::V1;
|
||||
|
||||
fn generate_psk_v1(responder_pub_key: &x25519::PublicKey) -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(NOISE_PSK_PREFIX);
|
||||
hasher.update(responder_pub_key.to_bytes());
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
async fn upgrade_noise_initiator_v1(
|
||||
conn: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
remote_pub_key: &x25519::PublicKey,
|
||||
) -> Result<Connection, NoiseError> {
|
||||
trace!("Perform Noise Handshake, initiator side");
|
||||
|
||||
let secret_hash = generate_psk_v1(remote_pub_key);
|
||||
let noise_stream = NoiseStream::new_initiator(conn, config, remote_pub_key, &secret_hash)?;
|
||||
|
||||
Ok(Connection::Noise(
|
||||
tokio::time::timeout(config.timeout, noise_stream.perform_handshake()).await??,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn upgrade_noise_initiator(
|
||||
conn: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
) -> Result<Connection, NoiseError> {
|
||||
if config.unsafe_disabled {
|
||||
warn!("Noise is disabled in the config. Not attempting any handshake");
|
||||
return Ok(Connection::Tcp(conn));
|
||||
}
|
||||
|
||||
//Get init material
|
||||
let responder_addr = conn.peer_addr().map_err(|err| {
|
||||
error!("Unable to extract peer address from connection - {err}");
|
||||
Error::Prereq(Prerequisite::RemotePublicKey)
|
||||
})?;
|
||||
|
||||
match config.get_noise_key(&responder_addr) {
|
||||
Some(key) => match key.version {
|
||||
NoiseVersion::V1 => upgrade_noise_initiator_v1(conn, config, &key.x25519_pubkey).await,
|
||||
// We're talking to a more recent node, but we can't adapt. Let's try to do our best and if it fails, it fails.
|
||||
// If that node sees we're older, it will try to adapt too.
|
||||
NoiseVersion::Unknown => {
|
||||
warn!("{responder_addr} is announcing an unknown version of Noise, we will still attempt our latest known version");
|
||||
upgrade_noise_initiator_v1(conn, config, &key.x25519_pubkey)
|
||||
.await
|
||||
.or(Err(NoiseError::UnknownVersion))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
warn!("{responder_addr} can't speak Noise yet, falling back to TCP");
|
||||
Ok(Connection::Tcp(conn))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn upgrade_noise_responder_v1(
|
||||
conn: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
) -> Result<Connection, NoiseError> {
|
||||
trace!("Perform Noise Handshake, responder side");
|
||||
|
||||
let secret_hash = generate_psk_v1(config.local_key.public_key());
|
||||
let noise_stream = NoiseStream::new_responder(conn, config, &secret_hash)?;
|
||||
|
||||
Ok(Connection::Noise(
|
||||
tokio::time::timeout(config.timeout, noise_stream.perform_handshake()).await??,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn upgrade_noise_responder(
|
||||
conn: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
) -> Result<Connection, NoiseError> {
|
||||
if config.unsafe_disabled {
|
||||
warn!("Noise is disabled in the config. Not attempting any handshake");
|
||||
return Ok(Connection::Tcp(conn));
|
||||
}
|
||||
|
||||
//Get init material
|
||||
let initiator_addr = match conn.peer_addr() {
|
||||
Ok(addr) => addr,
|
||||
Err(err) => {
|
||||
error!("Unable to extract peer address from connection - {err}");
|
||||
return Err(Error::Prereq(Prerequisite::RemotePublicKey).into());
|
||||
}
|
||||
};
|
||||
|
||||
// Port is random and we just need the support info
|
||||
match config.get_noise_support(initiator_addr.ip()) {
|
||||
None => {
|
||||
warn!("{initiator_addr} can't speak Noise yet, falling back to TCP",);
|
||||
Ok(Connection::Tcp(conn))
|
||||
}
|
||||
// responder's info on version is shaky, so ideally, initiator has to adapt.
|
||||
// if we are newer, it won't ba able to, so let's try to meet him on his ground.
|
||||
Some(LATEST_NOISE_VERSION) | Some(NoiseVersion::Unknown) => {
|
||||
// Node is announcing the same version as us, great or
|
||||
// Node is announcing a newer version than us, it should adapt to us though
|
||||
upgrade_noise_responder_v1(conn, config).await
|
||||
} //SW sample of code to allow backwards compatibility when we introduce new versions
|
||||
// Some(IntermediateNoiseVersion) => {
|
||||
// Node is announcing an older version, let's try to adapt
|
||||
// upgrade_noise_responder_Vwhatever
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{config::NoiseConfig, error::NoiseError};
|
||||
use bytes::BytesMut;
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use pin_project::pin_project;
|
||||
use snow::{Builder, HandshakeState, TransportState};
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll;
|
||||
use std::{cmp::min, task::ready};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite, ReadBuf},
|
||||
net::TcpStream,
|
||||
};
|
||||
use tokio_util::codec::{Framed, LengthDelimitedCodec};
|
||||
|
||||
const TAGLEN: usize = 16;
|
||||
const HANDSHAKE_MAX_LEN: usize = 1024; // using this constant to limit the handshake's buffer size
|
||||
|
||||
pub(crate) type Psk = [u8; 32];
|
||||
|
||||
/// Wrapper around a TcpStream
|
||||
#[pin_project]
|
||||
pub struct NoiseStream {
|
||||
#[pin]
|
||||
inner_stream: Framed<TcpStream, LengthDelimitedCodec>,
|
||||
handshake: Option<HandshakeState>,
|
||||
noise: Option<TransportState>,
|
||||
dec_buffer: BytesMut,
|
||||
}
|
||||
|
||||
impl NoiseStream {
|
||||
pub(crate) fn new_initiator(
|
||||
inner_stream: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
remote_pub_key: &x25519::PublicKey,
|
||||
psk: &Psk,
|
||||
) -> Result<NoiseStream, NoiseError> {
|
||||
let handshake = Builder::new(config.pattern.as_noise_params())
|
||||
.local_private_key(config.local_key.private_key().as_bytes())
|
||||
.remote_public_key(&remote_pub_key.to_bytes())
|
||||
.psk(config.pattern.psk_position(), psk)
|
||||
.build_initiator()?;
|
||||
Ok(NoiseStream::new_inner(inner_stream, handshake))
|
||||
}
|
||||
|
||||
pub(crate) fn new_responder(
|
||||
inner_stream: TcpStream,
|
||||
config: &NoiseConfig,
|
||||
psk: &Psk,
|
||||
) -> Result<NoiseStream, NoiseError> {
|
||||
let handshake = Builder::new(config.pattern.as_noise_params())
|
||||
.local_private_key(config.local_key.private_key().as_bytes())
|
||||
.psk(config.pattern.psk_position(), psk)
|
||||
.build_responder()?;
|
||||
Ok(NoiseStream::new_inner(inner_stream, handshake))
|
||||
}
|
||||
|
||||
fn new_inner(inner_stream: TcpStream, handshake: HandshakeState) -> NoiseStream {
|
||||
NoiseStream {
|
||||
inner_stream: LengthDelimitedCodec::builder()
|
||||
.length_field_type::<u16>()
|
||||
.new_framed(inner_stream),
|
||||
handshake: Some(handshake),
|
||||
noise: None,
|
||||
dec_buffer: BytesMut::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn perform_handshake(mut self) -> Result<Self, NoiseError> {
|
||||
//Check if we are in the correct state
|
||||
let Some(mut handshake) = self.handshake.take() else {
|
||||
return Err(NoiseError::IncorrectStateError);
|
||||
};
|
||||
|
||||
while !handshake.is_handshake_finished() {
|
||||
if handshake.is_my_turn() {
|
||||
self.send_handshake_msg(&mut handshake).await?;
|
||||
} else {
|
||||
self.recv_handshake_msg(&mut handshake).await?;
|
||||
}
|
||||
}
|
||||
|
||||
self.noise = Some(handshake.into_transport_mode()?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
async fn send_handshake_msg(
|
||||
&mut self,
|
||||
handshake: &mut HandshakeState,
|
||||
) -> Result<(), NoiseError> {
|
||||
let mut buf = BytesMut::zeroed(HANDSHAKE_MAX_LEN); // we're in the handshake, we can afford a smaller buffer
|
||||
let len = handshake.write_message(&[], &mut buf)?;
|
||||
buf.truncate(len);
|
||||
self.inner_stream.send(buf.into()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recv_handshake_msg(
|
||||
&mut self,
|
||||
handshake: &mut HandshakeState,
|
||||
) -> Result<(), NoiseError> {
|
||||
match self.inner_stream.next().await {
|
||||
Some(Ok(msg)) => {
|
||||
let mut buf = BytesMut::zeroed(HANDSHAKE_MAX_LEN); // we're in the handshake, we can afford a smaller buffer
|
||||
handshake.read_message(&msg, &mut buf)?;
|
||||
Ok(())
|
||||
}
|
||||
Some(Err(err)) => Err(NoiseError::IoError(err)),
|
||||
None => Err(NoiseError::HandshakeError),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peer_addr(&self) -> Result<std::net::SocketAddr, io::Error> {
|
||||
self.inner_stream.get_ref().peer_addr()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for NoiseStream {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
let projected_self = self.project();
|
||||
|
||||
let pending = match projected_self.inner_stream.poll_next(cx) {
|
||||
Poll::Pending => {
|
||||
//no new data, a return value of Poll::Pending means the waking is already scheduled
|
||||
//Nothing new to decrypt, only check if we can return something from dec_storage, happens after
|
||||
true
|
||||
}
|
||||
|
||||
Poll::Ready(Some(Ok(noise_msg))) => {
|
||||
// We have a new noise msg
|
||||
let mut dec_msg = BytesMut::zeroed(noise_msg.len() - TAGLEN);
|
||||
let len = match projected_self.noise {
|
||||
Some(transport_state) => {
|
||||
match transport_state.read_message(&noise_msg, &mut dec_msg) {
|
||||
Ok(len) => len,
|
||||
Err(_) => return Poll::Ready(Err(io::ErrorKind::InvalidInput.into())),
|
||||
}
|
||||
}
|
||||
None => return Poll::Ready(Err(io::ErrorKind::Other.into())),
|
||||
};
|
||||
projected_self.dec_buffer.extend(&dec_msg[..len]);
|
||||
false
|
||||
}
|
||||
|
||||
Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err)),
|
||||
|
||||
Poll::Ready(None) => {
|
||||
//Stream is done, we might still have data in the buffer though, happens afterwards
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
// Checking if there is something to return from the buffer
|
||||
let read_len = min(buf.remaining(), projected_self.dec_buffer.len());
|
||||
if read_len > 0 {
|
||||
buf.put_slice(&projected_self.dec_buffer.split_to(read_len));
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
// buf.remaining == 0 or nothing in the buffer, we must return the value we had from the inner_stream
|
||||
if pending {
|
||||
//If we end up here, it means the previous poll_next was pending as well, hence waking is already scheduled
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for NoiseStream {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
let mut projected_self = self.project();
|
||||
|
||||
// returns on Poll::Pending and Poll:Ready(Err)
|
||||
ready!(projected_self.inner_stream.as_mut().poll_ready(cx))?;
|
||||
|
||||
// Ready to send, encrypting message
|
||||
let mut noise_buf = BytesMut::zeroed(buf.len() + TAGLEN);
|
||||
|
||||
let Ok(len) = (match projected_self.noise {
|
||||
Some(transport_state) => transport_state.write_message(buf, &mut noise_buf),
|
||||
None => return Poll::Ready(Err(io::ErrorKind::Other.into())),
|
||||
}) else {
|
||||
return Poll::Ready(Err(io::ErrorKind::InvalidInput.into()));
|
||||
};
|
||||
noise_buf.truncate(len);
|
||||
|
||||
// Tokio uses the same `start_send ` in their SinkWriter implementation. https://docs.rs/tokio-util/latest/src/tokio_util/io/sink_writer.rs.html#104
|
||||
match projected_self
|
||||
.inner_stream
|
||||
.as_mut()
|
||||
.start_send(noise_buf.into())
|
||||
{
|
||||
Ok(()) => Poll::Ready(Ok(buf.len())),
|
||||
Err(e) => Poll::Ready(Err(e)),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
self.project().inner_stream.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
self.project().inner_stream.poll_close(cx)
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use std::str::FromStr;
|
||||
pub mod old_config_v1_1_20_2;
|
||||
pub mod old_config_v1_1_30;
|
||||
pub mod old_config_v1_1_33;
|
||||
pub mod old_config_v1_1_54;
|
||||
|
||||
pub use nym_service_providers_common::interface::ProviderInterfaceVersion;
|
||||
pub use nym_socks5_requests::Socks5ProtocolVersion;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{Config, Socks5, Socks5Debug};
|
||||
use super::old_config_v1_1_54::ConfigV1_1_54;
|
||||
use super::{Socks5, Socks5Debug};
|
||||
pub use nym_client_core::config::old_config_v1_1_33::ConfigV1_1_33 as BaseClientConfigV1_1_33;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
@@ -23,9 +24,9 @@ pub struct ConfigV1_1_33 {
|
||||
pub socks5: Socks5V1_1_33,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_33> for Config {
|
||||
impl From<ConfigV1_1_33> for ConfigV1_1_54 {
|
||||
fn from(value: ConfigV1_1_33) -> Self {
|
||||
Config {
|
||||
ConfigV1_1_54 {
|
||||
base: value.base.into(),
|
||||
socks5: value.socks5.into(),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
use super::Config;
|
||||
pub use nym_client_core::config::old_config_v1_1_54::ConfigV1_1_54 as BaseClientConfigV1_1_54;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Socks5;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_54 {
|
||||
#[serde(flatten)]
|
||||
pub base: BaseClientConfigV1_1_54,
|
||||
|
||||
pub socks5: Socks5,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_54> for Config {
|
||||
fn from(value: ConfigV1_1_54) -> Self {
|
||||
Config {
|
||||
base: value.base.into(),
|
||||
socks5: value.socks5,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ thiserror = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
si-scale = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::types::SessionType;
|
||||
|
||||
/// Channel for receiving incoming Stats events
|
||||
pub type GatewayStatsReceiver = tokio::sync::mpsc::UnboundedReceiver<GatewayStatsEvent>;
|
||||
|
||||
@@ -51,15 +52,9 @@ pub enum GatewaySessionEvent {
|
||||
/// Address of the remote client opening the connection
|
||||
client: DestinationAddressBytes,
|
||||
},
|
||||
/// A new ecash ticket has been added / requested
|
||||
EcashTicket {
|
||||
/// Type of ecash ticket that has been created as part of the session
|
||||
ticket_type: TicketType,
|
||||
/// Address of the remote client opening the connection
|
||||
client: DestinationAddressBytes,
|
||||
},
|
||||
SessionDelete {
|
||||
/// Address of the remote client opening the connection
|
||||
/// An active session should be given a type and remembered
|
||||
SessionRemember {
|
||||
session_type: SessionType,
|
||||
client: DestinationAddressBytes,
|
||||
},
|
||||
}
|
||||
@@ -81,18 +76,13 @@ impl GatewaySessionEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// A new ecash ticket has been added / requested
|
||||
pub fn new_ecash_ticket(
|
||||
pub fn new_session_remember(
|
||||
session_type: SessionType,
|
||||
client: DestinationAddressBytes,
|
||||
ticket_type: TicketType,
|
||||
) -> GatewaySessionEvent {
|
||||
GatewaySessionEvent::EcashTicket {
|
||||
ticket_type,
|
||||
GatewaySessionEvent::SessionRemember {
|
||||
session_type,
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_session_delete(client: DestinationAddressBytes) -> GatewaySessionEvent {
|
||||
GatewaySessionEvent::SessionDelete { client }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ pub mod error;
|
||||
pub mod gateways;
|
||||
/// Statistics reporting abstractions and implementations.
|
||||
pub mod report;
|
||||
/// Statistics related types.
|
||||
pub mod types;
|
||||
|
||||
const CLIENT_ID_PREFIX: &str = "client_stats_id";
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(
|
||||
PartialEq,
|
||||
Copy,
|
||||
Clone,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
Debug,
|
||||
)]
|
||||
pub enum SessionType {
|
||||
Vpn,
|
||||
Mixnet,
|
||||
Wasm,
|
||||
Native,
|
||||
Socks5,
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl SessionType {
|
||||
pub fn from_string<S: AsRef<str>>(s: S) -> Self {
|
||||
s.as_ref().parse().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,10 @@
|
||||
#![allow(clippy::drop_non_drop)]
|
||||
|
||||
use crate::error::WasmCoreError;
|
||||
use nym_client_core::config::ForgetMe;
|
||||
use nym_client_core::config::{ForgetMe, RememberMe};
|
||||
use nym_config::helpers::OptionalSet;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_statistics_common::types::SessionType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::prelude::*;
|
||||
@@ -113,6 +114,8 @@ pub struct DebugWasm {
|
||||
pub stats_reporting: StatsReportingWasm,
|
||||
|
||||
pub forget_me: ForgetMeWasm,
|
||||
|
||||
pub remember_me: RememberMeWasm,
|
||||
}
|
||||
|
||||
impl Default for DebugWasm {
|
||||
@@ -132,6 +135,7 @@ impl From<DebugWasm> for ConfigDebug {
|
||||
reply_surbs: debug.reply_surbs.into(),
|
||||
stats_reporting: debug.stats_reporting.into(),
|
||||
forget_me: debug.forget_me.into(),
|
||||
remember_me: debug.remember_me.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,6 +151,7 @@ impl From<ConfigDebug> for DebugWasm {
|
||||
reply_surbs: debug.reply_surbs.into(),
|
||||
stats_reporting: debug.stats_reporting.into(),
|
||||
forget_me: ForgetMeWasm::from(debug.forget_me),
|
||||
remember_me: RememberMeWasm::from(debug.remember_me),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -597,6 +602,27 @@ impl From<ForgetMe> for ForgetMeWasm {
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(inspectable)]
|
||||
#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Serialize, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RememberMeWasm {
|
||||
pub stats: bool,
|
||||
}
|
||||
|
||||
impl From<RememberMeWasm> for RememberMe {
|
||||
fn from(value: RememberMeWasm) -> Self {
|
||||
RememberMe::new(value.stats, SessionType::Wasm)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RememberMe> for RememberMeWasm {
|
||||
fn from(value: RememberMe) -> Self {
|
||||
Self {
|
||||
stats: value.stats(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(inspectable)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
use super::{
|
||||
AcknowledgementsWasm, CoverTrafficWasm, DebugWasm, ForgetMeWasm, GatewayConnectionWasm,
|
||||
ReplySurbsWasm, StatsReportingWasm, TopologyWasm, TrafficWasm,
|
||||
RememberMeWasm, ReplySurbsWasm, StatsReportingWasm, TopologyWasm, TrafficWasm,
|
||||
};
|
||||
use crate::config::ConfigDebug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -50,6 +50,9 @@ pub struct DebugWasmOverride {
|
||||
|
||||
#[tsify(optional)]
|
||||
pub forget_me: Option<ForgetMeWasmOverride>,
|
||||
|
||||
#[tsify(optional)]
|
||||
pub remember_me: Option<RememberMeWasmOverride>,
|
||||
}
|
||||
|
||||
impl From<DebugWasmOverride> for DebugWasm {
|
||||
@@ -63,6 +66,7 @@ impl From<DebugWasmOverride> for DebugWasm {
|
||||
reply_surbs: value.reply_surbs.map(Into::into).unwrap_or_default(),
|
||||
stats_reporting: value.stats_reporting.map(Into::into).unwrap_or_default(),
|
||||
forget_me: value.forget_me.map(Into::into).unwrap_or_default(),
|
||||
remember_me: value.remember_me.map(Into::into).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -457,6 +461,22 @@ impl From<ForgetMeWasmOverride> for ForgetMeWasm {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RememberMeWasmOverride {
|
||||
#[tsify(optional)]
|
||||
pub stats: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<RememberMeWasmOverride> for RememberMeWasm {
|
||||
fn from(value: RememberMeWasmOverride) -> Self {
|
||||
RememberMeWasm {
|
||||
stats: value.stats.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
|
||||
#[tsify(into_wasm_abi, from_wasm_abi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
+119
-25
@@ -1,45 +1,139 @@
|
||||
## Build with Docker & Docker Compose
|
||||
## Nym Validator
|
||||
|
||||
Currently you can build and run locally in a Docker containers the following components of the Nym Privacy Platform:
|
||||
This contains the configuration needed to run a Nym validator using Docker
|
||||
|
||||
* One genesis validator
|
||||
* Any number of secondary validators
|
||||
* A contract uploader, that uploads the contract built from the local sources
|
||||
* The web wallet application, accessible on port 3000
|
||||
* The block explorer application, accessible on port 3080
|
||||
* The network explorer application, accessible on port 3040, for registered users
|
||||
> **SECURITY**: This runs the validator as the root user inside the container for simplicity and development purposes. This setup should NOT be used in any other fashion.
|
||||
|
||||
### Running
|
||||
### Initial Setup
|
||||
|
||||
The following commands need to be run from the root of the Nym git project.
|
||||
|
||||
To build the entire dockerized environment, run the following command:
|
||||
Before building and running the validator, you'll need to create the required directories:
|
||||
|
||||
```
|
||||
PERSONAL_TOKEN=[network explorer token] docker-compose build
|
||||
# Create required directories
|
||||
mkdir -p data/validator data/addresses
|
||||
chmod -R 777 data
|
||||
```
|
||||
|
||||
or build each service separately, as changes are made to their relevant source code.
|
||||
**Note** network-explorer build time is currently very high, so building it more than once is not advisable.
|
||||
### Running on Apple (Mx) Macs with Colima
|
||||
|
||||
To start the dockerized environment, run the following command:
|
||||
When running on Apple Silicon Macs, we are using Colima with x86_64 emulation to properly run the validator:
|
||||
|
||||
1. Install Colima
|
||||
```
|
||||
METEOR_SETTINGS=$(cat docker/block_explorer/settings.json) docker-compose up -d --scale=secondary_validator=3
|
||||
brew install colima
|
||||
```
|
||||
|
||||
**Note**: The `secondary_validator=3` can take any other number as value, depending on the desired setup.
|
||||
|
||||
The web wallet interface will become available at `localhost:3000`.
|
||||
|
||||
The mnemonic needed to connect to the admin user, which also has a pre-added number of tokens, can be obtained by running:
|
||||
|
||||
2. Set up a Colima VM with x86_64 architecture and Rosetta support:
|
||||
```
|
||||
docker logs nym_mnemonic_echo_1
|
||||
# Create a Colima VM with x86_64 architecture
|
||||
colima start nym-validator --arch x86_64 --cpu 4 --memory 8 --disk 20 --vm-type=vz --vz-rosetta --mount-type=virtiofs
|
||||
```
|
||||
|
||||
To stop the dockerized environment, run:
|
||||
3. Build and start the validator:
|
||||
```
|
||||
# Make sure you're using the Colima context
|
||||
docker context use colima-nym-validator
|
||||
|
||||
# Build the validator
|
||||
docker-compose build validator
|
||||
|
||||
# Start the validator
|
||||
docker-compose up -d validator
|
||||
```
|
||||
|
||||
### Standard Operation (Intel/AMD x86_64 Systems)
|
||||
|
||||
For standard x86_64 systems:
|
||||
|
||||
```
|
||||
# Build the validator
|
||||
docker-compose build validator
|
||||
|
||||
# Start the validator
|
||||
docker-compose up -d validator
|
||||
```
|
||||
|
||||
The genesis validator will be initialized with the network configuration defined in the `docker-compose.yml` file.
|
||||
|
||||
### Managing the Validator
|
||||
|
||||
To check the validator logs:
|
||||
|
||||
```
|
||||
docker logs -f validator
|
||||
```
|
||||
|
||||
To get the admin mnemonic:
|
||||
|
||||
```
|
||||
docker exec validator cat /root/output/node_admin_mnemonic
|
||||
```
|
||||
|
||||
To stop the validator:
|
||||
|
||||
```
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### Terminating and Cleaning Up Validator Data
|
||||
|
||||
If you need to completely terminate your validator and remove all associated data:
|
||||
|
||||
```
|
||||
# Stop the containers
|
||||
docker-compose down
|
||||
|
||||
# Remove the volumes
|
||||
docker-compose down -v
|
||||
|
||||
# Delete the data directories
|
||||
rm -rf data
|
||||
|
||||
# If you want to start fresh, recreate the directories
|
||||
mkdir -p data/validator/config data/addresses
|
||||
chmod -R 777 data
|
||||
```
|
||||
|
||||
This will completely remove all blockchain state, keys, and configuration, allowing you to start with a clean validator instance
|
||||
|
||||
### Using nym-cli for Smart Contract Operations
|
||||
|
||||
The nym-cli utility can be used to manage and execute WASM smart contracts. You can access the CLI from within the validator container:
|
||||
|
||||
```
|
||||
docker exec -it validator ./nym-cli cosmwasm --help
|
||||
```
|
||||
|
||||
#### Available Commands:
|
||||
|
||||
- **upload**: Upload a smart contract WASM blob
|
||||
- **init**: Init a WASM smart contract
|
||||
- **generate-init-message**: Generate an instantiate message
|
||||
- **migrate**: Migrate a WASM smart contract
|
||||
- **execute**: Execute a WASM smart contract method
|
||||
- **raw-contract-state**: Obtain raw contract state of a cosmwasm smart contract
|
||||
|
||||
#### Example Usage:
|
||||
|
||||
To upload a contract:
|
||||
|
||||
```
|
||||
docker exec -it validator ./nym-cli cosmwasm upload \
|
||||
--mnemonic $(cat /root/output/node_admin_mnemonic) \
|
||||
--wasm-file /path/to/contract.wasm
|
||||
```
|
||||
|
||||
To initialize a contract:
|
||||
|
||||
```
|
||||
docker exec -it validator ./nym-cli cosmwasm init \
|
||||
--mnemonic $(cat /root/output/node_admin_mnemonic) \
|
||||
--code-id <CODE_ID> \
|
||||
--init-msg '{"key": "value"}'
|
||||
```
|
||||
|
||||
For more detailed options, use the help command:
|
||||
|
||||
```
|
||||
docker exec -it validator ./nym-cli cosmwasm <COMMAND> --help
|
||||
```
|
||||
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"public":{
|
||||
"chainName": "Nym",
|
||||
"chainId": "nymnet",
|
||||
"slashingWindow": 10000,
|
||||
"uptimeWindow": 250,
|
||||
"initialPageSize": 30,
|
||||
"secp256k1": false,
|
||||
"bech32PrefixAccAddr": "punk",
|
||||
"bech32PrefixAccPub": "punkpub",
|
||||
"bech32PrefixValAddr": "punkvaloper",
|
||||
"bech32PrefixValPub": "punkvaloperpub",
|
||||
"bech32PrefixConsAddr": "punkvalcons",
|
||||
"bech32PrefixConsPub": "punkvalconspub",
|
||||
"bondDenom": "punk",
|
||||
"powerReduction": 1000000,
|
||||
"coins": [
|
||||
{
|
||||
"denom": "upunk",
|
||||
"displayName": "PUNK",
|
||||
"fraction": 1000000
|
||||
},
|
||||
{
|
||||
"denom": "punk",
|
||||
"displayName": "PUNK",
|
||||
"fraction": 1000000
|
||||
}
|
||||
],
|
||||
"ledger":{
|
||||
"coinType": 118,
|
||||
"appName": "punk",
|
||||
"appVersion": "2.16.0",
|
||||
"gasPrice": 0.025
|
||||
},
|
||||
"modules": {
|
||||
"bank": true,
|
||||
"supply": true,
|
||||
"minting": false,
|
||||
"gov": true,
|
||||
"distribution": false
|
||||
},
|
||||
"coingeckoId": "punk",
|
||||
"networks": "https://gist.githubusercontent.com/kwunyeung/8be4598c77c61e497dfc7220a678b3ee/raw/bd-networks.json",
|
||||
"banners": false
|
||||
},
|
||||
"remote":{
|
||||
"rpc":"http://genesis_validator:26657",
|
||||
"api":"http://genesis_validator:1317"
|
||||
},
|
||||
"debug": {
|
||||
"startTimer": true
|
||||
},
|
||||
"params":{
|
||||
"startHeight": 0,
|
||||
"defaultBlockTime": 5000,
|
||||
"validatorUpdateWindow": 300,
|
||||
"blockInterval": 15000,
|
||||
"transactionsInterval": 18000,
|
||||
"keybaseFetchingInterval": 18000000,
|
||||
"consensusInterval": 1000,
|
||||
"statusInterval":7500,
|
||||
"signingInfoInterval": 1800000,
|
||||
"proposalInterval": 5000,
|
||||
"missedBlocksInterval": 60000,
|
||||
"delegationInterval": 900000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
FROM node:16
|
||||
|
||||
COPY ./setup.sh /setup.sh
|
||||
CMD /setup.sh
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
git clone https://github.com/nymtech/nym.git
|
||||
|
||||
mkdir /explorer-copy
|
||||
cp -r nym/explorer/* /explorer-copy
|
||||
|
||||
cd /explorer-copy
|
||||
|
||||
npm install
|
||||
#baseUrl
|
||||
sed -i 's/https:\/\/testnet-milhon-explorer.nymtech.net\//http:\/\/localhost:3080/' ./src/api/constants.ts
|
||||
|
||||
#master validator
|
||||
sed -i 's/https:\/\/testnet-milhon-validator1.nymtech.net/http:\/\/localhost:26657/' ./src/api/constants.ts
|
||||
|
||||
#big dipper url
|
||||
sed -i 's/https:\/\/testnet-milhon-blocks.nymtech.net\//http:\/\/localhost:3080/' ./src/api/constants.ts
|
||||
|
||||
nohup npm run start
|
||||
@@ -1,4 +0,0 @@
|
||||
FROM rust
|
||||
RUN rustup target add wasm32-unknown-unknown
|
||||
CMD cd nym/contracts/mixnet && RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM alpine
|
||||
COPY ./entrypoint.sh /entrypoint.sh
|
||||
CMD /entrypoint.sh
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Wait for the mnemonic(s) to be generated
|
||||
while ! [ -s /genesis_volume/genesis_mnemonic ]; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
while ! [ -s /genesis_volume/secondary_mnemonic ]; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "This is the current genesis mnemonic:"
|
||||
cat /genesis_volume/genesis_mnemonic
|
||||
|
||||
echo "This is the current secondary mnemonic:"
|
||||
cat /genesis_volume/secondary_mnemonic
|
||||
@@ -1,4 +0,0 @@
|
||||
FROM node
|
||||
RUN apt update && apt install -y netcat
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
CMD /entrypoint.sh
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd /nym/clients/validator
|
||||
yarn install
|
||||
yarn build
|
||||
|
||||
cd /nym/docker/typescript_client/upload_contract
|
||||
npm install
|
||||
|
||||
# Wait until the mnemonic is created
|
||||
while ! [ -s /genesis_volume/genesis_mnemonic ]; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Wait until the validator opens its port
|
||||
while ! nc -z genesis_validator 26657; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
chmod 777 /contract_volume
|
||||
npx ts-node upload-wasm.ts
|
||||
-2116
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"name": "nym-driver-example",
|
||||
"version": "1.0.0",
|
||||
"main": "./dist/index.js",
|
||||
"author": "Dave Hrycyszyn",
|
||||
"license": "MIT",
|
||||
"description": "The simplest cosmjs typescript client to work as an example driver program",
|
||||
"repository": "https://github.com/nymtech/nym",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/recommended": "^1.0.1",
|
||||
"prettier": "^2.8.7",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^14.14.22",
|
||||
"nodemon": "^2.0.20",
|
||||
"@nymproject/nym-validator-client" : "0.18.0",
|
||||
"save-dev": "0.0.1-security",
|
||||
"tasktimer": "^3.0.0",
|
||||
"ts-node": "^9.1.1"
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// npx ts-node upload-wasm.ts
|
||||
|
||||
import ValidatorClient from "@nymproject/nym-validator-client";
|
||||
import * as fs from 'fs';
|
||||
|
||||
async function newClient(): Promise<ValidatorClient> {
|
||||
let contract = "fakeContractAddress"; // we don't have one yet
|
||||
let mnemonic = fs.readFileSync("/genesis_volume/genesis_mnemonic", "utf8").slice(0, -1);
|
||||
let admin = ValidatorClient.connect(contract, mnemonic, "http://genesis_validator:26657", process.env["BECH32_PREFIX"]);
|
||||
return admin;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let admin = await newClient();
|
||||
console.log(`admin address: ${admin.address}`);
|
||||
|
||||
// check that we have actually connected to an account, query it to test
|
||||
let balance = await admin.getBalance(admin.address);
|
||||
console.log(`balance of admin account is: ${balance.amount}${balance.denom}`);
|
||||
|
||||
let wasm = fs.readFileSync("/nym/contracts/mixnet/target/wasm32-unknown-unknown/release/mixnet_contracts.wasm");
|
||||
console.log("wasm loaded");
|
||||
|
||||
// dave can upload (note: nobody else can)
|
||||
const uploadResult = await admin.upload(admin.address, wasm, undefined, "mixnet contract");//.then((uploadResult) => console.log("Upload from dave succeeded, codeId is: " + uploadResult.codeId)).catch((err) => console.log(err));
|
||||
|
||||
//todo
|
||||
//add vesting contract?
|
||||
|
||||
// Instantiate the copy of the option contract
|
||||
const { codeId } = uploadResult;
|
||||
console.log("code id is", codeId)
|
||||
const initMsg = {};
|
||||
const options = { memo: "v0.1.0", transferAmount: [{ denom: "u" + process.env["BECH32_PREFIX"], amount: "1000000" }], admin: admin.address }
|
||||
let instantiateResult = await admin.instantiate(admin.address, codeId, initMsg, "mixnet contract", options);
|
||||
let contractAddress = instantiateResult.contractAddress;
|
||||
console.log(`mixnet contract ${contractAddress} instantiated successfully`)
|
||||
fs.writeFileSync("/contract_volume/contract_address", contractAddress);
|
||||
}
|
||||
|
||||
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,25 @@
|
||||
CONFIGURED=true
|
||||
|
||||
NETWORK_NAME=nymtestnetwork
|
||||
|
||||
RUST_LOG=info
|
||||
RUST_BACKTRACE=1
|
||||
|
||||
BECH32_PREFIX=n
|
||||
MIX_DENOM=unym
|
||||
MIX_DENOM_DISPLAY=nym
|
||||
STAKE_DENOM=unyx
|
||||
STAKE_DENOM_DISPLAY=nyx
|
||||
DENOMS_EXPONENT=6
|
||||
|
||||
MIXNET_CONTRACT_ADDRESS=
|
||||
VESTING_CONTRACT_ADDRESS=
|
||||
GROUP_CONTRACT_ADDRESS=
|
||||
ECASH_CONTRACT_ADDRESS=
|
||||
MULTISIG_CONTRACT_ADDRESS=
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=
|
||||
|
||||
REWARDING_VALIDATOR_ADDRESS=
|
||||
NYXD="https://localhost:26656"
|
||||
NYM_API="https://insertvalues.io"
|
||||
NYXD_WS="wss://insertvalues.io"
|
||||
@@ -0,0 +1,15 @@
|
||||
# data
|
||||
|
||||
data/
|
||||
data/validator/
|
||||
data/addresses/
|
||||
|
||||
secrets/
|
||||
|
||||
# docker
|
||||
.docker/
|
||||
|
||||
# access
|
||||
*_mnemonic
|
||||
*.key
|
||||
genesis_mnemonic
|
||||
+41
-11
@@ -1,12 +1,42 @@
|
||||
FROM golang:buster as go_builder
|
||||
ARG BECH32_PREFIX
|
||||
ARG WASMD_VERSION
|
||||
RUN apt update && apt install -y git build-essential
|
||||
COPY setup.sh .
|
||||
RUN ./setup.sh
|
||||
FROM ubuntu:24.04
|
||||
|
||||
FROM ubuntu:20.04
|
||||
COPY --from=go_builder /go/wasmd/build/nymd /root/nymd
|
||||
COPY --from=go_builder /go/wasmd/build/libwasmvm*.so /root
|
||||
COPY init_and_start.sh .
|
||||
ENTRYPOINT ["./init_and_start.sh"]
|
||||
ARG WASMD_VERSION
|
||||
ARG NYM_CLI_GIT_TAG
|
||||
ARG RETAIN_BLOCKS
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
jq \
|
||||
curl \
|
||||
wget \
|
||||
vim \
|
||||
openssl \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
RUN wget -O nym-cli "https://github.com/nymtech/nym/releases/download/nym-binaries-v${NYM_CLI_GIT_TAG:-2025.8-tourist}/nym-cli" && \
|
||||
chmod 755 nym-cli || echo "nym-cli not available"
|
||||
|
||||
RUN wget "https://github.com/nymtech/nyxd/releases/download/${WASMD_VERSION:-v0.54.3}/nyxd-ubuntu-22.04.tar.gz" && \
|
||||
tar -zxvf nyxd-ubuntu-22.04.tar.gz && \
|
||||
mv libwasmvm.x86_64.so /lib/x86_64-linux-gnu/ && \
|
||||
chmod 755 nyxd && \
|
||||
rm nyxd-ubuntu-22.04.tar.gz
|
||||
|
||||
RUN mkdir -p "/root/.nyxd/config" "/root/output"
|
||||
RUN chmod 755 "/root/.nyxd" "/root/.nyxd/config" "/root/output"
|
||||
|
||||
VOLUME /root/.nyxd
|
||||
VOLUME /root/output
|
||||
|
||||
RUN touch .env
|
||||
|
||||
COPY start.sh .
|
||||
RUN chmod 755 *.sh
|
||||
|
||||
ENV RETAIN_BLOCKS=${RETAIN_BLOCKS}
|
||||
|
||||
ENTRYPOINT ["./start.sh"]
|
||||
CMD ["genesis"]
|
||||
@@ -0,0 +1,30 @@
|
||||
services:
|
||||
validator:
|
||||
build:
|
||||
context: ./
|
||||
args:
|
||||
NYM_CLI_GIT_TAG: "2025.8-tourist"
|
||||
WASMD_VERSION: "v0.54.3"
|
||||
image: validator:latest
|
||||
container_name: validator
|
||||
ports:
|
||||
- "127.0.0.1:26657:26657"
|
||||
- "127.0.0.1:26656:26656"
|
||||
- "127.0.0.1:1317:1317"
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2'
|
||||
memory: 4G
|
||||
volumes:
|
||||
- ./data/addresses:/root/output
|
||||
- ./data/validator:/root/.nyxd
|
||||
environment:
|
||||
BECH32_PREFIX: "n"
|
||||
DENOM: "nym"
|
||||
STAKE_DENOM: "nyx"
|
||||
WASMD_VERSION: "v0.54.3"
|
||||
CHAIN_ID: "nymtestnetwork"
|
||||
NYM_CLI_GIT_TAG: "2025.8-tourist"
|
||||
RETAIN_BLOCKS: "no"
|
||||
@@ -1,77 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root
|
||||
PASSPHRASE=passphrase
|
||||
|
||||
cd /root
|
||||
|
||||
if [ "$1" = "genesis" ]; then
|
||||
if [ ! -f "/root/.nymd/config/genesis.json" ]; then
|
||||
./nyxd init nymnet --chain-id nymnet 2> /dev/null
|
||||
# staking/governance token is hardcoded in config, change this
|
||||
sed -i "s/\"stake\"/\"u${STAKE_DENOM}\"/" /root/.nyxd/config/genesis.json
|
||||
sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${DENOM}"'"/' /root/.nyxd/config/app.toml
|
||||
sed -i '0,/enable = false/s//enable = true/g' /root/.nyxd/config/app.toml
|
||||
sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' /root/.nyxd/config/config.toml
|
||||
sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' /root/.nyxd/config/config.toml
|
||||
sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' /root/.nyxd/config/config.toml
|
||||
|
||||
# create accounts
|
||||
yes "${PASSPHRASE}" | ./nyxd keys add node_admin 2>&1 >/dev/null | tail -n 1 > /root/.nyxd/mnemonic
|
||||
yes "${PASSPHRASE}" | ./nyxd keys add secondary 2>&1 >/dev/null | tail -n 1 > /root/.nyxd/secondary_mnemonic
|
||||
cp /root/.nyxd/mnemonic /genesis_volume/genesis_mnemonic
|
||||
cp /root/.nyxd/secondary_mnemonic /genesis_volume/secondary_mnemonic
|
||||
|
||||
# add genesis accounts with some initial tokens
|
||||
GENESIS_ADDRESS=$(yes "${PASSPHRASE}" | ./nyxd keys show node_admin -a)
|
||||
SECONDARY_ADDRESS=$(yes "${PASSPHRASE}" | ./nyxd keys show secondary -a)
|
||||
yes "${PASSPHRASE}" | ./nyxd add-genesis-account "${GENESIS_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}"
|
||||
yes "${PASSPHRASE}" | ./nyxd add-genesis-account "${SECONDARY_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}"
|
||||
|
||||
yes "${PASSPHRASE}" | ./nyxd gentx node_admin 1000000000u"${STAKE_DENOM}" --chain-id nymnet 2> /dev/null
|
||||
./nyxd collect-gentxs 2> /dev/null
|
||||
./nyxd validate-genesis > /dev/null
|
||||
cp /root/.nyxd/config/genesis.json /genesis_volume/genesis.json
|
||||
else
|
||||
echo "Validator already initialized, starting with the existing configuration."
|
||||
echo "If you want to re-init the validator, destroy the existing container"
|
||||
fi
|
||||
./nyxd start
|
||||
elif [ "$1" = "secondary" ]; then
|
||||
if [ ! -f "/root/.nymd/config/genesis.json" ]; then
|
||||
./nyxd init nymnet --chain-id nym-secondary 2> /dev/null
|
||||
|
||||
# Wait until the genesis node writes the genesis.json to the shared volume
|
||||
while ! [ -s /genesis_volume/genesis.json ]; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# wait for the actual validator to start up
|
||||
sleep 5
|
||||
|
||||
cp /genesis_volume/genesis.json /root/.nyxd/config/genesis.json
|
||||
GENESIS_PEER=$(cat /root/.nyxd/config/genesis.json | grep '"memo"' | cut -d'"' -f 4)
|
||||
GENESIS_IP=$(cat /root/.nyxd/config/genesis.json | grep '"memo"' | cut -d'@' -f2 | cut -d: -f1)
|
||||
sed -i 's/persistent_peers = ""/persistent_peers = "'"${GENESIS_PEER}"'"/' /root/.nyxd/config/config.toml
|
||||
sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${BECH32_PREFIX}"'"/' /root/.nyxd/config/app.toml
|
||||
sed -i '0,/enable = false/s//enable = true/g' /root/.nyxd/config/app.toml
|
||||
sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' /root/.nyxd/config/config.toml
|
||||
sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' /root/.nyxd/config/config.toml
|
||||
sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' /root/.nyxd/config/config.toml
|
||||
|
||||
# import mnemonic generated by the genesis validator (have a local copy for ease of use)
|
||||
cp /genesis_volume/secondary_mnemonic /root/.nyxd/mnemonic
|
||||
{ cat /root/.nyxd/mnemonic; echo "${PASSPHRASE}"; echo "${PASSPHRASE}"; } | ./nyxd keys add node_admin --recover #> /dev/null
|
||||
./nyxd validate-genesis > /dev/null
|
||||
|
||||
# create validator
|
||||
# don't even ask about those sleeps...
|
||||
{ echo "${PASSPHRASE}"; sleep 10; yes; sleep 10; } | ./nyxd tx staking create-validator --amount=10000000u"${STAKE_DENOM}" --fees 100000u"${DENOM}" --pubkey="$(./nyxd tendermint show-validator)" --moniker="secondary" --commission-rate="0.10" --commission-max-rate="0.20" --commission-max-change-rate="0.01" --min-self-delegation="1" --chain-id=nymnet --from=node_admin -b async --node http://"${GENESIS_IP}":26657
|
||||
else
|
||||
echo "Validator already initialized, starting with the existing configuration."
|
||||
echo "If you want to re-init the validator, destroy the existing container"
|
||||
fi
|
||||
./nyxd start
|
||||
else
|
||||
echo "Wrong command. Usage: ./$0 [genesis/secondary]"
|
||||
fi
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -ue
|
||||
|
||||
git clone https://github.com/CosmWasm/wasmd.git
|
||||
cd wasmd
|
||||
git checkout "${WASMD_VERSION}"
|
||||
WASMD_COMMIT_HASH=$(git rev-parse HEAD)
|
||||
mkdir build
|
||||
go build \
|
||||
-o build/nyxd -mod=readonly -tags "netgo,ledger" \
|
||||
-ldflags "-X github.com/cosmos/cosmos-sdk/version.Name=nymd \
|
||||
-X github.com/cosmos/cosmos-sdk/version.AppName=nymd \
|
||||
-X github.com/CosmWasm/wasmd/app.NodeDir=.nymd \
|
||||
-X github.com/cosmos/cosmos-sdk/version.Version=${WASMD_VERSION} \
|
||||
-X github.com/cosmos/cosmos-sdk/version.Commit=${WASMD_COMMIT_HASH} \
|
||||
-X github.com/CosmWasm/wasmd/app.Bech32Prefix=${BECH32_PREFIX} \
|
||||
-X 'github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger'" \
|
||||
-trimpath ./cmd/wasmd
|
||||
find .. -type f -name 'libwasm*.so' -exec cp {} build \;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user