Compare commits

...

64 Commits

Author SHA1 Message Date
Jon Häggblad 424e94a04e Send shutdown instead of panic when reaching max fail (#5398)
* Send shutdown instead of panic when reaching max fail

* Stop quicker on failure

* Update comment
2025-02-03 17:28:10 +02:00
benedetta davico d1fb926a2a Merge pull request #5405 from nymtech/downgrade-to-debug
HU - Downgrade harmless log message from info to debug
2025-01-30 11:34:14 +01:00
benedettadavico dea69acd49 Downgrade harmless log message from info to debug 2025-01-30 11:32:54 +01:00
Tommy Verrall ada2d2247a Merge pull request #5404 from nymtech/jstuczyn-patch-1
lower default ticket verification quorum to 0.7
2025-01-30 11:28:32 +01:00
Jędrzej Stuczyński 0159d7c27a lower default ticket verification quorum to 0.7 2025-01-30 10:16:41 +00:00
benedettadavico 28997c7f97 adding changelog for hu 2025-01-28 09:02:54 +01:00
Jędrzej Stuczyński a6c586a33b chore :update version of chain watcher and validator rewarder (#5394) 2025-01-27 15:47:37 +00:00
Jędrzej Stuczyński 7c85c1a271 bugfix: correctly handle ingore epoch roles flag (#5390) 2025-01-24 15:35:06 +00:00
Jędrzej Stuczyński 92c8d1b73f bugfix: terminate mixnet socket listener on shutdown (#5389) 2025-01-24 12:59:14 +00:00
Jędrzej Stuczyński 554e9ca490 feat: make client ignore dual mode nodes by default (#5388) 2025-01-24 12:07:25 +00:00
Bogdan-Ștefan Neacşu 6e6675f7bf Handle ecash network errors differently (#5378) 2025-01-22 15:46:05 +01:00
Bogdan-Ștefan Neacşu a7f7ebfbae Remove empty ephemeral keys (#5376) 2025-01-22 12:11:01 +01:00
Jędrzej Stuczyński 1aec8be85e fixed sql migration for adding default message timestamp (#5374) 2025-01-21 10:00:11 +00:00
benedettadavico 4b474dd8ff bump versions for hu 2025-01-20 15:34:23 +01:00
benedetta davico 9b627dd70f Merge pull request #5363 from nymtech/fix-ci 2025-01-17 11:35:04 +01:00
Bogdan-Ștefan Neacşu 9a0b769425 Bind to [::] on nym-node for both IP versions (#5361)
* Bind to [::] on nym-node for both IP versions

* Force update to be run

* Fix after merging develop
2025-01-17 11:32:33 +01:00
Sachin Kamath 8e14f5f884 Update ci-build-upload-binaries.yml
remove observatory
2025-01-17 15:11:53 +05:30
import this 1b64cb42b0 [DOCs/operators]: Guides, changes and release-notes for v2025.1-reeses (#5340)
* create ToC snippet

* fund node client account

* revamp node guide

* finish setup page revamp

* add new update to changelog

* fix wallet dowload uls

* fix operator steps urls

* fix operator steps urls

* fix operator steps urls

* finish release notes

* finish changelog

* debug build

* correct links syntax

* add remote mnemonic pull command
2025-01-16 15:23:58 +00:00
Jędrzej Stuczyński 03c4895f2b feature: introduce /load endpoint for self-reported quantised NymNode load (#5326)
* feature: introduce /load endpoint for self-reported quantised NymNode load

* return Load::Unknown for value of 0 because it means we misread some data

* add additional filtering on 'en...' endpoints
2025-01-16 15:13:08 +00:00
Jędrzej Stuczyński dcfb092758 updated cosmrs and tendermint-rpc to their most recent versions (#5339) 2025-01-16 14:52:36 +00:00
Jędrzej Stuczyński 9305ad5364 exposed NymApiClient method for obtaining node performance history (#5360)
* exposed NymApiClient method for obtaining node performance history

* using path constants for route definition
2025-01-16 14:50:09 +00:00
Jędrzej Stuczyński ea5aef6c2f Client gateway selection (#5358)
* filter out dual-role gateways during selection

* changed behaviour of egress node validitiy
2025-01-16 14:24:27 +00:00
Jędrzej Stuczyński 61a4433cd9 chore: update indexed_db_futures (#5347)
* chore: update indexed_db_futures

* clippy
2025-01-16 14:23:43 +00:00
benedetta davico 5c89d36140 Merge pull request #5359 from nymtech/release/2025.1-reeses
merge reeses patch to develop
2025-01-16 13:34:36 +01:00
benedetta davico 5ab164d229 Update Cargo.toml 2025-01-16 12:51:53 +01:00
Jędrzej Stuczyński 26538c5884 bugfix: only consider pre-existing peers for wg bytes metric (#5357) 2025-01-16 11:50:26 +00:00
Jędrzej Stuczyński adb248dbcc chore: refresh wasm sdk (#5353)
* make packet statistics wasm-compatible

* fixed possible overflow issue in delay controller

* updated wasm-client to be compatible with the current network

* applied same logic to mixfetch client

* removed dead imports

* updated versions
2025-01-15 17:11:17 +00:00
Sachin Kamath fffec65cab NS API: add mixnet scraper (#5200)
* ns-api: add mixnode scraper

* clippy

* rebase
2025-01-15 13:12:11 +01:00
benedetta davico bb24004d46 Merge pull request #5352 from nymtech/merge/release/2025.1-reeses 2025-01-15 11:34:39 +01:00
Jędrzej Stuczyński c487eff7ca Merge branch 'release/2025.1-reeses' into develop 2025-01-15 10:18:45 +00:00
Jędrzej Stuczyński 5fa21c9aae chore: remove performed mixnet contract migration (#5350) 2025-01-15 10:06:04 +00:00
dependabot[bot] fd18aae0d6 build(deps): bump log in the patch-updates group across 1 directory (#5348)
Bumps the patch-updates group with 1 update in the / directory: [log](https://github.com/rust-lang/log).


Updates `log` from 0.4.22 to 0.4.25
- [Release notes](https://github.com/rust-lang/log/releases)
- [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/log/compare/0.4.22...0.4.25)

---
updated-dependencies:
- dependency-name: log
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-15 10:01:25 +00:00
benedettadavico c202e2d598 adding changelog for reeses 2025-01-15 10:27:39 +01:00
mfahampshire 62d23cff9f removed old todos (#5349) 2025-01-14 16:37:30 +00:00
mfahampshire e454d71b78 Max/client pool (#5188)
* tcp conn tracker

* make default decay const

* first pass connpool

* err handling conpool start

* added notes for next features

* first version working

* first pass spin out client_pool

* cancel token

* logging change

* bump default decay time

* bugfix: make sure to apply gateway score filtering when choosing initial node

* add duplicate packets received to troubleshooting

* client_pool.rs mod

* client pool example

* clippy

* client pool example done

* added disconnect to client pool

* update mod file

* add cancel token disconnect fn

* comments

* comments

* add clone

* added disconnect thread

* update example files tcpproxy

* client pool docs

* remove comments for future ffi push + lower default pool size from 4 to 2

* comment on ffi

* update command help

* clone impl

* remove clone

* fix clippy

* fix clippy again

* fix test

* tweaked text grammar

* updated comment in example

* future is now

* cherry

* cherry

* fix borked rebase

* fix fmt

* wasm fix

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2025-01-14 16:11:47 +00:00
huximaxi a7874add88 Merge pull request #5346 from nymtech/feture/legacy_alert
Feture/legacy alert
2025-01-14 15:00:49 +01:00
dependabot[bot] 0a47d5dcf8 build(deps): bump criterion from 0.4.0 to 0.5.1 (#4911)
Bumps [criterion](https://github.com/bheisler/criterion.rs) from 0.4.0 to 0.5.1.
- [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bheisler/criterion.rs/compare/0.4.0...0.5.1)

---
updated-dependencies:
- dependency-name: criterion
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-14 13:47:58 +00:00
RadekSabacky 3d84be22e2 + add releaseAlert component 2025-01-14 13:41:30 +01:00
dependabot[bot] 6ccbb30491 build(deps): bump http from 1.1.0 to 1.2.0 (#5228)
Bumps [http](https://github.com/hyperium/http) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/hyperium/http/releases)
- [Changelog](https://github.com/hyperium/http/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/http/compare/v1.1.0...v1.2.0)

---
updated-dependencies:
- dependency-name: http
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-14 12:33:17 +00:00
dependabot[bot] 91c205f83a build(deps): bump the patch-updates group with 8 updates (#5336)
Bumps the patch-updates group with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.84` | `0.1.85` |
| [clap](https://github.com/clap-rs/clap) | `4.5.23` | `4.5.26` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.40` | `4.5.42` |
| [futures](https://github.com/rust-lang/futures-rs) | `0.3.30` | `0.3.31` |
| [pin-project](https://github.com/taiki-e/pin-project) | `1.1.7` | `1.1.8` |
| [pin-project-lite](https://github.com/taiki-e/pin-project-lite) | `0.2.15` | `0.2.16` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.134` | `1.0.135` |
| [wasm-bindgen-test](https://github.com/rustwasm/wasm-bindgen) | `0.3.45` | `0.3.49` |


Updates `async-trait` from 0.1.84 to 0.1.85
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.84...0.1.85)

Updates `clap` from 4.5.23 to 4.5.26
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.23...clap_complete-v4.5.26)

Updates `clap_complete` from 4.5.40 to 4.5.42
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.40...clap_complete-v4.5.42)

Updates `futures` from 0.3.30 to 0.3.31
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.30...0.3.31)

Updates `pin-project` from 1.1.7 to 1.1.8
- [Release notes](https://github.com/taiki-e/pin-project/releases)
- [Changelog](https://github.com/taiki-e/pin-project/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/pin-project/compare/v1.1.7...v1.1.8)

Updates `pin-project-lite` from 0.2.15 to 0.2.16
- [Release notes](https://github.com/taiki-e/pin-project-lite/releases)
- [Changelog](https://github.com/taiki-e/pin-project-lite/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/pin-project-lite/compare/v0.2.15...v0.2.16)

Updates `serde_json` from 1.0.134 to 1.0.135
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.134...v1.0.135)

Updates `wasm-bindgen-test` from 0.3.45 to 0.3.49
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: futures
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: pin-project
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: pin-project-lite
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: wasm-bindgen-test
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-14 12:30:31 +00:00
dependabot[bot] 4a704e992a build(deps): bump tempfile from 3.14.0 to 3.15.0 (#5337)
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.14.0 to 3.15.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.14.0...v3.15.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-14 12:29:40 +00:00
dependabot[bot] 6c88c7df42 build(deps): bump ts-rs from 10.0.0 to 10.1.0 (#5338)
Bumps [ts-rs](https://github.com/Aleph-Alpha/ts-rs) from 10.0.0 to 10.1.0.
- [Release notes](https://github.com/Aleph-Alpha/ts-rs/releases)
- [Changelog](https://github.com/Aleph-Alpha/ts-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Aleph-Alpha/ts-rs/compare/v10.0.0...v10.1.0)

---
updated-dependencies:
- dependency-name: ts-rs
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-14 12:29:13 +00:00
dependabot[bot] 2a748fc968 build(deps): bump mikefarah/yq from 4.44.6 to 4.45.1 (#5342)
Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.44.6 to 4.45.1.
- [Release notes](https://github.com/mikefarah/yq/releases)
- [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt)
- [Commits](https://github.com/mikefarah/yq/compare/v4.44.6...v4.45.1)

---
updated-dependencies:
- dependency-name: mikefarah/yq
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-14 12:28:43 +00:00
RadekSabacky 25766dc0ec + add alert message into nav components 2025-01-14 13:22:31 +01:00
mfahampshire 07544d939e Max/docs gen update (#5333)
* update landing page icons

* new architecture diagram

* force dark theme

* new nyx consolidated page

* epoch page

* overhaul traffic flow + add diagram

* note on dvpn mode

* fix formatting of lists

* remove old todo
2025-01-14 11:25:06 +00:00
Jędrzej Stuczyński 102cd1033c feature: CancellationToken-based shutdowns (#5325)
* initial stub for ShutdownToken

* attempting to start using new ShutdownManager in NymNode

* migrated verloc tasks

* added custom shutdown signal registration

* integrated legacy task support

* migrated additional tasks inside nym-node

* removed import thats unused in wasm

* apply review comments

* windows fixes
2025-01-13 09:13:13 +00:00
Jędrzej Stuczyński 676e93a372 bugfix: make sure refresh data key matches bond info (#5329) 2025-01-10 14:52:52 +00:00
Jędrzej Stuczyński 5a6770e5e2 chore: readjusted --mode behaviour to fix the regression (#5331) 2025-01-10 13:17:03 +00:00
Jędrzej Stuczyński 529e8d49ee chore: apply 1.84 linter suggestions (#5330)
* chore: apply 1.84 linter suggestions

* updated wasm dependencies to fix the macro issue

* second batch of clippy fixes
2025-01-10 13:00:18 +00:00
benedettadavico a94c035c0a correct the nym-node bumped version 2025-01-09 12:36:05 +01:00
Jędrzej Stuczyński 24480418f0 Bugfix/contract version assignment (#5318)
* fixed contract version being overwritten

* introduced migration to fix existing [mainnet] state

* updated contract schema

* updated testnet manager migrate msg code
2025-01-09 10:00:37 +00:00
Jędrzej Stuczyński a46245ffe3 feat: warn users if node is run in exit mode only (#5320)
* added 'full-gateway' nymnode mode to enable both entry and exit at the same time

* warning for running node in exit mode only
2025-01-09 09:02:52 +00:00
Jędrzej Stuczyński 7c1c13e139 reduce log severity for number of packets being delayed (#5321) 2025-01-09 09:02:37 +00:00
Jędrzej Stuczyński 836a93cd96 fixed client session histogram buckets (#5316) 2025-01-08 10:26:40 +00:00
benedettadavico b47a742dd0 update nym-node binary version 2025-01-08 10:37:48 +01:00
benedetta davico 6e14882246 Merge pull request #5315 from nymtech/release/2024.14-crunch-patched
Merge crunch patched to reeses
2025-01-08 10:35:54 +01:00
Tommy Verrall a7466a0e02 Merge pull request #5313 from nymtech/bugfix/append-gb-cap
amend 250gb limit
2025-01-08 09:50:04 +01:00
Tommy Verrall 78f45012db amend 250gb limit 2025-01-08 09:44:14 +01:00
benedettadavico f6a2f62ea9 bump versions of binaries 2025-01-08 09:28:48 +01:00
Jędrzej Stuczyński 3efeededc5 feature: expand nym-node prometheus metrics (#5298)
* fixed bearer auth for prometheus route

* basic prometheus metrics

* added rates on global values

* improved structure on the prometheus metrics

* added additional metrics for ingress websockets and egress mixnet connections

* some channel business metrics

* fixed metrics registration and added additional variants

* added counter for number of disk persisted packets

* counter for pending egress packets

* counter for pending egress forward packets

* clippy
2025-01-07 13:34:18 +00:00
Jędrzej Stuczyński c482350ec6 feature: wireguard metrics (#5278)
* experimental log

* introduce wireguard metrics updates

* add wireguard traffic rates to console logger

* missing import

* changed order of displayed values

* expose bytes information via rest endpoint

* clippy
2025-01-07 13:32:07 +00:00
Bogdan-Ștefan Neacşu f7a7a8072f Move tun constants to network defaults (#5286) (#5287) 2024-12-18 16:23:18 +02:00
Jon Häggblad acd068e5ab Add close to credential storage (#5283)
* Add close method to credential storage

* wip
2024-12-18 12:37:16 +01:00
Bogdan-Ștefan Neacşu caa17d933c Add windows to CI builds (#5269)
* Add windows to CI builds

* Fix win build for node status api

* Fix win build for sdk

* Fix win build for cred proxy
2024-12-17 22:26:38 +01:00
257 changed files with 18122 additions and 2903 deletions
@@ -79,7 +79,6 @@ jobs:
target/release/nym-socks5-client
target/release/nym-api
target/release/nym-network-requester
target/release/nym-data-observatory
target/release/nym-cli
target/release/nymvisor
target/release/nym-node
@@ -97,7 +96,6 @@ jobs:
cp target/release/nym-socks5-client $OUTPUT_DIR
cp target/release/nym-api $OUTPUT_DIR
cp target/release/nym-network-requester $OUTPUT_DIR
cp target/release/nym-data-observatory $OUTPUT_DIR
cp target/release/nymvisor $OUTPUT_DIR
cp target/release/nym-node $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.6
uses: mikefarah/yq@v4.45.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.6
uses: mikefarah/yq@v4.45.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.6
uses: mikefarah/yq@v4.45.1
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.44.6
uses: mikefarah/yq@v4.45.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.6
uses: mikefarah/yq@v4.45.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.44.6
uses: mikefarah/yq@v4.45.1
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.44.6
uses: mikefarah/yq@v4.45.1
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.44.6
uses: mikefarah/yq@v4.45.1
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+150
View File
@@ -4,6 +4,156 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.2-hu] (2025-01-28)
- chore :update version of chain watcher and validator rewarder ([#5394])
- bugfix: correctly handle ingore epoch roles flag ([#5390])
- bugfix: terminate mixnet socket listener on shutdown ([#5389])
- feat: make client ignore dual mode nodes by default ([#5388])
- Handle ecash network errors differently ([#5378])
- Remove empty ephemeral keys ([#5376])
- fixed sql migration for adding default message timestamp ([#5374])
- Bind to [::] on nym-node for both IP versions ([#5361])
- exposed NymApiClient method for obtaining node performance history ([#5360])
- Client gateway selection ([#5358])
- chore: refresh wasm sdk ([#5353])
- chore: update indexed_db_futures ([#5347])
- build(deps): bump mikefarah/yq from 4.44.6 to 4.45.1 ([#5342])
- updated cosmrs and tendermint-rpc to their most recent versions ([#5339])
- build(deps): bump ts-rs from 10.0.0 to 10.1.0 ([#5338])
- build(deps): bump tempfile from 3.14.0 to 3.15.0 ([#5337])
- build(deps): bump the patch-updates group with 8 updates ([#5336])
- feature: introduce /load endpoint for self-reported quantised NymNode load ([#5326])
- feature: `CancellationToken`-based shutdowns ([#5325])
- Use expect in geodata test to give error message on failure ([#5314])
- feature: periodically remove stale gateway messages ([#5312])
- build(deps): bump the patch-updates group across 1 directory with 35 updates ([#5310])
- Add dependabot assignes for the root cargo ecosystem ([#5297])
- Move tun constants to network defaults ([#5286])
- Include IPINFO_API_TOKEN in nightly CI ([#5285])
- Nyx Chain Watcher ([#5274])
- bugfix: remove unnecessary arguments for nym-api swagger endpoints ([#5272])
- feature: nym topology revamp ([#5271])
- Add windows to CI builds ([#5269])
- http-api-client: deduplicate code ([#5267])
- build(deps): bump http from 1.1.0 to 1.2.0 ([#5228])
- NS API: add mixnet scraper ([#5200])
- build(deps): bump criterion from 0.4.0 to 0.5.1 ([#4911])
[#5394]: https://github.com/nymtech/nym/pull/5394
[#5390]: https://github.com/nymtech/nym/pull/5390
[#5389]: https://github.com/nymtech/nym/pull/5389
[#5388]: https://github.com/nymtech/nym/pull/5388
[#5378]: https://github.com/nymtech/nym/pull/5378
[#5376]: https://github.com/nymtech/nym/pull/5376
[#5374]: https://github.com/nymtech/nym/pull/5374
[#5361]: https://github.com/nymtech/nym/pull/5361
[#5360]: https://github.com/nymtech/nym/pull/5360
[#5358]: https://github.com/nymtech/nym/pull/5358
[#5353]: https://github.com/nymtech/nym/pull/5353
[#5347]: https://github.com/nymtech/nym/pull/5347
[#5342]: https://github.com/nymtech/nym/pull/5342
[#5339]: https://github.com/nymtech/nym/pull/5339
[#5338]: https://github.com/nymtech/nym/pull/5338
[#5337]: https://github.com/nymtech/nym/pull/5337
[#5336]: https://github.com/nymtech/nym/pull/5336
[#5326]: https://github.com/nymtech/nym/pull/5326
[#5325]: https://github.com/nymtech/nym/pull/5325
[#5314]: https://github.com/nymtech/nym/pull/5314
[#5312]: https://github.com/nymtech/nym/pull/5312
[#5310]: https://github.com/nymtech/nym/pull/5310
[#5297]: https://github.com/nymtech/nym/pull/5297
[#5286]: https://github.com/nymtech/nym/pull/5286
[#5285]: https://github.com/nymtech/nym/pull/5285
[#5274]: https://github.com/nymtech/nym/pull/5274
[#5272]: https://github.com/nymtech/nym/pull/5272
[#5271]: https://github.com/nymtech/nym/pull/5271
[#5269]: https://github.com/nymtech/nym/pull/5269
[#5267]: https://github.com/nymtech/nym/pull/5267
[#5228]: https://github.com/nymtech/nym/pull/5228
[#5200]: https://github.com/nymtech/nym/pull/5200
[#4911]: https://github.com/nymtech/nym/pull/4911
## [2025.1-reeses] (2025-01-15)
- Feture/legacy alert ([#5346])
- chore: readjusted --mode behaviour to fix the regression ([#5331])
- chore: apply 1.84 linter suggestions ([#5330])
- bugfix: make sure refresh data key matches bond info ([#5329])
- reduce log severity for number of packets being delayed ([#5321])
- feat: warn users if node is run in exit mode only ([#5320])
- Bugfix/contract version assignment ([#5318])
- fixed client session histogram buckets ([#5316])
- amend 250gb limit ([#5313])
- feature: expand nym-node prometheus metrics ([#5298])
- Cherry picked #5286 ([#5287])
- Add close to credential storage ([#5283])
- feature: wireguard metrics ([#5278])
- Add PATCH support to nym-http-api-client ([#5260])
- chore: removed legacy socks5 listener ([#5259])
- bugfix: make sure to apply gateway score filtering when choosing initial node ([#5256])
- Update TS bindings ([#5255])
- Add conversion unit tests for auth msg ([#5251])
- Add control messages to GatewayTransciver ([#5247])
- Remove unneeded async function annotation ([#5246])
- bugfix: make sure to update timestamp of last batch verification to prevent double redemption ([#5239])
- Add FromStr impl for UserAgent ([#5236])
- Extend swagger docs ([#5235])
- TicketType derive Hash and Eq ([#5233])
- Add fd callback to client core ([#5230])
- Extend raw ws fd for gateway client ([#5218])
- Shipping raw metrics to PG ([#5216])
- Change sqlite journal mode to WAL ([#5213])
- Derive serialize for UserAgent ([#5210])
- Restore Location fields ([#5208])
- better date serialization ([#5207])
- Fix overflow ([#5204])
- feature: hopefully final steps of the smoosh™️ ([#5201])
- Fix overflow ([#5184])
- NS API - Gateway stats scraping ([#5180])
- introduced initial internal commands for nym-cli: ecash key and request generation ([#5174])
- Move NS client to separate package under NS API ([#5171])
- build(deps): bump micromatch from 4.0.4 to 4.0.8 in /testnet-faucet ([#4813])
[#5346]: https://github.com/nymtech/nym/pull/5346
[#5331]: https://github.com/nymtech/nym/pull/5331
[#5330]: https://github.com/nymtech/nym/pull/5330
[#5329]: https://github.com/nymtech/nym/pull/5329
[#5321]: https://github.com/nymtech/nym/pull/5321
[#5320]: https://github.com/nymtech/nym/pull/5320
[#5318]: https://github.com/nymtech/nym/pull/5318
[#5316]: https://github.com/nymtech/nym/pull/5316
[#5313]: https://github.com/nymtech/nym/pull/5313
[#5298]: https://github.com/nymtech/nym/pull/5298
[#5287]: https://github.com/nymtech/nym/pull/5287
[#5283]: https://github.com/nymtech/nym/pull/5283
[#5278]: https://github.com/nymtech/nym/pull/5278
[#5260]: https://github.com/nymtech/nym/pull/5260
[#5259]: https://github.com/nymtech/nym/pull/5259
[#5256]: https://github.com/nymtech/nym/pull/5256
[#5255]: https://github.com/nymtech/nym/pull/5255
[#5251]: https://github.com/nymtech/nym/pull/5251
[#5247]: https://github.com/nymtech/nym/pull/5247
[#5246]: https://github.com/nymtech/nym/pull/5246
[#5239]: https://github.com/nymtech/nym/pull/5239
[#5236]: https://github.com/nymtech/nym/pull/5236
[#5235]: https://github.com/nymtech/nym/pull/5235
[#5233]: https://github.com/nymtech/nym/pull/5233
[#5230]: https://github.com/nymtech/nym/pull/5230
[#5218]: https://github.com/nymtech/nym/pull/5218
[#5216]: https://github.com/nymtech/nym/pull/5216
[#5213]: https://github.com/nymtech/nym/pull/5213
[#5210]: https://github.com/nymtech/nym/pull/5210
[#5208]: https://github.com/nymtech/nym/pull/5208
[#5207]: https://github.com/nymtech/nym/pull/5207
[#5204]: https://github.com/nymtech/nym/pull/5204
[#5201]: https://github.com/nymtech/nym/pull/5201
[#5184]: https://github.com/nymtech/nym/pull/5184
[#5180]: https://github.com/nymtech/nym/pull/5180
[#5174]: https://github.com/nymtech/nym/pull/5174
[#5171]: https://github.com/nymtech/nym/pull/5171
[#4813]: https://github.com/nymtech/nym/pull/4813
## [2024.14-crunch-patched] (2024-12-17)
- Fixes an issue to allow previously registred clients to connect to latest nym-nodes
Generated
+593 -342
View File
File diff suppressed because it is too large Load Diff
+20 -24
View File
@@ -194,8 +194,9 @@ aes-gcm = "0.10.1"
aes-gcm-siv = "0.11.1"
aead = "0.5.2"
anyhow = "1.0.95"
arc-swap = "1.7.1"
argon2 = "0.5.0"
async-trait = "0.1.84"
async-trait = "0.1.85"
axum-client-ip = "0.6.1"
axum = "0.7.5"
axum-extra = "0.9.4"
@@ -216,7 +217,7 @@ chacha20 = "0.9.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.39"
cipher = "0.4.3"
clap = "4.5.23"
clap = "4.5.26"
clap_complete = "4.5"
clap_complete_fig = "4.5"
colored = "2.0"
@@ -226,7 +227,7 @@ console-subscriber = "0.1.1"
console_error_panic_hook = "0.1"
const-str = "0.5.6"
const_format = "0.2.34"
criterion = "0.4"
criterion = "0.5"
csv = "1.3.1"
ctr = "0.9.1"
cupid = "0.6.1"
@@ -245,7 +246,7 @@ envy = "0.4"
eyre = "0.6.9"
fastrand = "2.1.1"
flate2 = "1.0.35"
futures = "0.3.28"
futures = "0.3.31"
futures-util = "0.3"
generic-array = "0.14.7"
getrandom = "0.2.10"
@@ -289,7 +290,7 @@ parking_lot = "0.12.3"
pem = "0.8"
petgraph = "0.6.5"
pin-project = "1.1"
pin-project-lite = "0.2.15"
pin-project-lite = "0.2.16"
pretty_env_logger = "0.4.0"
publicsuffix = "2.2.3"
quote = "1"
@@ -311,7 +312,7 @@ semver = "1.0.24"
serde = "1.0.217"
serde_bytes = "0.11.15"
serde_derive = "1.0"
serde_json = "1.0.134"
serde_json = "1.0.135"
serde_json_path = "0.7.1"
serde_repr = "0.1"
serde_with = "3.9.0"
@@ -324,10 +325,10 @@ strum = "0.26"
strum_macros = "0.26"
subtle-encoding = "0.5"
syn = "1"
sysinfo = "0.30.13"
sysinfo = "0.33.0"
tap = "1.0.1"
tar = "0.4.43"
tempfile = "3.14"
tempfile = "3.15"
thiserror = "1.0.64"
time = "0.3.37"
tokio = "1.39"
@@ -344,7 +345,7 @@ tracing-opentelemetry = "0.19.0"
tracing-subscriber = "0.3.19"
tracing-tree = "0.2.2"
tracing-log = "0.2"
ts-rs = "10.0.0"
ts-rs = "10.1.0"
tungstenite = { version = "0.20.1", default-features = false }
url = "2.5"
utoipa = "5.2"
@@ -353,7 +354,7 @@ utoipauto = "0.2"
uuid = "*"
vergen = { version = "=8.3.1", default-features = false }
walkdir = "2"
wasm-bindgen-test = "0.3.45"
wasm-bindgen-test = "0.3.49"
x25519-dalek = "2.0.0"
zeroize = "1.6.0"
@@ -386,29 +387,24 @@ cw-controllers = { version = "=1.1.0" }
# cosmrs-related
bip32 = { version = "0.5.2", default-features = false }
# temporarily using a fork again (yay.) because we need staking and slashing support (which are already on main but not released)
# plus response message parsing (which is, as of the time of writing this message, waiting to get merged)
#cosmrs = { path = "../cosmos-rust-fork/cosmos-rust/cosmrs" }
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev = "4b1332e6d8258ac845cef71589c8d362a669675a" } # unfortuntely we need a fork by yours truly to get the staking support
tendermint = "0.37.0" # same version as used by cosmrs
tendermint-rpc = "0.37.0" # same version as used by cosmrs
prost = { version = "0.12", default-features = false }
cosmrs = { version = "0.21.0" }
tendermint = "0.40.0"
tendermint-rpc = "0.40.0"
prost = { version = "0.13", default-features = false }
# wasm-related dependencies
gloo-utils = "0.2.0"
gloo-net = "0.5.0"
gloo-net = "0.6.0"
# use a separate branch due to feature unification failures
# this is blocked until the upstream removes outdates `wasm_bindgen` feature usage
# indexed_db_futures = "0.4.1"
indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", branch = "update-uuid" }
indexed_db_futures = "0.6.0"
js-sys = "0.3.76"
serde-wasm-bindgen = "0.6.5"
tsify = "0.4.5"
wasm-bindgen = "0.2.99"
wasm-bindgen-futures = "0.4.49"
wasmtimer = "0.2.0"
web-sys = "0.3.72"
wasmtimer = "0.4.1"
web-sys = "0.3.76"
# Profile settings for individual crates
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.45"
version = "1.1.47"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.45"
version = "1.1.47"
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"
@@ -28,7 +28,7 @@ pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IpPair {
+8 -3
View File
@@ -517,7 +517,7 @@ impl Default for Acknowledgements {
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
#[serde(default)]
pub struct Topology {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
@@ -553,12 +553,15 @@ pub struct Topology {
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
/// Useless without `ignore_epoch_roles = true`
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,
}
#[allow(clippy::large_enum_variant)]
@@ -596,7 +599,9 @@ impl Default for Topology {
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
use_extended_topology: false,
ignore_egress_epoch_role: false,
ignore_egress_epoch_role: true,
ignore_ingress_epoch_role: true,
}
}
}
@@ -115,11 +115,12 @@ where
hardcoded_topology.entry_capable_nodes().cloned().collect()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::current_gateways(
crate::init::helpers::gateways_for_init(
&mut rng,
&core.client.nym_api_urls,
user_agent,
core.debug.topology.minimum_gateway_performance,
core.debug.topology.ignore_ingress_epoch_role,
)
.await?
};
@@ -170,11 +170,12 @@ where
hardcoded_topology.entry_capable_nodes().cloned().collect()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::current_gateways(
crate::init::helpers::gateways_for_init(
&mut rng,
&core.client.nym_api_urls,
user_agent,
core.debug.topology.minimum_gateway_performance,
core.debug.topology.ignore_ingress_epoch_role,
)
.await?
};
@@ -472,6 +472,7 @@ where
.claim_initial_bandwidth()
.await
.map_err(gateway_failure)?;
gateway_client
.start_listening_for_mixnet_messages()
.map_err(gateway_failure)?;
@@ -4,6 +4,8 @@
// TODO: combine those more closely. Perhaps into a single underlying store.
// Like for persistent, on-disk, storage, what's the point of having 3 different databases?
use rand::rngs::OsRng;
use crate::client::key_manager::persistence::{InMemEphemeralKeys, KeyStore};
use crate::client::replies::reply_storage;
use crate::client::replies::reply_storage::ReplyStorageBackend;
@@ -63,7 +65,6 @@ pub trait MixnetClientStorage {
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
}
#[derive(Default)]
pub struct Ephemeral {
key_store: InMemEphemeralKeys,
reply_store: reply_storage::Empty,
@@ -71,9 +72,14 @@ pub struct Ephemeral {
gateway_details_store: InMemGatewaysDetails,
}
impl Ephemeral {
pub fn new() -> Self {
Default::default()
impl Default for Ephemeral {
fn default() -> Self {
Ephemeral {
key_store: InMemEphemeralKeys::new(&mut OsRng),
reply_store: Default::default(),
credential_store: Default::default(),
gateway_details_store: Default::default(),
}
}
}
@@ -3,6 +3,7 @@
use crate::client::key_manager::ClientKeys;
use async_trait::async_trait;
use rand::{CryptoRng, RngCore};
use std::error::Error;
use tokio::sync::Mutex;
@@ -193,9 +194,19 @@ impl KeyStore for OnDiskKeys {
}
}
#[derive(Default)]
pub struct InMemEphemeralKeys {
keys: Mutex<Option<ClientKeys>>,
keys: Mutex<ClientKeys>,
}
impl InMemEphemeralKeys {
pub fn new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
InMemEphemeralKeys {
keys: Mutex::new(ClientKeys::generate_new(rng)),
}
}
}
#[derive(Debug, thiserror::Error)]
@@ -208,11 +219,11 @@ impl KeyStore for InMemEphemeralKeys {
type StorageError = EphemeralKeysError;
async fn load_keys(&self) -> Result<ClientKeys, Self::StorageError> {
self.keys.lock().await.clone().ok_or(EphemeralKeysError)
Ok(self.keys.lock().await.clone())
}
async fn store_keys(&self, keys: &ClientKeys) -> Result<(), Self::StorageError> {
*self.keys.lock().await = Some(keys.clone());
*self.keys.lock().await = keys.clone();
Ok(())
}
}
@@ -2,10 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
use crate::error::ClientCoreError;
use crate::{spawn_future, ForgetMe};
use log::*;
use nym_gateway_requests::ClientRequest;
use nym_sphinx::forwarding::packet::MixPacket;
use transceiver::ErasedGatewayError;
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
@@ -68,7 +70,10 @@ impl MixTrafficController {
)
}
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
async fn on_messages(
&mut self,
mut mix_packets: Vec<MixPacket>,
) -> Result<(), ErasedGatewayError> {
debug_assert!(!mix_packets.is_empty());
let result = if mix_packets.len() == 1 {
@@ -80,21 +85,14 @@ impl MixTrafficController {
.await
};
match result {
Err(err) => {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
self.consecutive_gateway_failure_count += 1;
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// todo: in the future this should initiate a 'graceful' shutdown or try
// to reconnect?
panic!("failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead. Can't do anything about it yet :(")
}
}
Ok(_) => {
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
self.consecutive_gateway_failure_count = 0;
}
if result.is_err() {
self.consecutive_gateway_failure_count += 1;
} else {
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
self.consecutive_gateway_failure_count = 0;
}
result
}
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
@@ -105,7 +103,18 @@ impl MixTrafficController {
tokio::select! {
mix_packets = self.mix_rx.recv() => match mix_packets {
Some(mix_packets) => {
self.on_messages(mix_packets).await;
if let Err(err) = self.on_messages(mix_packets).await {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// Disconnect from the gateway. If we should try to re-connect
// is handled at a higher layer.
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
// Do we need to handle the embedded mixnet client case
// separately?
shutdown.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages));
break;
}
}
},
None => {
log::trace!("MixTrafficController: Stopping since channel closed");
@@ -626,9 +626,14 @@ where
messages: Vec<RealMessage>,
transmission_lane: TransmissionLane,
) {
self.real_message_sender
if let Err(err) = self
.real_message_sender
.send((messages, transmission_lane))
.await
.expect("real message receiver task (OutQueueControl) has died");
{
error!(
"Failed to forward messages to the real message sender (OutQueueControl): {err}"
);
}
}
}
@@ -9,10 +9,12 @@ use self::{
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
};
use crate::client::real_messages_control::message_handler::MessageHandler;
use crate::client::replies::reply_controller;
use crate::client::replies::reply_controller::{
ReplyController, ReplyControllerReceiver, ReplyControllerSender,
};
use crate::client::replies::reply_storage::CombinedReplyStorage;
use crate::config;
use crate::{
client::{
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
@@ -27,16 +29,13 @@ use nym_gateway_client::AcknowledgementReceiver;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType;
use nym_statistics_common::clients::ClientStatsSender;
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
use crate::client::replies::reply_controller;
use crate::config;
pub(crate) use acknowledgement_control::{AckActionSender, Action};
use nym_statistics_common::clients::ClientStatsSender;
pub(crate) mod acknowledgement_control;
pub(crate) mod message_handler;
pub(crate) mod real_traffic_stream;
@@ -545,7 +545,7 @@ where
loop {
tokio::select! {
biased;
_ = shutdown.recv_with_delay() => {
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
break;
}
@@ -70,7 +70,10 @@ impl SendingDelayController {
lower_bound,
multiplier_elevated_counter: 0,
time_when_logged_about_elevated_multiplier: now
- Duration::from_secs(INTERVAL_BETWEEN_WARNING_ABOUT_ELEVATED_MULTIPLIER_SECS),
.checked_sub(Duration::from_secs(
INTERVAL_BETWEEN_WARNING_ABOUT_ELEVATED_MULTIPLIER_SECS,
))
.unwrap_or(now),
time_when_changed: now,
time_when_backpressure_detected: now,
}
@@ -16,14 +16,14 @@
#![warn(clippy::todo)]
#![warn(clippy::dbg_macro)]
use std::time::Duration;
use futures::StreamExt;
use nym_client_core_config_types::StatsReporting;
use nym_sphinx::addressing::Recipient;
use nym_statistics_common::clients::{
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
};
use nym_task::connections::TransmissionLane;
use std::time::Duration;
use crate::{
client::inbound_messages::{InputMessage, InputMessageSender},
@@ -94,10 +94,32 @@ impl StatisticsControl {
async fn run_with_shutdown(&mut self, mut task_client: nym_task::TaskClient) {
log::debug!("Started StatisticsControl with graceful shutdown support");
let mut stats_report_interval =
tokio::time::interval(self.reporting_config.reporting_interval);
let mut local_report_interval = tokio::time::interval(LOCAL_REPORT_INTERVAL);
let mut snapshot_interval = tokio::time::interval(SNAPSHOT_INTERVAL);
#[cfg(not(target_arch = "wasm32"))]
let mut stats_report_interval = tokio_stream::wrappers::IntervalStream::new(
tokio::time::interval(self.reporting_config.reporting_interval),
);
#[cfg(not(target_arch = "wasm32"))]
let mut local_report_interval = tokio_stream::wrappers::IntervalStream::new(
tokio::time::interval(LOCAL_REPORT_INTERVAL),
);
#[cfg(not(target_arch = "wasm32"))]
let mut snapshot_interval =
tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(SNAPSHOT_INTERVAL));
#[cfg(target_arch = "wasm32")]
let mut stats_report_interval = gloo_timers::future::IntervalStream::new(
self.reporting_config.reporting_interval.as_millis() as u32,
);
#[cfg(target_arch = "wasm32")]
let mut local_report_interval =
gloo_timers::future::IntervalStream::new(LOCAL_REPORT_INTERVAL.as_millis() as u32);
#[cfg(target_arch = "wasm32")]
let mut snapshot_interval =
gloo_timers::future::IntervalStream::new(SNAPSHOT_INTERVAL.as_millis() as u32);
loop {
tokio::select! {
@@ -108,16 +130,20 @@ impl StatisticsControl {
break;
}
},
_ = snapshot_interval.tick() => {
_ = snapshot_interval.next() => {
self.stats.snapshot();
}
_ = stats_report_interval.tick(), if self.reporting_config.enabled && self.reporting_config.provider_address.is_some() => {
// SAFTEY : this branch executes only if reporting is not none, so unwrapp is fine
#[allow(clippy::unwrap_used)]
self.report_stats(self.reporting_config.provider_address.unwrap()).await;
_ = stats_report_interval.next() => {
let Some(recipient) = self.reporting_config.provider_address else {
continue
};
if self.reporting_config.enabled {
self.report_stats(recipient).await;
}
}
_ = local_report_interval.tick() => {
_ = local_report_interval.next() => {
self.stats.local_report(&mut task_client);
}
_ = task_client.recv_with_delay() => {
+3
View File
@@ -96,6 +96,9 @@ pub enum ClientCoreError {
#[error("timed out while trying to establish gateway connection")]
GatewayConnectionTimeout,
#[error("failed to forward mix messages to gateway")]
GatewayFailedToForwardMessages,
#[error("no ping measurements for the gateway ({identity}) performed")]
NoGatewayMeasurements { identity: String },
+5 -1
View File
@@ -86,11 +86,12 @@ impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
}
}
pub async fn current_gateways<R: Rng>(
pub async fn gateways_for_init<R: Rng>(
rng: &mut R,
nym_apis: &[Url],
user_agent: Option<UserAgent>,
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
@@ -108,8 +109,11 @@ pub async fn current_gateways<R: Rng>(
log::trace!("Gateways: {:#?}", gateways);
// filter out gateways below minimum performance and ones that could operate as a mixnode
// (we don't want instability)
let valid_gateways = gateways
.iter()
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
.filter_map(|gateway| gateway.try_into().ok())
.collect::<Vec<_>>();
+29 -1
View File
@@ -17,7 +17,7 @@ use nym_topology::node::RoutingNode;
use nym_validator_client::client::IdentityKey;
use nym_validator_client::nyxd::AccountId;
use serde::Serialize;
use std::fmt::Display;
use std::fmt::{Debug, Display};
use std::sync::Arc;
use time::OffsetDateTime;
use url::Url;
@@ -221,6 +221,34 @@ pub enum GatewaySetup {
},
}
impl Debug for GatewaySetup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GatewaySetup::MustLoad { gateway_id } => f
.debug_struct("GatewaySetup::MustLoad")
.field("gateway_id", gateway_id)
.finish(),
GatewaySetup::New {
specification,
available_gateways,
} => f
.debug_struct("GatewaySetup::New")
.field("specification", specification)
.field("available_gateways", available_gateways)
.field("gateways", specification)
.finish(),
GatewaySetup::ReuseConnection {
gateway_details, ..
} => f
.debug_struct("GatewaySetup::ReuseConnection")
.field("authenticated_ephemeral_client", &"***")
.field("gateway_details", gateway_details)
.field("client_keys", &"***")
.finish(),
}
}
}
impl GatewaySetup {
pub fn try_reuse_connection(init_res: InitialisationResult) -> Result<Self, ClientCoreError> {
if let Some(authenticated_ephemeral_client) = init_res.authenticated_ephemeral_client {
@@ -19,8 +19,9 @@ use nym_api_requests::ecash::{
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
};
use nym_api_requests::models::{
ApiHealthResponse, GatewayBondAnnotated, GatewayCoreStatusResponse, MixnodeCoreStatusResponse,
MixnodeStatusResponse, NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
ApiHealthResponse, GatewayBondAnnotated, GatewayCoreStatusResponse,
HistoricalPerformanceResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
};
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::SkimmedNode;
@@ -264,6 +265,31 @@ impl<C, S> Client<C, S> {
Ok(self.nym_api.get_gateways_detailed_unfiltered().await?)
}
pub async fn get_full_node_performance_history(
&self,
node_id: NodeId,
) -> Result<Vec<HistoricalPerformanceResponse>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut history = Vec::new();
loop {
let mut res = self
.nym_api
.get_node_performance_history(node_id, Some(page), None)
.await?;
history.append(&mut res.history.data);
if history.len() < res.history.pagination.total {
page += 1
} else {
break;
}
}
Ok(history)
}
// TODO: combine with NymApiClient...
pub async fn get_all_cached_described_nodes(
&self,
@@ -65,6 +65,12 @@ pub enum EcashApiError {
#[from]
source: cosmrs::ErrorReport,
},
#[error("nym api error")]
NymApi {
#[from]
source: crate::ValidatorClientError,
},
}
impl TryFrom<ContractVKShare> for EcashApiClient {
@@ -13,7 +13,7 @@ use nym_api_requests::ecash::models::{
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_api_requests::models::{
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
NodeRefreshBody, NymNodeDescription, RewardedSetResponse,
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
};
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
use nym_api_requests::pagination::PaginatedResponse;
@@ -163,6 +163,35 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[tracing::instrument(level = "debug", skip_all)]
async fn get_node_performance_history(
&self,
node_id: NodeId,
page: Option<u32>,
per_page: Option<u32>,
) -> Result<PerformanceHistoryResponse, NymAPIError> {
let mut params = Vec::new();
if let Some(page) = page {
params.push(("page", page.to_string()))
}
if let Some(per_page) = per_page {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_PERFORMANCE_HISTORY,
&*node_id.to_string(),
],
&params,
)
.await
}
#[tracing::instrument(level = "debug", skip_all)]
async fn get_nodes_described(
&self,
@@ -179,8 +208,15 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(&[routes::API_VERSION, "nym-nodes", "described"], &params)
.await
self.get_json(
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_DESCRIBED,
],
&params,
)
.await
}
#[tracing::instrument(level = "debug", skip_all)]
@@ -199,8 +235,15 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(&[routes::API_VERSION, "nym-nodes", "bonded"], &params)
.await
self.get_json(
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_BONDED,
],
&params,
)
.await
}
#[deprecated]
@@ -210,7 +253,7 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
"unstable",
"nym-nodes",
routes::NYM_NODES_ROUTES,
"mixnodes",
"skimmed",
],
@@ -226,7 +269,7 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
"unstable",
"nym-nodes",
routes::NYM_NODES_ROUTES,
"gateways",
"skimmed",
],
@@ -238,7 +281,11 @@ pub trait NymApiClientExt: ApiClient {
#[instrument(level = "debug", skip(self))]
async fn get_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
self.get_json(
&[routes::API_VERSION, "nym-nodes", "rewarded-set"],
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_REWARDED_SET,
],
NO_PARAMS,
)
.await
@@ -271,7 +318,7 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
"unstable",
"nym-nodes",
routes::NYM_NODES_ROUTES,
"skimmed",
"entry-gateways",
"all",
@@ -308,7 +355,7 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
"unstable",
"nym-nodes",
routes::NYM_NODES_ROUTES,
"skimmed",
"mixnodes",
"active",
@@ -345,7 +392,7 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
"unstable",
"nym-nodes",
routes::NYM_NODES_ROUTES,
"skimmed",
"mixnodes",
"all",
@@ -377,7 +424,12 @@ pub trait NymApiClientExt: ApiClient {
}
self.get_json(
&[routes::API_VERSION, "unstable", "nym-nodes", "skimmed"],
&[
routes::API_VERSION,
"unstable",
routes::NYM_NODES_ROUTES,
"skimmed",
],
&params,
)
.await
@@ -686,8 +738,8 @@ pub trait NymApiClientExt: ApiClient {
self.get_json(
&[
routes::API_VERSION,
"nym-nodes",
"performance",
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_PERFORMANCE,
&node_id.to_string(),
],
NO_PARAMS,
@@ -702,8 +754,8 @@ pub trait NymApiClientExt: ApiClient {
self.get_json(
&[
routes::API_VERSION,
"nym-nodes",
"annotation",
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_ANNOTATION,
&node_id.to_string(),
],
NO_PARAMS,
@@ -927,7 +979,11 @@ pub trait NymApiClientExt: ApiClient {
request: &NodeRefreshBody,
) -> Result<(), NymAPIError> {
self.post_json(
&[routes::API_VERSION, "nym-nodes", "refresh-described"],
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_REFRESH_DESCRIBED,
],
NO_PARAMS,
request,
)
@@ -34,6 +34,19 @@ pub mod ecash {
pub const EPOCH_ID_PARAM: &str = "epoch_id";
}
pub const NYM_NODES_ROUTES: &str = "nym-nodes";
pub use nym_nodes::*;
pub mod nym_nodes {
pub const NYM_NODES_PERFORMANCE_HISTORY: &str = "performance-history";
pub const NYM_NODES_PERFORMANCE: &str = "performance";
pub const NYM_NODES_ANNOTATION: &str = "annotation";
pub const NYM_NODES_DESCRIBED: &str = "described";
pub const NYM_NODES_BONDED: &str = "bonded";
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
}
pub const STATUS_ROUTES: &str = "status";
pub const API_STATUS_ROUTES: &str = "api-status";
pub const HEALTH: &str = "health";
@@ -153,13 +153,20 @@ pub trait CosmWasmClient: TendermintRpcClient {
let req = QueryAllBalancesRequest {
address: address.to_string(),
pagination,
resolve_denom: false,
};
let mut res = self
.make_abci_query::<_, QueryAllBalancesResponse>(path.clone(), req)
.await?;
let early_break = res.balances.is_empty();
raw_balances.append(&mut res.balances);
if early_break {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
@@ -187,7 +194,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
.make_abci_query::<_, QueryTotalSupplyResponse>(path.clone(), req)
.await?;
let early_break = res.supply.is_empty();
supply.append(&mut res.supply);
if early_break {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
@@ -328,7 +341,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
.make_abci_query::<_, QueryCodesResponse>(path.clone(), req)
.await?;
let early_break = res.code_infos.is_empty();
raw_codes.append(&mut res.code_infos);
if early_break {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
@@ -373,7 +392,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
.make_abci_query::<_, QueryContractsByCodeResponse>(path.clone(), req)
.await?;
let early_break = res.contracts.is_empty();
raw_contracts.append(&mut res.contracts);
if early_break {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
@@ -429,7 +454,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
.make_abci_query::<_, QueryContractHistoryResponse>(path.clone(), req)
.await?;
let early_break = res.entries.is_empty();
raw_entries.append(&mut res.entries);
if early_break {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
@@ -4,9 +4,11 @@
use crate::rpc::TendermintRpcClient;
use async_trait::async_trait;
use base64::Engine;
use cosmrs::tendermint;
use cosmrs::tendermint::{block::Height, evidence::Evidence, Hash};
use reqwest::header::HeaderMap;
use reqwest::{header, RequestBuilder};
use tendermint_rpc::dialect::{v0_34, v0_37, v0_38, LatestDialect};
use tendermint_rpc::{
client::CompatMode,
dialect::{self, Dialect},
@@ -21,8 +23,21 @@ macro_rules! perform_with_compat {
($self:expr, $request:expr) => {{
let request = $request;
match $self.compat {
CompatMode::V0_37 => $self.perform_v0_37(request).await,
CompatMode::V0_34 => $self.perform_v0_34(request).await,
CompatMode::V0_38 => {
$self
.perform_request_with_dialect(request, dialect::v0_38::Dialect)
.await
}
CompatMode::V0_37 => {
$self
.perform_request_with_dialect(request, dialect::v0_37::Dialect)
.await
}
CompatMode::V0_34 => {
$self
.perform_request_with_dialect(request, dialect::v0_34::Dialect)
.await
}
}
}};
}
@@ -70,7 +85,11 @@ impl ReqwestRpcClient {
.headers(headers)
}
async fn perform_request<R, S>(&self, request: R) -> Result<R::Output, Error>
async fn perform_request_with_dialect<R, S>(
&self,
request: R,
_dialect: S,
) -> Result<R::Output, Error>
where
R: SimpleRequest<S>,
S: Dialect,
@@ -81,26 +100,25 @@ impl ReqwestRpcClient {
.send()
.await
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
let response_status = response.status();
let bytes = response
.bytes()
.await
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
// Successful JSON-RPC requests are expected to return a 200 OK HTTP status.
// Otherwise, this means that the HTTP request failed as a whole,
// as opposed to the JSON-RPC request returning an error,
// and we cannot expect the response body to be a valid JSON-RPC response.
if response_status != reqwest::StatusCode::OK {
// hehe, that's so nasty but we have to somehow convert between different versions of the same lib
return Err(Error::http_request_failed(
response_status.as_u16().try_into().unwrap(),
));
}
R::Response::from_string(bytes).map(Into::into)
}
async fn perform_v0_34<R>(&self, request: R) -> Result<R::Output, Error>
where
R: SimpleRequest<dialect::v0_34::Dialect>,
{
self.perform_request(request).await
}
async fn perform_v0_37<R>(&self, request: R) -> Result<R::Output, Error>
where
R: SimpleRequest<dialect::v0_37::Dialect>,
{
self.perform_request(request).await
}
}
trait TendermintRpcErrorMap {
@@ -120,18 +138,50 @@ impl TendermintRpcClient for ReqwestRpcClient {
where
R: SimpleRequest,
{
self.perform_request(request).await
self.perform_request_with_dialect(request, LatestDialect)
.await
}
async fn block_results<H>(&self, height: H) -> Result<block_results::Response, Error>
async fn block<H>(&self, height: H) -> Result<endpoint::block::Response, Error>
where
H: Into<Height> + Send,
{
perform_with_compat!(self, block_results::Request::new(height.into()))
perform_with_compat!(self, endpoint::block::Request::new(height.into()))
}
async fn latest_block_results(&self) -> Result<block_results::Response, Error> {
perform_with_compat!(self, block_results::Request::default())
async fn block_by_hash(
&self,
hash: tendermint::Hash,
) -> Result<endpoint::block_by_hash::Response, Error> {
perform_with_compat!(self, endpoint::block_by_hash::Request::new(hash))
}
async fn latest_block(&self) -> Result<endpoint::block::Response, Error> {
perform_with_compat!(self, endpoint::block::Request::default())
}
async fn block_results<H>(&self, height: H) -> Result<endpoint::block_results::Response, Error>
where
H: Into<Height> + Send,
{
perform_with_compat!(self, endpoint::block_results::Request::new(height.into()))
}
async fn latest_block_results(&self) -> Result<endpoint::block_results::Response, Error> {
perform_with_compat!(self, endpoint::block_results::Request::default())
}
async fn block_search(
&self,
query: Query,
page: u32,
per_page: u8,
order: Order,
) -> Result<endpoint::block_search::Response, Error> {
perform_with_compat!(
self,
endpoint::block_search::Request::new(query, page, per_page, order)
)
}
async fn header<H>(&self, height: H) -> Result<endpoint::header::Response, Error>
@@ -140,11 +190,26 @@ impl TendermintRpcClient for ReqwestRpcClient {
{
let height = height.into();
match self.compat {
CompatMode::V0_37 => self.perform(endpoint::header::Request::new(height)).await,
CompatMode::V0_38 => {
self.perform_request_with_dialect(
endpoint::header::Request::new(height),
v0_38::Dialect,
)
.await
}
CompatMode::V0_37 => {
self.perform_request_with_dialect(
endpoint::header::Request::new(height),
v0_37::Dialect,
)
.await
}
CompatMode::V0_34 => {
// Back-fill with a request to /block endpoint and
// taking just the header from the response.
let resp = self.perform_v0_34(block::Request::new(height)).await?;
let resp = self
.perform_request_with_dialect(block::Request::new(height), v0_34::Dialect)
.await?;
Ok(resp.into())
}
}
@@ -152,12 +217,25 @@ impl TendermintRpcClient for ReqwestRpcClient {
async fn header_by_hash(&self, hash: Hash) -> Result<header_by_hash::Response, Error> {
match self.compat {
CompatMode::V0_37 => self.perform(header_by_hash::Request::new(hash)).await,
CompatMode::V0_38 => {
self.perform_request_with_dialect(
header_by_hash::Request::new(hash),
v0_38::Dialect,
)
.await
}
CompatMode::V0_37 => {
self.perform_request_with_dialect(
header_by_hash::Request::new(hash),
v0_37::Dialect,
)
.await
}
CompatMode::V0_34 => {
// Back-fill with a request to /block_by_hash endpoint and
// taking just the header from the response.
let resp = self
.perform_v0_34(block_by_hash::Request::new(hash))
.perform_request_with_dialect(block_by_hash::Request::new(hash), v0_34::Dialect)
.await?;
Ok(resp.into())
}
@@ -167,8 +245,18 @@ impl TendermintRpcClient for ReqwestRpcClient {
/// `/broadcast_evidence`: broadcast an evidence.
async fn broadcast_evidence(&self, e: Evidence) -> Result<evidence::Response, Error> {
match self.compat {
CompatMode::V0_37 => self.perform(evidence::Request::new(e)).await,
CompatMode::V0_34 => self.perform_v0_34(evidence::Request::new(e)).await,
CompatMode::V0_38 => {
self.perform_request_with_dialect(evidence::Request::new(e), v0_38::Dialect)
.await
}
CompatMode::V0_37 => {
self.perform_request_with_dialect(evidence::Request::new(e), v0_37::Dialect)
.await
}
CompatMode::V0_34 => {
self.perform_request_with_dialect(evidence::Request::new(e), v0_34::Dialect)
.await
}
}
}
@@ -863,11 +863,4 @@ pub enum QueryMsg {
pub struct MigrateMsg {
pub unsafe_skip_state_updates: Option<bool>,
pub vesting_contract_address: Option<String>,
pub current_nym_node_semver: String,
#[serde(default)]
pub version_score_weights: OutdatedVersionWeights,
#[serde(default)]
pub version_score_params: VersionScoreFormulaParams,
}
@@ -113,6 +113,10 @@ impl Role {
pub fn is_standby(&self) -> bool {
matches!(self, Role::Standby)
}
pub fn is_mixnode(&self) -> bool {
matches!(self, Role::Layer1 | Role::Layer2 | Role::Layer3)
}
}
impl Display for Role {
@@ -13,6 +13,7 @@ use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody};
use nym_credentials_interface::Bandwidth;
use nym_credentials_interface::{ClientTicket, TicketType};
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::{
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
@@ -352,7 +353,9 @@ impl CredentialHandler {
}
Err(err) => {
error!("failed to send ticket {ticket_id} for verification to ecash signer '{client}': {err}. if we don't reach quorum, we'll retry later");
Ok(false)
Err(EcashTicketError::ApiFailure(EcashApiError::NymApi {
source: err,
}))
}
}
}
@@ -3,5 +3,22 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
ALTER TABLE message_store
ADD COLUMN timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP;
RENAME TO message_store_old;
-- add new column with message timestamp.
-- note: we can't simply alter existing table to add it since the default value is non-constant
CREATE TABLE message_store
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
client_address_bs58 TEXT NOT NULL,
content BLOB NOT NULL,
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO message_store(id, client_address_bs58, content)
SELECT id, client_address_bs58, content
FROM message_store_old;
DROP TABLE message_store_old;
+2
View File
@@ -27,3 +27,5 @@ nym-credentials-interface = { path = "../credentials-interface" }
nym-metrics = { path = "../nym-metrics" }
nym-task = { path = "../task" }
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
workspace = true
@@ -3,10 +3,7 @@
use super::ClientStatsEvents;
use core::fmt;
use std::{
collections::VecDeque,
time::{Duration, Instant},
};
use std::{collections::VecDeque, time::Duration};
use nym_metrics::{inc, inc_by};
use serde::{Deserialize, Serialize};
@@ -18,6 +15,51 @@ use si_scale::helpers::bibytes2;
// Also, set it larger than the packet report interval so that we don't miss notable singular events
const RECORDING_WINDOW_MS: u64 = 2300;
#[derive(PartialOrd, PartialEq, Clone, Copy, Debug)]
struct Instant {
#[cfg(not(target_arch = "wasm32"))]
inner: std::time::Instant,
#[cfg(target_arch = "wasm32")]
inner: wasmtimer::std::Instant,
}
impl Instant {
#[cfg(not(target_arch = "wasm32"))]
fn inner(&self) -> &std::time::Instant {
&self.inner
}
#[cfg(target_arch = "wasm32")]
fn inner(&self) -> &wasmtimer::std::Instant {
&self.inner
}
#[cfg(not(target_arch = "wasm32"))]
fn now() -> Self {
Instant {
inner: std::time::Instant::now(),
}
}
#[cfg(target_arch = "wasm32")]
fn now() -> Self {
Instant {
inner: wasmtimer::std::Instant::now(),
}
}
fn checked_sub(&self, duration: Duration) -> Option<Instant> {
self.inner()
.checked_sub(duration)
.map(|inner| Instant { inner })
}
fn duration_since(&self, earlier: &Instant) -> Duration {
self.inner.duration_since(*earlier.inner())
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub(crate) struct PacketStatistics {
// Sent
@@ -424,7 +466,11 @@ impl PacketStatisticsControl {
self.history.push_back((Instant::now(), self.stats.clone()));
// Filter out old ones
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
let now = Instant::now();
let recording_window = Instant::now()
.checked_sub(Duration::from_millis(RECORDING_WINDOW_MS))
.unwrap_or(now);
while self
.history
.front()
@@ -442,7 +488,7 @@ impl PacketStatisticsControl {
// Do basic averaging over the entire history, which just uses the first and last
if let Some((start, start_stats)) = self.history.front() {
let duration_secs = Instant::now().duration_since(*start).as_secs_f64();
let duration_secs = Instant::now().duration_since(start).as_secs_f64();
let delta = self.stats.clone() - start_stats.clone();
let rates = PacketRates::from(delta) / duration_secs;
Some(rates)
@@ -458,7 +504,10 @@ impl PacketStatisticsControl {
}
// Filter out old ones
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
let now = Instant::now();
let recording_window = now
.checked_sub(Duration::from_millis(RECORDING_WINDOW_MS))
.unwrap_or(now);
while self
.rates
.front()
+1 -1
View File
@@ -67,7 +67,7 @@ impl Default for ClientStatsReport {
pub struct OsInformation {
pub(crate) os_type: String,
pub(crate) os_version: Option<String>,
pub(crate) os_arch: Option<String>,
pub(crate) os_arch: String,
}
impl OsInformation {
+3
View File
@@ -8,10 +8,13 @@ license.workspace = true
repository.workspace = true
[dependencies]
cfg-if = { workspace = true }
futures = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["macros", "sync"] }
tokio-util = { workspace = true, features = ["rt"] }
tracing = { workspace = true }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
workspace = true
+366
View File
@@ -0,0 +1,366 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{TaskClient, TaskManager};
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use std::future::Future;
use std::ops::Deref;
use std::pin::Pin;
use std::time::Duration;
use tokio::task::JoinSet;
use tokio::time::sleep;
use tokio_util::sync::{CancellationToken, DropGuard};
use tokio_util::task::TaskTracker;
use tracing::{debug, info, trace};
#[cfg(unix)]
use tokio::signal::unix::{signal, SignalKind};
pub const DEFAULT_MAX_SHUTDOWN_DURATION: Duration = Duration::from_secs(5);
pub fn token_name(name: &Option<String>) -> String {
name.clone().unwrap_or_else(|| "unknown".to_string())
}
// a wrapper around tokio's CancellationToken that adds optional `name` information to more easily
// track down sources of shutdown
#[derive(Debug, Default)]
pub struct ShutdownToken {
name: Option<String>,
inner: CancellationToken,
}
impl Clone for ShutdownToken {
fn clone(&self) -> Self {
// make sure to not accidentally overflow the stack if we keep cloning the handle
let name = if let Some(name) = &self.name {
if name != Self::OVERFLOW_NAME && name.len() < Self::MAX_NAME_LENGTH {
Some(format!("{name}-child"))
} else {
Some(Self::OVERFLOW_NAME.to_string())
}
} else {
None
};
ShutdownToken {
name,
inner: self.inner.clone(),
}
}
}
impl Deref for ShutdownToken {
type Target = CancellationToken;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ShutdownToken {
const MAX_NAME_LENGTH: usize = 128;
const OVERFLOW_NAME: &'static str = "reached maximum ShutdownToken children name depth";
pub fn new(name: impl Into<String>) -> Self {
ShutdownToken {
name: Some(name.into()),
inner: CancellationToken::new(),
}
}
// Creates a ShutdownToken which will get cancelled whenever the current token gets cancelled.
// Unlike a cloned/forked ShutdownToken, cancelling a child token does not cancel the parent token.
#[must_use]
pub fn child_token<S: Into<String>>(&self, child_suffix: S) -> Self {
let suffix = child_suffix.into();
let child_name = if let Some(base) = &self.name {
format!("{base}-{suffix}")
} else {
format!("unknown-{suffix}")
};
ShutdownToken {
name: Some(child_name),
inner: self.inner.child_token(),
}
}
// Creates a clone of the ShutdownToken which will get cancelled whenever the current token gets cancelled, and vice versa.
#[must_use]
pub fn clone_with_suffix<S: Into<String>>(&self, child_suffix: S) -> Self {
let mut child = self.clone();
let suffix = child_suffix.into();
let child_name = if let Some(base) = &self.name {
format!("{base}-{suffix}")
} else {
format!("unknown-{suffix}")
};
child.name = Some(child_name);
child
}
// exposed method with the old name for easier migration
// it will eventually be removed so please try to use `.clone_with_suffix` instead
#[must_use]
pub fn fork<S: Into<String>>(&self, child_suffix: S) -> Self {
self.clone_with_suffix(child_suffix)
}
// exposed method with the old name for easier migration
// it will eventually be removed so please try to use `.clone().named(name)` instead
#[must_use]
pub fn fork_named<S: Into<String>>(&self, name: S) -> Self {
self.clone().named(name)
}
#[must_use]
pub fn named<S: Into<String>>(mut self, name: S) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn add_suffix<S: Into<String>>(self, suffix: S) -> Self {
let suffix = suffix.into();
let name = if let Some(base) = &self.name {
format!("{base}-{suffix}")
} else {
format!("unknown-{suffix}")
};
self.named(name)
}
// Returned guard will cancel this token (and all its children) on drop unless disarmed.
pub fn drop_guard(self) -> ShutdownDropGuard {
ShutdownDropGuard {
name: self.name,
inner: self.inner.drop_guard(),
}
}
pub fn name(&self) -> String {
token_name(&self.name)
}
pub async fn run_until_cancelled<F>(&self, fut: F) -> Option<F::Output>
where
F: Future,
{
let res = self.inner.run_until_cancelled(fut).await;
trace!("'{}' got cancelled", self.name());
res
}
}
pub struct ShutdownDropGuard {
name: Option<String>,
inner: DropGuard,
}
impl Deref for ShutdownDropGuard {
type Target = DropGuard;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ShutdownDropGuard {
pub fn disarm(self) -> ShutdownToken {
ShutdownToken {
name: self.name,
inner: self.inner.disarm(),
}
}
pub fn name(&self) -> String {
token_name(&self.name)
}
}
pub struct ShutdownManager {
pub root_token: ShutdownToken,
legacy_task_manager: Option<TaskManager>,
shutdown_signals: JoinSet<()>,
// the reason I'm not using a `JoinSet` is because it forces us to use futures with the same `::Output` type
tracker: TaskTracker,
max_shutdown_duration: Duration,
}
impl Deref for ShutdownManager {
type Target = TaskTracker;
fn deref(&self) -> &Self::Target {
&self.tracker
}
}
impl ShutdownManager {
pub fn new(root_token_name: impl Into<String>) -> Self {
let manager = ShutdownManager {
root_token: ShutdownToken::new(root_token_name),
legacy_task_manager: None,
shutdown_signals: Default::default(),
tracker: Default::default(),
max_shutdown_duration: Default::default(),
};
// we need to add an explicit watcher for the cancellation token being cancelled
// so that we could cancel all legacy tasks
let cancel_watcher = manager.root_token.clone();
manager.with_shutdown(async move { cancel_watcher.cancelled().await })
}
pub fn with_legacy_task_manager(mut self) -> Self {
let mut legacy_manager =
TaskManager::default().named(format!("{}-legacy", self.root_token.name()));
let mut legacy_error_rx = legacy_manager.task_return_error_rx();
let mut legacy_drop_rx = legacy_manager.task_drop_rx();
self.legacy_task_manager = Some(legacy_manager);
// add a task that listens for legacy task clients being dropped to trigger cancellation
self.with_shutdown(async move {
tokio::select! {
_ = legacy_error_rx.recv() => (),
_ = legacy_drop_rx.recv() => (),
}
info!("received legacy shutdown signal");
})
}
#[cfg(not(target_arch = "wasm32"))]
pub fn with_default_shutdown_signals(self) -> std::io::Result<Self> {
cfg_if::cfg_if! {
if #[cfg(unix)] {
self.with_interrupt_signal()
.with_terminate_signal()?
.with_quit_signal()
} else {
Ok(self.with_interrupt_signal())
}
}
}
#[must_use]
pub fn with_shutdown<F>(mut self, shutdown: F) -> Self
where
F: Future<Output = ()>,
F: Send + 'static,
{
let shutdown_token = self.root_token.clone();
self.shutdown_signals.spawn(async move {
shutdown.await;
info!("sending cancellation after receiving shutdown signal");
shutdown_token.cancel();
});
self
}
#[cfg(unix)]
pub fn with_shutdown_signal(self, signal_kind: SignalKind) -> std::io::Result<Self> {
let mut sig = signal(signal_kind)?;
Ok(self.with_shutdown(async move {
sig.recv().await;
}))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn with_interrupt_signal(self) -> Self {
self.with_shutdown(async move {
let _ = tokio::signal::ctrl_c().await;
})
}
#[cfg(unix)]
pub fn with_terminate_signal(self) -> std::io::Result<Self> {
self.with_shutdown_signal(SignalKind::terminate())
}
#[cfg(unix)]
pub fn with_quit_signal(self) -> std::io::Result<Self> {
self.with_shutdown_signal(SignalKind::quit())
}
#[must_use]
pub fn with_shutdown_duration(mut self, duration: Duration) -> Self {
self.max_shutdown_duration = duration;
self
}
pub fn child_token<S: Into<String>>(&self, child_suffix: S) -> ShutdownToken {
self.root_token.child_token(child_suffix)
}
pub fn clone_token<S: Into<String>>(&self, child_suffix: S) -> ShutdownToken {
self.root_token.clone_with_suffix(child_suffix)
}
#[must_use]
pub fn subscribe_legacy<S: Into<String>>(&self, child_suffix: S) -> TaskClient {
// alternatively we could have set self.legacy_task_manager = Some(TaskManager::default());
// on demand if it wasn't unavailable, but then we'd have to use mutable reference
#[allow(clippy::expect_used)]
self.legacy_task_manager
.as_ref()
.expect("did not enable legacy shutdown support")
.subscribe_named(child_suffix)
}
async fn finish_shutdown(mut self) {
let mut wait_futures = FuturesUnordered::<Pin<Box<dyn Future<Output = ()>>>>::new();
// force shutdown via ctrl-c
wait_futures.push(Box::pin(async move {
#[cfg(not(target_arch = "wasm32"))]
let interrupt_future = tokio::signal::ctrl_c();
#[cfg(target_arch = "wasm32")]
let interrupt_future = futures::future::pending::<()>();
let _ = interrupt_future.await;
info!("received interrupt - forcing shutdown");
}));
// timeout
wait_futures.push(Box::pin(async move {
sleep(self.max_shutdown_duration).await;
info!("timeout reached, forcing shutdown");
}));
// graceful
wait_futures.push(Box::pin(async move {
self.tracker.wait().await;
debug!("migrated tasks successfully shutdown");
if let Some(legacy) = self.legacy_task_manager.as_mut() {
legacy.wait_for_graceful_shutdown().await;
debug!("legacy tasks successfully shutdown");
}
info!("all registered tasks successfully shutdown")
}));
wait_futures.next().await;
}
pub async fn wait_for_shutdown_signal(mut self) {
self.shutdown_signals.join_next().await;
if let Some(legacy_manager) = self.legacy_task_manager.as_mut() {
info!("attempting to shutdown legacy tasks");
let _ = legacy_manager.signal_shutdown();
}
info!("waiting for tasks to finish... (press ctrl-c to force)");
self.finish_shutdown().await;
}
}
+6 -3
View File
@@ -1,6 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod cancellation;
pub mod connections;
pub mod event;
pub mod manager;
@@ -8,9 +9,11 @@ pub mod manager;
pub mod signal;
pub mod spawn;
pub use cancellation::{ShutdownDropGuard, ShutdownManager, ShutdownToken};
pub use event::{StatusReceiver, StatusSender, TaskStatus, TaskStatusEvent};
pub use manager::{TaskClient, TaskHandle, TaskManager};
#[cfg(not(target_arch = "wasm32"))]
pub use signal::wait_for_signal_and_error;
pub use spawn::{spawn, spawn_with_report_error};
pub use tokio_util::task::TaskTracker;
#[cfg(not(target_arch = "wasm32"))]
pub use signal::{wait_for_signal, wait_for_signal_and_error};
+20
View File
@@ -185,6 +185,19 @@ impl TaskManager {
}
}
// used for compatibility with the ShutdownManager
pub(crate) fn task_return_error_rx(&mut self) -> ErrorReceiver {
self.task_return_error_rx
.take()
.expect("unable to get error channel: attempt to wait twice?")
}
pub(crate) fn task_drop_rx(&mut self) -> ErrorReceiver {
self.task_drop_rx
.take()
.expect("unable to get task drop channel: attempt to wait twice?")
}
pub async fn wait_for_error(&mut self) -> Option<SentError> {
let mut error_rx = self
.task_return_error_rx
@@ -208,6 +221,13 @@ impl TaskManager {
}
}
pub(crate) async fn wait_for_graceful_shutdown(&mut self) {
if let Some(notify_rx) = self.notify_rx.take() {
drop(notify_rx);
}
self.notify_tx.closed().await
}
pub async fn wait_for_shutdown(&mut self) {
log::debug!("Waiting for shutdown");
if let Some(notify_rx) = self.notify_rx.take() {
+8 -11
View File
@@ -401,20 +401,17 @@ impl NymTopology {
});
};
// a 'valid' egress is one assigned to either entry role (i.e. entry for another client)
// or exit role (as a service provider)
// a 'valid' egress is one that is currently **not** acting as a mixnode
if !ignore_epoch_roles {
let Some(role) = self.rewarded_set.role(node.node_id) else {
return Err(NymTopologyError::InvalidEgressRole {
node_identity: Box::new(node_identity),
});
};
if !matches!(role, Role::EntryGateway | Role::ExitGateway) {
return Err(NymTopologyError::InvalidEgressRole {
node_identity: Box::new(node_identity),
});
if let Some(role) = self.rewarded_set.role(node.node_id) {
if role.is_mixnode() {
return Err(NymTopologyError::InvalidEgressRole {
node_identity: Box::new(node_identity),
});
}
}
}
Ok(node)
}
+28 -53
View File
@@ -6,7 +6,7 @@ use crate::measurements::packet::{EchoPacket, ReplyPacket};
use bytes::{BufMut, BytesMut};
use futures::StreamExt;
use nym_crypto::asymmetric::identity;
use nym_task::TaskClient;
use nym_task::ShutdownToken;
use std::net::SocketAddr;
use std::sync::Arc;
use std::{io, process};
@@ -19,19 +19,19 @@ use tracing::{debug, error, info, trace, warn};
pub struct PacketListener {
address: SocketAddr,
connection_handler: Arc<ConnectionHandler>,
shutdown: TaskClient,
shutdown_token: ShutdownToken,
}
impl PacketListener {
pub fn new(
address: SocketAddr,
identity: Arc<identity::KeyPair>,
shutdown: TaskClient,
shutdown_token: ShutdownToken,
) -> Self {
PacketListener {
address,
connection_handler: Arc::new(ConnectionHandler { identity }),
shutdown,
shutdown_token,
}
}
}
@@ -51,26 +51,22 @@ impl PacketListener {
info!("Started listening for echo packets on {}", self.address);
let mut shutdown_listener = self.shutdown.clone();
while !shutdown_listener.is_shutdown() {
while !self.shutdown_token.is_cancelled() {
// cloning the arc as each accepted socket is handled in separate task
let connection_handler = Arc::clone(&self.connection_handler);
let mut handler_shutdown_listener = self.shutdown.clone();
handler_shutdown_listener.disarm();
tokio::select! {
socket = listener.accept() => {
match socket {
Ok((socket, remote_addr)) => {
debug!("New verloc connection from {}", remote_addr);
tokio::spawn(connection_handler.handle_connection(socket, remote_addr, handler_shutdown_listener));
debug!("New verloc connection from {remote_addr}");
let cancel = self.shutdown_token.child_token(format!("handler_{remote_addr}"));
tokio::spawn(async move { cancel.run_until_cancelled(connection_handler.handle_connection(socket, remote_addr)).await });
}
Err(err) => warn!("Failed to accept incoming connection - {err}"),
}
},
_ = shutdown_listener.recv() => {
_ = self.shutdown_token.cancelled() => {
trace!("PacketListener: Received shutdown");
}
}
@@ -88,50 +84,29 @@ impl ConnectionHandler {
packet.construct_reply(self.identity.private_key())
}
pub(crate) async fn handle_connection(
self: Arc<Self>,
conn: TcpStream,
remote: SocketAddr,
mut shutdown_listener: TaskClient,
) {
debug!("Starting connection handler for {:?}", remote);
pub(crate) async fn handle_connection(self: Arc<Self>, conn: TcpStream, remote: SocketAddr) {
debug!("Starting connection handler for {remote}");
let mut framed_conn = Framed::new(conn, EchoPacketCodec);
while !shutdown_listener.is_shutdown() {
tokio::select! {
biased;
_ = shutdown_listener.recv() => {
trace!("ConnectionHandler: Shutdown received");
while let Some(echo_packet) = framed_conn.next().await {
let reply_packet = match echo_packet {
Ok(echo_packet) => self.handle_echo_packet(echo_packet),
Err(err) => {
debug!(
"The socket connection got corrupted with error: {err}. Closing the socket"
);
return;
}
maybe_echo_packet = framed_conn.next() => {
// handle echo packet
let reply_packet = match maybe_echo_packet {
Some(Ok(echo_packet)) => self.handle_echo_packet(echo_packet),
Some(Err(err)) => {
debug!(
"The socket connection got corrupted with error: {err}. Closing the socket",
);
return;
}
None => {
debug!("The socket connection got terminated by the remote!");
return;
}
};
};
// write back the reply (note the lack of framing)
if let Err(err) = framed_conn
.get_mut()
.write_all(reply_packet.to_bytes().as_ref())
.await
{
debug!(
"Failed to write reply packet back to the sender - {}. Closing the socket on our end",
err
);
return;
}
},
// write back the reply (note the lack of framing)
if let Err(err) = framed_conn
.get_mut()
.write_all(reply_packet.to_bytes().as_ref())
.await
{
debug!("Failed to write reply packet back to the sender: {err}. Closing the socket on our end");
return;
}
}
}
+10 -13
View File
@@ -8,7 +8,7 @@ use crate::models::VerlocNodeResult;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use nym_crypto::asymmetric::identity;
use nym_task::TaskClient;
use nym_task::ShutdownToken;
use nym_validator_client::models::NymNodeDescription;
use nym_validator_client::NymApiClient;
use rand::prelude::SliceRandom;
@@ -23,7 +23,7 @@ pub struct VerlocMeasurer {
config: Config,
packet_sender: Arc<PacketSender>,
packet_listener: Arc<PacketListener>,
shutdown_listener: TaskClient,
shutdown_token: ShutdownToken,
state: SharedVerlocStats,
}
@@ -31,7 +31,7 @@ impl VerlocMeasurer {
pub fn new(
config: Config,
identity: Arc<identity::KeyPair>,
shutdown_listener: TaskClient,
shutdown_token: ShutdownToken,
) -> Self {
VerlocMeasurer {
packet_sender: Arc::new(PacketSender::new(
@@ -40,14 +40,14 @@ impl VerlocMeasurer {
config.packet_timeout,
config.connection_timeout,
config.delay_between_packets,
shutdown_listener.clone().named("VerlocPacketSender"),
shutdown_token.clone_with_suffix("packet_sender"),
)),
packet_listener: Arc::new(PacketListener::new(
config.listening_address,
Arc::clone(&identity),
shutdown_listener.clone().named("VerlocPacketListener"),
shutdown_token.clone_with_suffix("packet_listener"),
)),
shutdown_listener,
shutdown_token,
config,
state: SharedVerlocStats::default(),
}
@@ -69,9 +69,6 @@ impl VerlocMeasurer {
return MeasurementOutcome::Done;
}
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
shutdown_listener.disarm();
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
let mut chunk_results = Vec::with_capacity(chunk.len());
@@ -95,7 +92,7 @@ impl VerlocMeasurer {
.collect::<FuturesUnordered<_>>();
// exhaust the results
while !shutdown_listener.is_shutdown() {
while !self.shutdown_token.is_cancelled() {
tokio::select! {
measurement_result = measurement_chunk.next() => {
let Some(result) = measurement_result else {
@@ -120,7 +117,7 @@ impl VerlocMeasurer {
};
chunk_results.push(VerlocNodeResult::new(identity, measurement_result));
},
_ = shutdown_listener.recv() => {
_ = self.shutdown_token.cancelled() => {
trace!("Shutdown received while measuring");
return MeasurementOutcome::Shutdown;
}
@@ -155,7 +152,7 @@ impl VerlocMeasurer {
pub async fn run(&mut self) {
self.start_listening();
while !self.shutdown_listener.is_shutdown() {
while !self.shutdown_token.is_cancelled() {
info!("Starting verloc measurements");
// TODO: should we also measure gateways?
@@ -209,7 +206,7 @@ impl VerlocMeasurer {
tokio::select! {
_ = sleep(self.config.testing_interval) => {},
_ = self.shutdown_listener.recv() => {
_ = self.shutdown_token.cancelled() => {
trace!("Shutdown received while sleeping");
}
}
+6 -9
View File
@@ -5,7 +5,7 @@ use crate::error::VerlocError;
use crate::measurements::packet::{EchoPacket, ReplyPacket};
use crate::models::VerlocMeasurement;
use nym_crypto::asymmetric::ed25519;
use nym_task::TaskClient;
use nym_task::ShutdownToken;
use rand::{thread_rng, Rng};
use std::net::SocketAddr;
use std::sync::Arc;
@@ -45,7 +45,7 @@ pub struct PacketSender {
packet_timeout: Duration,
connection_timeout: Duration,
delay_between_packets: Duration,
shutdown_listener: TaskClient,
shutdown_token: ShutdownToken,
}
impl PacketSender {
@@ -55,7 +55,7 @@ impl PacketSender {
packet_timeout: Duration,
connection_timeout: Duration,
delay_between_packets: Duration,
shutdown_listener: TaskClient,
shutdown_token: ShutdownToken,
) -> Self {
PacketSender {
identity,
@@ -63,7 +63,7 @@ impl PacketSender {
packet_timeout,
connection_timeout,
delay_between_packets,
shutdown_listener,
shutdown_token,
}
}
@@ -83,9 +83,6 @@ impl PacketSender {
self: Arc<Self>,
tested_node: TestedNode,
) -> Result<VerlocMeasurement, VerlocError> {
let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string());
shutdown_listener.disarm();
let mut conn = match tokio::time::timeout(
self.connection_timeout,
TcpStream::connect(tested_node.address),
@@ -148,7 +145,7 @@ impl PacketSender {
Ok(Ok(_)) => {}
}
},
_ = shutdown_listener.recv() => {
_ = self.shutdown_token.cancelled() => {
trace!("PacketSender: Received shutdown while sending");
return Err(VerlocError::ShutdownReceived);
},
@@ -190,7 +187,7 @@ impl PacketSender {
}
}
},
_ = shutdown_listener.recv() => {
_ = self.shutdown_token.cancelled() => {
trace!("PacketSender: Received shutdown while waiting for reply");
return Err(VerlocError::ShutdownReceived);
}
@@ -0,0 +1,3 @@
[build]
target = "wasm32-unknown-unknown"
target_arch = "wasm32"
+6 -1
View File
@@ -390,12 +390,15 @@ pub struct TopologyWasm {
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
/// Useless without `ignore_epoch_roles = true`
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 TopologyWasm {
@@ -420,6 +423,7 @@ impl From<TopologyWasm> for ConfigTopology {
minimum_gateway_performance: topology.minimum_gateway_performance,
use_extended_topology: topology.use_extended_topology,
ignore_egress_epoch_role: topology.ignore_egress_epoch_role,
ignore_ingress_epoch_role: topology.ignore_ingress_epoch_role,
}
}
}
@@ -437,6 +441,7 @@ impl From<ConfigTopology> for TopologyWasm {
minimum_gateway_performance: topology.minimum_gateway_performance,
use_extended_topology: topology.use_extended_topology,
ignore_egress_epoch_role: topology.ignore_egress_epoch_role,
ignore_ingress_epoch_role: topology.ignore_ingress_epoch_role,
}
}
}
@@ -274,7 +274,6 @@ pub struct TopologyWasmOverride {
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
/// Useless without `ignore_epoch_roles = true`
#[tsify(optional)]
pub use_extended_topology: Option<bool>,
@@ -282,6 +281,11 @@ pub struct TopologyWasmOverride {
/// when constructing the final hop packets.
#[tsify(optional)]
pub ignore_egress_epoch_role: Option<bool>,
/// Specifies whether this client should ignore the current epoch role of the ingress node
/// when attempting to establish new connection
#[tsify(optional)]
pub ignore_ingress_epoch_role: Option<bool>,
}
impl From<TopologyWasmOverride> for TopologyWasm {
@@ -311,6 +315,9 @@ impl From<TopologyWasmOverride> for TopologyWasm {
ignore_egress_epoch_role: value
.ignore_egress_epoch_role
.unwrap_or(def.ignore_egress_epoch_role),
ignore_ingress_epoch_role: value
.ignore_ingress_epoch_role
.unwrap_or(def.ignore_ingress_epoch_role),
}
}
}
+113 -4
View File
@@ -5,12 +5,15 @@ use crate::error::WasmCoreError;
use crate::storage::wasm_client_traits::WasmClientStorage;
use crate::storage::ClientStorage;
use js_sys::Promise;
use nym_client_core::client::base_client::storage::helpers::set_active_gateway;
use nym_client_core::client::base_client::storage::GatewaysDetailsStore;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
use nym_client_core::init::helpers::current_gateways;
use nym_client_core::error::ClientCoreError;
use nym_client_core::init::helpers::gateways_for_init;
use nym_client_core::init::types::GatewaySelectionSpecification;
use nym_client_core::init::{
self,
self, setup_gateway,
types::{GatewaySetup, InitialisationResult},
};
use nym_sphinx::addressing::clients::Recipient;
@@ -18,7 +21,7 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
use nym_topology::{NymTopology, RoutingNode};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::NymApiClient;
use nym_validator_client::{NymApiClient, UserAgent};
use rand::thread_rng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
@@ -26,6 +29,7 @@ use wasm_bindgen_futures::future_to_promise;
use wasm_utils::error::PromisableResult;
pub use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use wasm_utils::console_log;
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no disk_persistence
@@ -128,12 +132,37 @@ pub async fn setup_gateway_from_api(
chosen_gateway: Option<IdentityKey>,
nym_apis: &[Url],
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<InitialisationResult, WasmCoreError> {
let mut rng = thread_rng();
let gateways = current_gateways(&mut rng, nym_apis, None, minimum_performance).await?;
let gateways = gateways_for_init(
&mut rng,
nym_apis,
None,
minimum_performance,
ignore_epoch_roles,
)
.await?;
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
}
pub async fn current_gateways_wasm(
nym_apis: &[Url],
user_agent: Option<UserAgent>,
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
let mut rng = thread_rng();
gateways_for_init(
&mut rng,
nym_apis,
user_agent,
minimum_performance,
ignore_epoch_roles,
)
.await
}
pub async fn setup_from_topology(
explicit_gateway: Option<IdentityKey>,
force_tls: bool,
@@ -143,3 +172,83 @@ pub async fn setup_from_topology(
let gateways = topology.entry_capable_nodes().cloned().collect::<Vec<_>>();
setup_gateway_wasm(client_store, force_tls, explicit_gateway, gateways).await
}
pub async fn generate_new_client_keys(store: &ClientStorage) -> Result<(), WasmCoreError> {
let mut rng = thread_rng();
init::generate_new_client_keys(&mut rng, store).await?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub async fn add_gateway(
preferred_gateway: Option<IdentityKey>,
latency_based_selection: Option<bool>,
force_tls: bool,
nym_apis: &[Url],
user_agent: UserAgent,
min_performance: u8,
ignore_epoch_roles: bool,
storage: &ClientStorage,
) -> Result<(), WasmCoreError> {
let selection_spec = GatewaySelectionSpecification::new(
preferred_gateway.clone(),
latency_based_selection,
force_tls,
);
let preferred_gateway = preferred_gateway
.as_ref()
.map(|g| g.parse())
.transpose()
.map_err(|source| WasmCoreError::InvalidGatewayIdentity { source })?;
let registered_gateways = storage.all_gateways_identities().await.map_err(|source| {
ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
}
})?;
// if user provided gateway id (and we can't overwrite data), make sure we're not trying to register
// with a known gateway
if let Some(user_chosen) = preferred_gateway {
if registered_gateways.contains(&user_chosen) {
return Err(ClientCoreError::AlreadyRegistered {
gateway_id: user_chosen.to_base58_string(),
}
.into());
}
}
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let available_gateways = current_gateways_wasm(
nym_apis,
Some(user_agent),
min_performance,
ignore_epoch_roles,
)
.await?;
// since we're registering with a brand new gateway,
// make sure the list of available gateways doesn't overlap the list of known gateways
let available_gateways = available_gateways
.into_iter()
.filter(|g| !registered_gateways.contains(&g.identity()))
.collect::<Vec<_>>();
if available_gateways.is_empty() {
return Err(ClientCoreError::NoNewGatewaysAvailable.into());
}
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
};
let init_details = setup_gateway(gateway_setup, storage, storage).await?;
let gateway = init_details.gateway_id().to_base58_string();
set_active_gateway(storage, &gateway).await?;
console_log!("finished registration with gateway {gateway}");
Ok(())
}
@@ -128,8 +128,12 @@ impl GatewaysDetailsStore for ClientStorage {
}
async fn all_gateways(&self) -> Result<Vec<GatewayRegistration>, Self::StorageError> {
todo!()
// let identities = self.all
let identities = self.registered_gateways().await?;
let mut registered = Vec::with_capacity(identities.len());
for gateway_id in identities {
registered.push(self.load_gateway_details(&gateway_id).await?);
}
Ok(registered)
}
async fn has_gateway_details(&self, gateway_id: &str) -> Result<bool, Self::StorageError> {
+20 -15
View File
@@ -4,13 +4,15 @@
use crate::error::WasmCoreError;
use crate::storage::wasm_client_traits::{v1, v2, WasmClientStorage};
use async_trait::async_trait;
use js_sys::{Array, Promise};
use js_sys::Promise;
use serde::de::DeserializeOwned;
use serde::Serialize;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_storage::traits::BaseWasmStorage;
use wasm_storage::{IdbVersionChangeEvent, WasmStorage};
use wasm_storage::{
Build, Database, RawDbResult, TryFromJs, TryToJs, VersionChangeEvent, WasmStorage,
};
use wasm_utils::error::{simple_js_error, PromisableResult};
use zeroize::Zeroizing;
@@ -44,26 +46,29 @@ impl ClientStorage {
// special care must be taken on JS side to ensure it's correctly used there.
let passphrase = Zeroizing::new(passphrase);
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
let migrate_fn = Some(|evt: VersionChangeEvent, db: Database| -> RawDbResult<()> {
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
// works with an unsigned integer.
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
let old_version = evt.old_version() as u32;
let db = evt.db();
if old_version < 1 {
// migrating to version 2
db.create_object_store(v1::KEYS_STORE)?;
db.create_object_store(v1::CORE_STORE)?;
db.create_object_store(v1::KEYS_STORE).build()?;
db.create_object_store(v1::CORE_STORE).build()?;
db.create_object_store(v2::GATEWAY_REGISTRATIONS_ACTIVE_GATEWAY_STORE)?;
db.create_object_store(v2::GATEWAY_REGISTRATIONS_REGISTERED_GATEWAYS_STORE)?;
db.create_object_store(v2::GATEWAY_REGISTRATIONS_ACTIVE_GATEWAY_STORE)
.build()?;
db.create_object_store(v2::GATEWAY_REGISTRATIONS_REGISTERED_GATEWAYS_STORE)
.build()?;
return Ok(());
}
// version 1 -> unimplemented migration
if old_version < 2 {
return Err(simple_js_error("this client is incompatible with existing storage. please initialise it again."));
return Err(simple_js_error("this client is incompatible with existing storage. please initialise it again.").into());
}
Ok(())
@@ -110,7 +115,7 @@ impl BaseWasmStorage for ClientStorage {
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
where
T: DeserializeOwned,
K: JsCast,
K: TryToJs,
{
Ok(self.inner.read_value(store, key).await?)
}
@@ -123,33 +128,33 @@ impl BaseWasmStorage for ClientStorage {
) -> Result<(), Self::StorageError>
where
T: Serialize,
K: JsCast,
K: TryToJs + TryFromJs,
{
Ok(self.inner.store_value(store, key, value).await?)
}
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
where
K: JsCast,
K: TryToJs,
{
Ok(self.inner.remove_value(store, key).await?)
}
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
where
K: JsCast,
K: TryToJs,
{
Ok(self.inner.has_value(store, key).await?)
}
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
where
K: JsCast,
K: TryToJs,
{
Ok(self.inner.key_count(store, key).await?)
}
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError> {
Ok(self.inner.get_all_keys(store).await?)
}
}
@@ -119,6 +119,15 @@ pub trait WasmClientStorage: BaseWasmStorage {
.map_err(Into::into)
}
async fn has_identity_key(&self) -> Result<bool, <Self as WasmClientStorage>::StorageError> {
self.has_value(
v1::KEYS_STORE,
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
)
.await
.map_err(Into::into)
}
async fn store_identity_keypair(
&self,
keypair: &identity::KeyPair,
@@ -277,8 +286,8 @@ pub trait WasmClientStorage: BaseWasmStorage {
.await
.map_err(Into::into)
.map(|arr| {
arr.to_vec()
.into_iter()
arr.iter()
.cloned()
.filter_map(|key| key.as_string())
.collect()
})
+3
View File
@@ -0,0 +1,3 @@
[build]
target = "wasm32-unknown-unknown"
target_arch = "wasm32"
+1 -1
View File
@@ -9,7 +9,7 @@ license.workspace = true
[dependencies]
async-trait = { workspace = true }
futures = { workspace = true }
getrandom = { workspace = true, features = ["js"] }
js-sys = { workspace = true }
wasm-bindgen = { workspace = true }
serde = { workspace = true, features = ["derive"] }
+17 -16
View File
@@ -1,7 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use indexed_db_futures::web_sys::DomException;
use serde_wasm_bindgen::Error;
use thiserror::Error;
use wasm_bindgen::JsValue;
@@ -12,15 +11,11 @@ pub enum StorageError {
#[error("{0}")]
Json(String),
#[error("DomException {name} ({code}): {message}")]
DomException {
/// DomException code
code: u16,
/// Specific name of the DomException
name: String,
/// Message given to the DomException
message: String,
},
#[error("storage failure: {message}")]
InternalStorageFailure { message: String },
#[error("failed to open the db file: {message}")]
DbOpenFailure { message: String },
#[error("FATAL ERROR: storage key is somehow present {count} times in the table!")]
DuplicateKey { count: u32 },
@@ -46,12 +41,18 @@ impl From<StorageError> for JsValue {
}
}
impl From<DomException> for StorageError {
fn from(value: DomException) -> StorageError {
StorageError::DomException {
name: value.name(),
message: value.message(),
code: value.code(),
impl From<indexed_db_futures::error::Error> for StorageError {
fn from(value: indexed_db_futures::error::Error) -> Self {
StorageError::InternalStorageFailure {
message: value.to_string(),
}
}
}
impl From<indexed_db_futures::error::OpenDbError> for StorageError {
fn from(value: indexed_db_futures::error::OpenDbError) -> Self {
StorageError::DbOpenFailure {
message: value.to_string(),
}
}
}
+69 -52
View File
@@ -1,8 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2023-2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::cipher_export::StoredExportedStoreCipher;
use crate::error::StorageError;
use indexed_db_futures::transaction::TransactionMode;
use nym_store_cipher::{
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned,
Version,
@@ -13,7 +14,10 @@ use std::future::IntoFuture;
use wasm_bindgen::JsValue;
use wasm_utils::console_log;
pub use indexed_db_futures::database::{Database, VersionChangeEvent};
pub use indexed_db_futures::prelude::*;
pub use indexed_db_futures::primitive::{TryFromJs, TryToJs};
pub use indexed_db_futures::Result as RawDbResult;
mod cipher_export;
pub mod error;
@@ -54,31 +58,29 @@ impl WasmStorage {
passphrase: Option<&[u8]>,
) -> Result<Self, StorageError>
where
F: Fn(&IdbVersionChangeEvent) -> Result<(), JsValue> + 'static,
F: Fn(VersionChangeEvent, Database) -> RawDbResult<()> + 'static,
{
let mut db_req: OpenDbRequest = IdbDatabase::open_u32(db_name, version)?;
// we must always ensure the cipher table is present
db_req.set_on_upgrade_needed(Some(
move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
let db = Database::open(db_name)
.with_version(version)
.with_on_upgrade_needed(move |event, db| {
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
// works with an unsigned integer.
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
let old_version = evt.old_version() as u32;
let old_version = event.old_version() as u32;
if old_version < 1 {
evt.db().create_object_store(CIPHER_INFO_STORE)?;
db.create_object_store(CIPHER_INFO_STORE).build()?;
}
if let Some(migrate) = migrate_fn.as_ref() {
migrate(evt)
migrate(event, db)
} else {
Ok(())
}
},
));
})
.await?;
let db: IdbDatabase = db_req.into_future().await?;
let inner = IdbWrapper(db);
let store_cipher = inner.setup_store_cipher(passphrase).await?;
@@ -94,13 +96,12 @@ impl WasmStorage {
}
pub async fn remove(db_name: &str) -> Result<(), StorageError> {
IdbDatabase::delete_by_name(db_name)?.into_future().await?;
Database::delete_by_name(db_name)?.into_future().await?;
Ok(())
}
pub async fn exists(db_name: &str) -> Result<bool, StorageError> {
let db_req: OpenDbRequest = IdbDatabase::open(db_name)?;
let db: IdbDatabase = db_req.into_future().await?;
let db = Database::open(db_name).await?;
// if the db was already created before, at the very least cipher info store should exist,
// thus the iterator should return at least one value
@@ -139,7 +140,7 @@ impl WasmStorage {
pub async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, StorageError>
where
T: DeserializeOwned,
K: wasm_bindgen::JsCast,
K: TryToJs,
{
self.inner
.read_value_raw(store, key)
@@ -156,7 +157,7 @@ impl WasmStorage {
) -> Result<(), StorageError>
where
T: Serialize,
K: wasm_bindgen::JsCast,
K: TryToJs + TryFromJs,
{
self.inner
.store_value_raw(store, key, &self.serialize_value(&value)?)
@@ -165,14 +166,14 @@ impl WasmStorage {
pub async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), StorageError>
where
K: wasm_bindgen::JsCast,
K: TryToJs,
{
self.inner.remove_value_raw(store, key).await
}
pub async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, StorageError>
where
K: wasm_bindgen::JsCast,
K: TryToJs,
{
match self.key_count(store, key).await? {
0 => Ok(false),
@@ -183,82 +184,98 @@ impl WasmStorage {
pub async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
where
K: wasm_bindgen::JsCast,
K: TryToJs,
{
self.inner.get_key_count(store, key).await
}
pub async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, StorageError> {
pub async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, StorageError> {
self.inner.get_all_keys(store).await
}
}
struct IdbWrapper(IdbDatabase);
struct IdbWrapper(Database);
impl IdbWrapper {
async fn read_value_raw<K>(&self, store: &str, key: K) -> Result<Option<JsValue>, StorageError>
where
K: wasm_bindgen::JsCast,
K: TryToJs,
{
self.0
.transaction_on_one_with_mode(store, IdbTransactionMode::Readonly)?
.transaction(store)
.with_mode(TransactionMode::Readonly)
.build()?
.object_store(store)?
.get(&key)?
.get(&key)
.primitive()?
.await
.map_err(Into::into)
}
async fn store_value_raw<K>(
async fn store_value_raw<K, T>(
&self,
store: &str,
key: K,
value: &JsValue,
value: &T,
) -> Result<(), StorageError>
where
K: wasm_bindgen::JsCast,
K: TryToJs + TryFromJs,
T: TryToJs,
{
self.0
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
.object_store(store)?
.put_key_val_owned(key, value)?
.into_future()
.await
.map_err(Into::into)
let tx = self
.0
.transaction(store)
.with_mode(TransactionMode::Readwrite)
.build()?;
let store = tx.object_store(store)?;
store.put(value).with_key(key).primitive()?.await?;
tx.commit().await.map_err(Into::into)
}
async fn remove_value_raw<K>(&self, store: &str, key: K) -> Result<(), StorageError>
where
K: wasm_bindgen::JsCast,
K: TryToJs,
{
self.0
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
.object_store(store)?
.delete_owned(key)?
.into_future()
.await
.map_err(Into::into)
let tx = self
.0
.transaction(store)
.with_mode(TransactionMode::Readwrite)
.build()?;
let store = tx.object_store(store)?;
store.delete(key).primitive()?.await?;
tx.commit().await.map_err(Into::into)
}
async fn get_key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
where
K: wasm_bindgen::JsCast,
K: TryToJs,
{
self.0
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
.transaction(store)
.with_mode(TransactionMode::Readonly)
.build()?
.object_store(store)?
.count_with_key_owned(key)?
.into_future()
.count()
.with_query(key)
.primitive()?
.await
.map_err(Into::into)
}
async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, StorageError> {
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, StorageError> {
self.0
.transaction_on_one_with_mode(store, IdbTransactionMode::Readonly)?
.transaction(store)
.with_mode(TransactionMode::Readonly)
.build()?
.object_store(store)?
.get_all_keys()?
.into_future()
.await
.get_all_keys()
.primitive()?
.await?
.collect::<Result<Vec<_>, _>>()
.map_err(Into::into)
}
+14 -13
View File
@@ -3,10 +3,11 @@
use crate::WasmStorage;
use async_trait::async_trait;
use js_sys::Array;
use indexed_db_futures::primitive::{TryFromJs, TryToJs};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::error::Error;
use wasm_bindgen::JsValue;
#[async_trait(?Send)]
pub trait BaseWasmStorage {
@@ -17,7 +18,7 @@ pub trait BaseWasmStorage {
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
where
T: DeserializeOwned,
K: wasm_bindgen::JsCast;
K: TryToJs;
async fn store_value<T, K>(
&self,
@@ -27,21 +28,21 @@ pub trait BaseWasmStorage {
) -> Result<(), Self::StorageError>
where
T: Serialize,
K: wasm_bindgen::JsCast;
K: TryToJs + TryFromJs;
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
where
K: wasm_bindgen::JsCast;
K: TryToJs;
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
where
K: wasm_bindgen::JsCast;
K: TryToJs;
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
where
K: wasm_bindgen::JsCast;
K: TryToJs;
async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, Self::StorageError>;
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError>;
}
#[async_trait(?Send)]
@@ -55,7 +56,7 @@ impl BaseWasmStorage for WasmStorage {
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
where
T: DeserializeOwned,
K: wasm_bindgen::JsCast,
K: TryToJs,
{
self.read_value(store, key).await
}
@@ -68,33 +69,33 @@ impl BaseWasmStorage for WasmStorage {
) -> Result<(), Self::StorageError>
where
T: Serialize,
K: wasm_bindgen::JsCast,
K: TryToJs + TryFromJs,
{
self.store_value(store, key, value).await
}
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
where
K: wasm_bindgen::JsCast,
K: TryToJs,
{
self.remove_value(store, key).await
}
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
where
K: wasm_bindgen::JsCast,
K: TryToJs,
{
self.has_value(store, key).await
}
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
where
K: wasm_bindgen::JsCast,
K: TryToJs,
{
self.key_count(store, key).await
}
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError> {
self.get_all_keys(store).await
}
}
+3
View File
@@ -0,0 +1,3 @@
[build]
target = "wasm32-unknown-unknown"
target_arch = "wasm32"
+18 -9
View File
@@ -267,18 +267,27 @@ impl PeerController {
}))
}
fn update_metrics(&self, new_host: &Host) {
async fn update_metrics(&self, new_host: &Host) {
let now = SystemTime::now();
const ACTIVITY_THRESHOLD: Duration = Duration::from_secs(60);
let old_host = self.host_information.read().await;
let total_peers = new_host.peers.len();
let mut active_peers = 0;
let mut total_rx = 0;
let mut total_tx = 0;
let mut new_rx = 0;
let mut new_tx = 0;
for peer in new_host.peers.values() {
total_rx += peer.rx_bytes;
total_tx += peer.tx_bytes;
for (peer_key, peer) in new_host.peers.iter() {
// only consider pre-existing peers,
// so that the value would always be increasing
if let Some(prior) = old_host.peers.get(peer_key) {
let delta_rx = peer.rx_bytes.saturating_sub(prior.rx_bytes);
let delta_tx = prior.tx_bytes.saturating_sub(prior.tx_bytes);
new_rx += delta_rx;
new_tx += delta_tx;
}
// if a peer hasn't performed a handshake in last minute,
// I think it's reasonable to assume it's no longer active
@@ -296,10 +305,10 @@ impl PeerController {
self.metrics.wireguard.update(
// if the conversion fails it means we're running not running on a 64bit system
// and that's a reason enough for this failure.
total_rx.try_into().expect(
new_rx.try_into().expect(
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
),
total_tx.try_into().expect(
new_tx.try_into().expect(
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
),
total_peers,
@@ -316,7 +325,7 @@ impl PeerController {
log::error!("Can't read wireguard kernel data");
continue;
};
self.update_metrics(&host);
self.update_metrics(&host).await;
*self.host_information.write().await = host;
}
+11 -11
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "aes"
@@ -1699,18 +1699,18 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.23"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.214"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
@@ -1735,9 +1735,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.214"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
@@ -1965,9 +1965,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.36"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
@@ -1988,9 +1988,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.18"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
@@ -464,10 +464,7 @@ pub fn instantiate_contracts(
mixnet_contract_address.clone(),
&nym_mixnet_contract_common::MigrateMsg {
vesting_contract_address: Some(vesting_contract_address.to_string()),
current_nym_node_semver: "1.1.10".to_string(),
version_score_weights: Default::default(),
unsafe_skip_state_updates: Some(true),
version_score_params: Default::default(),
},
mixnet_code_id,
)
@@ -3470,43 +3470,13 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "MigrateMsg",
"type": "object",
"required": [
"current_nym_node_semver"
],
"properties": {
"current_nym_node_semver": {
"type": "string"
},
"unsafe_skip_state_updates": {
"type": [
"boolean",
"null"
]
},
"version_score_params": {
"default": {
"penalty": "0.995",
"penalty_scaling": "1.65"
},
"allOf": [
{
"$ref": "#/definitions/VersionScoreFormulaParams"
}
]
},
"version_score_weights": {
"default": {
"major": 100,
"minor": 10,
"patch": 1,
"prerelease": 1
},
"allOf": [
{
"$ref": "#/definitions/OutdatedVersionWeights"
}
]
},
"vesting_contract_address": {
"type": [
"string",
@@ -3514,63 +3484,7 @@
]
}
},
"additionalProperties": false,
"definitions": {
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
},
"OutdatedVersionWeights": {
"description": "Defines weights for calculating numbers of versions behind the current release.",
"type": "object",
"required": [
"major",
"minor",
"patch",
"prerelease"
],
"properties": {
"major": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"minor": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"patch": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"prerelease": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
},
"VersionScoreFormulaParams": {
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
"type": "object",
"required": [
"penalty",
"penalty_scaling"
],
"properties": {
"penalty": {
"$ref": "#/definitions/Decimal"
},
"penalty_scaling": {
"$ref": "#/definitions/Decimal"
}
},
"additionalProperties": false
}
}
"additionalProperties": false
},
"sudo": null,
"responses": {
+1 -87
View File
@@ -2,43 +2,13 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "MigrateMsg",
"type": "object",
"required": [
"current_nym_node_semver"
],
"properties": {
"current_nym_node_semver": {
"type": "string"
},
"unsafe_skip_state_updates": {
"type": [
"boolean",
"null"
]
},
"version_score_params": {
"default": {
"penalty": "0.995",
"penalty_scaling": "1.65"
},
"allOf": [
{
"$ref": "#/definitions/VersionScoreFormulaParams"
}
]
},
"version_score_weights": {
"default": {
"major": 100,
"minor": 10,
"patch": 1,
"prerelease": 1
},
"allOf": [
{
"$ref": "#/definitions/OutdatedVersionWeights"
}
]
},
"vesting_contract_address": {
"type": [
"string",
@@ -46,61 +16,5 @@
]
}
},
"additionalProperties": false,
"definitions": {
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
},
"OutdatedVersionWeights": {
"description": "Defines weights for calculating numbers of versions behind the current release.",
"type": "object",
"required": [
"major",
"minor",
"patch",
"prerelease"
],
"properties": {
"major": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"minor": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"patch": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"prerelease": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
},
"VersionScoreFormulaParams": {
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
"type": "object",
"required": [
"penalty",
"penalty_scaling"
],
"properties": {
"penalty": {
"$ref": "#/definitions/Decimal"
},
"penalty_scaling": {
"$ref": "#/definitions/Decimal"
}
},
"additionalProperties": false
}
}
"additionalProperties": false
}
+7 -7
View File
@@ -602,18 +602,18 @@ pub fn query(
#[entry_point]
pub fn migrate(
mut deps: DepsMut<'_>,
env: Env,
deps: DepsMut<'_>,
_env: Env,
msg: MigrateMsg,
) -> Result<Response, MixnetContractError> {
set_build_information!(deps.storage)?;
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
let skip_state_updates = msg.unsafe_skip_state_updates.unwrap_or(false);
if !skip_state_updates {
crate::queued_migrations::add_config_score_params(deps.branch(), env, &msg)?;
}
// let skip_state_updates = msg.unsafe_skip_state_updates.unwrap_or(false);
//
// if !skip_state_updates {
//
// }
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
// and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh
@@ -34,9 +34,13 @@ impl NymNodeVersionHistory<'_> {
}
fn next_id(&self, storage: &mut dyn Storage) -> Result<u32, MixnetContractError> {
let next = self.id_counter.may_load(storage)?.unwrap_or_default();
self.id_counter.save(storage, &next)?;
Ok(next)
let id = self
.id_counter
.may_load(storage)?
.map(|current| current + 1)
.unwrap_or_default();
self.id_counter.save(storage, &id)?;
Ok(id)
}
pub fn current_version(
@@ -56,10 +60,10 @@ impl NymNodeVersionHistory<'_> {
pub fn insert_new(
&self,
storage: &mut dyn Storage,
entry: HistoricalNymNodeVersion,
entry: &HistoricalNymNodeVersion,
) -> Result<u32, MixnetContractError> {
let next_id = self.next_id(storage)?;
self.version_history.save(storage, next_id, &entry)?;
self.version_history.save(storage, next_id, entry)?;
Ok(next_id)
}
@@ -79,7 +83,7 @@ impl NymNodeVersionHistory<'_> {
// treat this as genesis
let genesis =
HistoricalNymNodeVersion::genesis(raw_semver.to_string(), env.block.height);
return self.insert_new(storage, genesis);
return self.insert_new(storage, &genesis);
};
let current_semver = current.version_information.semver_unchecked();
@@ -99,7 +103,7 @@ impl NymNodeVersionHistory<'_> {
introduced_at_height: env.block.height,
difference_since_genesis: diff,
};
self.insert_new(storage, entry)
self.insert_new(storage, &entry)
}
}
@@ -170,3 +174,218 @@ pub(crate) fn initialise_storage(
ADMIN.set(deps, Some(initial_admin))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(test)]
mod nym_node_version_history {
use super::*;
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::testing::{mock_dependencies, mock_env};
#[test]
fn getting_current() -> anyhow::Result<()> {
// empty storage
let deps = mock_dependencies();
let storage = NymNodeVersionHistory::new();
assert!(storage.current_version(&deps.storage)?.is_none());
let mut test = TestSetup::new();
let zeroth = storage.current_version(test.storage())?.unwrap();
let manual_zeroth = storage.version_history.load(test.storage(), 0)?;
assert_eq!(zeroth.version_information, manual_zeroth);
// manually update the counter to make sure data is still read correctly
let dummy = HistoricalNymNodeVersion {
semver: "1.2.3".to_string(),
introduced_at_height: 1234,
difference_since_genesis: Default::default(),
};
storage.id_counter.save(test.storage_mut(), &123)?;
storage
.version_history
.save(test.storage_mut(), 123, &dummy)?;
let updated = storage.current_version(test.storage())?.unwrap();
assert_eq!(updated.version_information, dummy);
Ok(())
}
#[test]
fn inserting_new_entry() -> anyhow::Result<()> {
let mut test = TestSetup::new();
let storage = NymNodeVersionHistory::new();
let first = HistoricalNymNodeVersion {
semver: "1.1.1".to_string(),
introduced_at_height: 12,
difference_since_genesis: Default::default(),
};
let second = HistoricalNymNodeVersion {
semver: "1.1.2".to_string(),
introduced_at_height: 123,
difference_since_genesis: Default::default(),
};
let third = HistoricalNymNodeVersion {
semver: "1.1.3".to_string(),
introduced_at_height: 1234,
difference_since_genesis: Default::default(),
};
assert_eq!(storage.id_counter.load(test.storage())?, 0);
// id is correctly incremented for each case and no entry is overwritten
storage.insert_new(test.storage_mut(), &first)?;
assert_eq!(storage.id_counter.load(test.storage())?, 1);
storage.insert_new(test.storage_mut(), &second)?;
assert_eq!(storage.id_counter.load(test.storage())?, 2);
storage.insert_new(test.storage_mut(), &third)?;
assert_eq!(storage.id_counter.load(test.storage())?, 3);
assert_eq!(storage.version_history.load(test.storage(), 1)?, first);
assert_eq!(storage.version_history.load(test.storage(), 2)?, second);
assert_eq!(storage.version_history.load(test.storage(), 3)?, third);
Ok(())
}
#[test]
fn inserting_initial_semver() -> anyhow::Result<()> {
// empty storage
let mut deps = mock_dependencies();
let env = mock_env();
let storage = NymNodeVersionHistory::new();
assert!(storage
.id_counter
.may_load(deps.as_mut().storage)?
.is_none());
storage.try_insert_new(deps.as_mut().storage, &env, "1.1.1")?;
assert_eq!(storage.id_counter.load(deps.as_mut().storage)?, 0);
assert_eq!(
storage
.version_history
.load(deps.as_ref().storage, 0)?
.semver,
"1.1.1"
);
assert_eq!(
storage
.current_version(deps.as_ref().storage)?
.unwrap()
.version_information
.semver,
"1.1.1"
);
Ok(())
}
#[test]
fn inserting_second_semver() -> anyhow::Result<()> {
let mut test = TestSetup::new();
let env = test.env();
let storage = NymNodeVersionHistory::new();
// lower version
assert!(storage
.try_insert_new(test.storage_mut(), &env, "1.1.9")
.is_err());
assert!(storage
.try_insert_new(test.storage_mut(), &env, "1.0.1")
.is_err());
// malformed
assert!(storage
.try_insert_new(test.storage_mut(), &env, "1.0")
.is_err());
assert!(storage
.try_insert_new(test.storage_mut(), &env, "1.0bad")
.is_err());
assert!(storage
.try_insert_new(test.storage_mut(), &env, "foomp")
.is_err());
// patch
let mut test = TestSetup::new();
storage.try_insert_new(test.storage_mut(), &env, "1.1.11")?;
let current = storage
.current_version(test.storage_mut())?
.unwrap()
.version_information;
assert_eq!(current.semver, "1.1.11");
assert_eq!(current.difference_since_genesis.major, 0);
assert_eq!(current.difference_since_genesis.minor, 0);
assert_eq!(current.difference_since_genesis.patch, 1);
assert_eq!(current.difference_since_genesis.prerelease, 0);
// minor
let mut test = TestSetup::new();
storage.try_insert_new(test.storage_mut(), &env, "1.2.0")?;
let current = storage
.current_version(test.storage_mut())?
.unwrap()
.version_information;
assert_eq!(current.semver, "1.2.0");
assert_eq!(current.difference_since_genesis.major, 0);
assert_eq!(current.difference_since_genesis.minor, 1);
assert_eq!(current.difference_since_genesis.patch, 0);
assert_eq!(current.difference_since_genesis.prerelease, 0);
// minor alt.
let mut test = TestSetup::new();
storage.try_insert_new(test.storage_mut(), &env, "1.2.3")?;
let current = storage
.current_version(test.storage_mut())?
.unwrap()
.version_information;
assert_eq!(current.semver, "1.2.3");
assert_eq!(current.difference_since_genesis.major, 0);
assert_eq!(current.difference_since_genesis.minor, 1);
assert_eq!(current.difference_since_genesis.patch, 0);
assert_eq!(current.difference_since_genesis.prerelease, 0);
Ok(())
}
#[test]
fn inserting_subsequent_semver() -> anyhow::Result<()> {
let mut test = TestSetup::new();
let env = test.env();
let storage = NymNodeVersionHistory::new();
storage.try_insert_new(test.storage_mut(), &env, "1.2.0")?;
storage.try_insert_new(test.storage_mut(), &env, "1.2.1")?;
storage.try_insert_new(test.storage_mut(), &env, "1.2.3")?;
let current = storage
.current_version(test.storage_mut())?
.unwrap()
.version_information;
assert_eq!(current.semver, "1.2.3");
assert_eq!(current.difference_since_genesis.major, 0);
assert_eq!(current.difference_since_genesis.minor, 1);
assert_eq!(current.difference_since_genesis.patch, 3);
assert_eq!(current.difference_since_genesis.prerelease, 0);
storage.try_insert_new(test.storage_mut(), &env, "1.3.0")?;
let current = storage
.current_version(test.storage_mut())?
.unwrap()
.version_information;
assert_eq!(current.semver, "1.3.0");
assert_eq!(current.difference_since_genesis.major, 0);
assert_eq!(current.difference_since_genesis.minor, 2);
assert_eq!(current.difference_since_genesis.patch, 3);
assert_eq!(current.difference_since_genesis.prerelease, 0);
Ok(())
}
}
}
@@ -238,4 +238,446 @@ pub mod tests {
// let res = try_update_contract_settings(deps.as_mut(), info, new_params);
// assert_eq!(Err(MixnetContractError::ZeroActiveSet), res);
}
#[cfg(test)]
mod updating_current_nym_node_semver {
use super::*;
use crate::mixnet_contract_settings::queries::query_current_nym_node_version;
use crate::support::tests::test_helpers::TestSetup;
#[test]
fn is_restricted_to_the_admin() -> anyhow::Result<()> {
let mut test = TestSetup::new();
let not_admin = mock_info("not-admin", &[]);
let admin = mock_info(test.admin().as_ref(), &[]);
let env = test.env();
let res = try_update_current_nym_node_semver(
test.deps_mut(),
env,
not_admin,
"1.2.1".to_string(),
);
assert!(res.is_err());
let env = test.env();
let res = try_update_current_nym_node_semver(
test.deps_mut(),
env,
admin,
"1.2.1".to_string(),
);
assert!(res.is_ok());
Ok(())
}
#[test]
fn updates_current_semver_value() -> anyhow::Result<()> {
let mut test = TestSetup::new();
let res = query_current_nym_node_version(test.deps())?;
let initial = res.version.unwrap().version_information.semver;
// sanity check to make sure our contract init hasn't changed
assert_eq!(initial, "1.1.10");
let update = "1.2.0".to_string();
let env = test.env();
let sender = test.admin_sender();
try_update_current_nym_node_semver(test.deps_mut(), env, sender, update.clone())?;
let updated = query_current_nym_node_version(test.deps())?;
let version = updated.version.unwrap().version_information.semver;
assert_eq!(version, update);
Ok(())
}
#[cfg(test)]
mod semver_chain_updates {
use super::*;
use crate::mixnet_contract_settings::queries::query_nym_node_version_history_paged;
use mixnet_contract_common::{
HistoricalNymNodeVersion, HistoricalNymNodeVersionEntry, TotalVersionDifference,
};
fn test_setup_with_initial_checks() -> anyhow::Result<TestSetup> {
let test = TestSetup::new();
let res = query_current_nym_node_version(test.deps())?;
let initial = res.version.unwrap().version_information.semver;
// sanity check to make sure our contract init hasn't changed
assert_eq!(initial, "1.1.10");
let history = query_nym_node_version_history_paged(test.deps(), None, None)?;
assert_eq!(history.history.len(), 1);
Ok(test)
}
#[test]
fn single_patch() -> anyhow::Result<()> {
let mut test = test_setup_with_initial_checks()?;
let initial = query_current_nym_node_version(test.deps())?
.version
.unwrap();
let env = test.env();
let sender = test.admin_sender();
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender,
"1.1.11".to_string(),
)?;
let history =
query_nym_node_version_history_paged(test.deps(), None, None)?.history;
assert_eq!(history.len(), 2);
assert_eq!(history[0], initial);
assert_eq!(
history[1],
HistoricalNymNodeVersionEntry {
id: 1,
version_information: HistoricalNymNodeVersion {
semver: "1.1.11".to_string(),
introduced_at_height: env.block.height,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 0,
patch: 1,
prerelease: 0,
},
},
}
);
Ok(())
}
#[test]
fn single_minor() -> anyhow::Result<()> {
let mut test = test_setup_with_initial_checks()?;
let initial = query_current_nym_node_version(test.deps())?
.version
.unwrap();
let env = test.env();
let sender = test.admin_sender();
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender,
"1.2.0".to_string(),
)?;
let history =
query_nym_node_version_history_paged(test.deps(), None, None)?.history;
assert_eq!(history.len(), 2);
assert_eq!(history[0], initial);
assert_eq!(
history[1],
HistoricalNymNodeVersionEntry {
id: 1,
version_information: HistoricalNymNodeVersion {
semver: "1.2.0".to_string(),
introduced_at_height: env.block.height,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 1,
patch: 0,
prerelease: 0,
},
},
}
);
Ok(())
}
#[test]
fn multiple_patches() -> anyhow::Result<()> {
let mut test = test_setup_with_initial_checks()?;
let initial = query_current_nym_node_version(test.deps())?
.version
.unwrap();
let mut env = test.env();
let sender = test.admin_sender();
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender.clone(),
"1.1.11".to_string(),
)?;
env.block.height += 1;
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender.clone(),
"1.1.12".to_string(),
)?;
env.block.height += 1;
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender,
"1.1.13".to_string(),
)?;
let history =
query_nym_node_version_history_paged(test.deps(), None, None)?.history;
assert_eq!(history.len(), 4);
assert_eq!(history[0], initial);
assert_eq!(
history[1],
HistoricalNymNodeVersionEntry {
id: 1,
version_information: HistoricalNymNodeVersion {
semver: "1.1.11".to_string(),
introduced_at_height: env.block.height - 2,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 0,
patch: 1,
prerelease: 0,
},
},
}
);
assert_eq!(
history[2],
HistoricalNymNodeVersionEntry {
id: 2,
version_information: HistoricalNymNodeVersion {
semver: "1.1.12".to_string(),
introduced_at_height: env.block.height - 1,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 0,
patch: 2,
prerelease: 0,
},
},
}
);
assert_eq!(
history[3],
HistoricalNymNodeVersionEntry {
id: 3,
version_information: HistoricalNymNodeVersion {
semver: "1.1.13".to_string(),
introduced_at_height: env.block.height,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 0,
patch: 3,
prerelease: 0,
},
},
}
);
Ok(())
}
#[test]
fn multiple_minors() -> anyhow::Result<()> {
let mut test = test_setup_with_initial_checks()?;
let initial = query_current_nym_node_version(test.deps())?
.version
.unwrap();
let mut env = test.env();
let sender = test.admin_sender();
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender.clone(),
"1.2.0".to_string(),
)?;
env.block.height += 1;
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender.clone(),
"1.3.0".to_string(),
)?;
env.block.height += 1;
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender,
"1.4.0".to_string(),
)?;
let history =
query_nym_node_version_history_paged(test.deps(), None, None)?.history;
assert_eq!(history.len(), 4);
assert_eq!(history[0], initial);
assert_eq!(
history[1],
HistoricalNymNodeVersionEntry {
id: 1,
version_information: HistoricalNymNodeVersion {
semver: "1.2.0".to_string(),
introduced_at_height: env.block.height - 2,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 1,
patch: 0,
prerelease: 0,
},
},
}
);
assert_eq!(
history[2],
HistoricalNymNodeVersionEntry {
id: 2,
version_information: HistoricalNymNodeVersion {
semver: "1.3.0".to_string(),
introduced_at_height: env.block.height - 1,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 2,
patch: 0,
prerelease: 0,
},
},
}
);
assert_eq!(
history[3],
HistoricalNymNodeVersionEntry {
id: 3,
version_information: HistoricalNymNodeVersion {
semver: "1.4.0".to_string(),
introduced_at_height: env.block.height,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 3,
patch: 0,
prerelease: 0,
},
},
}
);
Ok(())
}
#[test]
fn mixed_multiple_updates() -> anyhow::Result<()> {
let mut test = test_setup_with_initial_checks()?;
let initial = query_current_nym_node_version(test.deps())?
.version
.unwrap();
let mut env = test.env();
let sender = test.admin_sender();
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender.clone(),
"1.2.0".to_string(),
)?;
env.block.height += 1;
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender.clone(),
"1.2.1".to_string(),
)?;
env.block.height += 1;
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender.clone(),
"1.2.3".to_string(),
)?;
env.block.height += 1;
try_update_current_nym_node_semver(
test.deps_mut(),
env.clone(),
sender,
"1.3.0".to_string(),
)?;
let history =
query_nym_node_version_history_paged(test.deps(), None, None)?.history;
assert_eq!(history.len(), 5);
assert_eq!(history[0], initial);
assert_eq!(
history[1],
HistoricalNymNodeVersionEntry {
id: 1,
version_information: HistoricalNymNodeVersion {
semver: "1.2.0".to_string(),
introduced_at_height: env.block.height - 3,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 1,
patch: 0,
prerelease: 0,
},
},
}
);
assert_eq!(
history[2],
HistoricalNymNodeVersionEntry {
id: 2,
version_information: HistoricalNymNodeVersion {
semver: "1.2.1".to_string(),
introduced_at_height: env.block.height - 2,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 1,
patch: 1,
prerelease: 0,
},
},
}
);
assert_eq!(
history[3],
HistoricalNymNodeVersionEntry {
id: 3,
version_information: HistoricalNymNodeVersion {
semver: "1.2.3".to_string(),
introduced_at_height: env.block.height - 1,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 1,
patch: 3,
prerelease: 0,
},
},
}
);
assert_eq!(
history[4],
HistoricalNymNodeVersionEntry {
id: 4,
version_information: HistoricalNymNodeVersion {
semver: "1.3.0".to_string(),
introduced_at_height: env.block.height,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 2,
patch: 3,
prerelease: 0,
},
},
}
);
Ok(())
}
}
}
}
-84
View File
@@ -1,86 +1,2 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod config_score_params {
use crate::constants::CONTRACT_STATE_KEY;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnet_contract_settings::storage::NymNodeVersionHistory;
use cosmwasm_std::{Addr, Coin, DepsMut, Env};
use cw_storage_plus::Item;
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{
ConfigScoreParams, ContractState, ContractStateParams, DelegationsParams, MigrateMsg,
OperatingCostRange, OperatorsParams, ProfitMarginRange,
};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
pub(crate) fn add_config_score_params(
deps: DepsMut<'_>,
env: Env,
msg: &MigrateMsg,
) -> Result<(), MixnetContractError> {
if semver::Version::from_str(&msg.current_nym_node_semver).is_err() {
return Err(MixnetContractError::InvalidNymNodeSemver {
provided: msg.current_nym_node_semver.to_string(),
});
}
#[derive(Serialize, Deserialize)]
pub struct OldContractState {
pub owner: Option<Addr>,
pub rewarding_validator_address: Addr,
pub vesting_contract_address: Addr,
pub rewarding_denom: String,
pub params: OldContractStateParams,
}
#[derive(Serialize, Deserialize)]
pub struct OldContractStateParams {
pub minimum_delegation: Option<Coin>,
pub minimum_pledge: Coin,
#[serde(default)]
pub profit_margin: ProfitMarginRange,
#[serde(default)]
pub interval_operating_cost: OperatingCostRange,
}
const OLD_CONTRACT_STATE: Item<'_, OldContractState> = Item::new(CONTRACT_STATE_KEY);
let old_state = OLD_CONTRACT_STATE.load(deps.storage)?;
#[allow(deprecated)]
let new_state = ContractState {
owner: old_state.owner,
rewarding_validator_address: old_state.rewarding_validator_address,
vesting_contract_address: old_state.vesting_contract_address,
rewarding_denom: old_state.rewarding_denom,
params: ContractStateParams {
delegations_params: DelegationsParams {
minimum_delegation: old_state.params.minimum_delegation,
},
operators_params: OperatorsParams {
minimum_pledge: old_state.params.minimum_pledge,
profit_margin: old_state.params.profit_margin,
interval_operating_cost: old_state.params.interval_operating_cost,
},
config_score_params: ConfigScoreParams {
version_weights: msg.version_score_weights,
version_score_formula_params: msg.version_score_params,
},
},
};
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &new_state)?;
// initialise the version chain
NymNodeVersionHistory::new().try_insert_new(
deps.storage,
&env,
&msg.current_nym_node_semver,
)?;
Ok(())
}
}
pub(crate) use config_score_params::add_config_score_params;
+17 -1
View File
@@ -18,7 +18,7 @@ pub mod test_helpers {
};
use crate::interval::{pending_events, storage as interval_storage};
use crate::mixnet_contract_settings::storage::{
self as mixnet_params_storage, minimum_node_pledge,
self as mixnet_params_storage, minimum_node_pledge, ADMIN,
};
use crate::mixnet_contract_settings::storage::{rewarding_denom, rewarding_validator_address};
use crate::mixnodes::helpers::get_mixnode_details_by_id;
@@ -318,6 +318,10 @@ pub mod test_helpers {
compare_decimals(mix_info.delegates, subtotal, Some(epsilon))
}
pub fn admin(&self) -> Addr {
ADMIN.get(self.deps()).unwrap().unwrap()
}
pub fn random_address(&mut self) -> String {
format!("n1foomp{}", self.rng.next_u64())
}
@@ -330,6 +334,14 @@ pub mod test_helpers {
self.deps.as_mut()
}
pub fn storage(&self) -> &dyn Storage {
self.deps().storage
}
pub fn storage_mut(&mut self) -> &mut dyn Storage {
self.deps_mut().storage
}
pub fn env(&self) -> Env {
self.env.clone()
}
@@ -470,6 +482,10 @@ pub mod test_helpers {
.unwrap()
}
pub fn admin_sender(&self) -> MessageInfo {
mock_info(self.admin().as_ref(), &[])
}
pub fn owner(&self) -> MessageInfo {
self.owner.clone()
}
@@ -10,7 +10,7 @@ import Stack from '@mui/material/Stack';
import Paper from '@mui/material/Paper';
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
const defaultUrl = 'https://nymtech.net/favicon.svg';
const defaultUrl = 'https://nym.com/favicon.svg';
const args = { mode: 'unsafe-ignore-cors' };
const mixFetchOptions: SetupMixFetchOps = {
@@ -6,10 +6,14 @@ import { useTheme } from "@mui/material/styles";
import Image from "next/image";
import Link from "next/link";
import networkDocs from "../public/images/landing/network-docs.png";
import developerDocs from "../public/images/landing/developer-docs.png";
import sdkDocs from "../public/images/landing/sdk-docs.png";
import operatorGuide from "../public/images/landing/operator-guide.png";
// import networkDocs from "../public/images/landing/network-docs.png";
// import developerDocs from "../public/images/landing/developer-docs.png";
// import sdkDocs from "../public/images/landing/sdk-docs.png";
// import operatorGuide from "../public/images/landing/operator-guide.png";
import networkDocs from "../public/images/landing/Vector1.png";
import developerDocs from "../public/images/landing/Vector2.png";
import sdkDocs from "../public/images/landing/Vector3.png";
import operatorGuide from "../public/images/landing/Vector4.png";
export const LandingPage = () => {
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.up("md"));
@@ -0,0 +1,9 @@
import { Callout } from 'nextra/components';
### Terms & Conditions
<Callout type="info" emoji="️">
From `nym-node` version `1.1.3` onward is required to accept [**Operators Terms & Conditions**](https://nymtech.net/terms-and-conditions/operators/v1.0.0) in order to be part of the active set. Make sure to read them before you add the flag.
</Callout>
There has been a long ongoing discussion whether and how to apply Terms and Conditions for Nym network operators, with an aim to stay aligned with the philosophy of Free Software and provide legal defense for both node operators and Nym developers. To understand better the reasoning behind this decision, you can listen to the first [Nym Operator Town Hall](https://www.youtube.com/live/7hwb8bAZIuc?si=3mQ2ed7AyUA1SsCp&t=915) introducing the T&Cs or to [Operator AMA with CEO Harry Halpin](https://www.youtube.com/watch?v=yIN-zYQw0I0) from June 4th, 2024, explaining pros and cons of T&Cs implementation.
@@ -1 +1 @@
Wednesday, January 8th 2025, 15:01:28 UTC
Thursday, January 16th 2025, 09:57:52 UTC
+5 -7
View File
@@ -100,7 +100,7 @@ const config = {
},
{
source: "/docs/nodes/overview.html ",
destination: "/docs/network/architecture/mixnet/nodes",
destination: "/docs/network/architecture/mixnet#nym-nodes",
permanent: true,
basePath: false,
},
@@ -132,21 +132,19 @@ const config = {
},
{
source: "/docs/nyx/smart-contracts.html",
destination: "/docs/network/architecture/nyx/smart-contracts",
destination: "/docs/network/architecture/nyx#smart-contracts",
permanent: true,
basePath: false,
},
{
source: "/docs/nyx/mixnet-contract.html",
destination:
"/docs/network/architecture/nyx/smart-contracts/mixnet-contract",
destination: "/docs/network/architecture/nyx#mixnet-contract",
permanent: true,
basePath: false,
},
{
source: "/docs/nyx/vesting-contract.html",
destination:
"/docs/network/architecture/nyx/smart-contracts/vesting-contract",
destination: "/docs/network/architecture/nyx#vesting-contract",
permanent: true,
basePath: false,
},
@@ -631,7 +629,7 @@ const config = {
},
{
source: "/docs/network/architecture/nyx/smart-contracts/ecash",
destination: "/docs/network/architecture/nyx/smart-contracts/zknym",
destination: "/docs/network/architecture/nyx#zk-nym-contract",
permanent: true,
basePath: false,
},
@@ -9,7 +9,7 @@ At present, there are three Nym clients. These are built as standalone binaries
- the SOCKS5 client - most easily accessed via the [Rust SDK](./rust).
- the wasm (webassembly) client - most easily via the [Typescript SDK](./typescript).
> For information about the role that clients play within the Nym system and their role when communicating with the Mixnet, see the [Client network docs](../network/architecture/mixnet/clients).
> For information about the role that clients play within the Nym system and their role when communicating with the Mixnet, see the [Client network docs](../network/architecture/mixnet#nym-clients).
### The websocket client
This is a compiled program that can run on Linux, Mac OS X, and Windows machines. It can be run as a persistent process on a desktop or server machine. You can connect to it with **any language that supports websockets**.
@@ -45,7 +45,7 @@ make
```
## Mnemonic Generation
Create an account at [nymvpn.com](https://nymvpn.com) to obtain your mnemonic.
Head to the mnemonic generator at [https://nym.com/account/create](https://nym.com/account/create) and obtain a mnemonic.
## Start the daemon
```sh
@@ -3,6 +3,7 @@
"development-status": "Development Status",
"mixnet": "Mixnet Module",
"tcpproxy": "TcpProxy Module",
"client-pool": "Client Pool",
"ffi": "FFI",
"tutorials": "Tutorials (Coming Soon)"
}
@@ -0,0 +1,7 @@
# Client Pool
We have a configurable-size Client Pool for processes that require multiple clients in quick succession (this is used by default by the [`TcpProxyClient`](./tcpproxy) for instance)
This will be useful for developers looking to build connection logic, or just are using raw SDK clients in a sitatuation where there are multiple connections with a lot of churn.
> You can find this code [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs)
@@ -0,0 +1,4 @@
{
"architecture": "Architecture",
"example": "Example"
}
@@ -0,0 +1,19 @@
# Client Pool Architecture
## Motivations
In situations where multiple connections are expected, and the number of connections can vary greatly, the Client Pool reduces time spent waiting for the creation of a Mixnet Client blocking your code sending traffic through the Mixnet. Instead, a configurable number of Clients can be generated and run in the background which can be very quickly grabbed, used, and disconnected.
The Pool can be simply run as a background process for the runtime of your program.
## Clients & Lifetimes
The Client Pool creates **ephemeral Mixnet Clients** which are used and then disconnected. Using the [`TcpProxy`](../tcpproxy) as an example, Clients are used for the lifetime of a single incoming TCP connection; after the TCP connection is closed, the Mixnet client is disconnected.
Clients are popped from the pool when in use, and another Client is created to take its place. If connections are coming in faster than Clients are replenished, you can instead generate an ephemeral Client on the fly, or wait; this is up to the developer to decide. You can see an example of this logic in the example on the next page.
## Runtime Loop
Aside from a few helper / getter functions and a graceful `disconnect_pool()`, the Client Pool is mostly made up of a very simple loop around some conditional logic making up `start()`:
- if the number of Clients in the pool is `< client_pool_reserve_number` (set on `new()`) then create more,
- if the number of Clients in the pool `== client_pool_reserve_number` (set on `new()`) then `sleep`,
- if `client_pool_reserve_number == 0` just `sleep`.
`disconnect_pool()` will cause this loop to `break` via cancellation token.
@@ -0,0 +1,100 @@
# Client Pool Example
> You can find this code [here](https://github.com/nymtech/nym/blob/develop/sdk/rust/nym-sdk/examples/client_pool.rs)
```rust
use anyhow::Result;
use nym_network_defaults::setup_env;
use nym_sdk::client_pool::ClientPool;
use nym_sdk::mixnet::{MixnetClientBuilder, NymNetworkDetails};
use tokio::signal::ctrl_c;
// This client pool is used internally by the TcpProxyClient but can also be used by the Mixnet module, in case you're quickly swapping clients in and out but won't want to use the TcpProxy module.
//
// Run with: cargo run --example client_pool -- ../../../envs/<NETWORK>.env
#[tokio::main]
async fn main() -> Result<()> {
nym_bin_common::logging::setup_logging();
setup_env(std::env::args().nth(1));
let conn_pool = ClientPool::new(2); // Start the Client Pool with 2 Clients always being kept in reserve
let client_maker = conn_pool.clone();
tokio::spawn(async move {
client_maker.start().await?;
Ok::<(), anyhow::Error>(())
});
println!("\n\nWaiting a few seconds to fill pool\n\n");
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
let pool_clone_one = conn_pool.clone();
let pool_clone_two = conn_pool.clone();
tokio::spawn(async move {
let client_one = match pool_clone_one.get_mixnet_client().await {
Some(client) => {
println!("Grabbed client {} from pool", client.nym_address());
client
}
None => {
println!("Not enough clients in pool, creating ephemeral client");
let net = NymNetworkDetails::new_from_env();
let client = MixnetClientBuilder::new_ephemeral()
.network_details(net)
.build()?
.connect_to_mixnet()
.await?;
println!(
"Using {} for the moment, created outside of the connection pool",
client.nym_address()
);
client
}
};
let our_address = client_one.nym_address();
println!("\n\nClient 1: {our_address}\n\n");
client_one.disconnect().await;
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; // Emulate doing something
return Ok::<(), anyhow::Error>(());
});
tokio::spawn(async move {
let client_two = match pool_clone_two.get_mixnet_client().await {
Some(client) => {
println!("Grabbed client {} from pool", client.nym_address());
client
}
None => {
println!("Not enough clients in pool, creating ephemeral client");
let net = NymNetworkDetails::new_from_env();
let client = MixnetClientBuilder::new_ephemeral()
.network_details(net)
.build()?
.connect_to_mixnet()
.await?;
println!(
"Using {} for the moment, created outside of the connection pool",
client.nym_address()
);
client
}
};
let our_address = *client_two.nym_address();
println!("\n\nClient 2: {our_address}\n\n");
client_two.disconnect().await;
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; // Emulate doing something
return Ok::<(), anyhow::Error>(());
});
wait_for_ctrl_c(conn_pool).await?;
Ok(())
}
async fn wait_for_ctrl_c(pool: ClientPool) -> Result<()> {
println!("\n\nPress CTRL_C to disconnect pool\n\n");
ctrl_c().await?;
println!("CTRL_C received. Killing client pool");
pool.disconnect_pool().await;
Ok(())
}
```
@@ -7,9 +7,12 @@ In the future the SDK will be made up of several modules, each of which will all
|-----------|---------------------------------------------------------------------------------------|----------|
| Mixnet | Create / load clients & keypairs, subscribe to Mixnet events, send & receive messages | ✔️ |
| TcpProxy | Utilise the TcpProxyClient and TcpProxyServer abstractions for streaming | ✔️ |
| ClientPool| Create a pool of quickly useable Mixnet clients | ✔️ |
| Ecash | Create & verify Ecash credentials | ❌ |
| Validator | Sign & broadcast Nyx blockchain transactions, query the blockchain | ❌ |
The `Mixnet` module currently exposes the logic of two clients: the [websocket client](../clients/websocket), and the [socks client](../clients/socks5).
The `TcpProxy` module exposes functionality to set up client/server instances that expose a localhost TcpSocket to read/write to.
The `ClientPool` is a configurable pool of ephemeral clients which can be created as a background process and quickly grabbed.
@@ -22,7 +22,7 @@ The main functionality of exposed functions will be imported from `sdk/ffi/share
Furthermore, the `shared/` code makes sure that client access is thread-safe, and that client actions happen in blocking threads on the Rust side of the FFI boundary.
### Mixnet Module
## Mixnet Module
This is the basic mixnet component of the SDK, exposing client functionality with which people can build custom interfaces with the Mixnet. These functions are exposed to both Go and C/C++ via the `sdk/ffi/shared/` crate.
| `shared/lib.rs` function | Rust Function |
@@ -36,13 +36,13 @@ This is the basic mixnet component of the SDK, exposing client functionality wit
> We have also implemented `listen_for_incoming_internal()` which is a wrapper around the Mixnet client's `wait_for_messages()`. This is a helper method for listening out for and handling incoming messages.
#### Currently Unsupported Functionality
### Currently Unsupported Functionality
At the time of writing the following functionality is not exposed to the shared FFI library:
- `split_sender()`: the ability to [split a client into sender and receiver](./mixnet/examples/split-send) for concurrent send/receive.
- The use of [custom network topologies](./mixnet/examples/custom-topology).
- `Socks5::new()`: creation and use of the [socks5/4a/4 proxy client](./mixnet/examples/socks).
### TcpProxy Module
## TcpProxy Module
A connection abstraction which exposes a local TCP socket which developers are able to interact with basically as expected, being able to read/write to/from a bytestream, without really having to take into account the workings of the Mixnet/Sphinx/the [message-based](../concepts/messages) format of the underlying client.
<Callout type="info" emoji="️">
@@ -58,3 +58,6 @@ A connection abstraction which exposes a local TCP socket which developers are a
| `proxy_server_new_internal(upstream_address: &str, config_dir: &str, env: Option<String>)` | `NymProxyServer::new(upstream_address, config_dir, env)` |
| `proxy_server_run_internal()` | `NymProxyServer.run_with_shutdown()` |
| `proxy_server_address_internal()` | `NymProxyServer.nym_address()` |
## Client Pool
There are currently no FFI bindings for the Client Pool. This will be coming in the future. The bindings for the TcpProxy have been updated to be able to use the Client Pool under the hood, but the standalone Pool is not yet exposed to FFI.
@@ -112,3 +112,6 @@ Whether the `data` of a SURB request being empty is a feature or a bug is to be
You can find a few helper functions [here](./message-helpers.md) to help deal with this issue in the meantime.
> If you can think of a more succinct or different way of handling this do reach out - we're happy to hear other opinions
## Lots of `duplicate fragment received` messages
You might see a lot of `WARN` level logs about duplicate fragments in your logs, depending on the log level you're using. This occurs when a packet is retransmitted somewhere in the Mixnet, but then the original makes it to the destination client as well. This is not something to do with your client logic, but instead the state of the Mixnet.
@@ -1,4 +1,5 @@
{
"architecture": "Architecture",
"examples": "Examples"
"examples": "Examples",
"troubleshooting": "Troubleshooting"
}
@@ -13,7 +13,7 @@ The motivation behind the creation of the `TcpProxy` module is to allow develope
## Clients
Each of the sub-modules exposed by the `TcpProxy` deal with Nym clients in a different way.
- the `NymProxyClient` creates an ephemeral client per new TCP connection, which is closed according to the configurable timeout: we map one ephemeral client per TCP connection. This is to deal with multiple simultaneous streams. In the future, this will be superceded by a connection pool in order to speed up new connections.
- the `NymProxyClient` relies on the [`Client Pool`](../client-pool) to create clients and keep a certain number of them in reserve. If the amount of incoming TCP connections rises quicker than the Client Pool can create clients, or you have the pool size set to `0`, the `TcpProxyClient` creates an ephemeral client per new TCP connection, which is closed according to the configurable timeout: we map one ephemeral client per TCP connection. This is to deal with multiple simultaneous streams.
- the `NymProxyServer` has a single Nym client with a persistent identity.
## Framing
@@ -18,6 +18,8 @@ use tokio::net::TcpStream;
use tokio::signal;
use tokio_stream::StreamExt;
use tokio_util::codec;
use tokio_util::sync::CancellationToken;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
#[derive(Serialize, Deserialize, Debug)]
struct ExampleMessage {
@@ -26,6 +28,8 @@ struct ExampleMessage {
tcp_conn: i8,
}
// This example just starts off a bunch of Tcp connections on a loop to a remote endpoint: in this case the TcpListener behind the NymProxyServer instance on the echo server found in `nym/tools/echo-server/`. It pipes a few messages to it, logs the replies, and keeps track of the number of replies received per connection.
//
// To run:
// - run the echo server with `cargo run`
// - run this example with `cargo run --example tcp_proxy_multistream -- <ECHO_SERVER_NYM_ADDRESS> <ENV_FILE_PATH> <CLIENT_PORT>` e.g.
@@ -40,8 +44,13 @@ async fn main() -> anyhow::Result<()> {
// Nym client logging is very informative but quite verbose.
// The Message Decay related logging gives you an ideas of the internals of the proxy message ordering: you need to switch
// to DEBUG to see the contents of the msg buffer, sphinx packet chunking, etc.
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
tracing_subscriber::registry()
.with(fmt::layer())
.with(
EnvFilter::new("info")
.add_directive("nym_sdk::client_pool=info".parse().unwrap())
.add_directive("nym_sdk::tcp_proxy_client=debug".parse().unwrap()),
)
.init();
let env_path = env::args().nth(2).expect("Env file not specified");
@@ -49,23 +58,42 @@ async fn main() -> anyhow::Result<()> {
let listen_port = env::args().nth(3).expect("Port not specified");
// Within the TcpProxyClient, individual client shutdown is triggered by the timeout.
// Within the TcpProxyClient, individual client shutdown is triggered by the timeout. The final argument is how many clients to keep in reserve in the client pool when running the TcpProxy.
let proxy_client =
tcp_proxy::NymProxyClient::new(server, "127.0.0.1", &listen_port, 45, Some(env)).await?;
tcp_proxy::NymProxyClient::new(server, "127.0.0.1", &listen_port, 45, Some(env), 2).await?;
// For our disconnect() logic below
let proxy_clone = proxy_client.clone();
tokio::spawn(async move {
proxy_client.run().await?;
Ok::<(), anyhow::Error>(())
});
let example_cancel_token = CancellationToken::new();
let client_cancel_token = example_cancel_token.clone();
let watcher_cancel_token = example_cancel_token.clone();
// Cancel listener thread
tokio::spawn(async move {
signal::ctrl_c().await?;
println!(":: CTRL_C received, shutting down + cleanup up proxy server config files");
watcher_cancel_token.cancel();
proxy_clone.disconnect().await;
Ok::<(), anyhow::Error>(())
});
println!("waiting for everything to be set up..");
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
println!("done. sending bytes");
// In the info traces you will see the different session IDs being set up, one for each TcpStream.
for i in 0..4 {
for i in 0..8 {
let client_cancel_inner_token = client_cancel_token.clone();
if client_cancel_token.is_cancelled() {
break;
}
let conn_id = i;
println!("Starting TCP connection {}", conn_id);
let local_tcp_addr = format!("127.0.0.1:{}", listen_port.clone());
tokio::spawn(async move {
// Now the client and server proxies are running we can create and pipe traffic to/from
@@ -81,7 +109,10 @@ async fn main() -> anyhow::Result<()> {
// Lets just send a bunch of messages to the server with variable delays between them, with a message and tcp connection ids to keep track of ordering on the server side (for illustrative purposes **only**; keeping track of anonymous replies is handled by the proxy under the hood with Single Use Reply Blocks (SURBs); for this illustration we want some kind of app-level message id, but irl most of the time you'll probably be parsing on e.g. the incoming response type instead)
tokio::spawn(async move {
for i in 0..4 {
for i in 0..8 {
if client_cancel_inner_token.is_cancelled() {
break;
}
let mut rng = SmallRng::from_entropy();
let delay: f64 = rng.gen_range(2.5..5.0);
tokio::time::sleep(tokio::time::Duration::from_secs_f64(delay)).await;
@@ -96,12 +127,7 @@ async fn main() -> anyhow::Result<()> {
.write_all(&serialised)
.await
.expect("couldn't write to stream");
println!(
">> client sent {}: {} bytes on conn {}",
&i,
msg.message_bytes.len(),
&conn_id
);
println!(">> client sent msg {} on conn {}", &i, &conn_id);
}
Ok::<(), anyhow::Error>(())
});
@@ -113,17 +139,8 @@ async fn main() -> anyhow::Result<()> {
while let Some(Ok(bytes)) = framed_read.next().await {
match bincode::deserialize::<ExampleMessage>(&bytes) {
Ok(msg) => {
println!(
"<< client received {}: {} bytes on conn {}",
msg.message_id,
msg.message_bytes.len(),
msg.tcp_conn
);
reply_counter += 1;
println!(
"tcp connection {} replies received {}/4",
msg.tcp_conn, reply_counter
);
println!("<< conn {} received {}/8", msg.tcp_conn, reply_counter);
}
Err(e) => {
println!("<< client received something that wasn't an example message of {} bytes. error: {}", bytes.len(), e);
@@ -138,15 +155,12 @@ async fn main() -> anyhow::Result<()> {
tokio::time::sleep(tokio::time::Duration::from_secs_f64(delay)).await;
}
// Once timeout is passed, you can either wait for graceful shutdown or just hard stop it.
signal::ctrl_c().await?;
println!("CTRL+C received, shutting down");
Ok(())
}
// emulate a series of small messages followed by a closing larger one
fn gen_bytes_fixed(i: usize) -> Vec<u8> {
let amounts = [10, 15, 50, 1000];
let amounts = [10, 15, 50, 1000, 10, 15, 500, 2000];
let len = amounts[i];
let mut rng = rand::thread_rng();
(0..len).map(|_| rng.gen::<u8>()).collect()
@@ -21,6 +21,8 @@ use tokio::net::{TcpListener, TcpStream};
use tokio::signal;
use tokio_stream::StreamExt;
use tokio_util::codec;
use tokio_util::sync::CancellationToken;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
#[derive(Serialize, Deserialize, Debug)]
struct ExampleMessage {
@@ -28,7 +30,14 @@ struct ExampleMessage {
message_bytes: Vec<u8>,
}
// This is a basic example which opens a single TCP connection and writes a bunch of messages between a client and an echo
// server, so only uses a single session under the hood and doesn't really show off the message ordering capabilities; this is mainly
// just a quick introductory illustration on how:
// - the mixnet does message ordering
// - the NymProxyClient and NymProxyServer can be hooked into and used to communicate between two otherwise pretty vanilla TcpStreams
//
// For a more irl example checkout tcp_proxy_multistream.rs
//
// Run this with:
// `cargo run --example tcp_proxy_single_connection <SERVER_LISTEN_PORT> <ENV_FILE_PATH> <CLIENT_LISTEN_PATH>` e.g.
// `cargo run --example tcp_proxy_single_connection 8081 ../../../envs/canary.env 8080 `
@@ -39,8 +48,9 @@ async fn main() -> anyhow::Result<()> {
// Comment this out to just see println! statements from this example, as Nym client logging is very informative but quite verbose.
// The Message Decay related logging gives you an ideas of the internals of the proxy message ordering. To see the contents of the msg buffer, sphinx packet chunking, etc change the tracing::Level to DEBUG.
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::new("nym_sdk::tcp_proxy=info"))
.init();
let server_port = env::args()
@@ -63,10 +73,14 @@ async fn main() -> anyhow::Result<()> {
// We'll run the instance with a long timeout since we're sending everything down the same Tcp connection, so should be using a single session.
// Within the TcpProxyClient, individual client shutdown is triggered by the timeout.
// The final argument is how many clients to keep in reserve in the client pool when running the TcpProxy.
let proxy_client =
tcp_proxy::NymProxyClient::new(*proxy_nym_addr, "127.0.0.1", &client_port, 60, Some(env))
tcp_proxy::NymProxyClient::new(*proxy_nym_addr, "127.0.0.1", &client_port, 5, Some(env), 1)
.await?;
// For our disconnect() logic below
let proxy_clone = proxy_client.clone();
tokio::spawn(async move {
proxy_server.run_with_shutdown().await?;
Ok::<(), anyhow::Error>(())
@@ -77,10 +91,28 @@ async fn main() -> anyhow::Result<()> {
Ok::<(), anyhow::Error>(())
});
let example_cancel_token = CancellationToken::new();
let server_cancel_token = example_cancel_token.clone();
let client_cancel_token = example_cancel_token.clone();
let watcher_cancel_token = example_cancel_token.clone();
// Cancel listener thread
tokio::spawn(async move {
signal::ctrl_c().await?;
println!(":: CTRL_C received, shutting down + cleanup up proxy server config files");
fs::remove_dir_all(conf_path)?;
watcher_cancel_token.cancel();
proxy_clone.disconnect().await;
Ok::<(), anyhow::Error>(())
});
// 'Server side' thread: echo back incoming as response to the messages sent in the 'client side' thread below
tokio::spawn(async move {
let listener = TcpListener::bind(upstream_tcp_addr).await?;
loop {
if server_cancel_token.is_cancelled() {
break;
}
let (socket, _) = listener.accept().await.unwrap();
let (read, mut write) = socket.into_split();
let codec = codec::BytesCodec::new();
@@ -118,9 +150,9 @@ async fn main() -> anyhow::Result<()> {
Ok::<(), anyhow::Error>(())
});
// Just wait for Nym clients to connect, TCP clients to bind, etc.
// Just wait for Nym clients to connect, TCP clients to bind, etc. If there isn't a client in the pool (or you started it with 0) already then the TcpProxyClient just spins up an ephemeral client itself.
println!("waiting for everything to be set up..");
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
println!("done. sending bytes");
// Now the client and server proxies are running we can create and pipe traffic to/from
@@ -140,6 +172,9 @@ async fn main() -> anyhow::Result<()> {
tokio::spawn(async move {
let mut rng = SmallRng::from_entropy();
for i in 0..10 {
if client_cancel_token.is_cancelled() {
break;
}
let random_bytes = gen_bytes_fixed(i as usize);
let msg = ExampleMessage {
message_id: i,
@@ -179,15 +214,12 @@ async fn main() -> anyhow::Result<()> {
}
}
// Once timeout is passed, you can either wait for graceful shutdown or just hard stop it.
signal::ctrl_c().await?;
println!(":: CTRL+C received, shutting down + cleanup up proxy server config files");
fs::remove_dir_all(conf_path)?;
Ok(())
}
fn gen_bytes_fixed(i: usize) -> Vec<u8> {
let amounts = vec![1, 10, 50, 100, 150, 200, 350, 500, 750, 1000];
// let amounts = vec![1, 10, 50, 100, 150, 200, 350, 500, 750, 1000];
let amounts = [158, 1088, 505, 1001, 150, 200, 3500, 500, 750, 100];
let len = amounts[i];
let mut rng = rand::thread_rng();
(0..len).map(|_| rng.gen::<u8>()).collect()
@@ -0,0 +1,4 @@
# Troubleshooting
## Lots of `duplicate fragment received` messages
You might see a lot of `WARN` level logs about duplicate fragments in your logs, depending on the log level you're using. This occurs when a packet is retransmitted somewhere in the Mixnet, but then the original makes it to the destination client as well. This is not something to do with your client logic, but instead the state of the Mixnet.
@@ -39,6 +39,8 @@ Options:
Listen port [default: 8080]
-e, --env-path <ENV_PATH>
Optional env filepath - if none is supplied then the proxy defaults to using mainnet else just use a path to one of the supplied files in envs/ e.g. ./envs/sandbox.env
--client-pool-reserve <CLIENT_POOL_RESERVE>
How many clients to have running in reserve for quick access by incoming connections [default: 2]
-h, --help
Print help
```
@@ -43,7 +43,7 @@ npm install @nymproject/sdk-full-fat
The Nyx blockchain is a general-purpose CosmWasm-enabled smart contract platform, and the home of the smart contracts which keep track of the mixnet, amongst others.
Further information about the chain can be found on the [Nyx blockchain explorer](https://nym.explorers.guru/).
Using the [Nym mixnet smart contract clients](../../network/architecture/nyx/smart-contracts), you will be able to query contract states or execute methods when providing a signing key.
Using the [Nym mixnet smart contract clients](../../network/architecture/nyx#smart-contracts), you will be able to query contract states or execute methods when providing a signing key.
*You can learn about our different methods to interact with the chain [here](../chain)*.
@@ -1,6 +1,8 @@
# Network Components
Core components:
* A **Mixnet**, which encrypts and mixes Sphinx packet traffic so that it cannot be determined who is communicating with whom. Our mixnet is based on a modified version of the [**Loopix** design](concepts/loopix). This is made up of [Nym nodes](architecture/mixnet/nodes) runnning on servers around the world.
* A **Mixnet**, which mixes Sphinx packet traffic so that it cannot be determined who is communicating with whom. Our mixnet is based on a modified version of the [**Loopix** design](concepts/loopix). This is made up of [Nym nodes](architecture/mixnet/nodes) runnning on servers around the world maintained by a decentralised group of Operators.
* Various [**Nym clients**](architecture/mixnet/clients) which manage sending and receiving Sphinx packets, encrypting/decrypting traffic, and providing [cover traffic](./concepts/cover-traffic) to hide 'real' traffic timing.
* A CosmWasm-enabled blockchain called [**Nyx**](architecture/nyx), the home of the various smart contracts used by the mixnet.
* A CosmWasm-enabled blockchain called [**Nyx**](architecture/nyx), the home of the various smart contracts used by the mixnet. A subset of Nyx Validators run [NymAPI](./architecture/nyx#nymapi) instances, taking part in also producing and verifying [zk-nym credentials](cryptography/zk-nym).
![arch_overview](/images/network/arch/overall-arch.png)
@@ -1,16 +1,51 @@
import { Callout } from 'nextra/components'
# Mixnet Components
The mixnet is made up of nodes running in several different roles:
* **Mix Nodes** provide network security for network content _and_ metadata by performing packet-mixing on traffic travelling through the network.
* **Gateways** act as message storage for clients which may go offline and come back online again, and (once zk-nyms are enabled) check for anonymous proof of access credentials. They represent the first and last hop Mixnet packets travel through when travelling between clients.
* **Services** are applications that communicate with nym clients, listening and sending traffic to the mixnet. This is an umbrella term for a variety of different pieces of code, such as the Network Requester.
> **Clients** are used to connect to and send messages through the Mixnet to other clients, utilising Gateways for ingress and exit. These are however on the 'user' side and not a Mixnet component per se.
## Nym Nodes
## Smoosh Status
<Callout type="info" emoji="️">
If you want to run a node, the setup and maintenance guides can be found in the [Operator Docs](../../../operators/introduction).
</Callout>
Although a large proportion of the Nym mixnet's functionality is implemented client-side, several key anonymity features rely on the decentralised node network that make up the Mixnet that run in different modes:
* Nym Nodes running in **Mix Node** mode provide network security for network content _and_ metadata by performing packet-mixing on traffic travelling through the network: accepting incoming Sphinx packets from other Nym nodes and, using a variable delay, 'mixing' them with other packets (not forwarding on received packets according to FIFO but instead relying on a randomised delay function).
* Nym Nodes running in **Entry Gateway** mode act as message storage for clients which may go offline and come back online again, and (once zk-nyms are enabled) check for anonymous proof of access credentials. They represent the first hop Mixnet packets travel through when travelling between clients.
* Nym Nodes running in **Exit Gateway** mode act as message storage for clients which may go offline and come back online again, and communicate with the wider internet on behalf of Nym Clients. They represent the last hop Mixnet packets travel through when travelling between clients and/or external services. These can be thought of somewhat analogously to Tor Exit Nodes.
* **Services** are applications that communicate with Nym Clients, listening and sending traffic to the Mixnet.
See the [traffic flow](../traffic) page for detailed information on how traffic moves through the Mixnet as well as the [Loopix](https://arxiv.org/pdf/1703.00536) design paper for overview of the stratified nature of the Mixnet.
## Node Smoosh Status
The various Mixnet components were originally completely separate binaries. They are in the process of being 'smooshed' together into a single `nym-node` binary which runs in different modes for ease of use, as well as to allow for a more developed and responsive Mixnet design, where the role of a node in a given time period is decided and changed automatically based on network conditions (more on this in the future).
Completed:
* The `nym-network-requester` is now part of a `nym-node` running in as an Exit Gateway.
* All nodes are now a `nym-node`. A node's role is defined manually at runtime by the operator.
* The `nym-network-requester` is now part of a `nym-node` running in Exit Gateway mode.
Upcoming:
* Whether a `nym-node` is running as a Gateway or Mix Node will be set based on network conditions, and change epoch to epoch. Currently the role is set manually by the operator and does not change automatically over time. A node will be able to be running in the role of a Mix Node, an Entry Gateway, or an Exit Gateway.
## Nym Clients
<Callout type="info" emoji="️">
You can read about setting up and using various clients in the [Developer Docs](../../developers/clients).
</Callout>
A large proportion of the Nym Mixnet's functionality is implemented client-side.
Clients perform the following actions on behalf of users:
* Determine network topology - what nodes exist, their public encryption keys and IP, etc.
* Register with a Gateway
* Authenticate with a Gateway
* Receive and decrypt messages from the Gateway
* Create layer-encrypted [Sphinx packets](../cryptography/sphinx)
* Send Sphinx packets with real messages
* Send Sphinx packet [cover traffic](../concepts/cover-traffic) when no real messages are being sent
* Retransmit [un-acknowledged packet sends](../traffic/acks)
> At the moment due to the fact that Nym clients are [message-based](../../developers/concepts/messages), using the Mixnet requires another client on the 'other side' of the mixet to send packets to, unless you're using the `nymvpn` client (part of the NymVPN app) or the `socks5` client, which operates as a SOCKS4,4a, or 5 proxy and is able to utilise the client embedded within the `nym-node`'s Exit Gateway functionality (prev. this functionality was a standalone service, the Network Requester). In the future we wish to remove this point of friction and have all Nym clients construct IP packets instead, easing the integration burden and abstracting away the message-based nature of client communication.
@@ -1,4 +0,0 @@
{
"nodes": "Nodes",
"clients": "Clients"
}
@@ -1,22 +0,0 @@
import { Callout } from 'nextra/components'
# Nym Clients
<Callout type="info" emoji="️">
You can read about setting up and using various clients in the [developer docs](../../../developers/clients).
</Callout>
A large proportion of the Nym mixnet's functionality is implemented client-side.
Clients perform the following actions on behalf of users:
* Determine network topology - what nodes exist, their public encryption keys, etc.
* Register with a gateway
* Authenticate with a gateway
* Receive and decrypt messages from the gateway
* Create layer-encrypted [Sphinx packets](../../cryptography/sphinx)
* Send Sphinx packets with real messages
* Send Sphinx packet [cover traffic](../../concepts/cover-traffic) when no real messages are being sent
* Retransmit [un-acknowledged packet sends](../../traffic/acks)
> At the moment due to the fact that Nym clients are [message-based](../../../developers/concepts/messages), using the Mixnet requires another client on the 'other side' of the mixet to send packets to, unless you're using the `nymvpn` client (part of the NymVPN app) or the `socks5` client, which operates as a SOCKS4,4a, or 5 proxy and is able to utilise the client embedded within the `nym-node`'s Exit Gateway functionality (prev. this functionality was a standalone service, the Network Requester). In the future we wish to remove this point of friction and have all Nym clients construct IP packets instead, easing the integration burden and abstracting away the message-based nature of client communication.

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