Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c491545f4 | |||
| 8d2cd48da5 | |||
| f2eb97514a | |||
| 735535d902 | |||
| f7603a9973 | |||
| 693b8d5519 | |||
| f96103ab97 | |||
| b599ededf3 | |||
| d2114d3c2e | |||
| 0356f0c682 | |||
| 365b12c069 | |||
| 032281dc00 | |||
| 9cc57f8f63 | |||
| 64c940a12a | |||
| d6f0d50760 | |||
| f6f361299c | |||
| 3c5677b4ff | |||
| b243062695 | |||
| 946b10cc30 | |||
| 1c8831ec17 | |||
| 3e606be545 | |||
| 3f8c2c096b | |||
| 3ede03e1d1 | |||
| ac12455f97 | |||
| 1c6db86259 | |||
| e126c1f7f1 | |||
| 31772019cd | |||
| 5369e5eab9 | |||
| 2e634c59a7 | |||
| d7383d74f3 | |||
| e98d60d7ce | |||
| f47650d6c8 | |||
| 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 | |||
| e594630314 | |||
| 9c2595d9ef | |||
| 5ad1f0b61a |
@@ -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
|
||||
|
||||
@@ -27,7 +27,8 @@ jobs:
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
# pinned due to issues building contracts
|
||||
toolchain: 1.86.0
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: 1.86.0
|
||||
override: true
|
||||
|
||||
- name: Build all binaries
|
||||
|
||||
@@ -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
|
||||
|
||||
+100
@@ -4,6 +4,106 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.10-brie] (2025-05-27)
|
||||
|
||||
- Backport PR 5779 ([#5801])
|
||||
- Expanded Accept Encoding for `reqwest` ([#5779])
|
||||
- Teach HttpClientError how to report its status code and timeout ([#5770])
|
||||
- Skip refreshing the topology on startup as we already have an initial set ([#5768])
|
||||
- Fetch the topology from the nym-api concurrently ([#5767])
|
||||
- feat: use bincode by default in NymApiClient + remove feature-lock ([#5761])
|
||||
- Instrument create_request ([#5760])
|
||||
- Add node_bonded field to delegations ([#5759])
|
||||
- build(deps): bump mikefarah/yq from 4.45.1 to 4.45.4 ([#5758])
|
||||
- Raw route submissions ([#5756])
|
||||
- feat: expires header for `/active` nym-api responses ([#5755])
|
||||
- Decrease default average packet delay to 15 ms ([#5754])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 12 updates ([#5753])
|
||||
- Remove pretty_env_logger and switch remaining crates to use tracing ([#5749])
|
||||
- Update pretty_env_logger to latest to not depend on unmaintained crate atty ([#5748])
|
||||
- Upgrade prometheus crate to fix security warning ([#5747])
|
||||
- Downgrade deranged crate to 0.4.0 ([#5746])
|
||||
- feat: nym-api bincode + yaml support ([#5745])
|
||||
- fix parallel feature in ecash crate with send + sync ([#5744])
|
||||
- Remove old test directory - Update validator docker ([#5743])
|
||||
- [Feature] `RememberMe` is the new don't `ForgetMe` ([#5742])
|
||||
- build(deps): bump ammonia from 4.0.0 to 4.1.0 ([#5739])
|
||||
- build(deps): bump base-x from 3.0.9 to 3.0.11 in /testnet-faucet ([#5737])
|
||||
- build(deps): bump http-proxy-middleware from 2.0.8 to 2.0.9 ([#5730])
|
||||
|
||||
[#5801]: https://github.com/nymtech/nym/pull/5801
|
||||
[#5779]: https://github.com/nymtech/nym/pull/5779
|
||||
[#5770]: https://github.com/nymtech/nym/pull/5770
|
||||
[#5768]: https://github.com/nymtech/nym/pull/5768
|
||||
[#5767]: https://github.com/nymtech/nym/pull/5767
|
||||
[#5761]: https://github.com/nymtech/nym/pull/5761
|
||||
[#5760]: https://github.com/nymtech/nym/pull/5760
|
||||
[#5759]: https://github.com/nymtech/nym/pull/5759
|
||||
[#5758]: https://github.com/nymtech/nym/pull/5758
|
||||
[#5756]: https://github.com/nymtech/nym/pull/5756
|
||||
[#5755]: https://github.com/nymtech/nym/pull/5755
|
||||
[#5754]: https://github.com/nymtech/nym/pull/5754
|
||||
[#5753]: https://github.com/nymtech/nym/pull/5753
|
||||
[#5749]: https://github.com/nymtech/nym/pull/5749
|
||||
[#5748]: https://github.com/nymtech/nym/pull/5748
|
||||
[#5747]: https://github.com/nymtech/nym/pull/5747
|
||||
[#5746]: https://github.com/nymtech/nym/pull/5746
|
||||
[#5745]: https://github.com/nymtech/nym/pull/5745
|
||||
[#5744]: https://github.com/nymtech/nym/pull/5744
|
||||
[#5743]: https://github.com/nymtech/nym/pull/5743
|
||||
[#5742]: https://github.com/nymtech/nym/pull/5742
|
||||
[#5739]: https://github.com/nymtech/nym/pull/5739
|
||||
[#5737]: https://github.com/nymtech/nym/pull/5737
|
||||
[#5730]: https://github.com/nymtech/nym/pull/5730
|
||||
|
||||
## [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])
|
||||
- Remove inactive peers ([#5721])
|
||||
- Update Hickory DNS "0.24.4" to "0.25" ([#5709])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 7 updates ([#5708])
|
||||
- Peer handle should die more gracefully ([#5704])
|
||||
- build(deps): bump crossbeam-channel from 0.5.14 to 0.5.15 ([#5702])
|
||||
- build(deps): bump actions/checkout from 3 to 4 ([#5700])
|
||||
- Feature/updated sphinx payload keys ([#5698])
|
||||
- Bump the nym-vpn deb metapackage to 1.0 ([#5697])
|
||||
- Make mix hops optional for Mixnet Client ([#5696])
|
||||
- build(deps): bump tokio from 1.44.1 to 1.44.2 ([#5693])
|
||||
- Feature/replay protection ([#5682])
|
||||
- Adding fresh nym-api tests and workflow ([#5659])
|
||||
- build(deps): bump next from 14.2.21 to 14.2.25 ([#5655])
|
||||
- build(deps): bump pnpm/action-setup from 4.0.0 to 4.1.0 ([#5436])
|
||||
|
||||
[#5731]: https://github.com/nymtech/nym/pull/5731
|
||||
[#5721]: https://github.com/nymtech/nym/pull/5721
|
||||
[#5709]: https://github.com/nymtech/nym/pull/5709
|
||||
[#5708]: https://github.com/nymtech/nym/pull/5708
|
||||
[#5704]: https://github.com/nymtech/nym/pull/5704
|
||||
[#5702]: https://github.com/nymtech/nym/pull/5702
|
||||
[#5700]: https://github.com/nymtech/nym/pull/5700
|
||||
[#5698]: https://github.com/nymtech/nym/pull/5698
|
||||
[#5697]: https://github.com/nymtech/nym/pull/5697
|
||||
[#5696]: https://github.com/nymtech/nym/pull/5696
|
||||
[#5693]: https://github.com/nymtech/nym/pull/5693
|
||||
[#5682]: https://github.com/nymtech/nym/pull/5682
|
||||
[#5659]: https://github.com/nymtech/nym/pull/5659
|
||||
[#5655]: https://github.com/nymtech/nym/pull/5655
|
||||
[#5436]: https://github.com/nymtech/nym/pull/5436
|
||||
|
||||
## [2025.7-tex] (2025-04-14)
|
||||
|
||||
- Expand /v3/nym-nodes with geodata ([#5686])
|
||||
|
||||
Generated
+421
-243
File diff suppressed because it is too large
Load Diff
+13
-18
@@ -122,7 +122,7 @@ members = [
|
||||
"service-providers/common",
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
"tools/echo-server",
|
||||
"sqlx-pool-guard",
|
||||
"tools/echo-server",
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
@@ -161,12 +161,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 +204,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.37"
|
||||
clap = "4.5.38"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.2"
|
||||
@@ -286,8 +281,8 @@ 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"
|
||||
proc_pidinfo = "0.1.3"
|
||||
quote = "1"
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3"
|
||||
@@ -310,7 +305,7 @@ 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"
|
||||
sphinx-packet = "=0.6.0"
|
||||
sqlx = "0.7.4"
|
||||
@@ -330,8 +325,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 +337,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 +350,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 +380,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.53"
|
||||
version = "1.1.56"
|
||||
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.53"
|
||||
version = "1.1.56"
|
||||
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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::BadGateway;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::{io, path::PathBuf};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -19,7 +18,6 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to perform sqlx migration: {source}")]
|
||||
MigrationError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::migrate::MigrateError,
|
||||
},
|
||||
@@ -32,7 +30,6 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to run the SQL query: {source}")]
|
||||
QueryError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::error::Error,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::{
|
||||
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
|
||||
use crate::{
|
||||
client::replies::reply_storage::{fs_backend, CombinedReplyStorage, ReplyStorageBackend},
|
||||
config,
|
||||
config::Config,
|
||||
error::ClientCoreError,
|
||||
};
|
||||
use crate::config;
|
||||
use crate::config::Config;
|
||||
use crate::error::ClientCoreError;
|
||||
use log::{error, info, trace};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core_gateways_storage::OnDiskGatewaysDetails;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_validator_client::nyxd;
|
||||
use nym_validator_client::QueryHttpRpcNyxdClient;
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient};
|
||||
use std::{io, path::Path};
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
@@ -22,11 +20,11 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
db_path: P,
|
||||
surb_config: &config::ReplySurbs,
|
||||
) -> Result<fs_backend::Backend, ClientCoreError> {
|
||||
info!("creating fresh surb database");
|
||||
info!("Creating fresh surb database");
|
||||
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
|
||||
Ok(backend) => backend,
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}");
|
||||
error!("setup_fresh_backend: Failed to setup persistent storage backend for our reply needs: {err}");
|
||||
return Err(ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
});
|
||||
@@ -40,14 +38,15 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
surb_config.minimum_reply_surb_storage_threshold,
|
||||
surb_config.maximum_reply_surb_storage_threshold,
|
||||
);
|
||||
storage_backend
|
||||
.init_fresh(&mem_store)
|
||||
.await
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
|
||||
Ok(storage_backend)
|
||||
match storage_backend.init_fresh(&mem_store).await {
|
||||
Ok(()) => Ok(storage_backend),
|
||||
Err(err) => {
|
||||
storage_backend.shutdown().await;
|
||||
Err(ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn setup_inactive_backend(surb_config: &config::ReplySurbs) -> fs_backend::Backend {
|
||||
@@ -58,12 +57,11 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
// )
|
||||
// }
|
||||
|
||||
fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
async fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
let db_path = db_path.as_ref();
|
||||
debug_assert!(db_path.exists());
|
||||
|
||||
let now = OffsetDateTime::now_utc().unix_timestamp();
|
||||
|
||||
let suffix = format!("_{now}.corrupted");
|
||||
|
||||
let new_extension =
|
||||
@@ -72,11 +70,15 @@ fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
} else {
|
||||
suffix
|
||||
};
|
||||
let renamed = db_path.with_extension(new_extension);
|
||||
|
||||
let mut renamed = db_path.to_owned();
|
||||
renamed.set_extension(new_extension);
|
||||
|
||||
fs::rename(db_path, renamed)
|
||||
tokio::fs::rename(db_path, &renamed).await.inspect_err(|_| {
|
||||
error!(
|
||||
"Failed to rename corrupt database file: {} to {}",
|
||||
db_path.display(),
|
||||
renamed.display()
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
@@ -87,13 +89,12 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
// the existing one
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("loading existing surb database");
|
||||
info!("Loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
|
||||
archive_corrupted_database(db_path)?;
|
||||
error!("setup_fs_reply_surb_backend: Failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
archive_corrupted_database(db_path).await?;
|
||||
setup_fresh_backend(db_path, surb_config).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -17,15 +17,26 @@ nym-crypto = { path = "../../crypto", optional = true, default-features = false
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-task = { path = "../../task" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
workspace = true
|
||||
features = ["fs"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
workspace = true
|
||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
optional = true
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard]
|
||||
path = "../../../sqlx-pool-guard"
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
|
||||
[features]
|
||||
fs-surb-storage = ["sqlx", "nym-crypto", "nym-crypto/hashing"]
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::{io, path::PathBuf};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -30,7 +29,6 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to perform sqlx migration: {source}")]
|
||||
MigrationError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::migrate::MigrateError,
|
||||
},
|
||||
@@ -43,7 +41,6 @@ pub enum StorageError {
|
||||
|
||||
#[error("failed to run the SQL query: {source}")]
|
||||
QueryError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::error::Error,
|
||||
},
|
||||
|
||||
@@ -15,9 +15,11 @@ use sqlx::{
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
use sqlx_pool_guard::SqlitePoolGuard;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StorageManager {
|
||||
pub connection_pool: sqlx::SqlitePool,
|
||||
connection_pool: SqlitePoolGuard,
|
||||
}
|
||||
|
||||
// all SQL goes here
|
||||
@@ -37,7 +39,7 @@ impl StorageManager {
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.filename(&database_path)
|
||||
.create_if_missing(fresh)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -49,11 +51,15 @@ impl StorageManager {
|
||||
}
|
||||
};
|
||||
|
||||
let connection_pool =
|
||||
SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool);
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./fs_surbs_migrations")
|
||||
.run(&connection_pool)
|
||||
.run(&*connection_pool)
|
||||
.await
|
||||
{
|
||||
error!("Failed to initialize SQLx database: {err}");
|
||||
connection_pool.close().await;
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
@@ -61,38 +67,43 @@ impl StorageManager {
|
||||
Ok(StorageManager { connection_pool })
|
||||
}
|
||||
|
||||
/// Close connection pool waiting for all connections to be closed.
|
||||
pub async fn close_pool(&self) {
|
||||
self.connection_pool.close().await;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn status_table_exists(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT name FROM sqlite_master WHERE type='table' AND name='status'")
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.is_some())
|
||||
}
|
||||
|
||||
pub async fn create_status_table(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("INSERT INTO status(flush_in_progress, previous_flush_timestamp, client_in_use) VALUES (0, 0, 1)")
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_flush_status(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT flush_in_progress FROM status;")
|
||||
.fetch_one(&self.connection_pool)
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.flush_in_progress > 0)
|
||||
}
|
||||
|
||||
pub async fn set_previous_flush_timestamp(&self, timestamp: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("UPDATE status SET previous_flush_timestamp = ?", timestamp)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_previous_flush_timestamp(&self) -> Result<i64, sqlx::Error> {
|
||||
sqlx::query!("SELECT previous_flush_timestamp FROM status;")
|
||||
.fetch_one(&self.connection_pool)
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.previous_flush_timestamp)
|
||||
}
|
||||
@@ -100,14 +111,14 @@ impl StorageManager {
|
||||
pub async fn set_flush_status(&self, in_progress: bool) -> Result<(), sqlx::Error> {
|
||||
let in_progress_int = i64::from(in_progress);
|
||||
sqlx::query!("UPDATE status SET flush_in_progress = ?", in_progress_int)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_client_in_use_status(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT client_in_use FROM status;")
|
||||
.fetch_one(&self.connection_pool)
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.client_in_use > 0)
|
||||
}
|
||||
@@ -115,21 +126,21 @@ impl StorageManager {
|
||||
pub async fn set_client_in_use_status(&self, in_use: bool) -> Result<(), sqlx::Error> {
|
||||
let in_use_int = i64::from(in_use);
|
||||
sqlx::query!("UPDATE status SET client_in_use = ?", in_use_int)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_tags(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM sender_tag;")
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_tags(&self) -> Result<Vec<StoredSenderTag>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredSenderTag, "SELECT * FROM sender_tag;",)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -141,21 +152,21 @@ impl StorageManager {
|
||||
stored_tag.recipient,
|
||||
stored_tag.tag
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_all_reply_keys(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM reply_key;")
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_reply_keys(&self) -> Result<Vec<StoredReplyKey>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredReplyKey, "SELECT * FROM reply_key;",)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -171,14 +182,14 @@ impl StorageManager {
|
||||
stored_reply_key.reply_key,
|
||||
stored_reply_key.sent_at_timestamp
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_surb_senders(&self) -> Result<Vec<StoredSurbSender>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredSurbSender, "SELECT * FROM reply_surb_sender;",)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -193,7 +204,7 @@ impl StorageManager {
|
||||
stored_surb_sender.tag,
|
||||
stored_surb_sender.last_sent_timestamp
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
Ok(id)
|
||||
@@ -208,17 +219,17 @@ impl StorageManager {
|
||||
"SELECT * FROM reply_surb WHERE reply_surb_sender_id = ?",
|
||||
sender_id
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_reply_surb_data(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM reply_surb;")
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query!("DELETE FROM reply_surb_sender;")
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@@ -235,7 +246,7 @@ impl StorageManager {
|
||||
stored_reply_surb.reply_surb_sender_id,
|
||||
stored_reply_surb.reply_surb
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -249,7 +260,7 @@ impl StorageManager {
|
||||
SELECT min_reply_surb_threshold as "min_reply_surb_threshold: u32", max_reply_surb_threshold as "max_reply_surb_threshold: u32" FROM reply_surb_storage_metadata;
|
||||
"#,
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.fetch_one(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -263,7 +274,7 @@ impl StorageManager {
|
||||
"#,
|
||||
metadata.min_reply_surb_threshold,
|
||||
metadata.max_reply_surb_threshold,
|
||||
).execute(&self.connection_pool).await?;
|
||||
).execute(&*self.connection_pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::backend::fs_backend::manager::StorageManager;
|
||||
use crate::backend::fs_backend::models::{
|
||||
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag, StoredSurbSender,
|
||||
};
|
||||
use crate::surb_storage::ReceivedReplySurbs;
|
||||
use crate::{
|
||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
|
||||
backend::fs_backend::{
|
||||
manager::StorageManager,
|
||||
models::{
|
||||
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag,
|
||||
StoredSurbSender,
|
||||
},
|
||||
},
|
||||
surb_storage::ReceivedReplySurbs,
|
||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys,
|
||||
UsedSenderTags,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error, info, warn};
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -41,15 +44,17 @@ impl Backend {
|
||||
}
|
||||
|
||||
let manager = StorageManager::init(database_path, true).await?;
|
||||
manager.create_status_table().await?;
|
||||
|
||||
let backend = Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager,
|
||||
};
|
||||
|
||||
Ok(backend)
|
||||
match manager.create_status_table().await {
|
||||
Ok(()) => Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager,
|
||||
}),
|
||||
Err(err) => {
|
||||
manager.close_pool().await;
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn try_load<P: AsRef<Path>>(
|
||||
@@ -64,7 +69,28 @@ impl Backend {
|
||||
}
|
||||
|
||||
let manager = StorageManager::init(database_path, false).await?;
|
||||
match Self::try_load_inner(&manager, fresh_sender_tags).await {
|
||||
Ok(()) => Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager,
|
||||
}),
|
||||
Err(e) => {
|
||||
manager.close_pool().await;
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gracefully close sqlite connection pool and drop backend.
|
||||
pub async fn shutdown(self) {
|
||||
self.manager.close_pool().await
|
||||
}
|
||||
|
||||
async fn try_load_inner(
|
||||
manager: &StorageManager,
|
||||
fresh_sender_tags: bool,
|
||||
) -> Result<(), StorageError> {
|
||||
// the database flush wasn't fully finished and thus the data is in inconsistent state
|
||||
// (we don't really know what's properly saved or what's not)
|
||||
if manager.get_flush_status().await? {
|
||||
@@ -126,20 +152,11 @@ impl Backend {
|
||||
manager.delete_all_tags().await?;
|
||||
}
|
||||
|
||||
Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
// manager: StorageManagerState::Storage(manager),
|
||||
manager,
|
||||
})
|
||||
}
|
||||
|
||||
async fn close_pool(&mut self) {
|
||||
self.manager.connection_pool.close().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn rotate(&mut self) -> Result<(), StorageError> {
|
||||
self.close_pool().await;
|
||||
self.manager.close_pool().await;
|
||||
|
||||
let new_extension = if let Some(existing_extension) =
|
||||
self.database_path.extension().and_then(|ext| ext.to_str())
|
||||
@@ -152,7 +169,8 @@ impl Backend {
|
||||
let mut temp_old = self.database_path.clone();
|
||||
temp_old.set_extension(new_extension);
|
||||
|
||||
fs::rename(&self.database_path, &temp_old)
|
||||
tokio::fs::rename(&self.database_path, &temp_old)
|
||||
.await
|
||||
.map_err(|err| StorageError::DatabaseRenameError { source: err })?;
|
||||
self.manager = StorageManager::init(&self.database_path, true).await?;
|
||||
self.manager.create_status_table().await?;
|
||||
@@ -161,9 +179,10 @@ impl Backend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_old(&mut self) -> Result<(), StorageError> {
|
||||
async fn remove_old(&mut self) -> Result<(), StorageError> {
|
||||
if let Some(old_path) = self.temporary_old_path.take() {
|
||||
fs::remove_file(old_path)
|
||||
tokio::fs::remove_file(old_path)
|
||||
.await
|
||||
.map_err(|err| StorageError::DatabaseOldFileRemoveError { source: err })
|
||||
} else {
|
||||
warn!("the old database file doesn't seem to exist!");
|
||||
@@ -335,7 +354,7 @@ impl ReplyStorageBackend for Backend {
|
||||
self.dump_reply_surb_storage_metadata(surbs_ref).await?;
|
||||
self.dump_reply_surbs(surbs_ref).await?;
|
||||
|
||||
self.remove_old()?;
|
||||
self.remove_old().await?;
|
||||
self.end_storage_flush().await
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,7 @@ 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);
|
||||
|
||||
@@ -318,6 +318,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 +334,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 +360,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 +376,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 +402,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 +418,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 +442,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,7 +458,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",
|
||||
|
||||
@@ -20,6 +20,8 @@ nym-credentials = { path = "../credentials" }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard]
|
||||
path = "../../sqlx-pool-guard"
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
workspace = true
|
||||
@@ -31,8 +33,13 @@ features = ["rt-multi-thread", "net", "signal", "fs"]
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
[features]
|
||||
persistent-storage = ["bincode", "serde"]
|
||||
persistent-storage = ["bincode", "serde"]
|
||||
|
||||
@@ -7,10 +7,11 @@ use crate::models::{
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use sqlx::{Executor, Sqlite, Transaction};
|
||||
use sqlx_pool_guard::SqlitePoolGuard;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SqliteEcashTicketbookManager {
|
||||
connection_pool: sqlx::SqlitePool,
|
||||
connection_pool: SqlitePoolGuard,
|
||||
}
|
||||
|
||||
impl SqliteEcashTicketbookManager {
|
||||
@@ -19,7 +20,7 @@ impl SqliteEcashTicketbookManager {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `connection_pool`: database connection pool to use.
|
||||
pub fn new(connection_pool: sqlx::SqlitePool) -> Self {
|
||||
pub fn new(connection_pool: SqlitePoolGuard) -> Self {
|
||||
SqliteEcashTicketbookManager { connection_pool }
|
||||
}
|
||||
|
||||
@@ -33,7 +34,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"DELETE FROM ecash_ticketbook WHERE expiration_date <= ?",
|
||||
deadline
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -60,7 +61,7 @@ impl SqliteEcashTicketbookManager {
|
||||
data,
|
||||
expiration_date,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@@ -90,7 +91,7 @@ impl SqliteEcashTicketbookManager {
|
||||
epoch_id,
|
||||
total_tickets,
|
||||
used_tickets,
|
||||
).execute(&self.connection_pool).await?;
|
||||
).execute(&*self.connection_pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -106,7 +107,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
)
|
||||
.bind(data)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await?
|
||||
.is_some();
|
||||
|
||||
@@ -122,7 +123,7 @@ impl SqliteEcashTicketbookManager {
|
||||
FROM ecash_ticketbook
|
||||
"#,
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ impl SqliteEcashTicketbookManager {
|
||||
ticketbook_id,
|
||||
expected_current_total_spent
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?
|
||||
.rows_affected();
|
||||
Ok(affected > 0)
|
||||
@@ -154,7 +155,7 @@ impl SqliteEcashTicketbookManager {
|
||||
&self,
|
||||
) -> Result<Vec<StoredPendingTicketbook>, sqlx::Error> {
|
||||
sqlx::query_as("SELECT * FROM pending_issuance")
|
||||
.fetch_all(&self.connection_pool)
|
||||
.fetch_all(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -166,7 +167,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"DELETE FROM pending_issuance WHERE deposit_id = ?",
|
||||
pending_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -183,7 +184,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -209,7 +210,7 @@ impl SqliteEcashTicketbookManager {
|
||||
serialisation_revision,
|
||||
epoch_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -226,7 +227,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
epoch_id
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -252,7 +253,7 @@ impl SqliteEcashTicketbookManager {
|
||||
serialisation_revision,
|
||||
epoch_id,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -270,7 +271,7 @@ impl SqliteEcashTicketbookManager {
|
||||
"#,
|
||||
expiration_date
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -299,7 +300,7 @@ impl SqliteEcashTicketbookManager {
|
||||
serialisation_revision,
|
||||
expiration_date
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use sqlx_pool_guard::SqlitePoolGuard;
|
||||
use std::path::Path;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
@@ -62,7 +63,7 @@ impl PersistentStorage {
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.filename(&database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -74,13 +75,17 @@ impl PersistentStorage {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await {
|
||||
let connection_pool =
|
||||
SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool);
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./migrations").run(&*connection_pool).await {
|
||||
error!("Failed to perform migration on the SQLx database: {err}");
|
||||
connection_pool.close().await;
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
Ok(PersistentStorage {
|
||||
storage_manager: SqliteEcashTicketbookManager::new(connection_pool.clone()),
|
||||
storage_manager: SqliteEcashTicketbookManager::new(connection_pool),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 \;
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH":/root
|
||||
PASSPHRASE=passphrase
|
||||
APP_NAME=nyxd
|
||||
OUTPUT_DIRECTORY="/root/output"
|
||||
VALIDATOR_DATA_DIRECTORY="/root/.${APP_NAME}"
|
||||
|
||||
mkdir -p "${VALIDATOR_DATA_DIRECTORY}/config"
|
||||
mkdir -p "${OUTPUT_DIRECTORY}"
|
||||
|
||||
if [ ! -f "${VALIDATOR_DATA_DIRECTORY}/config/genesis.json" ]; then
|
||||
# initialise the validator
|
||||
./${APP_NAME} init "${CHAIN_ID}" --chain-id "${CHAIN_ID}" 2>/dev/null
|
||||
|
||||
echo "init chain successful"
|
||||
sleep 5
|
||||
|
||||
echo "checking config files:"
|
||||
ls -la ${VALIDATOR_DATA_DIRECTORY}/config/
|
||||
|
||||
echo "changing params"
|
||||
sed -i "s/\"stake\"/\"u${STAKE_DENOM}\"/" "${VALIDATOR_DATA_DIRECTORY}/config/genesis.json"
|
||||
sed -i 's/minimum-gas-prices = "0stake"/minimum-gas-prices = "0.025u'"${DENOM}"'"/' "${VALIDATOR_DATA_DIRECTORY}/config/app.toml"
|
||||
sed -i '0,/enable = false/s//enable = true/g' "${VALIDATOR_DATA_DIRECTORY}/config/app.toml"
|
||||
if [ "$RETAIN_BLOCKS" = "no" ]; then
|
||||
# amending to say if min retain blocks should be set yes or no...
|
||||
sed -i 's/min-retain-blocks = 0/min-retain-blocks = 70000/' "${VALIDATOR_DATA_DIRECTORY}/config/app.toml"
|
||||
fi
|
||||
sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' "${VALIDATOR_DATA_DIRECTORY}/config/config.toml"
|
||||
sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' "${VALIDATOR_DATA_DIRECTORY}/config/config.toml"
|
||||
sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' "${VALIDATOR_DATA_DIRECTORY}/config/config.toml"
|
||||
sed -i 's/address = "tcp:\/\/localhost:1317"/address = "tcp:\/\/0.0.0.0:1317"/' "${VALIDATOR_DATA_DIRECTORY}/config/app.toml"
|
||||
|
||||
echo "params changed"
|
||||
|
||||
echo "adding parent mnemonic account details"
|
||||
yes "${PASSPHRASE}" | ./${APP_NAME} keys add node_admin 2>&1 >/dev/null | tail -n 1 >${OUTPUT_DIRECTORY}/node_admin_mnemonic
|
||||
|
||||
# add genesis accounts with some initial tokens
|
||||
echo "adding genesis account details"
|
||||
GENESIS_ADDRESS=$(yes "${PASSPHRASE}" | ./${APP_NAME} keys show node_admin -a)
|
||||
yes "${PASSPHRASE}" | ./${APP_NAME} genesis add-genesis-account "${GENESIS_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}"
|
||||
|
||||
echo "adding gentx time :)"
|
||||
yes "${PASSPHRASE}" | ./${APP_NAME} genesis gentx node_admin 100000000000u"${STAKE_DENOM}" --chain-id "${CHAIN_ID}" 2>/dev/null
|
||||
./${APP_NAME} genesis collect-gentxs 2>/dev/null
|
||||
./${APP_NAME} genesis validate-genesis >/dev/null
|
||||
|
||||
# make a copy of the genesis file to the output directory
|
||||
cp "${VALIDATOR_DATA_DIRECTORY}/config/genesis.json" "${OUTPUT_DIRECTORY}/genesis.json"
|
||||
fi
|
||||
|
||||
./${APP_NAME} start &
|
||||
sleep 10
|
||||
|
||||
sleep infinity
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM rust
|
||||
RUN rustup target add wasm32-unknown-unknown
|
||||
CMD cd nym/contracts/vesting && RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function RewardsCalculator() {
|
||||
const [a, setA] = useState(0)
|
||||
const [b, setB] = useState(0)
|
||||
const [c, setC] = useState(0)
|
||||
const [d, setD] = useState(0)
|
||||
const [e, setE] = useState(0)
|
||||
|
||||
const result =
|
||||
e !== 0
|
||||
? `${(
|
||||
a * b * c * ((1 / 240) + 0.3 * ((d / e) / 240)) * 1 / (1 + 0.3)
|
||||
).toFixed(4)} NYM`
|
||||
: '—'
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
margin: '1.5em 0',
|
||||
padding: '1em',
|
||||
border: '1px solid #9ca3af',
|
||||
borderRadius: '8px',
|
||||
maxWidth: '600px',
|
||||
backgroundColor: '#162B2C',
|
||||
color: '#9ca3af',
|
||||
fontSize: '0.95rem',
|
||||
}}
|
||||
>
|
||||
<h3
|
||||
style={{
|
||||
marginTop: 0,
|
||||
marginBottom: '0.75em',
|
||||
color: '#9ca3af',
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: '600',
|
||||
}}
|
||||
>
|
||||
Epoch Reward Calculator
|
||||
</h3>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr auto',
|
||||
gap: '0.75em 1em',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<label htmlFor="a">
|
||||
{' '}
|
||||
<a
|
||||
href="https://validator.nymtech.net/api/v1/epoch/reward_params"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: 'inherit', textDecoration: 'underline' }}
|
||||
>
|
||||
Current epoch reward budget
|
||||
</a>{' '}
|
||||
(NYM):
|
||||
</label>
|
||||
<input
|
||||
id="a"
|
||||
type="number"
|
||||
value={a}
|
||||
onChange={(e) => setA(Number(e.target.value))}
|
||||
/>
|
||||
|
||||
<label htmlFor="b">Node performance score:</label>
|
||||
<input
|
||||
id="b"
|
||||
type="number"
|
||||
value={b}
|
||||
onChange={(e) => setB(Number(e.target.value))}
|
||||
/>
|
||||
|
||||
<label htmlFor="c">Node stake saturation:</label>
|
||||
<input
|
||||
id="c"
|
||||
type="number"
|
||||
value={c}
|
||||
onChange={(e) => setC(Number(e.target.value))}
|
||||
/>
|
||||
|
||||
<label htmlFor="d">Node self bond size (NYM):</label>
|
||||
<input
|
||||
id="d"
|
||||
type="number"
|
||||
value={d}
|
||||
onChange={(e) => setD(Number(e.target.value))}
|
||||
/>
|
||||
|
||||
<label htmlFor="e">
|
||||
<a
|
||||
href="https://validator.nymtech.net/api/v1/epoch/reward_params"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: 'inherit', textDecoration: 'underline' }}
|
||||
>
|
||||
Current stake saturation point
|
||||
</a>{' '}
|
||||
(NYM):
|
||||
</label>
|
||||
<input
|
||||
id="e"
|
||||
type="number"
|
||||
value={e}
|
||||
onChange={(e) => setE(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p style={{ marginTop: '1.5em' }}>
|
||||
<strong>Node epoch rewards (if active):</strong>
|
||||
<br />
|
||||
{result}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user