Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2437ebe3e | |||
| e72103bbe6 | |||
| f7ebddf84b | |||
| c8e825bd1e | |||
| 8c6f84b3fe | |||
| 27dc9c8024 | |||
| 42d559bc69 | |||
| 41b9b0e5bd | |||
| 6c781a0064 | |||
| 080ec80722 | |||
| 9c17239831 | |||
| f6c19ec02b | |||
| 94ff8a79ee | |||
| 155c4d37ef | |||
| 7060fa6dad | |||
| 9be9c04f52 | |||
| 2a6fe6624d | |||
| 4f7124e661 | |||
| f52f07f6ec | |||
| b709d3ba0b | |||
| 128f69a5d6 | |||
| 40dd7dc95e | |||
| f13ce6bf2d | |||
| 856dbfe1ac | |||
| b2f6836756 | |||
| 87e429d78a | |||
| 4178809555 | |||
| e6f6e1342f | |||
| 65175fee09 | |||
| 69b2448500 | |||
| 8ba5322997 | |||
| 2cb3817b2c | |||
| 80b395cd8e | |||
| 8f5457e698 | |||
| 9de5d7213a | |||
| 94eb362a71 | |||
| 0f615f48f2 | |||
| d511611641 | |||
| 26f97d3c34 | |||
| 17d3ff2d77 | |||
| dd3dcfa7fe | |||
| 86ea2d23cb | |||
| 42a37442e8 | |||
| 6b24f081e1 | |||
| 6e5d0dac1b | |||
| 63a8f96ea5 | |||
| 5f2740bf66 | |||
| ecb15034d3 | |||
| bd49c222a3 | |||
| 50b044a100 | |||
| ba645694d4 | |||
| be44811a65 | |||
| 62e1d32e4f | |||
| 9a4bbe1d67 | |||
| 98090d18b4 | |||
| 79f8066c13 | |||
| d0209766a3 | |||
| 844030091f | |||
| a7a421b006 | |||
| 6680fbd61a | |||
| fe2d21cf88 | |||
| eeaca9fc10 | |||
| 7255f79b9c | |||
| 589069504a | |||
| 4da7bc7442 | |||
| 35be8de9f1 | |||
| 2b14a9e6f8 | |||
| e9269da897 | |||
| 7bceeadf16 | |||
| e72ce8fa92 | |||
| 1ccdd5d660 | |||
| c6d38d3c4f | |||
| e8e2bf107f | |||
| efe4e5c1c1 | |||
| 2230609a72 | |||
| 6d80c37b21 | |||
| cb8b4c56af | |||
| 4d486abfef | |||
| b694845e4c | |||
| 5cb2800d15 | |||
| fd14394958 | |||
| 134883522d | |||
| 0d397ab5cc | |||
| 221e01e9b8 | |||
| dcc48db301 | |||
| 7528109693 | |||
| 203d682f2c | |||
| 589575eed8 | |||
| 35bf1cc717 | |||
| f5e02d5652 | |||
| 2fc641a7ff | |||
| 0ccca19cc2 | |||
| a07e567eb2 | |||
| f3400a0aa5 | |||
| bf8614a545 | |||
| b7e3687757 | |||
| b9b969b7d3 | |||
| 47303e5b3b | |||
| 6b38ffd4f3 | |||
| 169c313404 | |||
| a3e19b4563 | |||
| ccf430ea62 | |||
| cf13b79e93 | |||
| 134a0196f8 | |||
| 54aef7c242 | |||
| 6c45c9f0b0 | |||
| b5afae0916 | |||
| 988eca857f | |||
| 3c05db2874 | |||
| a8e268f84a | |||
| ac22533ecd | |||
| bdc0b875a4 | |||
| d7b67c1408 | |||
| 606e29ebb0 | |||
| 21e3c1538d | |||
| 0fc7cc657d | |||
| 23a7f01c05 | |||
| 3a21cfa1ab | |||
| 1d2e6d916c | |||
| 4c2bf3642e | |||
| 70e2e32385 | |||
| 68a192daa3 | |||
| d6aacae14e | |||
| 6f00023d09 | |||
| 982ec56874 | |||
| 5dcc1ed6dc | |||
| d62bc0a10b | |||
| d1fb926a2a | |||
| dea69acd49 | |||
| ada2d2247a | |||
| 0159d7c27a | |||
| 882003c08c | |||
| b71a491872 | |||
| 8f48ae08c4 | |||
| 31b9623407 | |||
| 6d90ffdd2c | |||
| 28997c7f97 | |||
| 9550934d1f | |||
| a6c586a33b | |||
| 7c85c1a271 | |||
| 92c8d1b73f | |||
| 554e9ca490 | |||
| 6e6675f7bf | |||
| a7f7ebfbae | |||
| 1aec8be85e | |||
| 4b474dd8ff | |||
| b163dba2d4 | |||
| a0daabab03 | |||
| b0a5b60945 | |||
| aa83501ed0 | |||
| 5f06414a12 | |||
| 656838811a | |||
| 7b8458630a | |||
| cf2ab08b4d | |||
| 2466112829 | |||
| e5306908e4 | |||
| b628a5f814 | |||
| 62045d76b3 | |||
| f8317f5a03 | |||
| c3ec970a37 | |||
| 5a573bc278 | |||
| 3d200db722 | |||
| e4139713cb |
@@ -26,6 +26,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ jobs:
|
|||||||
runs-on: arc-ubuntu-22.04
|
runs-on: arc-ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository code
|
- name: Check out repository code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
|
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
|
||||||
|
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||||
steps:
|
steps:
|
||||||
- name: Install Dependencies (Linux)
|
- name: Install Dependencies (Linux)
|
||||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
|
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
|
||||||
@@ -54,6 +55,20 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
# To avoid running out of disk space, skip generating debug symbols
|
||||||
|
- name: Set debug to false (unix)
|
||||||
|
if: contains(matrix.os, 'ubuntu') || contains(matrix.os, 'mac')
|
||||||
|
run: |
|
||||||
|
sed -i.bak 's/\[profile.dev\]/\[profile.dev\]\ndebug = false/' Cargo.toml
|
||||||
|
git diff
|
||||||
|
|
||||||
|
- name: Set debug to false (win)
|
||||||
|
if: contains(matrix.os, 'windows')
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
(Get-Content Cargo.toml) -replace '\[profile.dev\]', "`$&`ndebug = false" | Set-Content Cargo.toml
|
||||||
|
git diff
|
||||||
|
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'contracts/**'
|
- 'contracts/**'
|
||||||
- 'common/**'
|
- 'common/**'
|
||||||
|
- 'Cargo.lock'
|
||||||
|
- 'Cargo.toml'
|
||||||
- '.github/workflows/ci-contracts.yml'
|
- '.github/workflows/ci-contracts.yml'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Set debug to false
|
||||||
|
working-directory: nym-wallet
|
||||||
|
run: |
|
||||||
|
sed -i.bak '1s/^/\[profile.dev\]\ndebug = false\n\n/' Cargo.toml
|
||||||
|
git diff
|
||||||
|
|
||||||
- name: Build all binaries
|
- name: Build all binaries
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ jobs:
|
|||||||
- name: Install wasm-bindgen-cli
|
- name: Install wasm-bindgen-cli
|
||||||
run: cargo install wasm-bindgen-cli
|
run: cargo install wasm-bindgen-cli
|
||||||
|
|
||||||
|
- name: Set debug to false
|
||||||
|
run: |
|
||||||
|
sed -i.bak 's/\[profile.dev\]/\[profile.dev\]\ndebug = false/' Cargo.toml
|
||||||
|
git diff
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: make sdk-wasm-build
|
run: make sdk-wasm-build
|
||||||
|
|
||||||
|
|||||||
+206
@@ -4,6 +4,212 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2025.4-dorina] (2025-03-04)
|
||||||
|
|
||||||
|
- fixed sphinx version metrics registration ([#5546])
|
||||||
|
- Feature/chain status api ([#5539])
|
||||||
|
- Add SURBs soft threshold ([#5535])
|
||||||
|
- Simplify IPR v8 ([#5532])
|
||||||
|
- Shared instance for DNS AsyncResolver ([#5523])
|
||||||
|
- merge #5512 again after reverting due to incorrect rebase ([#5520])
|
||||||
|
- cherry-pick 17d3ff2d775f61aee381d90a304ed416c08f33fc onto dorina ([#5519])
|
||||||
|
- cherry-pick 6e5d0dac1b75413c5f09122b0d953f8ec6ef48df onto dorina ([#5518])
|
||||||
|
- chore: workspace global panic preventing lints ([#5512])
|
||||||
|
- bugfix: dont query for ecash apis unless necessary when spending ticketbooks ([#5508])
|
||||||
|
- bugfix: bound check when recovering a reply SURB ([#5502])
|
||||||
|
- chore: removed all old coconut code ([#5500])
|
||||||
|
- IPR request types v8 ([#5498])
|
||||||
|
- Support static routes for HTTP requests ([#5487])
|
||||||
|
- build(deps): bump the patch-updates group across 1 directory with 3 updates ([#5482])
|
||||||
|
- added missing import to doctest ([#5480])
|
||||||
|
- adjusted TestSetup::new_complex to ensure bonded node's existence ([#5478])
|
||||||
|
- Trigger contracts CI on main workspace Cargo changes ([#5477])
|
||||||
|
- build(deps): bump http from 1.1.0 to 1.2.0 ([#5472])
|
||||||
|
- build(deps): bump utoipa-swagger-ui from 8.0.3 to 8.1.0 ([#5471])
|
||||||
|
- build(deps): bump colored from 2.1.0 to 2.2.0 ([#5470])
|
||||||
|
- build(deps): bump celes from 2.4.0 to 2.5.0 ([#5469])
|
||||||
|
- build(deps): bump the patch-updates group with 2 updates ([#5467])
|
||||||
|
- build(deps): bump elliptic from 6.5.4 to 6.6.1 in /docker/typescript_client/upload_contract ([#5463])
|
||||||
|
- Run cargo autoinherit ([#5460])
|
||||||
|
- Fix clippy::precedence ([#5457])
|
||||||
|
- Provide Interval context with node descriptor endpoints ([#5456])
|
||||||
|
- fix: update fx average rate calcs to ignore 0 values ([#5454])
|
||||||
|
- Feature/add gbp currency ([#5453])
|
||||||
|
- Add helper to extract a list of sqlite files with journal files wal/shm ([#5452])
|
||||||
|
- Add a middleware layer to the nym api allowing for data compression ([#5451])
|
||||||
|
- Condense core API functionalities and enable gzip decompression for reqwest payloads ([#5450])
|
||||||
|
- build(deps): bump uniffi_build from 0.25.3 to 0.29.0 ([#5448])
|
||||||
|
- Upgrade tower to 0.5.2 ([#5446])
|
||||||
|
- build(deps): bump hickory-proto from 0.24.2 to 0.24.3 ([#5444])
|
||||||
|
- Seedable clients ([#5440])
|
||||||
|
- build(deps): bump the patch-updates group across 1 directory with 10 updates ([#5439])
|
||||||
|
- Remove all recv_with_delay and add shutdown condition to loops in client-core ([#5435])
|
||||||
|
- Disable the test for checking the remaining bandwidth in nym-node-status-api ([#5425])
|
||||||
|
- Dz nym node stats ([#5418])
|
||||||
|
- build(deps): bump hyper from 1.4.1 to 1.6.0 ([#5416])
|
||||||
|
- build(deps): bump publicsuffix from 2.2.3 to 2.3.0 ([#5367])
|
||||||
|
- Nymnode entrypoint docker ([#5300])
|
||||||
|
|
||||||
|
[#5546]: https://github.com/nymtech/nym/pull/5546
|
||||||
|
[#5539]: https://github.com/nymtech/nym/pull/5539
|
||||||
|
[#5535]: https://github.com/nymtech/nym/pull/5535
|
||||||
|
[#5532]: https://github.com/nymtech/nym/pull/5532
|
||||||
|
[#5523]: https://github.com/nymtech/nym/pull/5523
|
||||||
|
[#5520]: https://github.com/nymtech/nym/pull/5520
|
||||||
|
[#5519]: https://github.com/nymtech/nym/pull/5519
|
||||||
|
[#5518]: https://github.com/nymtech/nym/pull/5518
|
||||||
|
[#5512]: https://github.com/nymtech/nym/pull/5512
|
||||||
|
[#5508]: https://github.com/nymtech/nym/pull/5508
|
||||||
|
[#5502]: https://github.com/nymtech/nym/pull/5502
|
||||||
|
[#5500]: https://github.com/nymtech/nym/pull/5500
|
||||||
|
[#5498]: https://github.com/nymtech/nym/pull/5498
|
||||||
|
[#5487]: https://github.com/nymtech/nym/pull/5487
|
||||||
|
[#5482]: https://github.com/nymtech/nym/pull/5482
|
||||||
|
[#5480]: https://github.com/nymtech/nym/pull/5480
|
||||||
|
[#5478]: https://github.com/nymtech/nym/pull/5478
|
||||||
|
[#5477]: https://github.com/nymtech/nym/pull/5477
|
||||||
|
[#5472]: https://github.com/nymtech/nym/pull/5472
|
||||||
|
[#5471]: https://github.com/nymtech/nym/pull/5471
|
||||||
|
[#5470]: https://github.com/nymtech/nym/pull/5470
|
||||||
|
[#5469]: https://github.com/nymtech/nym/pull/5469
|
||||||
|
[#5467]: https://github.com/nymtech/nym/pull/5467
|
||||||
|
[#5463]: https://github.com/nymtech/nym/pull/5463
|
||||||
|
[#5460]: https://github.com/nymtech/nym/pull/5460
|
||||||
|
[#5457]: https://github.com/nymtech/nym/pull/5457
|
||||||
|
[#5456]: https://github.com/nymtech/nym/pull/5456
|
||||||
|
[#5454]: https://github.com/nymtech/nym/pull/5454
|
||||||
|
[#5453]: https://github.com/nymtech/nym/pull/5453
|
||||||
|
[#5452]: https://github.com/nymtech/nym/pull/5452
|
||||||
|
[#5451]: https://github.com/nymtech/nym/pull/5451
|
||||||
|
[#5450]: https://github.com/nymtech/nym/pull/5450
|
||||||
|
[#5448]: https://github.com/nymtech/nym/pull/5448
|
||||||
|
[#5446]: https://github.com/nymtech/nym/pull/5446
|
||||||
|
[#5444]: https://github.com/nymtech/nym/pull/5444
|
||||||
|
[#5440]: https://github.com/nymtech/nym/pull/5440
|
||||||
|
[#5439]: https://github.com/nymtech/nym/pull/5439
|
||||||
|
[#5435]: https://github.com/nymtech/nym/pull/5435
|
||||||
|
[#5425]: https://github.com/nymtech/nym/pull/5425
|
||||||
|
[#5418]: https://github.com/nymtech/nym/pull/5418
|
||||||
|
[#5416]: https://github.com/nymtech/nym/pull/5416
|
||||||
|
[#5367]: https://github.com/nymtech/nym/pull/5367
|
||||||
|
[#5300]: https://github.com/nymtech/nym/pull/5300
|
||||||
|
|
||||||
|
## [2025.3-ruta] (2025-02-10)
|
||||||
|
|
||||||
|
- Push down forget me to client configs ([#5431])
|
||||||
|
- Fix statistics shutdown ([#5426])
|
||||||
|
- Make wait_for_graceful_shutdown to be pub ([#5424])
|
||||||
|
- Upgrade to thiserror 2.0 ([#5414])
|
||||||
|
- build(deps): bump the patch-updates group across 1 directory with 9 updates ([#5406])
|
||||||
|
- Relocate a validator api function ([#5401])
|
||||||
|
- Send shutdown instead of panic when reaching max fail ([#5398])
|
||||||
|
- Change Explorer URL to new smooshed nodes ([#5396])
|
||||||
|
- reduce log severity for checking topology validity ([#5395])
|
||||||
|
- MixnetClient can send ClientRequests ([#5381])
|
||||||
|
- Fix missing path triggers for CI ([#5380])
|
||||||
|
- Uncouple storage reference for bandwidth client ([#5372])
|
||||||
|
- build(deps): bump tokio from 1.40.0 to 1.43.0 ([#5370])
|
||||||
|
- DNS resolver configuration for internal HTTP client lookups ([#5355])
|
||||||
|
- Update README.md ([#5328])
|
||||||
|
- Update README.md ([#5327])
|
||||||
|
|
||||||
|
[#5431]: https://github.com/nymtech/nym/pull/5431
|
||||||
|
[#5426]: https://github.com/nymtech/nym/pull/5426
|
||||||
|
[#5424]: https://github.com/nymtech/nym/pull/5424
|
||||||
|
[#5414]: https://github.com/nymtech/nym/pull/5414
|
||||||
|
[#5406]: https://github.com/nymtech/nym/pull/5406
|
||||||
|
[#5401]: https://github.com/nymtech/nym/pull/5401
|
||||||
|
[#5398]: https://github.com/nymtech/nym/pull/5398
|
||||||
|
[#5396]: https://github.com/nymtech/nym/pull/5396
|
||||||
|
[#5395]: https://github.com/nymtech/nym/pull/5395
|
||||||
|
[#5381]: https://github.com/nymtech/nym/pull/5381
|
||||||
|
[#5380]: https://github.com/nymtech/nym/pull/5380
|
||||||
|
[#5372]: https://github.com/nymtech/nym/pull/5372
|
||||||
|
[#5370]: https://github.com/nymtech/nym/pull/5370
|
||||||
|
[#5355]: https://github.com/nymtech/nym/pull/5355
|
||||||
|
[#5328]: https://github.com/nymtech/nym/pull/5328
|
||||||
|
[#5327]: https://github.com/nymtech/nym/pull/5327
|
||||||
|
|
||||||
|
## [2025.2-hu] (2025-02-04)
|
||||||
|
|
||||||
|
- Feature/remove double spending bloomfilter ([#5417])
|
||||||
|
- HU - Downgrade harmless log message from info to debug ([#5405])
|
||||||
|
- lower default ticket verification quorum to 0.7 ([#5404])
|
||||||
|
- Downgrade harmless log message from info to debug ([#5403])
|
||||||
|
- Redirect from mixnode page to nodes page ([#5397])
|
||||||
|
- 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])
|
||||||
|
|
||||||
|
[#5417]: https://github.com/nymtech/nym/pull/5417
|
||||||
|
[#5405]: https://github.com/nymtech/nym/pull/5405
|
||||||
|
[#5404]: https://github.com/nymtech/nym/pull/5404
|
||||||
|
[#5403]: https://github.com/nymtech/nym/pull/5403
|
||||||
|
[#5397]: https://github.com/nymtech/nym/pull/5397
|
||||||
|
[#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)
|
## [2025.1-reeses] (2025-01-15)
|
||||||
|
|
||||||
- Feture/legacy alert ([#5346])
|
- Feture/legacy alert ([#5346])
|
||||||
|
|||||||
Generated
+1294
-1137
File diff suppressed because it is too large
Load Diff
+54
-39
@@ -48,7 +48,6 @@ members = [
|
|||||||
"common/credentials-interface",
|
"common/credentials-interface",
|
||||||
"common/crypto",
|
"common/crypto",
|
||||||
"common/dkg",
|
"common/dkg",
|
||||||
"common/ecash-double-spending",
|
|
||||||
"common/ecash-time",
|
"common/ecash-time",
|
||||||
"common/execute",
|
"common/execute",
|
||||||
"common/exit-policy",
|
"common/exit-policy",
|
||||||
@@ -67,7 +66,6 @@ members = [
|
|||||||
"common/nym-id",
|
"common/nym-id",
|
||||||
"common/nym-metrics",
|
"common/nym-metrics",
|
||||||
"common/nym_offline_compact_ecash",
|
"common/nym_offline_compact_ecash",
|
||||||
"common/nymcoconut",
|
|
||||||
"common/nymsphinx",
|
"common/nymsphinx",
|
||||||
"common/nymsphinx/acknowledgements",
|
"common/nymsphinx/acknowledgements",
|
||||||
"common/nymsphinx/addressing",
|
"common/nymsphinx/addressing",
|
||||||
@@ -188,40 +186,43 @@ readme = "README.md"
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
addr = "0.15.6"
|
addr = "0.15.6"
|
||||||
|
aead = "0.5.2"
|
||||||
aes = "0.8.1"
|
aes = "0.8.1"
|
||||||
aes-gcm = "0.10.1"
|
aes-gcm = "0.10.1"
|
||||||
aes-gcm-siv = "0.11.1"
|
aes-gcm-siv = "0.11.1"
|
||||||
aead = "0.5.2"
|
ammonia = "4"
|
||||||
anyhow = "1.0.95"
|
anyhow = "1.0.97"
|
||||||
arc-swap = "1.7.1"
|
arc-swap = "1.7.1"
|
||||||
argon2 = "0.5.0"
|
argon2 = "0.5.0"
|
||||||
async-trait = "0.1.85"
|
async-trait = "0.1.87"
|
||||||
axum-client-ip = "0.6.1"
|
|
||||||
axum = "0.7.5"
|
axum = "0.7.5"
|
||||||
|
axum-client-ip = "0.6.1"
|
||||||
axum-extra = "0.9.4"
|
axum-extra = "0.9.4"
|
||||||
|
axum-test = "16.2.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
|
base85rs = "0.1.3"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||||
bit-vec = "0.7.0" # can we unify those?
|
bit-vec = "0.7.0" # can we unify those?
|
||||||
bitvec = "1.0.0"
|
bitvec = "1.0.0"
|
||||||
blake3 = "1.5.5"
|
blake3 = "1.6.1"
|
||||||
bloomfilter = "1.0.14"
|
bloomfilter = "1.0.14"
|
||||||
bs58 = "0.5.1"
|
bs58 = "0.5.1"
|
||||||
bytecodec = "0.4.15"
|
bytecodec = "0.4.15"
|
||||||
bytes = "1.7.2"
|
bytes = "1.7.2"
|
||||||
cargo_metadata = "0.18.1"
|
cargo_metadata = "0.18.1"
|
||||||
celes = "2.4.0"
|
celes = "2.5.0"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
chacha20 = "0.9.0"
|
chacha20 = "0.9.0"
|
||||||
chacha20poly1305 = "0.10.1"
|
chacha20poly1305 = "0.10.1"
|
||||||
chrono = "0.4.39"
|
chrono = "0.4.40"
|
||||||
cipher = "0.4.3"
|
cipher = "0.4.3"
|
||||||
clap = "4.5.26"
|
clap = "4.5.31"
|
||||||
clap_complete = "4.5"
|
clap_complete = "4.5"
|
||||||
clap_complete_fig = "4.5"
|
clap_complete_fig = "4.5"
|
||||||
colored = "2.0"
|
colored = "2.2"
|
||||||
comfy-table = "7.1.3"
|
comfy-table = "7.1.4"
|
||||||
console = "0.15.10"
|
console = "0.15.11"
|
||||||
console-subscriber = "0.1.1"
|
console-subscriber = "0.1.1"
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
const-str = "0.5.6"
|
const-str = "0.5.6"
|
||||||
@@ -240,50 +241,50 @@ doc-comment = "0.3"
|
|||||||
dotenvy = "0.15.6"
|
dotenvy = "0.15.6"
|
||||||
ecdsa = "0.16"
|
ecdsa = "0.16"
|
||||||
ed25519-dalek = "2.1"
|
ed25519-dalek = "2.1"
|
||||||
etherparse = "0.13.0"
|
env_logger = "0.11.6"
|
||||||
envy = "0.4"
|
envy = "0.4"
|
||||||
|
etherparse = "0.13.0"
|
||||||
eyre = "0.6.9"
|
eyre = "0.6.9"
|
||||||
fastrand = "2.1.1"
|
fastrand = "2.1.1"
|
||||||
flate2 = "1.0.35"
|
flate2 = "1.1.0"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
generic-array = "0.14.7"
|
generic-array = "0.14.7"
|
||||||
getrandom = "0.2.10"
|
getrandom = "0.2.10"
|
||||||
getset = "0.1.3"
|
getset = "0.1.5"
|
||||||
handlebars = "3.5.5"
|
handlebars = "3.5.5"
|
||||||
headers = "0.4.0"
|
headers = "0.4.0"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
hex-literal = "0.3.3"
|
hex-literal = "0.3.3"
|
||||||
hickory-resolver = "0.24.2"
|
hickory-resolver = "0.24.4"
|
||||||
hkdf = "0.12.3"
|
hkdf = "0.12.3"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
http = "1"
|
http = "1"
|
||||||
http-body-util = "0.1"
|
http-body-util = "0.1"
|
||||||
httpcodec = "0.2.3"
|
httpcodec = "0.2.3"
|
||||||
|
human-repr = "1.1.0"
|
||||||
humantime = "2.1.0"
|
humantime = "2.1.0"
|
||||||
humantime-serde = "1.1.1"
|
humantime-serde = "1.1.1"
|
||||||
human-repr = "1.1.0"
|
hyper = "1.6.0"
|
||||||
hyper = "1.4.1"
|
|
||||||
hyper-util = "0.1"
|
hyper-util = "0.1"
|
||||||
indicatif = "0.17.9"
|
indicatif = "0.17.11"
|
||||||
inquire = "0.6.2"
|
inquire = "0.6.2"
|
||||||
ip_network = "0.4.1"
|
ip_network = "0.4.1"
|
||||||
ipnetwork = "0.20"
|
ipnetwork = "0.20"
|
||||||
isocountry = "0.3.2"
|
isocountry = "0.3.2"
|
||||||
itertools = "0.13.0"
|
itertools = "0.14.0"
|
||||||
k256 = "0.13"
|
k256 = "0.13"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
ledger-transport = "0.10.0"
|
ledger-transport = "0.10.0"
|
||||||
ledger-transport-hid = "0.10.0"
|
ledger-transport-hid = "0.10.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
maxminddb = "0.23.0"
|
maxminddb = "0.23.0"
|
||||||
rs_merkle = "1.4.2"
|
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
moka = { version = "0.12", features = ["future"] }
|
moka = { version = "0.12", features = ["future"] }
|
||||||
nix = "0.27.1"
|
nix = "0.27.1"
|
||||||
notify = "5.1.0"
|
notify = "5.1.0"
|
||||||
okapi = "0.7.0"
|
okapi = "0.7.0"
|
||||||
once_cell = "1.20.2"
|
once_cell = "1.20.3"
|
||||||
opentelemetry = "0.19.0"
|
opentelemetry = "0.19.0"
|
||||||
opentelemetry-jaeger = "0.18.0"
|
opentelemetry-jaeger = "0.18.0"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
@@ -292,7 +293,7 @@ petgraph = "0.6.5"
|
|||||||
pin-project = "1.1"
|
pin-project = "1.1"
|
||||||
pin-project-lite = "0.2.16"
|
pin-project-lite = "0.2.16"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
publicsuffix = "2.2.3"
|
publicsuffix = "2.3.0"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rand_chacha = "0.3"
|
rand_chacha = "0.3"
|
||||||
@@ -306,20 +307,21 @@ reqwest = { version = "0.12.4", default-features = false }
|
|||||||
rocket = "0.5.0"
|
rocket = "0.5.0"
|
||||||
rocket_cors = "0.6.0"
|
rocket_cors = "0.6.0"
|
||||||
rocket_okapi = "0.8.0"
|
rocket_okapi = "0.8.0"
|
||||||
|
rs_merkle = "1.4.2"
|
||||||
safer-ffi = "0.1.13"
|
safer-ffi = "0.1.13"
|
||||||
schemars = "0.8.21"
|
schemars = "0.8.22"
|
||||||
semver = "1.0.24"
|
semver = "1.0.25"
|
||||||
serde = "1.0.217"
|
serde = "1.0.217"
|
||||||
serde_bytes = "0.11.15"
|
serde_bytes = "0.11.16"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0.135"
|
serde_json = "1.0.140"
|
||||||
serde_json_path = "0.7.1"
|
serde_json_path = "0.7.2"
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
serde_with = "3.9.0"
|
serde_with = "3.9.0"
|
||||||
serde_yaml = "0.9.25"
|
serde_yaml = "0.9.25"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
si-scale = "0.2.3"
|
si-scale = "0.2.3"
|
||||||
sphinx-packet = "0.1.1"
|
sphinx-packet = "=0.4.0" # make sure to use version 0.4.0 (or higher) that has removed backwards compatibility
|
||||||
sqlx = "0.7.4"
|
sqlx = "0.7.4"
|
||||||
strum = "0.26"
|
strum = "0.26"
|
||||||
strum_macros = "0.26"
|
strum_macros = "0.26"
|
||||||
@@ -327,29 +329,32 @@ subtle-encoding = "0.5"
|
|||||||
syn = "1"
|
syn = "1"
|
||||||
sysinfo = "0.33.0"
|
sysinfo = "0.33.0"
|
||||||
tap = "1.0.1"
|
tap = "1.0.1"
|
||||||
tar = "0.4.43"
|
tar = "0.4.44"
|
||||||
tempfile = "3.15"
|
tempfile = "3.15"
|
||||||
thiserror = "1.0.64"
|
thiserror = "2.0"
|
||||||
time = "0.3.37"
|
time = "0.3.37"
|
||||||
tokio = "1.39"
|
tokio = "1.43"
|
||||||
|
tokio-postgres = "0.7"
|
||||||
tokio-stream = "0.1.17"
|
tokio-stream = "0.1.17"
|
||||||
tokio-test = "0.4.4"
|
tokio-test = "0.4.4"
|
||||||
tokio-tun = "0.11.5"
|
tokio-tun = "0.11.5"
|
||||||
tokio-tungstenite = { version = "0.20.1" }
|
tokio-tungstenite = { version = "0.20.1" }
|
||||||
tokio-util = "0.7.13"
|
tokio-util = "0.7.13"
|
||||||
toml = "0.8.19"
|
toml = "0.8.20"
|
||||||
tower = "0.4.13"
|
tower = "0.5.2"
|
||||||
tower-http = "0.5.2"
|
tower-http = "0.5.2"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
|
tracing-log = "0.2"
|
||||||
tracing-opentelemetry = "0.19.0"
|
tracing-opentelemetry = "0.19.0"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
tracing-tree = "0.2.2"
|
tracing-tree = "0.2.2"
|
||||||
tracing-log = "0.2"
|
|
||||||
ts-rs = "10.1.0"
|
ts-rs = "10.1.0"
|
||||||
tungstenite = { version = "0.20.1", default-features = false }
|
tungstenite = { version = "0.20.1", default-features = false }
|
||||||
|
uniffi = "0.29.0"
|
||||||
|
uniffi_build = "0.29.0"
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
utoipa = "5.2"
|
utoipa = "5.2"
|
||||||
utoipa-swagger-ui = "8.0"
|
utoipa-swagger-ui = "8.1"
|
||||||
utoipauto = "0.2"
|
utoipauto = "0.2"
|
||||||
uuid = "*"
|
uuid = "*"
|
||||||
vergen = { version = "=8.3.1", default-features = false }
|
vergen = { version = "=8.3.1", default-features = false }
|
||||||
@@ -385,10 +390,10 @@ cw4 = { version = "=1.1.2" }
|
|||||||
cw-controllers = { version = "=1.1.0" }
|
cw-controllers = { version = "=1.1.0" }
|
||||||
|
|
||||||
# cosmrs-related
|
# cosmrs-related
|
||||||
bip32 = { version = "0.5.2", default-features = false }
|
bip32 = { version = "0.5.3", default-features = false }
|
||||||
|
|
||||||
|
|
||||||
cosmrs = { version = "0.21.0" }
|
cosmrs = { version = "0.21.1" }
|
||||||
tendermint = "0.40.0"
|
tendermint = "0.40.0"
|
||||||
tendermint-rpc = "0.40.0"
|
tendermint-rpc = "0.40.0"
|
||||||
prost = { version = "0.13", default-features = false }
|
prost = { version = "0.13", default-features = false }
|
||||||
@@ -432,3 +437,13 @@ opt-level = 'z'
|
|||||||
[profile.release.package.mix-fetch-wasm]
|
[profile.release.package.mix-fetch-wasm]
|
||||||
# lto = true
|
# lto = true
|
||||||
opt-level = 'z'
|
opt-level = 'z'
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
unwrap_used = "deny"
|
||||||
|
expect_used = "deny"
|
||||||
|
todo = "deny"
|
||||||
|
dbg_macro = "deny"
|
||||||
|
exit = "deny"
|
||||||
|
panic = "deny"
|
||||||
|
unimplemented = "deny"
|
||||||
|
unreachable = "deny"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-client"
|
name = "nym-client"
|
||||||
version = "1.1.46"
|
version = "1.1.49"
|
||||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||||
description = "Implementation of the Nym Client"
|
description = "Implementation of the Nym Client"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
|||||||
.join(DEFAULT_DATA_DIR)
|
.join(DEFAULT_DATA_DIR)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub base: BaseClientConfig,
|
pub base: BaseClientConfig,
|
||||||
@@ -94,6 +94,10 @@ impl CliClientConfig for Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
pub fn base(&self) -> BaseClientConfig {
|
||||||
|
self.base.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new<S: AsRef<str>>(id: S) -> Self {
|
pub fn new<S: AsRef<str>>(id: S) -> Self {
|
||||||
Config {
|
Config {
|
||||||
base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")),
|
base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")),
|
||||||
@@ -209,7 +213,7 @@ impl SocketType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||||
#[serde(default, deny_unknown_fields)]
|
#[serde(default, deny_unknown_fields)]
|
||||||
pub struct Socket {
|
pub struct Socket {
|
||||||
pub socket_type: SocketType,
|
pub socket_type: SocketType,
|
||||||
|
|||||||
@@ -107,5 +107,8 @@ enabled = {{ debug.stats_reporting.enabled }}
|
|||||||
provider_address = '{{ debug.stats_reporting.provider_address }}'
|
provider_address = '{{ debug.stats_reporting.provider_address }}'
|
||||||
reporting_interval = '{{ debug.stats_reporting.reporting_interval }}'
|
reporting_interval = '{{ debug.stats_reporting.reporting_interval }}'
|
||||||
|
|
||||||
|
[debug.forget_me]
|
||||||
|
client = {{ debug.forget_me.client }}
|
||||||
|
stats = {{ debug.forget_me.stats }}
|
||||||
|
|
||||||
"#;
|
"#;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pub use nym_sphinx::addressing::clients::Recipient;
|
|||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
type NativeClientBuilder<'a> = BaseClientBuilder<'a, QueryHttpRpcNyxdClient, OnDiskPersistent>;
|
type NativeClientBuilder = BaseClientBuilder<QueryHttpRpcNyxdClient, OnDiskPersistent>;
|
||||||
|
|
||||||
pub struct SocketClient {
|
pub struct SocketClient {
|
||||||
/// Client configuration options, including, among other things, packet sending rates,
|
/// Client configuration options, including, among other things, packet sending rates,
|
||||||
@@ -32,6 +32,10 @@ pub struct SocketClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SocketClient {
|
impl SocketClient {
|
||||||
|
pub fn config(&self) -> Config {
|
||||||
|
self.config.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(config: Config, custom_mixnet: Option<PathBuf>) -> Self {
|
pub fn new(config: Config, custom_mixnet: Option<PathBuf>) -> Self {
|
||||||
SocketClient {
|
SocketClient {
|
||||||
config,
|
config,
|
||||||
@@ -45,7 +49,7 @@ impl SocketClient {
|
|||||||
client_output: ClientOutput,
|
client_output: ClientOutput,
|
||||||
client_state: ClientState,
|
client_state: ClientState,
|
||||||
self_address: &Recipient,
|
self_address: &Recipient,
|
||||||
shutdown: nym_task::TaskClient,
|
task_client: nym_task::TaskClient,
|
||||||
packet_type: PacketType,
|
packet_type: PacketType,
|
||||||
) {
|
) {
|
||||||
info!("Starting websocket listener...");
|
info!("Starting websocket listener...");
|
||||||
@@ -73,10 +77,15 @@ impl SocketClient {
|
|||||||
shared_lane_queue_lengths,
|
shared_lane_queue_lengths,
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
Some(packet_type),
|
Some(packet_type),
|
||||||
|
task_client.fork("websocket_handler"),
|
||||||
);
|
);
|
||||||
|
|
||||||
websocket::Listener::new(config.socket.host, config.socket.listening_port)
|
websocket::Listener::new(
|
||||||
.start(websocket_handler, shutdown);
|
config.socket.host,
|
||||||
|
config.socket.listening_port,
|
||||||
|
task_client.with_suffix("websocket_listener"),
|
||||||
|
)
|
||||||
|
.start(websocket_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
|
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
|
||||||
@@ -108,8 +117,9 @@ impl SocketClient {
|
|||||||
let storage = self.initialise_storage().await?;
|
let storage = self.initialise_storage().await?;
|
||||||
let user_agent = nym_bin_common::bin_info!().into();
|
let user_agent = nym_bin_common::bin_info!().into();
|
||||||
|
|
||||||
let mut base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client)
|
let mut base_client =
|
||||||
.with_user_agent(user_agent);
|
BaseClientBuilder::new(self.config().base(), storage, dkg_query_client)
|
||||||
|
.with_user_agent(user_agent);
|
||||||
|
|
||||||
if let Some(custom_mixnet) = &self.custom_mixnet {
|
if let Some(custom_mixnet) = &self.custom_mixnet {
|
||||||
base_client = base_client.with_stored_topology(custom_mixnet)?;
|
base_client = base_client.with_stored_topology(custom_mixnet)?;
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ impl From<Init> for OverrideConfig {
|
|||||||
nyxd_urls: init_config.common_args.nyxd_urls,
|
nyxd_urls: init_config.common_args.nyxd_urls,
|
||||||
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
|
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
|
||||||
stats_reporting_address: init_config.common_args.stats_reporting_address,
|
stats_reporting_address: init_config.common_args.stats_reporting_address,
|
||||||
|
forget_me: init_config.common_args.forget_me.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use nym_bin_common::completions::{fig_generate, ArgShell};
|
|||||||
use nym_client::client::Recipient;
|
use nym_client::client::Recipient;
|
||||||
use nym_client_core::cli_helpers::CliClient;
|
use nym_client_core::cli_helpers::CliClient;
|
||||||
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
|
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
|
||||||
|
use nym_client_core::config::ForgetMe;
|
||||||
use nym_config::OptionalSet;
|
use nym_config::OptionalSet;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
@@ -106,6 +107,7 @@ pub(crate) struct OverrideConfig {
|
|||||||
nyxd_urls: Option<Vec<url::Url>>,
|
nyxd_urls: Option<Vec<url::Url>>,
|
||||||
enabled_credentials_mode: Option<bool>,
|
enabled_credentials_mode: Option<bool>,
|
||||||
stats_reporting_address: Option<Recipient>,
|
stats_reporting_address: Option<Recipient>,
|
||||||
|
forget_me: ForgetMe,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
@@ -133,6 +135,7 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
|||||||
args.fastmode,
|
args.fastmode,
|
||||||
)
|
)
|
||||||
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
|
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
|
||||||
|
.with_base(BaseClientConfig::with_forget_me, args.forget_me)
|
||||||
.with_optional(Config::with_port, args.port)
|
.with_optional(Config::with_port, args.port)
|
||||||
.with_optional(Config::with_host, args.host)
|
.with_optional(Config::with_host, args.host)
|
||||||
.with_optional_custom_env_ext(
|
.with_optional_custom_env_ext(
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ impl From<Run> for OverrideConfig {
|
|||||||
nyxd_urls: run_config.common_args.nyxd_urls,
|
nyxd_urls: run_config.common_args.nyxd_urls,
|
||||||
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
|
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
|
||||||
stats_reporting_address: run_config.common_args.stats_reporting_address,
|
stats_reporting_address: run_config.common_args.stats_reporting_address,
|
||||||
|
forget_me: run_config.common_args.forget_me.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use nym_sphinx::receiver::ReconstructedMessage;
|
|||||||
use nym_task::connections::{
|
use nym_task::connections::{
|
||||||
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
|
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||||
};
|
};
|
||||||
|
use nym_task::TaskClient;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
@@ -43,9 +44,11 @@ pub(crate) struct HandlerBuilder {
|
|||||||
lane_queue_lengths: LaneQueueLengths,
|
lane_queue_lengths: LaneQueueLengths,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
packet_type: Option<PacketType>,
|
packet_type: Option<PacketType>,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HandlerBuilder {
|
impl HandlerBuilder {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
msg_input: InputMessageSender,
|
msg_input: InputMessageSender,
|
||||||
client_connection_tx: ConnectionCommandSender,
|
client_connection_tx: ConnectionCommandSender,
|
||||||
@@ -54,6 +57,7 @@ impl HandlerBuilder {
|
|||||||
lane_queue_lengths: LaneQueueLengths,
|
lane_queue_lengths: LaneQueueLengths,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
packet_type: Option<PacketType>,
|
packet_type: Option<PacketType>,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
msg_input,
|
msg_input,
|
||||||
@@ -63,11 +67,14 @@ impl HandlerBuilder {
|
|||||||
lane_queue_lengths,
|
lane_queue_lengths,
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
packet_type,
|
packet_type,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make sure we only ever have one active handler
|
// TODO: make sure we only ever have one active handler
|
||||||
pub fn create_active_handler(&self) -> Handler {
|
pub fn create_active_handler(&self) -> Handler {
|
||||||
|
let mut task_client = self.task_client.fork("active_handler");
|
||||||
|
task_client.disarm();
|
||||||
Handler {
|
Handler {
|
||||||
msg_input: self.msg_input.clone(),
|
msg_input: self.msg_input.clone(),
|
||||||
client_connection_tx: self.client_connection_tx.clone(),
|
client_connection_tx: self.client_connection_tx.clone(),
|
||||||
@@ -78,6 +85,7 @@ impl HandlerBuilder {
|
|||||||
lane_queue_lengths: self.lane_queue_lengths.clone(),
|
lane_queue_lengths: self.lane_queue_lengths.clone(),
|
||||||
reply_controller_sender: self.reply_controller_sender.clone(),
|
reply_controller_sender: self.reply_controller_sender.clone(),
|
||||||
packet_type: self.packet_type,
|
packet_type: self.packet_type,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,16 +100,18 @@ pub(crate) struct Handler {
|
|||||||
lane_queue_lengths: LaneQueueLengths,
|
lane_queue_lengths: LaneQueueLengths,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
packet_type: Option<PacketType>,
|
packet_type: Option<PacketType>,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Handler {
|
impl Drop for Handler {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self
|
if let Err(err) = self
|
||||||
.buffer_requester
|
.buffer_requester
|
||||||
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
|
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
|
||||||
.is_err()
|
|
||||||
{
|
{
|
||||||
error!("we failed to disconnect the receiver from the buffer! presumably the shutdown procedure has been initiated!")
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("failed to disconnect the receiver from the buffer: {err}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,10 +135,23 @@ impl Handler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// get the number of pending replies waiting for reply surbs
|
// get the number of pending replies waiting for reply surbs
|
||||||
let reply_queue_length = self
|
let reply_queue_length = match self
|
||||||
.reply_controller_sender
|
.reply_controller_sender
|
||||||
.get_lane_queue_length(connection_id)
|
.get_lane_queue_length(connection_id)
|
||||||
.await;
|
.await
|
||||||
|
{
|
||||||
|
Ok(length) => length,
|
||||||
|
Err(err) => {
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!(
|
||||||
|
"Failed to get reply queue length for connection {connection_id}: {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// We're just going to assume that the queue is empty, and I think that's okay
|
||||||
|
// during shutdown.
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let queue_length = base_length + reply_queue_length;
|
let queue_length = base_length + reply_queue_length;
|
||||||
|
|
||||||
@@ -168,10 +191,11 @@ impl Handler {
|
|||||||
|
|
||||||
// the ack control is now responsible for chunking, etc.
|
// the ack control is now responsible for chunking, etc.
|
||||||
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
|
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
|
||||||
self.msg_input
|
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||||
.send(input_msg)
|
if !self.task_client.is_shutdown_poll() {
|
||||||
.await
|
error!("Failed to send message to the input buffer: {err}");
|
||||||
.expect("InputMessageReceiver has stopped receiving!");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||||
@@ -200,10 +224,11 @@ impl Handler {
|
|||||||
|
|
||||||
let input_msg =
|
let input_msg =
|
||||||
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
|
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
|
||||||
self.msg_input
|
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||||
.send(input_msg)
|
if !self.task_client.is_shutdown_poll() {
|
||||||
.await
|
error!("Failed to send anonymous message to the input buffer: {err}");
|
||||||
.expect("InputMessageReceiver has stopped receiving!");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||||
@@ -227,10 +252,11 @@ impl Handler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
|
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
|
||||||
self.msg_input
|
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||||
.send(input_msg)
|
if !self.task_client.is_shutdown_poll() {
|
||||||
.await
|
error!("Failed to send reply message to the input buffer: {err}");
|
||||||
.expect("InputMessageReceiver has stopped receiving!");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||||
@@ -245,9 +271,14 @@ impl Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_closed_connection(&self, connection_id: u64) -> Option<ServerResponse> {
|
fn handle_closed_connection(&self, connection_id: u64) -> Option<ServerResponse> {
|
||||||
self.client_connection_tx
|
if let Err(err) = self
|
||||||
|
.client_connection_tx
|
||||||
.unbounded_send(ConnectionCommand::Close(connection_id))
|
.unbounded_send(ConnectionCommand::Close(connection_id))
|
||||||
.unwrap();
|
{
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("Failed to send close connection command: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,11 +393,10 @@ impl Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn listen_for_requests(
|
async fn listen_for_requests(&mut self, mut msg_receiver: ReconstructedMessagesReceiver) {
|
||||||
&mut self,
|
let mut task_client = self.task_client.fork("select");
|
||||||
mut msg_receiver: ReconstructedMessagesReceiver,
|
task_client.disarm();
|
||||||
mut task_client: nym_task::TaskClient,
|
|
||||||
) {
|
|
||||||
while !task_client.is_shutdown() {
|
while !task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
// we can either get a client request from the websocket
|
// we can either get a client request from the websocket
|
||||||
@@ -415,15 +445,7 @@ impl Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// consume self to make sure `drop` is called after this is done
|
// consume self to make sure `drop` is called after this is done
|
||||||
pub(crate) async fn handle_connection(
|
pub(crate) async fn handle_connection(mut self, socket: TcpStream) {
|
||||||
mut self,
|
|
||||||
socket: TcpStream,
|
|
||||||
mut task_client: nym_task::TaskClient,
|
|
||||||
) {
|
|
||||||
// We don't want a crash in the connection handler to trigger a shutdown of the whole
|
|
||||||
// process.
|
|
||||||
task_client.disarm();
|
|
||||||
|
|
||||||
let ws_stream = match accept_async(socket).await {
|
let ws_stream = match accept_async(socket).await {
|
||||||
Ok(ws_stream) => ws_stream,
|
Ok(ws_stream) => ws_stream,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -436,14 +458,18 @@ impl Handler {
|
|||||||
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
|
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
|
||||||
|
|
||||||
// tell the buffer to start sending stuff to us
|
// tell the buffer to start sending stuff to us
|
||||||
self.buffer_requester
|
if let Err(err) =
|
||||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
self.buffer_requester
|
||||||
reconstructed_sender,
|
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
||||||
))
|
reconstructed_sender,
|
||||||
.expect("the buffer request failed!");
|
))
|
||||||
|
{
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("failed to announce the receiver to the buffer: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.listen_for_requests(reconstructed_receiver, task_client)
|
self.listen_for_requests(reconstructed_receiver).await;
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
use super::handler::HandlerBuilder;
|
use super::handler::HandlerBuilder;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use nym_task::TaskClient;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
use std::{net::SocketAddr, process, sync::Arc};
|
use std::{net::SocketAddr, process, sync::Arc};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
@@ -22,21 +23,19 @@ impl State {
|
|||||||
pub(crate) struct Listener {
|
pub(crate) struct Listener {
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
state: State,
|
state: State,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Listener {
|
impl Listener {
|
||||||
pub(crate) fn new(host: IpAddr, port: u16) -> Self {
|
pub(crate) fn new(host: IpAddr, port: u16, task_client: TaskClient) -> Self {
|
||||||
Listener {
|
Listener {
|
||||||
address: SocketAddr::new(host, port),
|
address: SocketAddr::new(host, port),
|
||||||
state: State::AwaitingConnection,
|
state: State::AwaitingConnection,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(&mut self, handler: HandlerBuilder) {
|
||||||
&mut self,
|
|
||||||
handler: HandlerBuilder,
|
|
||||||
mut task_client: nym_task::TaskClient,
|
|
||||||
) {
|
|
||||||
let tcp_listener = match tokio::net::TcpListener::bind(self.address).await {
|
let tcp_listener = match tokio::net::TcpListener::bind(self.address).await {
|
||||||
Ok(listener) => listener,
|
Ok(listener) => listener,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -47,11 +46,11 @@ impl Listener {
|
|||||||
|
|
||||||
let notify = Arc::new(Notify::new());
|
let notify = Arc::new(Notify::new());
|
||||||
|
|
||||||
loop {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
// When the handler finishes we check if shutdown is signalled
|
// When the handler finishes we check if shutdown is signalled
|
||||||
_ = notify.notified() => {
|
_ = notify.notified() => {
|
||||||
if task_client.is_shutdown() {
|
if self.task_client.is_shutdown() {
|
||||||
log::trace!("Websocket listener: detected shutdown after connection closed");
|
log::trace!("Websocket listener: detected shutdown after connection closed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -60,7 +59,7 @@ impl Listener {
|
|||||||
}
|
}
|
||||||
// ... but when there is no connected client at the time of shutdown being
|
// ... but when there is no connected client at the time of shutdown being
|
||||||
// signalled, we handle it here.
|
// signalled, we handle it here.
|
||||||
_ = task_client.recv() => {
|
_ = self.task_client.recv() => {
|
||||||
if !self.state.is_connected() {
|
if !self.state.is_connected() {
|
||||||
log::trace!("Not connected: shutting down");
|
log::trace!("Not connected: shutting down");
|
||||||
break;
|
break;
|
||||||
@@ -88,9 +87,8 @@ impl Listener {
|
|||||||
// hanging because the executor doesn't come back here
|
// hanging because the executor doesn't come back here
|
||||||
let notify_clone = Arc::clone(¬ify);
|
let notify_clone = Arc::clone(¬ify);
|
||||||
let fresh_handler = handler.create_active_handler();
|
let fresh_handler = handler.create_active_handler();
|
||||||
let task_client_handler = task_client.clone();
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
fresh_handler.handle_connection(socket, task_client_handler).await;
|
fresh_handler.handle_connection(socket).await;
|
||||||
notify_clone.notify_one();
|
notify_clone.notify_one();
|
||||||
});
|
});
|
||||||
self.state = State::Connected;
|
self.state = State::Connected;
|
||||||
@@ -104,13 +102,9 @@ impl Listener {
|
|||||||
log::debug!("Websocket listener: Exiting");
|
log::debug!("Websocket listener: Exiting");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn start(
|
pub(crate) fn start(mut self, handler: HandlerBuilder) -> JoinHandle<()> {
|
||||||
mut self,
|
|
||||||
handler: HandlerBuilder,
|
|
||||||
shutdown: nym_task::TaskClient,
|
|
||||||
) -> JoinHandle<()> {
|
|
||||||
info!("Running websocket on {:?}", self.address.to_string());
|
info!("Running websocket on {:?}", self.address.to_string());
|
||||||
|
|
||||||
tokio::spawn(async move { self.run(handler, shutdown).await })
|
tokio::spawn(async move { self.run(handler).await })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nym-socks5-client"
|
name = "nym-socks5-client"
|
||||||
version = "1.1.46"
|
version = "1.1.49"
|
||||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
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"
|
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ impl From<Init> for OverrideConfig {
|
|||||||
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
|
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
|
||||||
outfox: false,
|
outfox: false,
|
||||||
stats_reporting_address: init_config.common_args.stats_reporting_address,
|
stats_reporting_address: init_config.common_args.stats_reporting_address,
|
||||||
|
forget_me: init_config.common_args.forget_me.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use nym_bin_common::completions::{fig_generate, ArgShell};
|
|||||||
use nym_client_core::cli_helpers::CliClient;
|
use nym_client_core::cli_helpers::CliClient;
|
||||||
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
|
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
|
||||||
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
|
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
|
||||||
use nym_client_core::config::{GroupBy, TopologyStructure};
|
use nym_client_core::config::{ForgetMe, GroupBy, TopologyStructure};
|
||||||
use nym_config::OptionalSet;
|
use nym_config::OptionalSet;
|
||||||
use nym_sphinx::addressing::Recipient;
|
use nym_sphinx::addressing::Recipient;
|
||||||
use nym_sphinx::params::{PacketSize, PacketType};
|
use nym_sphinx::params::{PacketSize, PacketType};
|
||||||
@@ -113,6 +113,7 @@ pub(crate) struct OverrideConfig {
|
|||||||
enabled_credentials_mode: Option<bool>,
|
enabled_credentials_mode: Option<bool>,
|
||||||
outfox: bool,
|
outfox: bool,
|
||||||
stats_reporting_address: Option<Recipient>,
|
stats_reporting_address: Option<Recipient>,
|
||||||
|
forget_me: ForgetMe,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
@@ -179,6 +180,7 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
|||||||
BaseClientConfig::with_topology_structure,
|
BaseClientConfig::with_topology_structure,
|
||||||
topology_structure,
|
topology_structure,
|
||||||
)
|
)
|
||||||
|
.with_base(BaseClientConfig::with_forget_me, args.forget_me)
|
||||||
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
|
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
|
||||||
.with_optional(Config::with_port, args.port)
|
.with_optional(Config::with_port, args.port)
|
||||||
.with_optional(Config::with_ip, args.ip)
|
.with_optional(Config::with_ip, args.ip)
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ impl From<Run> for OverrideConfig {
|
|||||||
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
|
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
|
||||||
outfox: run_config.outfox,
|
outfox: run_config.outfox,
|
||||||
stats_reporting_address: run_config.common_args.stats_reporting_address,
|
stats_reporting_address: run_config.common_args.stats_reporting_address,
|
||||||
|
forget_me: run_config.common_args.forget_me.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,4 +113,8 @@ enabled = {{ core.debug.stats_reporting.enabled }}
|
|||||||
provider_address = '{{ core.debug.stats_reporting.provider_address }}'
|
provider_address = '{{ core.debug.stats_reporting.provider_address }}'
|
||||||
reporting_interval = '{{ core.debug.stats_reporting.reporting_interval }}'
|
reporting_interval = '{{ core.debug.stats_reporting.reporting_interval }}'
|
||||||
|
|
||||||
|
[core.debug.forget_me]
|
||||||
|
client = {{ core.debug.forget_me.client }}
|
||||||
|
stats = {{ core.debug.forget_me.stats }}
|
||||||
|
|
||||||
"#;
|
"#;
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
allow-unwrap-in-tests = true
|
allow-unwrap-in-tests = true
|
||||||
allow-expect-in-tests = true
|
allow-expect-in-tests = true
|
||||||
|
allow-panic-in-tests = true
|
||||||
@@ -60,7 +60,7 @@ impl From<IpAddr> for IpPair {
|
|||||||
std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
|
std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
|
||||||
std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
|
std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
|
||||||
};
|
};
|
||||||
let last_bytes = (before_last_byte as u16) << 8 | last_byte as u16;
|
let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16;
|
||||||
let ipv4 = Ipv4Addr::new(
|
let ipv4 = Ipv4Addr::new(
|
||||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
|
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
|
||||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
|
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
|
||||||
|
|||||||
@@ -105,26 +105,24 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
|||||||
async fn get_aggregate_verification_key(
|
async fn get_aggregate_verification_key(
|
||||||
&self,
|
&self,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
apis: &mut ApiClientsWrapper,
|
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||||
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
C: DkgQueryClient + Sync + Send,
|
C: DkgQueryClient + Sync + Send,
|
||||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
|
|
||||||
get_aggregate_verification_key(&self.storage, epoch_id, ecash_apis).await
|
get_aggregate_verification_key(&self.storage, epoch_id, ecash_apis).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_coin_index_signatures(
|
async fn get_coin_index_signatures(
|
||||||
&self,
|
&self,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
apis: &mut ApiClientsWrapper,
|
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||||
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
C: DkgQueryClient + Sync + Send,
|
C: DkgQueryClient + Sync + Send,
|
||||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
|
|
||||||
get_coin_index_signatures(&self.storage, epoch_id, ecash_apis).await
|
get_coin_index_signatures(&self.storage, epoch_id, ecash_apis).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,13 +130,12 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
|||||||
&self,
|
&self,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
expiration_date: Date,
|
expiration_date: Date,
|
||||||
apis: &mut ApiClientsWrapper,
|
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||||
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
C: DkgQueryClient + Sync + Send,
|
C: DkgQueryClient + Sync + Send,
|
||||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let ecash_apis = apis.get_or_init(epoch_id, &self.client).await?;
|
|
||||||
get_expiration_date_signatures(&self.storage, epoch_id, expiration_date, ecash_apis).await
|
get_expiration_date_signatures(&self.storage, epoch_id, expiration_date, ecash_apis).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +151,7 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
|||||||
{
|
{
|
||||||
let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
|
let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
|
||||||
let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
|
let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
|
||||||
let mut api_clients = Default::default();
|
let mut api_clients = ApiClientsWrapper::new(&self.client, epoch_id);
|
||||||
|
|
||||||
let verification_key = self
|
let verification_key = self
|
||||||
.get_aggregate_verification_key(epoch_id, &mut api_clients)
|
.get_aggregate_verification_key(epoch_id, &mut api_clients)
|
||||||
|
|||||||
@@ -21,30 +21,67 @@ use rand::thread_rng;
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
// it really doesn't need the RwLock because it's never moved across tasks,
|
pub(crate) trait EcashClientsProvider {
|
||||||
// but we need all the Send/Sync action
|
async fn try_get_ecash_clients(
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct ApiClientsWrapper(Option<Vec<EcashApiClient>>);
|
|
||||||
|
|
||||||
impl ApiClientsWrapper {
|
|
||||||
pub(crate) async fn get_or_init<C>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
|
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EcashClientsProvider for Vec<EcashApiClient> {
|
||||||
|
async fn try_get_ecash_clients(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Vec<EcashApiClient>, BandwidthControllerError> {
|
||||||
|
Ok(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> EcashClientsProvider for &mut ApiClientsWrapper<'_, C>
|
||||||
|
where
|
||||||
|
C: DkgQueryClient + Sync + Send,
|
||||||
|
{
|
||||||
|
async fn try_get_ecash_clients(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Vec<EcashApiClient>, BandwidthControllerError> {
|
||||||
|
self.clients().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum ApiClientsWrapper<'a, C> {
|
||||||
|
Uninitialised {
|
||||||
|
query_client: &'a C,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
dkg_client: &C,
|
},
|
||||||
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>
|
Cached {
|
||||||
|
clients: Vec<EcashApiClient>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, C> ApiClientsWrapper<'a, C> {
|
||||||
|
pub(crate) fn new(query_client: &'a C, epoch_id: EpochId) -> Self {
|
||||||
|
ApiClientsWrapper::Uninitialised {
|
||||||
|
query_client,
|
||||||
|
epoch_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn clients(&mut self) -> Result<Vec<EcashApiClient>, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
C: DkgQueryClient + Sync + Send,
|
C: DkgQueryClient + Sync + Send,
|
||||||
{
|
{
|
||||||
if let Some(cached) = &self.0 {
|
match self {
|
||||||
return Ok(cached.clone());
|
ApiClientsWrapper::Uninitialised {
|
||||||
|
query_client,
|
||||||
|
epoch_id,
|
||||||
|
} => {
|
||||||
|
let clients = all_ecash_api_clients(*query_client, *epoch_id).await?;
|
||||||
|
*self = ApiClientsWrapper::Cached {
|
||||||
|
clients: clients.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(clients)
|
||||||
|
}
|
||||||
|
ApiClientsWrapper::Cached { clients } => Ok(clients.clone()),
|
||||||
}
|
}
|
||||||
|
|
||||||
let clients = all_ecash_api_clients(dkg_client, epoch_id).await?;
|
|
||||||
|
|
||||||
// technically we don't have to be cloning all the clients here, but it's way simpler than
|
|
||||||
// dealing with locking and whatnot given the performance penalty is negligible
|
|
||||||
self.0 = Some(clients.clone());
|
|
||||||
Ok(clients)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +113,7 @@ where
|
|||||||
pub(crate) async fn get_aggregate_verification_key<St>(
|
pub(crate) async fn get_aggregate_verification_key<St>(
|
||||||
storage: &St,
|
storage: &St,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
ecash_apis: Vec<EcashApiClient>,
|
mut ecash_apis: impl EcashClientsProvider,
|
||||||
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
St: Storage,
|
St: Storage,
|
||||||
@@ -90,6 +127,8 @@ where
|
|||||||
return Ok(stored);
|
return Ok(stored);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||||
|
|
||||||
let master_vk = query_random_apis_until_success(
|
let master_vk = query_random_apis_until_success(
|
||||||
ecash_apis,
|
ecash_apis,
|
||||||
|api| async move { api.api_client.master_verification_key(Some(epoch_id)).await },
|
|api| async move { api.api_client.master_verification_key(Some(epoch_id)).await },
|
||||||
@@ -115,7 +154,7 @@ where
|
|||||||
pub(crate) async fn get_coin_index_signatures<St>(
|
pub(crate) async fn get_coin_index_signatures<St>(
|
||||||
storage: &St,
|
storage: &St,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
ecash_apis: Vec<EcashApiClient>,
|
mut ecash_apis: impl EcashClientsProvider,
|
||||||
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
St: Storage,
|
St: Storage,
|
||||||
@@ -129,6 +168,8 @@ where
|
|||||||
return Ok(stored);
|
return Ok(stored);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||||
|
|
||||||
let index_sigs = query_random_apis_until_success(
|
let index_sigs = query_random_apis_until_success(
|
||||||
ecash_apis,
|
ecash_apis,
|
||||||
|api| async move {
|
|api| async move {
|
||||||
@@ -159,7 +200,7 @@ pub(crate) async fn get_expiration_date_signatures<St>(
|
|||||||
storage: &St,
|
storage: &St,
|
||||||
epoch_id: EpochId,
|
epoch_id: EpochId,
|
||||||
expiration_date: Date,
|
expiration_date: Date,
|
||||||
ecash_apis: Vec<EcashApiClient>,
|
mut ecash_apis: impl EcashClientsProvider,
|
||||||
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
||||||
where
|
where
|
||||||
St: Storage,
|
St: Storage,
|
||||||
@@ -173,6 +214,8 @@ where
|
|||||||
return Ok(stored);
|
return Ok(stored);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||||
|
|
||||||
let expiration_sigs = query_random_apis_until_success(
|
let expiration_sigs = query_random_apis_until_success(
|
||||||
ecash_apis,
|
ecash_apis,
|
||||||
|api| async move {
|
|api| async move {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ nym-crypto = { path = "../crypto" }
|
|||||||
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
|
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
|
||||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||||
nym-gateway-requests = { path = "../gateway-requests" }
|
nym-gateway-requests = { path = "../gateway-requests" }
|
||||||
|
nym-http-api-client = { path = "../http-api-client" }
|
||||||
nym-metrics = { path = "../nym-metrics" }
|
nym-metrics = { path = "../nym-metrics" }
|
||||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||||
nym-sphinx = { path = "../nymsphinx" }
|
nym-sphinx = { path = "../nymsphinx" }
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
|
|||||||
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
|
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
|
||||||
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
|
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
|
||||||
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
|
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
|
||||||
|
const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
|
||||||
|
|
||||||
// define how much to request at once
|
// define how much to request at once
|
||||||
// clients/client-core/src/client/replies/reply_controller.rs
|
// clients/client-core/src/client/replies/reply_controller.rs
|
||||||
@@ -145,6 +146,11 @@ impl Config {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_forget_me(mut self, forget_me: ForgetMe) -> Self {
|
||||||
|
self.debug.forget_me = forget_me;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: this should be refactored properly
|
// TODO: this should be refactored properly
|
||||||
// as of 12.09.23 the below is true (not sure how this comment will rot in the future)
|
// as of 12.09.23 the below is true (not sure how this comment will rot in the future)
|
||||||
// medium_toggle:
|
// medium_toggle:
|
||||||
@@ -517,7 +523,7 @@ impl Default for Acknowledgements {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||||
#[serde(default, deny_unknown_fields)]
|
#[serde(default)]
|
||||||
pub struct Topology {
|
pub struct Topology {
|
||||||
/// The uniform delay every which clients are querying the directory server
|
/// The uniform delay every which clients are querying the directory server
|
||||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||||
@@ -558,6 +564,10 @@ pub struct Topology {
|
|||||||
/// Specifies whether this client should ignore the current epoch role of the target egress node
|
/// Specifies whether this client should ignore the current epoch role of the target egress node
|
||||||
/// when constructing the final hop packets.
|
/// when constructing the final hop packets.
|
||||||
pub ignore_egress_epoch_role: bool,
|
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)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
@@ -595,7 +605,9 @@ impl Default for Topology {
|
|||||||
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
|
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
|
||||||
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
|
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
|
||||||
use_extended_topology: false,
|
use_extended_topology: false,
|
||||||
ignore_egress_epoch_role: false,
|
|
||||||
|
ignore_egress_epoch_role: true,
|
||||||
|
ignore_ingress_epoch_role: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -610,6 +622,10 @@ pub struct ReplySurbs {
|
|||||||
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
||||||
pub maximum_reply_surb_storage_threshold: usize,
|
pub maximum_reply_surb_storage_threshold: usize,
|
||||||
|
|
||||||
|
/// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client
|
||||||
|
/// should proactively request additional reply surbs.
|
||||||
|
pub minimum_reply_surb_threshold_buffer: usize,
|
||||||
|
|
||||||
/// Defines the minimum number of reply surbs the client would request.
|
/// Defines the minimum number of reply surbs the client would request.
|
||||||
pub minimum_reply_surb_request_size: u32,
|
pub minimum_reply_surb_request_size: u32,
|
||||||
|
|
||||||
@@ -649,6 +665,7 @@ impl Default for ReplySurbs {
|
|||||||
ReplySurbs {
|
ReplySurbs {
|
||||||
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||||
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||||
|
minimum_reply_surb_threshold_buffer: DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER,
|
||||||
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
|
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||||
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
|
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||||
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
|
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
|
||||||
@@ -713,6 +730,9 @@ pub struct DebugConfig {
|
|||||||
|
|
||||||
/// Defines all configuration options related to stats reporting.
|
/// Defines all configuration options related to stats reporting.
|
||||||
pub stats_reporting: StatsReporting,
|
pub stats_reporting: StatsReporting,
|
||||||
|
|
||||||
|
/// Defines all configuration options related to the forget me flag.
|
||||||
|
pub forget_me: ForgetMe,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebugConfig {
|
impl DebugConfig {
|
||||||
@@ -735,6 +755,69 @@ impl Default for DebugConfig {
|
|||||||
topology: Default::default(),
|
topology: Default::default(),
|
||||||
reply_surbs: Default::default(),
|
reply_surbs: Default::default(),
|
||||||
stats_reporting: Default::default(),
|
stats_reporting: Default::default(),
|
||||||
|
forget_me: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
|
||||||
|
pub struct ForgetMe {
|
||||||
|
client: bool,
|
||||||
|
stats: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for ForgetMe {
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
if value {
|
||||||
|
Self::new_all()
|
||||||
|
} else {
|
||||||
|
Self::new_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ForgetMe {
|
||||||
|
pub fn new_all() -> Self {
|
||||||
|
Self {
|
||||||
|
client: true,
|
||||||
|
stats: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_client() -> Self {
|
||||||
|
Self {
|
||||||
|
client: true,
|
||||||
|
stats: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_stats() -> Self {
|
||||||
|
Self {
|
||||||
|
client: false,
|
||||||
|
stats: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(client: bool, stats: bool) -> Self {
|
||||||
|
Self { client, stats }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn any(&self) -> bool {
|
||||||
|
self.client || self.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client(&self) -> bool {
|
||||||
|
self.client
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stats(&self) -> bool {
|
||||||
|
self.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_none() -> Self {
|
||||||
|
Self {
|
||||||
|
client: false,
|
||||||
|
stats: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,8 +181,9 @@ impl From<ConfigV5> for Config {
|
|||||||
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
|
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
|
||||||
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
|
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
|
||||||
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
|
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
stats_reporting: Default::default(),
|
..Default::default()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
-- make aes256gcm column non-nullable and drop any gateways that still use the legacy keys
|
||||||
|
-- (since they'd be unusable after this change)
|
||||||
|
CREATE TABLE remote_gateway_details_tmp
|
||||||
|
(
|
||||||
|
gateway_id_bs58 TEXT NOT NULL UNIQUE PRIMARY KEY REFERENCES registered_gateway (gateway_id_bs58),
|
||||||
|
derived_aes256_gcm_siv_key BLOB NOT NULL,
|
||||||
|
gateway_owner_address TEXT,
|
||||||
|
gateway_listener TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO remote_gateway_details_tmp (gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_owner_address,
|
||||||
|
gateway_listener)
|
||||||
|
SELECT gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener
|
||||||
|
FROM remote_gateway_details
|
||||||
|
WHERE derived_aes256_gcm_siv_key IS NOT NULL;
|
||||||
|
|
||||||
|
DROP TABLE remote_gateway_details;
|
||||||
|
ALTER TABLE remote_gateway_details_tmp
|
||||||
|
RENAME TO remote_gateway_details;
|
||||||
@@ -156,48 +156,26 @@ impl StorageManager {
|
|||||||
|
|
||||||
pub(crate) async fn set_remote_gateway_details(
|
pub(crate) async fn set_remote_gateway_details(
|
||||||
&self,
|
&self,
|
||||||
remote: &RawRemoteGatewayDetails,
|
gateway_id_bs58: String,
|
||||||
|
derived_aes256_gcm_siv_key: &[u8],
|
||||||
|
gateway_owner_address: Option<String>,
|
||||||
|
gateway_listener: String,
|
||||||
) -> Result<(), sqlx::Error> {
|
) -> Result<(), sqlx::Error> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener)
|
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
"#,
|
"#,
|
||||||
remote.gateway_id_bs58,
|
gateway_id_bs58,
|
||||||
remote.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
derived_aes256_gcm_siv_key,
|
||||||
remote.derived_aes256_gcm_siv_key,
|
gateway_owner_address,
|
||||||
remote.gateway_owner_address,
|
gateway_listener,
|
||||||
remote.gateway_listener,
|
|
||||||
)
|
)
|
||||||
.execute(&self.connection_pool)
|
.execute(&self.connection_pool)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn update_remote_gateway_key(
|
|
||||||
&self,
|
|
||||||
gateway_id_bs58: &str,
|
|
||||||
derived_aes128_ctr_blake3_hmac_keys_bs58: Option<&str>,
|
|
||||||
derived_aes256_gcm_siv_key: Option<&[u8]>,
|
|
||||||
) -> Result<(), sqlx::Error> {
|
|
||||||
sqlx::query!(
|
|
||||||
r#"
|
|
||||||
UPDATE remote_gateway_details
|
|
||||||
SET
|
|
||||||
derived_aes128_ctr_blake3_hmac_keys_bs58 = ?,
|
|
||||||
derived_aes256_gcm_siv_key = ?
|
|
||||||
WHERE gateway_id_bs58 = ?
|
|
||||||
"#,
|
|
||||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
|
||||||
derived_aes256_gcm_siv_key,
|
|
||||||
gateway_id_bs58
|
|
||||||
)
|
|
||||||
.execute(&self.connection_pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn remove_remote_gateway_details(
|
pub(crate) async fn remove_remote_gateway_details(
|
||||||
&self,
|
&self,
|
||||||
gateway_id: &str,
|
gateway_id: &str,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use crate::{
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use manager::StorageManager;
|
use manager::StorageManager;
|
||||||
use nym_crypto::asymmetric::ed25519;
|
use nym_crypto::asymmetric::ed25519;
|
||||||
use nym_gateway_requests::SharedSymmetricKey;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
@@ -119,9 +118,16 @@ impl GatewaysDetailsStore for OnDiskGatewaysDetails {
|
|||||||
|
|
||||||
match &details.details {
|
match &details.details {
|
||||||
GatewayDetails::Remote(remote_details) => {
|
GatewayDetails::Remote(remote_details) => {
|
||||||
let raw_details = remote_details.into();
|
|
||||||
self.manager
|
self.manager
|
||||||
.set_remote_gateway_details(&raw_details)
|
.set_remote_gateway_details(
|
||||||
|
remote_details.gateway_id.to_base58_string(),
|
||||||
|
remote_details.shared_key.as_bytes(),
|
||||||
|
remote_details
|
||||||
|
.gateway_owner_address
|
||||||
|
.as_ref()
|
||||||
|
.map(|o| o.to_string()),
|
||||||
|
remote_details.gateway_listener.to_string(),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
GatewayDetails::Custom(custom_details) => {
|
GatewayDetails::Custom(custom_details) => {
|
||||||
@@ -134,21 +140,6 @@ impl GatewaysDetailsStore for OnDiskGatewaysDetails {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upgrade_stored_remote_gateway_key(
|
|
||||||
&self,
|
|
||||||
gateway_id: ed25519::PublicKey,
|
|
||||||
updated_key: &SharedSymmetricKey,
|
|
||||||
) -> Result<(), Self::StorageError> {
|
|
||||||
self.manager
|
|
||||||
.update_remote_gateway_key(
|
|
||||||
&gateway_id.to_base58_string(),
|
|
||||||
None,
|
|
||||||
Some(updated_key.as_bytes()),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ideally all of those should be run under a storage tx to ensure storage consistency,
|
// ideally all of those should be run under a storage tx to ensure storage consistency,
|
||||||
// but at that point it's fine
|
// but at that point it's fine
|
||||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
|
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::types::{ActiveGateway, GatewayRegistration};
|
use crate::types::{ActiveGateway, GatewayRegistration};
|
||||||
use crate::{BadGateway, GatewayDetails, GatewaysDetailsStore};
|
use crate::{BadGateway, GatewaysDetailsStore};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
|
||||||
use nym_gateway_requests::{SharedGatewayKey, SharedSymmetricKey};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -96,29 +94,6 @@ impl GatewaysDetailsStore for InMemGatewaysDetails {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upgrade_stored_remote_gateway_key(
|
|
||||||
&self,
|
|
||||||
gateway_id: PublicKey,
|
|
||||||
updated_key: &SharedSymmetricKey,
|
|
||||||
) -> Result<(), Self::StorageError> {
|
|
||||||
let mut guard = self.inner.write().await;
|
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
if let Some(target) = guard.gateways.get_mut(&gateway_id.to_string()) {
|
|
||||||
let GatewayDetails::Remote(details) = &mut target.details else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
assert_eq!(Arc::strong_count(&details.shared_key), 1);
|
|
||||||
|
|
||||||
// eh. that's nasty, but it's only ever used for ephemeral clients so should be fine for now...
|
|
||||||
details.shared_key = Arc::new(SharedGatewayKey::Current(
|
|
||||||
SharedSymmetricKey::try_from_bytes(updated_key.as_bytes()).unwrap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
|
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||||
let mut guard = self.inner.write().await;
|
let mut guard = self.inner.write().await;
|
||||||
if let Some(active) = guard.active_gateway.as_ref() {
|
if let Some(active) = guard.active_gateway.as_ref() {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use nym_crypto::asymmetric::identity;
|
use nym_crypto::asymmetric::identity;
|
||||||
use nym_gateway_requests::SharedSymmetricKey;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
@@ -62,12 +61,6 @@ pub trait GatewaysDetailsStore {
|
|||||||
details: &GatewayRegistration,
|
details: &GatewayRegistration,
|
||||||
) -> Result<(), Self::StorageError>;
|
) -> Result<(), Self::StorageError>;
|
||||||
|
|
||||||
async fn upgrade_stored_remote_gateway_key(
|
|
||||||
&self,
|
|
||||||
gateway_id: identity::PublicKey,
|
|
||||||
updated_key: &SharedSymmetricKey,
|
|
||||||
) -> Result<(), Self::StorageError>;
|
|
||||||
|
|
||||||
/// Remove given gateway details from the underlying store.
|
/// Remove given gateway details from the underlying store.
|
||||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError>;
|
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,9 @@
|
|||||||
use crate::BadGateway;
|
use crate::BadGateway;
|
||||||
use cosmrs::AccountId;
|
use cosmrs::AccountId;
|
||||||
use nym_crypto::asymmetric::identity;
|
use nym_crypto::asymmetric::identity;
|
||||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::ops::Deref;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
@@ -65,7 +64,7 @@ impl From<GatewayDetails> for GatewayRegistration {
|
|||||||
impl GatewayDetails {
|
impl GatewayDetails {
|
||||||
pub fn new_remote(
|
pub fn new_remote(
|
||||||
gateway_id: identity::PublicKey,
|
gateway_id: identity::PublicKey,
|
||||||
shared_key: Arc<SharedGatewayKey>,
|
shared_key: Arc<SharedSymmetricKey>,
|
||||||
gateway_owner_address: Option<AccountId>,
|
gateway_owner_address: Option<AccountId>,
|
||||||
gateway_listener: Url,
|
gateway_listener: Url,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -88,7 +87,7 @@ impl GatewayDetails {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shared_key(&self) -> Option<&SharedGatewayKey> {
|
pub fn shared_key(&self) -> Option<&SharedSymmetricKey> {
|
||||||
match self {
|
match self {
|
||||||
GatewayDetails::Remote(details) => Some(&details.shared_key),
|
GatewayDetails::Remote(details) => Some(&details.shared_key),
|
||||||
GatewayDetails::Custom(_) => None,
|
GatewayDetails::Custom(_) => None,
|
||||||
@@ -168,8 +167,7 @@ pub struct RegisteredGateway {
|
|||||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||||
pub struct RawRemoteGatewayDetails {
|
pub struct RawRemoteGatewayDetails {
|
||||||
pub gateway_id_bs58: String,
|
pub gateway_id_bs58: String,
|
||||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
pub derived_aes256_gcm_siv_key: Vec<u8>,
|
||||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
|
||||||
pub gateway_owner_address: Option<String>,
|
pub gateway_owner_address: Option<String>,
|
||||||
pub gateway_listener: String,
|
pub gateway_listener: String,
|
||||||
}
|
}
|
||||||
@@ -186,35 +184,11 @@ impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let shared_key =
|
let shared_key = SharedSymmetricKey::try_from_bytes(&value.derived_aes256_gcm_siv_key)
|
||||||
match (
|
.map_err(|source| BadGateway::MalformedSharedKeys {
|
||||||
&value.derived_aes256_gcm_siv_key,
|
gateway_id: value.gateway_id_bs58.clone(),
|
||||||
&value.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
source,
|
||||||
) {
|
})?;
|
||||||
(None, None) => {
|
|
||||||
return Err(BadGateway::MissingSharedKey {
|
|
||||||
gateway_id: value.gateway_id_bs58.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
(Some(aes256gcm_siv), _) => {
|
|
||||||
let current_key =
|
|
||||||
SharedSymmetricKey::try_from_bytes(aes256gcm_siv).map_err(|source| {
|
|
||||||
BadGateway::MalformedSharedKeys {
|
|
||||||
gateway_id: value.gateway_id_bs58.clone(),
|
|
||||||
source,
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
SharedGatewayKey::Current(current_key)
|
|
||||||
}
|
|
||||||
(None, Some(aes128ctr_hmac)) => {
|
|
||||||
let legacy_key = LegacySharedKeys::try_from_base58_string(aes128ctr_hmac)
|
|
||||||
.map_err(|source| BadGateway::MalformedSharedKeys {
|
|
||||||
gateway_id: value.gateway_id_bs58.clone(),
|
|
||||||
source,
|
|
||||||
})?;
|
|
||||||
SharedGatewayKey::Legacy(legacy_key)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let gateway_owner_address = value
|
let gateway_owner_address = value
|
||||||
.gateway_owner_address
|
.gateway_owner_address
|
||||||
@@ -247,29 +221,11 @@ impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a RemoteGatewayDetails> for RawRemoteGatewayDetails {
|
|
||||||
fn from(value: &'a RemoteGatewayDetails) -> Self {
|
|
||||||
let (derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key) =
|
|
||||||
match value.shared_key.deref() {
|
|
||||||
SharedGatewayKey::Current(key) => (None, Some(key.to_bytes())),
|
|
||||||
SharedGatewayKey::Legacy(key) => (Some(key.to_base58_string()), None),
|
|
||||||
};
|
|
||||||
|
|
||||||
RawRemoteGatewayDetails {
|
|
||||||
gateway_id_bs58: value.gateway_id.to_base58_string(),
|
|
||||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
|
||||||
derived_aes256_gcm_siv_key,
|
|
||||||
gateway_owner_address: value.gateway_owner_address.as_ref().map(|o| o.to_string()),
|
|
||||||
gateway_listener: value.gateway_listener.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RemoteGatewayDetails {
|
pub struct RemoteGatewayDetails {
|
||||||
pub gateway_id: identity::PublicKey,
|
pub gateway_id: identity::PublicKey,
|
||||||
|
|
||||||
pub shared_key: Arc<SharedGatewayKey>,
|
pub shared_key: Arc<SharedSymmetricKey>,
|
||||||
|
|
||||||
pub gateway_owner_address: Option<AccountId>,
|
pub gateway_owner_address: Option<AccountId>,
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ where
|
|||||||
&core.client.nym_api_urls,
|
&core.client.nym_api_urls,
|
||||||
user_agent,
|
user_agent,
|
||||||
core.debug.topology.minimum_gateway_performance,
|
core.debug.topology.minimum_gateway_performance,
|
||||||
|
core.debug.topology.ignore_ingress_epoch_role,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ pub struct CommonClientInitArgs {
|
|||||||
/// Sets the address to report statistics
|
/// Sets the address to report statistics
|
||||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||||
pub stats_reporting_address: Option<Recipient>,
|
pub stats_reporting_address: Option<Recipient>,
|
||||||
|
|
||||||
|
/// Sets the forget me flag
|
||||||
|
#[cfg_attr(feature = "cli", clap(long, hide = true, default_value_t = false))]
|
||||||
|
pub forget_me: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct InitResultsWithConfig<T> {
|
pub struct InitResultsWithConfig<T> {
|
||||||
@@ -175,6 +179,7 @@ where
|
|||||||
&core.client.nym_api_urls,
|
&core.client.nym_api_urls,
|
||||||
user_agent,
|
user_agent,
|
||||||
core.debug.topology.minimum_gateway_performance,
|
core.debug.topology.minimum_gateway_performance,
|
||||||
|
core.debug.topology.ignore_ingress_epoch_role,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,4 +61,8 @@ pub struct CommonClientRunArgs {
|
|||||||
/// Sets the address to report statistics
|
/// Sets the address to report statistics
|
||||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||||
pub stats_reporting_address: Option<Recipient>,
|
pub stats_reporting_address: Option<Recipient>,
|
||||||
|
|
||||||
|
/// Sets the forget me flag
|
||||||
|
#[cfg_attr(feature = "cli", clap(long, hide = true, default_value_t = false))]
|
||||||
|
pub forget_me: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
use super::mix_traffic::ClientRequestSender;
|
||||||
use super::received_buffer::ReceivedBufferMessage;
|
use super::received_buffer::ReceivedBufferMessage;
|
||||||
use super::statistics_control::StatisticsControl;
|
use super::statistics_control::StatisticsControl;
|
||||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||||
@@ -31,13 +32,15 @@ use crate::init::{
|
|||||||
setup_gateway,
|
setup_gateway,
|
||||||
types::{GatewaySetup, InitialisationResult},
|
types::{GatewaySetup, InitialisationResult},
|
||||||
};
|
};
|
||||||
use crate::{config, spawn_future, ForgetMe};
|
use crate::{config, spawn_future};
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
use log::*;
|
use log::*;
|
||||||
use nym_bandwidth_controller::BandwidthController;
|
use nym_bandwidth_controller::BandwidthController;
|
||||||
|
use nym_client_core_config_types::ForgetMe;
|
||||||
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
|
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
|
||||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||||
use nym_crypto::asymmetric::{encryption, identity};
|
use nym_crypto::asymmetric::{encryption, identity};
|
||||||
|
use nym_crypto::hkdf::DerivationMaterial;
|
||||||
use nym_gateway_client::client::config::GatewayClientConfig;
|
use nym_gateway_client::client::config::GatewayClientConfig;
|
||||||
use nym_gateway_client::{
|
use nym_gateway_client::{
|
||||||
AcknowledgementReceiver, GatewayClient, GatewayConfig, MixnetMessageReceiver, PacketRouter,
|
AcknowledgementReceiver, GatewayClient, GatewayConfig, MixnetMessageReceiver, PacketRouter,
|
||||||
@@ -175,8 +178,8 @@ impl From<bool> for CredentialsToggle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||||
config: &'a Config,
|
config: Config,
|
||||||
client_store: S,
|
client_store: S,
|
||||||
dkg_query_client: Option<C>,
|
dkg_query_client: Option<C>,
|
||||||
|
|
||||||
@@ -191,19 +194,19 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
|
||||||
forget_me: ForgetMe,
|
derivation_material: Option<DerivationMaterial>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, C, S> BaseClientBuilder<'a, C, S>
|
impl<C, S> BaseClientBuilder<C, S>
|
||||||
where
|
where
|
||||||
S: MixnetClientStorage + 'static,
|
S: MixnetClientStorage + 'static,
|
||||||
C: DkgQueryClient + Send + Sync + 'static,
|
C: DkgQueryClient + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
base_config: &'a Config,
|
base_config: Config,
|
||||||
client_store: S,
|
client_store: S,
|
||||||
dkg_query_client: Option<C>,
|
dkg_query_client: Option<C>,
|
||||||
) -> BaseClientBuilder<'a, C, S> {
|
) -> BaseClientBuilder<C, S> {
|
||||||
BaseClientBuilder {
|
BaseClientBuilder {
|
||||||
config: base_config,
|
config: base_config,
|
||||||
client_store,
|
client_store,
|
||||||
@@ -216,13 +219,22 @@ where
|
|||||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: None,
|
connection_fd_callback: None,
|
||||||
forget_me: Default::default(),
|
derivation_material: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_derivation_material(
|
||||||
|
mut self,
|
||||||
|
derivation_material: Option<DerivationMaterial>,
|
||||||
|
) -> Self {
|
||||||
|
self.derivation_material = derivation_material;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
||||||
self.forget_me = forget_me.clone();
|
self.config.debug.forget_me = *forget_me;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +310,7 @@ where
|
|||||||
topology_accessor: TopologyAccessor,
|
topology_accessor: TopologyAccessor,
|
||||||
mix_tx: BatchMixMessageSender,
|
mix_tx: BatchMixMessageSender,
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
shutdown: TaskClient,
|
task_client: TaskClient,
|
||||||
) {
|
) {
|
||||||
info!("Starting loop cover traffic stream...");
|
info!("Starting loop cover traffic stream...");
|
||||||
|
|
||||||
@@ -311,9 +323,10 @@ where
|
|||||||
debug_config.traffic,
|
debug_config.traffic,
|
||||||
debug_config.cover_traffic,
|
debug_config.cover_traffic,
|
||||||
stats_tx,
|
stats_tx,
|
||||||
|
task_client,
|
||||||
);
|
);
|
||||||
|
|
||||||
stream.start_with_shutdown(shutdown);
|
stream.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@@ -328,7 +341,7 @@ where
|
|||||||
reply_controller_receiver: ReplyControllerReceiver,
|
reply_controller_receiver: ReplyControllerReceiver,
|
||||||
lane_queue_lengths: LaneQueueLengths,
|
lane_queue_lengths: LaneQueueLengths,
|
||||||
client_connection_rx: ConnectionCommandReceiver,
|
client_connection_rx: ConnectionCommandReceiver,
|
||||||
shutdown: TaskClient,
|
task_client: TaskClient,
|
||||||
packet_type: PacketType,
|
packet_type: PacketType,
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
) {
|
) {
|
||||||
@@ -346,8 +359,9 @@ where
|
|||||||
lane_queue_lengths,
|
lane_queue_lengths,
|
||||||
client_connection_rx,
|
client_connection_rx,
|
||||||
stats_tx,
|
stats_tx,
|
||||||
|
task_client,
|
||||||
)
|
)
|
||||||
.start_with_shutdown(shutdown, packet_type);
|
.start(packet_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// buffer controlling all messages fetched from provider
|
// buffer controlling all messages fetched from provider
|
||||||
@@ -370,8 +384,9 @@ where
|
|||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
metrics_reporter,
|
metrics_reporter,
|
||||||
|
shutdown,
|
||||||
);
|
);
|
||||||
controller.start_with_shutdown(shutdown)
|
controller.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@@ -379,7 +394,6 @@ where
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
initialisation_result: InitialisationResult,
|
initialisation_result: InitialisationResult,
|
||||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||||
details_store: &S::GatewaysDetailsStore,
|
|
||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
stats_reporter: ClientStatsSender,
|
stats_reporter: ClientStatsSender,
|
||||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
@@ -388,7 +402,6 @@ where
|
|||||||
where
|
where
|
||||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||||
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
|
|
||||||
{
|
{
|
||||||
let managed_keys = initialisation_result.client_keys;
|
let managed_keys = initialisation_result.client_keys;
|
||||||
let GatewayDetails::Remote(details) = initialisation_result.gateway_registration.details
|
let GatewayDetails::Remote(details) = initialisation_result.gateway_registration.details
|
||||||
@@ -443,31 +456,13 @@ where
|
|||||||
// we need to:
|
// we need to:
|
||||||
// - perform handshake (reg or auth)
|
// - perform handshake (reg or auth)
|
||||||
// - check for key upgrade
|
// - check for key upgrade
|
||||||
// - maybe perform another upgrade handshake
|
|
||||||
// - check for bandwidth
|
// - check for bandwidth
|
||||||
// - start background tasks
|
// - start background tasks
|
||||||
let auth_res = gateway_client
|
let _auth_res = gateway_client
|
||||||
.perform_initial_authentication()
|
.perform_initial_authentication()
|
||||||
.await
|
.await
|
||||||
.map_err(gateway_failure)?;
|
.map_err(gateway_failure)?;
|
||||||
|
|
||||||
if auth_res.requires_key_upgrade {
|
|
||||||
// drop the shared_key arc because we don't need it and we can't hold it for the purposes of upgrade
|
|
||||||
drop(auth_res);
|
|
||||||
|
|
||||||
let updated_key = gateway_client
|
|
||||||
.upgrade_key_authenticated()
|
|
||||||
.await
|
|
||||||
.map_err(gateway_failure)?;
|
|
||||||
|
|
||||||
details_store
|
|
||||||
.upgrade_stored_remote_gateway_key(gateway_client.gateway_identity(), &updated_key)
|
|
||||||
.await.map_err(|err| {
|
|
||||||
error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
|
|
||||||
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
|
|
||||||
gateway_client
|
gateway_client
|
||||||
.claim_initial_bandwidth()
|
.claim_initial_bandwidth()
|
||||||
.await
|
.await
|
||||||
@@ -486,7 +481,6 @@ where
|
|||||||
config: &Config,
|
config: &Config,
|
||||||
initialisation_result: InitialisationResult,
|
initialisation_result: InitialisationResult,
|
||||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||||
details_store: &S::GatewaysDetailsStore,
|
|
||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
stats_reporter: ClientStatsSender,
|
stats_reporter: ClientStatsSender,
|
||||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
@@ -495,7 +489,6 @@ where
|
|||||||
where
|
where
|
||||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||||
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
|
|
||||||
{
|
{
|
||||||
// if we have setup custom gateway sender and persisted details agree with it, return it
|
// if we have setup custom gateway sender and persisted details agree with it, return it
|
||||||
if let Some(mut custom_gateway_transceiver) = custom_gateway_transceiver {
|
if let Some(mut custom_gateway_transceiver) = custom_gateway_transceiver {
|
||||||
@@ -518,7 +511,6 @@ where
|
|||||||
config,
|
config,
|
||||||
initialisation_result,
|
initialisation_result,
|
||||||
bandwidth_controller,
|
bandwidth_controller,
|
||||||
details_store,
|
|
||||||
packet_router,
|
packet_router,
|
||||||
stats_reporter,
|
stats_reporter,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@@ -560,15 +552,22 @@ where
|
|||||||
topology_accessor: TopologyAccessor,
|
topology_accessor: TopologyAccessor,
|
||||||
local_gateway: NodeIdentity,
|
local_gateway: NodeIdentity,
|
||||||
wait_for_gateway: bool,
|
wait_for_gateway: bool,
|
||||||
mut shutdown: TaskClient,
|
mut task_client: TaskClient,
|
||||||
) -> Result<(), ClientCoreError> {
|
) -> Result<(), ClientCoreError> {
|
||||||
let topology_refresher_config =
|
let topology_refresher_config =
|
||||||
TopologyRefresherConfig::new(topology_config.topology_refresh_rate);
|
TopologyRefresherConfig::new(topology_config.topology_refresh_rate);
|
||||||
|
|
||||||
|
if topology_config.disable_refreshing {
|
||||||
|
// if we're not spawning the refresher, don't cause shutdown immediately
|
||||||
|
info!("The background topology refesher is not going to be started");
|
||||||
|
task_client.disarm();
|
||||||
|
}
|
||||||
|
|
||||||
let mut topology_refresher = TopologyRefresher::new(
|
let mut topology_refresher = TopologyRefresher::new(
|
||||||
topology_refresher_config,
|
topology_refresher_config,
|
||||||
topology_accessor,
|
topology_accessor,
|
||||||
topology_provider,
|
topology_provider,
|
||||||
|
task_client,
|
||||||
);
|
);
|
||||||
// before returning, block entire runtime to refresh the current network view so that any
|
// before returning, block entire runtime to refresh the current network view so that any
|
||||||
// components depending on topology would see a non-empty view
|
// components depending on topology would see a non-empty view
|
||||||
@@ -609,15 +608,11 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if topology_config.disable_refreshing {
|
if !topology_config.disable_refreshing {
|
||||||
// if we're not spawning the refresher, don't cause shutdown immediately
|
|
||||||
info!("The topology refesher is not going to be started");
|
|
||||||
shutdown.disarm();
|
|
||||||
} else {
|
|
||||||
// don't spawn the refresher if we don't want to be refreshing the topology.
|
// don't spawn the refresher if we don't want to be refreshing the topology.
|
||||||
// only use the initial values obtained
|
// only use the initial values obtained
|
||||||
info!("Starting topology refresher...");
|
info!("Starting topology refresher...");
|
||||||
topology_refresher.start_with_shutdown(shutdown);
|
topology_refresher.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -628,30 +623,29 @@ where
|
|||||||
user_agent: Option<UserAgent>,
|
user_agent: Option<UserAgent>,
|
||||||
client_stats_id: String,
|
client_stats_id: String,
|
||||||
input_sender: Sender<InputMessage>,
|
input_sender: Sender<InputMessage>,
|
||||||
shutdown: TaskClient,
|
task_client: TaskClient,
|
||||||
) -> ClientStatsSender {
|
) -> ClientStatsSender {
|
||||||
info!("Starting statistics control...");
|
info!("Starting statistics control...");
|
||||||
StatisticsControl::create_and_start_with_shutdown(
|
StatisticsControl::create_and_start(
|
||||||
config.debug.stats_reporting,
|
config.debug.stats_reporting,
|
||||||
user_agent
|
user_agent
|
||||||
.map(|u| u.application)
|
.map(|u| u.application)
|
||||||
.unwrap_or("unknown".to_string()),
|
.unwrap_or("unknown".to_string()),
|
||||||
client_stats_id,
|
client_stats_id,
|
||||||
input_sender.clone(),
|
input_sender.clone(),
|
||||||
shutdown.with_suffix("controller"),
|
task_client,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_mix_traffic_controller(
|
fn start_mix_traffic_controller(
|
||||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||||
shutdown: TaskClient,
|
shutdown: TaskClient,
|
||||||
forget_me: ForgetMe,
|
) -> (BatchMixMessageSender, ClientRequestSender) {
|
||||||
) -> BatchMixMessageSender {
|
|
||||||
info!("Starting mix traffic controller...");
|
info!("Starting mix traffic controller...");
|
||||||
let (mix_traffic_controller, mix_tx) =
|
let (mix_traffic_controller, mix_tx, client_tx) =
|
||||||
MixTrafficController::new(gateway_transceiver, forget_me);
|
MixTrafficController::new(gateway_transceiver, shutdown);
|
||||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
mix_traffic_controller.start();
|
||||||
mix_tx
|
(mix_tx, client_tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
|
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
|
||||||
@@ -686,6 +680,7 @@ where
|
|||||||
setup_method: GatewaySetup,
|
setup_method: GatewaySetup,
|
||||||
key_store: &S::KeyStore,
|
key_store: &S::KeyStore,
|
||||||
details_store: &S::GatewaysDetailsStore,
|
details_store: &S::GatewaysDetailsStore,
|
||||||
|
derivation_material: Option<DerivationMaterial>,
|
||||||
) -> Result<InitialisationResult, ClientCoreError>
|
) -> Result<InitialisationResult, ClientCoreError>
|
||||||
where
|
where
|
||||||
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
|
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
|
||||||
@@ -695,7 +690,12 @@ where
|
|||||||
if key_store.load_keys().await.is_err() {
|
if key_store.load_keys().await.is_err() {
|
||||||
info!("could not find valid client keys - a new set will be generated");
|
info!("could not find valid client keys - a new set will be generated");
|
||||||
let mut rng = OsRng;
|
let mut rng = OsRng;
|
||||||
let keys = ClientKeys::generate_new(&mut rng);
|
let keys = if let Some(derivation_material) = derivation_material {
|
||||||
|
ClientKeys::from_master_key(&mut rng, &derivation_material)
|
||||||
|
.map_err(|_| ClientCoreError::HkdfDerivationError {})?
|
||||||
|
} else {
|
||||||
|
ClientKeys::generate_new(&mut rng)
|
||||||
|
};
|
||||||
store_client_keys(keys, key_store).await?;
|
store_client_keys(keys, key_store).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -717,11 +717,11 @@ where
|
|||||||
self.setup_method,
|
self.setup_method,
|
||||||
self.client_store.key_store(),
|
self.client_store.key_store(),
|
||||||
self.client_store.gateway_details_store(),
|
self.client_store.gateway_details_store(),
|
||||||
|
self.derivation_material,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let (reply_storage_backend, credential_store, details_store) =
|
let (reply_storage_backend, credential_store, _) = self.client_store.into_runtime_stores();
|
||||||
self.client_store.into_runtime_stores();
|
|
||||||
|
|
||||||
// channels for inter-component communication
|
// channels for inter-component communication
|
||||||
// TODO: make the channels be internally created by the relevant components
|
// TODO: make the channels be internally created by the relevant components
|
||||||
@@ -773,7 +773,7 @@ where
|
|||||||
);
|
);
|
||||||
|
|
||||||
let stats_reporter = Self::start_statistics_control(
|
let stats_reporter = Self::start_statistics_control(
|
||||||
self.config,
|
&self.config,
|
||||||
self.user_agent.clone(),
|
self.user_agent.clone(),
|
||||||
generate_client_stats_id(*self_address.identity()),
|
generate_client_stats_id(*self_address.identity()),
|
||||||
input_sender.clone(),
|
input_sender.clone(),
|
||||||
@@ -799,10 +799,9 @@ where
|
|||||||
|
|
||||||
let gateway_transceiver = Self::setup_gateway_transceiver(
|
let gateway_transceiver = Self::setup_gateway_transceiver(
|
||||||
self.custom_gateway_transceiver,
|
self.custom_gateway_transceiver,
|
||||||
self.config,
|
&self.config,
|
||||||
init_res,
|
init_res,
|
||||||
bandwidth_controller,
|
bandwidth_controller,
|
||||||
&details_store,
|
|
||||||
gateway_packet_router,
|
gateway_packet_router,
|
||||||
stats_reporter.clone(),
|
stats_reporter.clone(),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@@ -833,10 +832,9 @@ where
|
|||||||
// traffic stream.
|
// traffic stream.
|
||||||
// The MixTrafficController then sends the actual traffic
|
// The MixTrafficController then sends the actual traffic
|
||||||
|
|
||||||
let message_sender = Self::start_mix_traffic_controller(
|
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
|
||||||
gateway_transceiver,
|
gateway_transceiver,
|
||||||
shutdown.fork("mix_traffic_controller"),
|
shutdown.fork("mix_traffic_controller"),
|
||||||
self.forget_me,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Channels that the websocket listener can use to signal downstream to the real traffic
|
// Channels that the websocket listener can use to signal downstream to the real traffic
|
||||||
@@ -911,6 +909,8 @@ where
|
|||||||
},
|
},
|
||||||
stats_reporter,
|
stats_reporter,
|
||||||
task_handle: shutdown,
|
task_handle: shutdown,
|
||||||
|
client_request_sender,
|
||||||
|
forget_me: self.config.debug.forget_me,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -922,6 +922,7 @@ pub struct BaseClient {
|
|||||||
pub client_output: ClientOutputStatus,
|
pub client_output: ClientOutputStatus,
|
||||||
pub client_state: ClientState,
|
pub client_state: ClientState,
|
||||||
pub stats_reporter: ClientStatsSender,
|
pub stats_reporter: ClientStatsSender,
|
||||||
|
pub client_request_sender: ClientRequestSender,
|
||||||
pub task_handle: TaskHandle,
|
pub task_handle: TaskHandle,
|
||||||
|
pub forget_me: ForgetMe,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,11 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
pub mod v1_1_33 {
|
pub mod v1_1_33 {
|
||||||
use crate::client::base_client::{
|
|
||||||
non_wasm_helpers::setup_fs_gateways_storage,
|
|
||||||
storage::helpers::{set_active_gateway, store_gateway_details},
|
|
||||||
};
|
|
||||||
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||||
use crate::config::disk_persistence::CommonClientPaths;
|
use crate::config::disk_persistence::CommonClientPaths;
|
||||||
use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
|
use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
|
||||||
use crate::error::ClientCoreError;
|
use crate::error::ClientCoreError;
|
||||||
use nym_client_core_gateways_storage::{
|
|
||||||
CustomGatewayDetails, GatewayDetails, GatewayRegistration, RemoteGatewayDetails,
|
|
||||||
};
|
|
||||||
use nym_gateway_requests::shared_key::LegacySharedKeys;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{digest::Digest, Sha256};
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use zeroize::Zeroizing;
|
|
||||||
|
|
||||||
mod base64 {
|
mod base64 {
|
||||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||||
@@ -57,155 +44,18 @@ pub mod v1_1_33 {
|
|||||||
details: OldGatewayEndpointConfigV1_1_33,
|
details: OldGatewayEndpointConfigV1_1_33,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PersistedGatewayConfig {
|
|
||||||
fn verify(&self, shared_key: &LegacySharedKeys) -> bool {
|
|
||||||
let key_bytes = Zeroizing::new(shared_key.to_bytes());
|
|
||||||
|
|
||||||
let mut key_hasher = Sha256::new();
|
|
||||||
key_hasher.update(&key_bytes);
|
|
||||||
let key_hash = key_hasher.finalize();
|
|
||||||
|
|
||||||
self.key_hash == key_hash.deref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
struct PersistedCustomGatewayDetails {
|
struct PersistedCustomGatewayDetails {
|
||||||
gateway_id: String,
|
gateway_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_shared_key<P: AsRef<Path>>(path: P) -> Result<LegacySharedKeys, ClientCoreError> {
|
|
||||||
// the shared key was a simple pem file
|
|
||||||
Ok(nym_pemstore::load_key(path)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gateway_details_from_raw(
|
|
||||||
gateway_id: String,
|
|
||||||
gateway_owner: String,
|
|
||||||
gateway_listener: String,
|
|
||||||
gateway_shared_key: LegacySharedKeys,
|
|
||||||
) -> Result<GatewayDetails, ClientCoreError> {
|
|
||||||
Ok(GatewayDetails::Remote(RemoteGatewayDetails {
|
|
||||||
gateway_id: gateway_id
|
|
||||||
.parse()
|
|
||||||
.map_err(|err| ClientCoreError::UpgradeFailure {
|
|
||||||
message: format!("the stored gateway id was malformed: {err}"),
|
|
||||||
})?,
|
|
||||||
shared_key: Arc::new(gateway_shared_key.into()),
|
|
||||||
gateway_owner_address: Some(gateway_owner.parse().map_err(|err| {
|
|
||||||
ClientCoreError::UpgradeFailure {
|
|
||||||
message: format!("the stored gateway owner address was malformed: {err}"),
|
|
||||||
}
|
|
||||||
})?),
|
|
||||||
gateway_listener: gateway_listener.parse().map_err(|err| {
|
|
||||||
ClientCoreError::UpgradeFailure {
|
|
||||||
message: format!("the stored gateway listener address was malformed: {err}"),
|
|
||||||
}
|
|
||||||
})?,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper to extract shared key and gateway details into the new GatewayRegistration
|
|
||||||
fn extract_gateway_registration(
|
|
||||||
storage_paths: &CommonClientPathsV1_1_33,
|
|
||||||
) -> Result<GatewayRegistration, ClientCoreError> {
|
|
||||||
let details_file = std::fs::File::open(&storage_paths.gateway_details).map_err(|err| {
|
|
||||||
ClientCoreError::UpgradeFailure {
|
|
||||||
message: format!(
|
|
||||||
"failed to open gateway details file at {}: {err}",
|
|
||||||
storage_paths.gateway_details.display()
|
|
||||||
),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// in v1.1.33 of the clients, the gateway details struct was saved as json
|
|
||||||
let details: PersistedGatewayDetails =
|
|
||||||
serde_json::from_reader(details_file).map_err(|err| {
|
|
||||||
ClientCoreError::UpgradeFailure {
|
|
||||||
message: format!(
|
|
||||||
"failed to deserialize gateway details from {}: {err}",
|
|
||||||
storage_paths.gateway_details.display()
|
|
||||||
),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let details = match details {
|
|
||||||
PersistedGatewayDetails::Default(config) => {
|
|
||||||
let gateway_shared_key =
|
|
||||||
load_shared_key(&storage_paths.keys.gateway_shared_key_file)?;
|
|
||||||
if !config.verify(&gateway_shared_key) {
|
|
||||||
return Err(ClientCoreError::UpgradeFailure {
|
|
||||||
message: "failed to verify consistency of the existing gateway details"
|
|
||||||
.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
gateway_details_from_raw(
|
|
||||||
config.details.gateway_id,
|
|
||||||
config.details.gateway_owner,
|
|
||||||
config.details.gateway_listener,
|
|
||||||
gateway_shared_key,
|
|
||||||
)?
|
|
||||||
}
|
|
||||||
PersistedGatewayDetails::Custom(custom) => {
|
|
||||||
GatewayDetails::Custom(CustomGatewayDetails {
|
|
||||||
gateway_id: custom.gateway_id.parse().map_err(|err| {
|
|
||||||
ClientCoreError::UpgradeFailure {
|
|
||||||
message: format!("the stored gateway id was malformed: {err}"),
|
|
||||||
}
|
|
||||||
})?,
|
|
||||||
data: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(details.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
// it's responsibility of the caller to ensure this is called **after** new registration has already been saved
|
|
||||||
fn remove_old_gateway_details(storage_paths: &CommonClientPathsV1_1_33) -> std::io::Result<()> {
|
|
||||||
std::fs::remove_file(&storage_paths.gateway_details)?;
|
|
||||||
|
|
||||||
if storage_paths.keys.gateway_shared_key_file.exists() {
|
|
||||||
std::fs::remove_file(&storage_paths.keys.gateway_shared_key_file)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn migrate_gateway_details(
|
pub async fn migrate_gateway_details(
|
||||||
old_storage_paths: &CommonClientPathsV1_1_33,
|
_old_storage_paths: &CommonClientPathsV1_1_33,
|
||||||
new_storage_paths: &CommonClientPaths,
|
_new_storage_paths: &CommonClientPaths,
|
||||||
preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
|
_preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
|
||||||
) -> Result<(), ClientCoreError> {
|
) -> Result<(), ClientCoreError> {
|
||||||
let gateway_registration = match preloaded_config {
|
Err(ClientCoreError::UnsupportedMigration(
|
||||||
Some(config) => {
|
"migration of legacy keys has been removed and is no longer supported".into(),
|
||||||
let gateway_shared_key =
|
))
|
||||||
load_shared_key(&old_storage_paths.keys.gateway_shared_key_file)?;
|
|
||||||
gateway_details_from_raw(
|
|
||||||
config.gateway_id,
|
|
||||||
config.gateway_owner,
|
|
||||||
config.gateway_listener,
|
|
||||||
gateway_shared_key,
|
|
||||||
)?
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
None => extract_gateway_registration(old_storage_paths)?,
|
|
||||||
};
|
|
||||||
|
|
||||||
// since we're migrating to a brand new store, the store should be empty
|
|
||||||
// and thus set the 'new' gateway as the active one
|
|
||||||
let details_store =
|
|
||||||
setup_fs_gateways_storage(&new_storage_paths.gateway_registrations).await?;
|
|
||||||
store_gateway_details(&details_store, &gateway_registration).await?;
|
|
||||||
set_active_gateway(
|
|
||||||
&details_store,
|
|
||||||
&gateway_registration.details.gateway_id().to_base58_string(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
remove_old_gateway_details(old_storage_paths).map_err(|err| {
|
|
||||||
ClientCoreError::UpgradeFailure {
|
|
||||||
message: format!("failed to remove old data: {err}"),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
// TODO: combine those more closely. Perhaps into a single underlying store.
|
// 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?
|
// 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::key_manager::persistence::{InMemEphemeralKeys, KeyStore};
|
||||||
use crate::client::replies::reply_storage;
|
use crate::client::replies::reply_storage;
|
||||||
use crate::client::replies::reply_storage::ReplyStorageBackend;
|
use crate::client::replies::reply_storage::ReplyStorageBackend;
|
||||||
@@ -63,7 +65,7 @@ pub trait MixnetClientStorage {
|
|||||||
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
|
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone)]
|
||||||
pub struct Ephemeral {
|
pub struct Ephemeral {
|
||||||
key_store: InMemEphemeralKeys,
|
key_store: InMemEphemeralKeys,
|
||||||
reply_store: reply_storage::Empty,
|
reply_store: reply_storage::Empty,
|
||||||
@@ -71,9 +73,14 @@ pub struct Ephemeral {
|
|||||||
gateway_details_store: InMemGatewaysDetails,
|
gateway_details_store: InMemGatewaysDetails,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ephemeral {
|
impl Default for Ephemeral {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
Default::default()
|
Ephemeral {
|
||||||
|
key_store: InMemEphemeralKeys::new(&mut OsRng),
|
||||||
|
reply_store: Default::default(),
|
||||||
|
credential_store: Default::default(),
|
||||||
|
gateway_details_store: Default::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use nym_sphinx::cover::generate_loop_cover_packet;
|
|||||||
use nym_sphinx::params::{PacketSize, PacketType};
|
use nym_sphinx::params::{PacketSize, PacketType};
|
||||||
use nym_sphinx::utils::sample_poisson_duration;
|
use nym_sphinx::utils::sample_poisson_duration;
|
||||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||||
|
use nym_task::TaskClient;
|
||||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -64,6 +65,8 @@ where
|
|||||||
packet_type: PacketType,
|
packet_type: PacketType,
|
||||||
|
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
|
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> Stream for LoopCoverTrafficStream<R>
|
impl<R> Stream for LoopCoverTrafficStream<R>
|
||||||
@@ -110,6 +113,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
|||||||
traffic_config: config::Traffic,
|
traffic_config: config::Traffic,
|
||||||
cover_config: config::CoverTraffic,
|
cover_config: config::CoverTraffic,
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rng = OsRng;
|
let rng = OsRng;
|
||||||
|
|
||||||
@@ -128,6 +132,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
|||||||
secondary_packet_size: traffic_config.secondary_packet_size,
|
secondary_packet_size: traffic_config.secondary_packet_size,
|
||||||
packet_type: traffic_config.packet_type,
|
packet_type: traffic_config.packet_type,
|
||||||
stats_tx,
|
stats_tx,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +180,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let cover_message = generate_loop_cover_packet(
|
let cover_message = match generate_loop_cover_packet(
|
||||||
&mut self.rng,
|
&mut self.rng,
|
||||||
topology_ref,
|
topology_ref,
|
||||||
&self.ack_key,
|
&self.ack_key,
|
||||||
@@ -184,8 +189,15 @@ impl LoopCoverTrafficStream<OsRng> {
|
|||||||
self.cover_traffic.loop_cover_traffic_average_delay,
|
self.cover_traffic.loop_cover_traffic_average_delay,
|
||||||
cover_traffic_packet_size,
|
cover_traffic_packet_size,
|
||||||
self.packet_type,
|
self.packet_type,
|
||||||
)
|
) {
|
||||||
.expect("Somehow failed to generate a loop cover message with a valid topology");
|
Ok(cover_message) => cover_message,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(
|
||||||
|
"Somehow failed to generate a loop cover message with a valid topology: {err}"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(err) = self.mix_tx.try_send(vec![cover_message]) {
|
if let Err(err) = self.mix_tx.try_send(vec![cover_message]) {
|
||||||
match err {
|
match err {
|
||||||
@@ -217,7 +229,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
|||||||
tokio::task::yield_now().await;
|
tokio::task::yield_now().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
pub fn start(mut self) {
|
||||||
if self.cover_traffic.disable_loop_cover_traffic_stream {
|
if self.cover_traffic.disable_loop_cover_traffic_stream {
|
||||||
// we should have never got here in the first place - the task should have never been created to begin with
|
// we should have never got here in the first place - the task should have never been created to begin with
|
||||||
// so panic and review the code that lead to this branch
|
// so panic and review the code that lead to this branch
|
||||||
@@ -231,6 +243,8 @@ impl LoopCoverTrafficStream<OsRng> {
|
|||||||
);
|
);
|
||||||
self.set_next_delay(sampled);
|
self.set_next_delay(sampled);
|
||||||
|
|
||||||
|
let mut shutdown = self.task_client.fork("select");
|
||||||
|
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
debug!("Started LoopCoverTrafficStream with graceful shutdown support");
|
debug!("Started LoopCoverTrafficStream with graceful shutdown support");
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,18 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::client::key_manager::persistence::KeyStore;
|
use crate::client::key_manager::persistence::KeyStore;
|
||||||
use nym_crypto::asymmetric::{encryption, identity};
|
use nym_crypto::{
|
||||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
asymmetric::{encryption, identity},
|
||||||
|
hkdf::{DerivationMaterial, InvalidLength},
|
||||||
|
};
|
||||||
|
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||||
use nym_sphinx::acknowledgements::AckKey;
|
use nym_sphinx::acknowledgements::AckKey;
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use zeroize::ZeroizeOnDrop;
|
use zeroize::ZeroizeOnDrop;
|
||||||
|
|
||||||
pub mod persistence;
|
pub mod persistence;
|
||||||
|
mod test;
|
||||||
|
|
||||||
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
|
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
|
||||||
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
|
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
|
||||||
@@ -43,6 +47,24 @@ impl ClientKeys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_master_key<R>(
|
||||||
|
rng: &mut R,
|
||||||
|
derivation_material: &DerivationMaterial,
|
||||||
|
) -> Result<Self, InvalidLength>
|
||||||
|
where
|
||||||
|
R: RngCore + CryptoRng,
|
||||||
|
{
|
||||||
|
let secret = derivation_material.derive_secret()?;
|
||||||
|
Ok(ClientKeys {
|
||||||
|
identity_keypair: Arc::new(identity::KeyPair::from_secret(
|
||||||
|
secret,
|
||||||
|
derivation_material.index(),
|
||||||
|
)),
|
||||||
|
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
|
||||||
|
ack_key: Arc::new(AckKey::new(rng)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_keys(
|
pub fn from_keys(
|
||||||
id_keypair: identity::KeyPair,
|
id_keypair: identity::KeyPair,
|
||||||
enc_keypair: encryption::KeyPair,
|
enc_keypair: encryption::KeyPair,
|
||||||
@@ -84,7 +106,5 @@ fn _assert_keys_zeroize_on_drop() {
|
|||||||
_assert_zeroize_on_drop::<identity::KeyPair>();
|
_assert_zeroize_on_drop::<identity::KeyPair>();
|
||||||
_assert_zeroize_on_drop::<encryption::KeyPair>();
|
_assert_zeroize_on_drop::<encryption::KeyPair>();
|
||||||
_assert_zeroize_on_drop::<AckKey>();
|
_assert_zeroize_on_drop::<AckKey>();
|
||||||
_assert_zeroize_on_drop::<LegacySharedKeys>();
|
|
||||||
_assert_zeroize_on_drop::<SharedSymmetricKey>();
|
_assert_zeroize_on_drop::<SharedSymmetricKey>();
|
||||||
_assert_zeroize_on_drop::<SharedGatewayKey>();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
use crate::client::key_manager::ClientKeys;
|
use crate::client::key_manager::ClientKeys;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use rand::{CryptoRng, RngCore};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -195,9 +196,20 @@ impl KeyStore for OnDiskKeys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone)]
|
||||||
pub struct InMemEphemeralKeys {
|
pub struct InMemEphemeralKeys {
|
||||||
keys: Arc<Mutex<Option<ClientKeys>>>,
|
keys: Arc<Mutex<ClientKeys>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InMemEphemeralKeys {
|
||||||
|
pub fn new<R>(rng: &mut R) -> Self
|
||||||
|
where
|
||||||
|
R: RngCore + CryptoRng,
|
||||||
|
{
|
||||||
|
InMemEphemeralKeys {
|
||||||
|
keys: Arc::new(Mutex::new(ClientKeys::generate_new(rng))),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
@@ -210,11 +222,11 @@ impl KeyStore for InMemEphemeralKeys {
|
|||||||
type StorageError = EphemeralKeysError;
|
type StorageError = EphemeralKeysError;
|
||||||
|
|
||||||
async fn load_keys(&self) -> Result<ClientKeys, Self::StorageError> {
|
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> {
|
async fn store_keys(&self, keys: &ClientKeys) -> Result<(), Self::StorageError> {
|
||||||
*self.keys.lock().await = Some(keys.clone());
|
*self.keys.lock().await = keys.clone();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::client::key_manager::ClientKeys;
|
||||||
|
use nym_crypto::hkdf::DerivationMaterial;
|
||||||
|
use rand::SeedableRng;
|
||||||
|
use rand_chacha::ChaCha20Rng;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_master_key_success() {
|
||||||
|
// Set up a deterministic RNG.
|
||||||
|
let seed = [33u8; 32];
|
||||||
|
let mut rng = ChaCha20Rng::from_seed(seed);
|
||||||
|
|
||||||
|
// Set up the derivation material.
|
||||||
|
let master_key = b"this is a secret master key";
|
||||||
|
let salt = b"unique-salt";
|
||||||
|
let derivation_material = DerivationMaterial::new(master_key, 0, salt);
|
||||||
|
|
||||||
|
// Generate ClientKeys from the master key.
|
||||||
|
let client_keys = ClientKeys::from_master_key(&mut rng, &derivation_material)
|
||||||
|
.expect("Failed to create client keys");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
client_keys.identity_keypair().public_key().to_string(),
|
||||||
|
String::from("FX4Undr5LPPBA7zThWWpAKXKQTXSbW1C28PnxbCqUkU4")
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
client_keys.identity_keypair().private_key().to_string(),
|
||||||
|
String::from("6S3uMi2rU5SwyUUYCiMrF5qqdcYnEDMYLggBSvavVzEt")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_master_key_deterministic_identity() {
|
||||||
|
// Using identical derivation material should result in the exactly same identity keypair.
|
||||||
|
let seed = [1u8; 32];
|
||||||
|
let mut rng1 = ChaCha20Rng::from_seed(seed);
|
||||||
|
let mut rng2 = ChaCha20Rng::from_seed(seed);
|
||||||
|
|
||||||
|
let master_key = b"another secret master key";
|
||||||
|
let salt = b"deterministic-salt";
|
||||||
|
let index = 7u32;
|
||||||
|
let derivation_material = DerivationMaterial::new(master_key, index, salt);
|
||||||
|
|
||||||
|
let client_keys1 = ClientKeys::from_master_key(&mut rng1, &derivation_material)
|
||||||
|
.expect("Failed to create client keys (first instance)");
|
||||||
|
let client_keys2 = ClientKeys::from_master_key(&mut rng2, &derivation_material)
|
||||||
|
.expect("Failed to create client keys (second instance)");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
client_keys1.identity_keypair().public_key().to_string(),
|
||||||
|
client_keys2.identity_keypair().public_key().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
client_keys1.identity_keypair().private_key().to_string(),
|
||||||
|
client_keys2.identity_keypair().private_key().to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_master_key_different_indices() {
|
||||||
|
// Changing the index should yield a different identity key.
|
||||||
|
let seed = [5u8; 32];
|
||||||
|
let mut rng = ChaCha20Rng::from_seed(seed);
|
||||||
|
|
||||||
|
let master_key = b"same secret key";
|
||||||
|
let salt = b"same-salt";
|
||||||
|
|
||||||
|
let derivation_material1 = DerivationMaterial::new(master_key, 1, salt);
|
||||||
|
let derivation_material2 = DerivationMaterial::new(master_key, 2, salt);
|
||||||
|
|
||||||
|
let client_keys1 = ClientKeys::from_master_key(&mut rng, &derivation_material1)
|
||||||
|
.expect("Failed to create client keys for index 1");
|
||||||
|
let client_keys2 = ClientKeys::from_master_key(&mut rng, &derivation_material2)
|
||||||
|
.expect("Failed to create client keys for index 2");
|
||||||
|
|
||||||
|
assert_ne!(
|
||||||
|
client_keys1.identity_keypair().public_key().to_string(),
|
||||||
|
client_keys2.identity_keypair().public_key().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_ne!(
|
||||||
|
client_keys1.identity_keypair().private_key().to_string(),
|
||||||
|
client_keys2.identity_keypair().private_key().to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,18 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
||||||
use crate::{spawn_future, ForgetMe};
|
use crate::error::ClientCoreError;
|
||||||
|
use crate::spawn_future;
|
||||||
use log::*;
|
use log::*;
|
||||||
use nym_gateway_requests::ClientRequest;
|
use nym_gateway_requests::ClientRequest;
|
||||||
use nym_sphinx::forwarding::packet::MixPacket;
|
use nym_sphinx::forwarding::packet::MixPacket;
|
||||||
|
use nym_task::TaskClient;
|
||||||
|
use transceiver::ErasedGatewayError;
|
||||||
|
|
||||||
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
|
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
|
||||||
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
|
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
|
||||||
|
pub type ClientRequestReceiver = tokio::sync::mpsc::Receiver<ClientRequest>;
|
||||||
|
pub type ClientRequestSender = tokio::sync::mpsc::Sender<ClientRequest>;
|
||||||
|
|
||||||
pub mod transceiver;
|
pub mod transceiver;
|
||||||
|
|
||||||
@@ -23,52 +28,73 @@ pub struct MixTrafficController {
|
|||||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||||
|
|
||||||
mix_rx: BatchMixMessageReceiver,
|
mix_rx: BatchMixMessageReceiver,
|
||||||
|
client_rx: ClientRequestReceiver,
|
||||||
|
|
||||||
// TODO: this is temporary work-around.
|
// TODO: this is temporary work-around.
|
||||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||||
consecutive_gateway_failure_count: usize,
|
consecutive_gateway_failure_count: usize,
|
||||||
forget_me: ForgetMe,
|
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MixTrafficController {
|
impl MixTrafficController {
|
||||||
pub fn new<T>(
|
pub fn new<T>(
|
||||||
gateway_transceiver: T,
|
gateway_transceiver: T,
|
||||||
forget_me: ForgetMe,
|
task_client: TaskClient,
|
||||||
) -> (MixTrafficController, BatchMixMessageSender)
|
) -> (
|
||||||
|
MixTrafficController,
|
||||||
|
BatchMixMessageSender,
|
||||||
|
ClientRequestSender,
|
||||||
|
)
|
||||||
where
|
where
|
||||||
T: GatewayTransceiver + Send + 'static,
|
T: GatewayTransceiver + Send + 'static,
|
||||||
{
|
{
|
||||||
let (message_sender, message_receiver) =
|
let (message_sender, message_receiver) =
|
||||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||||
|
|
||||||
|
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
|
||||||
|
|
||||||
(
|
(
|
||||||
MixTrafficController {
|
MixTrafficController {
|
||||||
gateway_transceiver: Box::new(gateway_transceiver),
|
gateway_transceiver: Box::new(gateway_transceiver),
|
||||||
mix_rx: message_receiver,
|
mix_rx: message_receiver,
|
||||||
|
client_rx: client_receiver,
|
||||||
consecutive_gateway_failure_count: 0,
|
consecutive_gateway_failure_count: 0,
|
||||||
forget_me,
|
task_client,
|
||||||
},
|
},
|
||||||
message_sender,
|
message_sender,
|
||||||
|
client_sender,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_dynamic(
|
pub fn new_dynamic(
|
||||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||||
forget_me: ForgetMe,
|
task_client: TaskClient,
|
||||||
) -> (MixTrafficController, BatchMixMessageSender) {
|
) -> (
|
||||||
|
MixTrafficController,
|
||||||
|
BatchMixMessageSender,
|
||||||
|
ClientRequestSender,
|
||||||
|
) {
|
||||||
let (message_sender, message_receiver) =
|
let (message_sender, message_receiver) =
|
||||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||||
|
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
|
||||||
(
|
(
|
||||||
MixTrafficController {
|
MixTrafficController {
|
||||||
gateway_transceiver,
|
gateway_transceiver,
|
||||||
mix_rx: message_receiver,
|
mix_rx: message_receiver,
|
||||||
|
client_rx: client_receiver,
|
||||||
consecutive_gateway_failure_count: 0,
|
consecutive_gateway_failure_count: 0,
|
||||||
forget_me,
|
task_client,
|
||||||
},
|
},
|
||||||
message_sender,
|
message_sender,
|
||||||
|
client_sender,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
debug_assert!(!mix_packets.is_empty());
|
||||||
|
|
||||||
let result = if mix_packets.len() == 1 {
|
let result = if mix_packets.len() == 1 {
|
||||||
@@ -80,64 +106,60 @@ impl MixTrafficController {
|
|||||||
.await
|
.await
|
||||||
};
|
};
|
||||||
|
|
||||||
match result {
|
if result.is_err() {
|
||||||
Err(err) => {
|
self.consecutive_gateway_failure_count += 1;
|
||||||
error!("Failed to send sphinx packet(s) to the gateway: {err}");
|
} else {
|
||||||
self.consecutive_gateway_failure_count += 1;
|
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
|
||||||
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
|
self.consecutive_gateway_failure_count = 0;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
pub fn start(mut self) {
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
debug!("Started MixTrafficController with graceful shutdown support");
|
debug!("Started MixTrafficController with graceful shutdown support");
|
||||||
|
|
||||||
loop {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
mix_packets = self.mix_rx.recv() => match mix_packets {
|
mix_packets = self.mix_rx.recv() => match mix_packets {
|
||||||
Some(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?
|
||||||
|
self.task_client.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
log::trace!("MixTrafficController: Stopping since channel closed");
|
log::trace!("MixTrafficController: Stopping since channel closed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = shutdown.recv_with_delay() => {
|
client_request = self.client_rx.recv() => match client_request {
|
||||||
|
Some(client_request) => {
|
||||||
|
match self.gateway_transceiver.send_client_request(client_request).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => error!("Failed to send client request: {}", e),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log::trace!("MixTrafficController, client request channel closed");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ = self.task_client.recv() => {
|
||||||
log::trace!("MixTrafficController: Received shutdown");
|
log::trace!("MixTrafficController: Received shutdown");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shutdown.recv_timeout().await;
|
self.task_client.recv_timeout().await;
|
||||||
|
|
||||||
if self.forget_me.any() {
|
|
||||||
log::info!("Sending forget me request to the gateway");
|
|
||||||
match self
|
|
||||||
.gateway_transceiver
|
|
||||||
.send_client_request(ClientRequest::ForgetMe {
|
|
||||||
client: self.forget_me.client(),
|
|
||||||
stats: self.forget_me.stats(),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {
|
|
||||||
log::info!("Successfully sent forget me request to the gateway");
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::error!("Failed to send forget me request to the gateway: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::debug!("MixTrafficController: Exiting");
|
log::debug!("MixTrafficController: Exiting");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
message: ClientRequest,
|
message: ClientRequest,
|
||||||
) -> Result<(), GatewayClientError> {
|
) -> Result<(), GatewayClientError> {
|
||||||
(**self).send_client_request(message).await
|
let _ = (**self).send_client_request(message.clone()).await?;
|
||||||
|
log::debug!("Sent client request: {:?}", message);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,14 +145,7 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
message: ClientRequest,
|
message: ClientRequest,
|
||||||
) -> Result<(), GatewayClientError> {
|
) -> Result<(), GatewayClientError> {
|
||||||
if let Some(shared_key) = self.gateway_client.shared_key() {
|
self.gateway_client.send_client_request(message).await
|
||||||
self.gateway_client
|
|
||||||
.send_websocket_message(message.encrypt(&*shared_key)?)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(GatewayClientError::ConnectionInInvalidState)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-6
@@ -11,6 +11,7 @@ use nym_sphinx::{
|
|||||||
acknowledgements::{identifier::recover_identifier, AckKey},
|
acknowledgements::{identifier::recover_identifier, AckKey},
|
||||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||||
};
|
};
|
||||||
|
use nym_task::TaskClient;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Module responsible for listening for any data resembling acknowledgements from the network
|
/// Module responsible for listening for any data resembling acknowledgements from the network
|
||||||
@@ -20,6 +21,7 @@ pub(super) struct AcknowledgementListener {
|
|||||||
ack_receiver: AcknowledgementReceiver,
|
ack_receiver: AcknowledgementReceiver,
|
||||||
action_sender: AckActionSender,
|
action_sender: AckActionSender,
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AcknowledgementListener {
|
impl AcknowledgementListener {
|
||||||
@@ -28,12 +30,14 @@ impl AcknowledgementListener {
|
|||||||
ack_receiver: AcknowledgementReceiver,
|
ack_receiver: AcknowledgementReceiver,
|
||||||
action_sender: AckActionSender,
|
action_sender: AckActionSender,
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
AcknowledgementListener {
|
AcknowledgementListener {
|
||||||
ack_key,
|
ack_key,
|
||||||
ack_receiver,
|
ack_receiver,
|
||||||
action_sender,
|
action_sender,
|
||||||
stats_tx,
|
stats_tx,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,9 +68,14 @@ impl AcknowledgementListener {
|
|||||||
trace!("Received {} from the mix network", frag_id);
|
trace!("Received {} from the mix network", frag_id);
|
||||||
self.stats_tx
|
self.stats_tx
|
||||||
.report(PacketStatisticsEvent::RealAckReceived(ack_content.len()).into());
|
.report(PacketStatisticsEvent::RealAckReceived(ack_content.len()).into());
|
||||||
self.action_sender
|
if let Err(err) = self
|
||||||
|
.action_sender
|
||||||
.unbounded_send(Action::new_remove(frag_id))
|
.unbounded_send(Action::new_remove(frag_id))
|
||||||
.unwrap();
|
{
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("Failed to send remove action to action controller: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_ack_receiver_item(&mut self, item: Vec<Vec<u8>>) {
|
async fn handle_ack_receiver_item(&mut self, item: Vec<Vec<u8>>) {
|
||||||
@@ -76,10 +85,10 @@ impl AcknowledgementListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
pub(super) async fn run(&mut self) {
|
||||||
debug!("Started AcknowledgementListener with graceful shutdown support");
|
debug!("Started AcknowledgementListener with graceful shutdown support");
|
||||||
|
|
||||||
while !shutdown.is_shutdown() {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
acks = self.ack_receiver.next() => match acks {
|
acks = self.ack_receiver.next() => match acks {
|
||||||
Some(acks) => self.handle_ack_receiver_item(acks).await,
|
Some(acks) => self.handle_ack_receiver_item(acks).await,
|
||||||
@@ -88,12 +97,12 @@ impl AcknowledgementListener {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = shutdown.recv_with_delay() => {
|
_ = self.task_client.recv() => {
|
||||||
log::trace!("AcknowledgementListener: Received shutdown");
|
log::trace!("AcknowledgementListener: Received shutdown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shutdown.recv_timeout().await;
|
self.task_client.recv_timeout().await;
|
||||||
log::debug!("AcknowledgementListener: Exiting");
|
log::debug!("AcknowledgementListener: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-16
@@ -9,6 +9,7 @@ use log::*;
|
|||||||
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey};
|
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey};
|
||||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||||
use nym_sphinx::Delay as SphinxDelay;
|
use nym_sphinx::Delay as SphinxDelay;
|
||||||
|
use nym_task::TaskClient;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -101,6 +102,8 @@ pub(super) struct ActionController {
|
|||||||
|
|
||||||
/// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements.
|
/// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements.
|
||||||
retransmission_sender: RetransmissionRequestSender,
|
retransmission_sender: RetransmissionRequestSender,
|
||||||
|
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActionController {
|
impl ActionController {
|
||||||
@@ -108,6 +111,7 @@ impl ActionController {
|
|||||||
config: Config,
|
config: Config,
|
||||||
retransmission_sender: RetransmissionRequestSender,
|
retransmission_sender: RetransmissionRequestSender,
|
||||||
incoming_actions: AckActionReceiver,
|
incoming_actions: AckActionReceiver,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ActionController {
|
ActionController {
|
||||||
config,
|
config,
|
||||||
@@ -115,6 +119,7 @@ impl ActionController {
|
|||||||
pending_acks_timers: NonExhaustiveDelayQueue::new(),
|
pending_acks_timers: NonExhaustiveDelayQueue::new(),
|
||||||
incoming_actions,
|
incoming_actions,
|
||||||
retransmission_sender,
|
retransmission_sender,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,11 +221,7 @@ impl ActionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// note: when the entry expires it's automatically removed from pending_acks_timers
|
// note: when the entry expires it's automatically removed from pending_acks_timers
|
||||||
fn handle_expired_ack_timer(
|
fn handle_expired_ack_timer(&mut self, expired_ack: Expired<FragmentIdentifier>) {
|
||||||
&mut self,
|
|
||||||
expired_ack: Expired<FragmentIdentifier>,
|
|
||||||
task_client: &mut nym_task::TaskClient,
|
|
||||||
) {
|
|
||||||
// I'm honestly not sure how to handle it, because getting it means other things in our
|
// I'm honestly not sure how to handle it, because getting it means other things in our
|
||||||
// system are already misbehaving. If we ever see this panic, then I guess we should worry
|
// system are already misbehaving. If we ever see this panic, then I guess we should worry
|
||||||
// about it. Perhaps just reschedule it at later point?
|
// about it. Perhaps just reschedule it at later point?
|
||||||
@@ -238,15 +239,13 @@ impl ActionController {
|
|||||||
// downgrading an arc and then upgrading vs cloning is difference of 30ns vs 15ns
|
// downgrading an arc and then upgrading vs cloning is difference of 30ns vs 15ns
|
||||||
// so it's literally a NO difference while it might prevent us from unnecessarily
|
// so it's literally a NO difference while it might prevent us from unnecessarily
|
||||||
// resending data (in maybe 1 in 1 million cases, but it's something)
|
// resending data (in maybe 1 in 1 million cases, but it's something)
|
||||||
if self
|
if let Err(err) = self
|
||||||
.retransmission_sender
|
.retransmission_sender
|
||||||
.unbounded_send(Arc::downgrade(pending_ack_data))
|
.unbounded_send(Arc::downgrade(pending_ack_data))
|
||||||
.is_err()
|
|
||||||
{
|
{
|
||||||
assert!(
|
if !self.task_client.is_shutdown_poll() {
|
||||||
task_client.is_shutdown_poll(),
|
log::error!("Failed to send pending ack for retransmission: {err}");
|
||||||
"Failed to send pending ack for retransmission"
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// this shouldn't cause any issues but shouldn't have happened to begin with!
|
// this shouldn't cause any issues but shouldn't have happened to begin with!
|
||||||
@@ -265,10 +264,10 @@ impl ActionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
pub(super) async fn run(&mut self) {
|
||||||
debug!("Started ActionController with graceful shutdown support");
|
debug!("Started ActionController with graceful shutdown support");
|
||||||
|
|
||||||
loop {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
action = self.incoming_actions.next() => match action {
|
action = self.incoming_actions.next() => match action {
|
||||||
Some(action) => self.process_action(action),
|
Some(action) => self.process_action(action),
|
||||||
@@ -280,19 +279,19 @@ impl ActionController {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
expired_ack = self.pending_acks_timers.next() => match expired_ack {
|
expired_ack = self.pending_acks_timers.next() => match expired_ack {
|
||||||
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack, &mut shutdown),
|
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack),
|
||||||
None => {
|
None => {
|
||||||
log::trace!("ActionController: Stopping since ack channel closed");
|
log::trace!("ActionController: Stopping since ack channel closed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = shutdown.recv_with_delay() => {
|
_ = self.task_client.recv() => {
|
||||||
log::trace!("ActionController: Received shutdown");
|
log::trace!("ActionController: Received shutdown");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shutdown.recv_timeout().await;
|
self.task_client.recv_timeout().await;
|
||||||
log::debug!("ActionController: Exiting");
|
log::debug!("ActionController: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-5
@@ -11,6 +11,7 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
|||||||
use nym_sphinx::forwarding::packet::MixPacket;
|
use nym_sphinx::forwarding::packet::MixPacket;
|
||||||
use nym_sphinx::params::PacketType;
|
use nym_sphinx::params::PacketType;
|
||||||
use nym_task::connections::TransmissionLane;
|
use nym_task::connections::TransmissionLane;
|
||||||
|
use nym_task::TaskClient;
|
||||||
use rand::{CryptoRng, Rng};
|
use rand::{CryptoRng, Rng};
|
||||||
|
|
||||||
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
|
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
|
||||||
@@ -23,6 +24,7 @@ where
|
|||||||
input_receiver: InputMessageReceiver,
|
input_receiver: InputMessageReceiver,
|
||||||
message_handler: MessageHandler<R>,
|
message_handler: MessageHandler<R>,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> InputMessageListener<R>
|
impl<R> InputMessageListener<R>
|
||||||
@@ -36,11 +38,13 @@ where
|
|||||||
input_receiver: InputMessageReceiver,
|
input_receiver: InputMessageReceiver,
|
||||||
message_handler: MessageHandler<R>,
|
message_handler: MessageHandler<R>,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
InputMessageListener {
|
InputMessageListener {
|
||||||
input_receiver,
|
input_receiver,
|
||||||
message_handler,
|
message_handler,
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,8 +67,14 @@ where
|
|||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
) {
|
) {
|
||||||
// offload reply handling to the dedicated task
|
// offload reply handling to the dedicated task
|
||||||
self.reply_controller_sender
|
if let Err(err) = self
|
||||||
|
.reply_controller_sender
|
||||||
.send_reply(recipient_tag, data, lane)
|
.send_reply(recipient_tag, data, lane)
|
||||||
|
{
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("failed to send a reply - {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_plain_message(
|
async fn handle_plain_message(
|
||||||
@@ -164,10 +174,10 @@ where
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
pub(super) async fn run(&mut self) {
|
||||||
debug!("Started InputMessageListener with graceful shutdown support");
|
debug!("Started InputMessageListener with graceful shutdown support");
|
||||||
|
|
||||||
while !shutdown.is_shutdown() {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
input_msg = self.input_receiver.recv() => match input_msg {
|
input_msg = self.input_receiver.recv() => match input_msg {
|
||||||
Some(input_msg) => {
|
Some(input_msg) => {
|
||||||
@@ -178,12 +188,12 @@ where
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = shutdown.recv_with_delay() => {
|
_ = self.task_client.recv() => {
|
||||||
log::trace!("InputMessageListener: Received shutdown");
|
log::trace!("InputMessageListener: Received shutdown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shutdown.recv_timeout().await;
|
self.task_client.recv_timeout().await;
|
||||||
log::debug!("InputMessageListener: Exiting");
|
log::debug!("InputMessageListener: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-27
@@ -24,6 +24,7 @@ use nym_sphinx::{
|
|||||||
Delay as SphinxDelay,
|
Delay as SphinxDelay,
|
||||||
};
|
};
|
||||||
use nym_statistics_common::clients::ClientStatsSender;
|
use nym_statistics_common::clients::ClientStatsSender;
|
||||||
|
use nym_task::TaskClient;
|
||||||
use rand::{CryptoRng, Rng};
|
use rand::{CryptoRng, Rng};
|
||||||
use std::{
|
use std::{
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
@@ -66,7 +67,7 @@ pub(crate) enum PacketDestination {
|
|||||||
|
|
||||||
/// Structure representing a data `Fragment` that is on-route to the specified `Recipient`
|
/// Structure representing a data `Fragment` that is on-route to the specified `Recipient`
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct PendingAcknowledgement {
|
pub struct PendingAcknowledgement {
|
||||||
message_chunk: Fragment,
|
message_chunk: Fragment,
|
||||||
delay: SphinxDelay,
|
delay: SphinxDelay,
|
||||||
destination: PacketDestination,
|
destination: PacketDestination,
|
||||||
@@ -216,6 +217,7 @@ where
|
|||||||
message_handler: MessageHandler<R>,
|
message_handler: MessageHandler<R>,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
||||||
|
|
||||||
@@ -225,6 +227,7 @@ where
|
|||||||
action_config,
|
action_config,
|
||||||
retransmission_tx,
|
retransmission_tx,
|
||||||
connectors.ack_action_receiver,
|
connectors.ack_action_receiver,
|
||||||
|
task_client.fork("action_controller"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// will listen for any acks coming from the network
|
// will listen for any acks coming from the network
|
||||||
@@ -233,6 +236,7 @@ where
|
|||||||
connectors.ack_receiver,
|
connectors.ack_receiver,
|
||||||
connectors.ack_action_sender.clone(),
|
connectors.ack_action_sender.clone(),
|
||||||
stats_tx,
|
stats_tx,
|
||||||
|
task_client.fork("acknowledgement_listener"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// will listen for any new messages from the client
|
// will listen for any new messages from the client
|
||||||
@@ -240,6 +244,7 @@ where
|
|||||||
connectors.input_receiver,
|
connectors.input_receiver,
|
||||||
message_handler.clone(),
|
message_handler.clone(),
|
||||||
reply_controller_sender.clone(),
|
reply_controller_sender.clone(),
|
||||||
|
task_client.fork("input_message_listener"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// will listen for any ack timeouts and trigger retransmission
|
// will listen for any ack timeouts and trigger retransmission
|
||||||
@@ -249,12 +254,16 @@ where
|
|||||||
message_handler,
|
message_handler,
|
||||||
retransmission_rx,
|
retransmission_rx,
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
|
task_client.fork("retransmission_request_listener"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// will listen for events indicating the packet was sent through the network so that
|
// will listen for events indicating the packet was sent through the network so that
|
||||||
// the retransmission timer should be started.
|
// the retransmission timer should be started.
|
||||||
let sent_notification_listener =
|
let sent_notification_listener = SentNotificationListener::new(
|
||||||
SentNotificationListener::new(connectors.sent_notifier, connectors.ack_action_sender);
|
connectors.sent_notifier,
|
||||||
|
connectors.ack_action_sender,
|
||||||
|
task_client.with_suffix("sent_notification_listener"),
|
||||||
|
);
|
||||||
|
|
||||||
AcknowledgementController {
|
AcknowledgementController {
|
||||||
acknowledgement_listener,
|
acknowledgement_listener,
|
||||||
@@ -265,53 +274,35 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn start_with_shutdown(
|
pub(super) fn start(self, packet_type: PacketType) {
|
||||||
self,
|
|
||||||
shutdown: nym_task::TaskClient,
|
|
||||||
packet_type: PacketType,
|
|
||||||
) {
|
|
||||||
let mut acknowledgement_listener = self.acknowledgement_listener;
|
let mut acknowledgement_listener = self.acknowledgement_listener;
|
||||||
let mut input_message_listener = self.input_message_listener;
|
let mut input_message_listener = self.input_message_listener;
|
||||||
let mut retransmission_request_listener = self.retransmission_request_listener;
|
let mut retransmission_request_listener = self.retransmission_request_listener;
|
||||||
let mut sent_notification_listener = self.sent_notification_listener;
|
let mut sent_notification_listener = self.sent_notification_listener;
|
||||||
let mut action_controller = self.action_controller;
|
let mut action_controller = self.action_controller;
|
||||||
|
|
||||||
let shutdown_handle = shutdown.fork("acknowledgement_listener");
|
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
acknowledgement_listener
|
acknowledgement_listener.run().await;
|
||||||
.run_with_shutdown(shutdown_handle)
|
|
||||||
.await;
|
|
||||||
debug!("The acknowledgement listener has finished execution!");
|
debug!("The acknowledgement listener has finished execution!");
|
||||||
});
|
});
|
||||||
|
|
||||||
let shutdown_handle = shutdown.fork("input_message_listener");
|
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
input_message_listener
|
input_message_listener.run().await;
|
||||||
.run_with_shutdown(shutdown_handle)
|
|
||||||
.await;
|
|
||||||
debug!("The input listener has finished execution!");
|
debug!("The input listener has finished execution!");
|
||||||
});
|
});
|
||||||
|
|
||||||
let shutdown_handle = shutdown.fork("retransmission_request_listener");
|
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
retransmission_request_listener
|
retransmission_request_listener.run(packet_type).await;
|
||||||
.run_with_shutdown(shutdown_handle, packet_type)
|
|
||||||
.await;
|
|
||||||
debug!("The retransmission request listener has finished execution!");
|
debug!("The retransmission request listener has finished execution!");
|
||||||
});
|
});
|
||||||
|
|
||||||
let shutdown_handle = shutdown.fork("sent_notification_listener");
|
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
sent_notification_listener
|
sent_notification_listener.run().await;
|
||||||
.run_with_shutdown(shutdown_handle)
|
|
||||||
.await;
|
|
||||||
debug!("The sent notification listener has finished execution!");
|
debug!("The sent notification listener has finished execution!");
|
||||||
});
|
});
|
||||||
|
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
action_controller
|
action_controller.run().await;
|
||||||
.run_with_shutdown(shutdown.with_suffix("action_controller"))
|
|
||||||
.await;
|
|
||||||
debug!("The controller has finished execution!");
|
debug!("The controller has finished execution!");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-17
@@ -14,7 +14,7 @@ use log::*;
|
|||||||
use nym_sphinx::chunking::fragment::Fragment;
|
use nym_sphinx::chunking::fragment::Fragment;
|
||||||
use nym_sphinx::preparer::PreparedFragment;
|
use nym_sphinx::preparer::PreparedFragment;
|
||||||
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
|
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
|
||||||
use nym_task::connections::TransmissionLane;
|
use nym_task::{connections::TransmissionLane, TaskClient};
|
||||||
use rand::{CryptoRng, Rng};
|
use rand::{CryptoRng, Rng};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
@@ -25,6 +25,7 @@ pub(super) struct RetransmissionRequestListener<R> {
|
|||||||
message_handler: MessageHandler<R>,
|
message_handler: MessageHandler<R>,
|
||||||
request_receiver: RetransmissionRequestReceiver,
|
request_receiver: RetransmissionRequestReceiver,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> RetransmissionRequestListener<R>
|
impl<R> RetransmissionRequestListener<R>
|
||||||
@@ -37,6 +38,7 @@ where
|
|||||||
message_handler: MessageHandler<R>,
|
message_handler: MessageHandler<R>,
|
||||||
request_receiver: RetransmissionRequestReceiver,
|
request_receiver: RetransmissionRequestReceiver,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
RetransmissionRequestListener {
|
RetransmissionRequestListener {
|
||||||
maximum_retransmissions,
|
maximum_retransmissions,
|
||||||
@@ -44,6 +46,7 @@ where
|
|||||||
message_handler,
|
message_handler,
|
||||||
request_receiver,
|
request_receiver,
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +82,12 @@ where
|
|||||||
if let Some(limit) = self.maximum_retransmissions {
|
if let Some(limit) = self.maximum_retransmissions {
|
||||||
if timed_out_ack.retransmissions >= limit {
|
if timed_out_ack.retransmissions >= limit {
|
||||||
warn!("reached maximum number of allowed retransmissions for the packet");
|
warn!("reached maximum number of allowed retransmissions for the packet");
|
||||||
self.action_sender
|
if let Err(err) = self
|
||||||
|
.action_sender
|
||||||
.unbounded_send(Action::new_remove(frag_id))
|
.unbounded_send(Action::new_remove(frag_id))
|
||||||
.unwrap();
|
{
|
||||||
|
error!("Failed to send remove action to the controller: {err}");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,11 +99,16 @@ where
|
|||||||
} => {
|
} => {
|
||||||
// if this is retransmission for reply, offload it to the dedicated task
|
// if this is retransmission for reply, offload it to the dedicated task
|
||||||
// that deals with all the surbs
|
// that deals with all the surbs
|
||||||
return self.reply_controller_sender.send_retransmission_data(
|
if let Err(err) = self.reply_controller_sender.send_retransmission_data(
|
||||||
*recipient_tag,
|
*recipient_tag,
|
||||||
weak_timed_out_ack,
|
weak_timed_out_ack,
|
||||||
*extra_surb_request,
|
*extra_surb_request,
|
||||||
);
|
) {
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("Failed to send retransmission data to the reply controller: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
PacketDestination::KnownRecipient(recipient) => {
|
PacketDestination::KnownRecipient(recipient) => {
|
||||||
self.prepare_normal_retransmission_chunk(
|
self.prepare_normal_retransmission_chunk(
|
||||||
@@ -114,9 +125,12 @@ where
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("Could not retransmit the packet - {err}");
|
warn!("Could not retransmit the packet - {err}");
|
||||||
// we NEED to start timer here otherwise we will have this guy permanently stuck in memory
|
// we NEED to start timer here otherwise we will have this guy permanently stuck in memory
|
||||||
self.action_sender
|
if let Err(err) = self
|
||||||
|
.action_sender
|
||||||
.unbounded_send(Action::new_start_timer(frag_id))
|
.unbounded_send(Action::new_start_timer(frag_id))
|
||||||
.unwrap();
|
{
|
||||||
|
error!("Failed to send start timer action to the controller: {err}");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -141,9 +155,14 @@ where
|
|||||||
// is sent to the `OutQueueControl` and has gone through its internal queue
|
// is sent to the `OutQueueControl` and has gone through its internal queue
|
||||||
// with the additional poisson delay.
|
// with the additional poisson delay.
|
||||||
// And since Actions are executed in order `UpdateTimer` will HAVE TO be executed before `StartTimer`
|
// And since Actions are executed in order `UpdateTimer` will HAVE TO be executed before `StartTimer`
|
||||||
self.action_sender
|
if let Err(err) = self
|
||||||
|
.action_sender
|
||||||
.unbounded_send(Action::new_update_pending_ack(frag_id, new_delay))
|
.unbounded_send(Action::new_update_pending_ack(frag_id, new_delay))
|
||||||
.unwrap();
|
{
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("Failed to send update pending ack action to the controller: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// send to `OutQueueControl` to eventually send to the mix network
|
// send to `OutQueueControl` to eventually send to the mix network
|
||||||
self.message_handler
|
self.message_handler
|
||||||
@@ -157,14 +176,10 @@ where
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn run_with_shutdown(
|
pub(super) async fn run(&mut self, packet_type: PacketType) {
|
||||||
&mut self,
|
|
||||||
mut shutdown: nym_task::TaskClient,
|
|
||||||
packet_type: PacketType,
|
|
||||||
) {
|
|
||||||
debug!("Started RetransmissionRequestListener with graceful shutdown support");
|
debug!("Started RetransmissionRequestListener with graceful shutdown support");
|
||||||
|
|
||||||
while !shutdown.is_shutdown() {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
|
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
|
||||||
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack, packet_type).await,
|
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack, packet_type).await,
|
||||||
@@ -173,12 +188,12 @@ where
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = shutdown.recv() => {
|
_ = self.task_client.recv() => {
|
||||||
log::trace!("RetransmissionRequestListener: Received shutdown");
|
log::trace!("RetransmissionRequestListener: Received shutdown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shutdown.recv_timeout().await;
|
self.task_client.recv_timeout().await;
|
||||||
log::debug!("RetransmissionRequestListener: Exiting");
|
log::debug!("RetransmissionRequestListener: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-6
@@ -6,6 +6,7 @@ use super::SentPacketNotificationReceiver;
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use log::*;
|
use log::*;
|
||||||
use nym_sphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
use nym_sphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||||
|
use nym_task::TaskClient;
|
||||||
|
|
||||||
/// Module responsible for starting up retransmission timers.
|
/// Module responsible for starting up retransmission timers.
|
||||||
/// It is required because when we send our packet to the `real traffic stream` controlled
|
/// It is required because when we send our packet to the `real traffic stream` controlled
|
||||||
@@ -14,16 +15,19 @@ use nym_sphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
|||||||
pub(super) struct SentNotificationListener {
|
pub(super) struct SentNotificationListener {
|
||||||
sent_notifier: SentPacketNotificationReceiver,
|
sent_notifier: SentPacketNotificationReceiver,
|
||||||
action_sender: AckActionSender,
|
action_sender: AckActionSender,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SentNotificationListener {
|
impl SentNotificationListener {
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
sent_notifier: SentPacketNotificationReceiver,
|
sent_notifier: SentPacketNotificationReceiver,
|
||||||
action_sender: AckActionSender,
|
action_sender: AckActionSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
SentNotificationListener {
|
SentNotificationListener {
|
||||||
sent_notifier,
|
sent_notifier,
|
||||||
action_sender,
|
action_sender,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,15 +36,20 @@ impl SentNotificationListener {
|
|||||||
trace!("sent off a cover message - no need to start retransmission timer!");
|
trace!("sent off a cover message - no need to start retransmission timer!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.action_sender
|
if let Err(err) = self
|
||||||
|
.action_sender
|
||||||
.unbounded_send(Action::new_start_timer(frag_id))
|
.unbounded_send(Action::new_start_timer(frag_id))
|
||||||
.unwrap();
|
{
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("Failed to send start timer action to action controller: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
pub(super) async fn run(&mut self) {
|
||||||
debug!("Started SentNotificationListener with graceful shutdown support");
|
debug!("Started SentNotificationListener with graceful shutdown support");
|
||||||
|
|
||||||
loop {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
frag_id = self.sent_notifier.next() => match frag_id {
|
frag_id = self.sent_notifier.next() => match frag_id {
|
||||||
Some(frag_id) => {
|
Some(frag_id) => {
|
||||||
@@ -51,13 +60,13 @@ impl SentNotificationListener {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = shutdown.recv_with_delay() => {
|
_ = self.task_client.recv() => {
|
||||||
log::trace!("SentNotificationListener: Received shutdown");
|
log::trace!("SentNotificationListener: Received shutdown");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert!(shutdown.is_shutdown_poll());
|
assert!(self.task_client.is_shutdown_poll());
|
||||||
log::debug!("SentNotificationListener: Exiting");
|
log::debug!("SentNotificationListener: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use nym_sphinx::params::{PacketSize, PacketType};
|
|||||||
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
|
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
|
||||||
use nym_sphinx::Delay;
|
use nym_sphinx::Delay;
|
||||||
use nym_task::connections::TransmissionLane;
|
use nym_task::connections::TransmissionLane;
|
||||||
|
use nym_task::TaskClient;
|
||||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||||
use rand::{CryptoRng, Rng};
|
use rand::{CryptoRng, Rng};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -149,12 +150,14 @@ pub(crate) struct MessageHandler<R> {
|
|||||||
topology_access: TopologyAccessor,
|
topology_access: TopologyAccessor,
|
||||||
reply_key_storage: SentReplyKeys,
|
reply_key_storage: SentReplyKeys,
|
||||||
tag_storage: UsedSenderTags,
|
tag_storage: UsedSenderTags,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> MessageHandler<R>
|
impl<R> MessageHandler<R>
|
||||||
where
|
where
|
||||||
R: CryptoRng + Rng,
|
R: CryptoRng + Rng,
|
||||||
{
|
{
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
config: Config,
|
config: Config,
|
||||||
rng: R,
|
rng: R,
|
||||||
@@ -163,6 +166,7 @@ where
|
|||||||
topology_access: TopologyAccessor,
|
topology_access: TopologyAccessor,
|
||||||
reply_key_storage: SentReplyKeys,
|
reply_key_storage: SentReplyKeys,
|
||||||
tag_storage: UsedSenderTags,
|
tag_storage: UsedSenderTags,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
R: Copy,
|
R: Copy,
|
||||||
@@ -183,6 +187,7 @@ where
|
|||||||
topology_access,
|
topology_access,
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
tag_storage,
|
tag_storage,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,15 +614,25 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_ack_delay(&self, id: FragmentIdentifier, new_delay: Delay) {
|
pub(crate) fn update_ack_delay(&self, id: FragmentIdentifier, new_delay: Delay) {
|
||||||
self.action_sender
|
if let Err(err) = self
|
||||||
|
.action_sender
|
||||||
.unbounded_send(Action::UpdatePendingAck(id, new_delay))
|
.unbounded_send(Action::UpdatePendingAck(id, new_delay))
|
||||||
.expect("action control task has died")
|
{
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("Failed to send update action to the controller: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn insert_pending_acks(&self, pending_acks: Vec<PendingAcknowledgement>) {
|
pub(crate) fn insert_pending_acks(&self, pending_acks: Vec<PendingAcknowledgement>) {
|
||||||
self.action_sender
|
if let Err(err) = self
|
||||||
|
.action_sender
|
||||||
.unbounded_send(Action::new_insert(pending_acks))
|
.unbounded_send(Action::new_insert(pending_acks))
|
||||||
.expect("action control task has died")
|
{
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("Failed to send insert action to the controller: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tells real message sender (with the poisson timer) to send this to the mix network
|
// tells real message sender (with the poisson timer) to send this to the mix network
|
||||||
@@ -626,9 +641,14 @@ where
|
|||||||
messages: Vec<RealMessage>,
|
messages: Vec<RealMessage>,
|
||||||
transmission_lane: TransmissionLane,
|
transmission_lane: TransmissionLane,
|
||||||
) {
|
) {
|
||||||
self.real_message_sender
|
if let Err(err) = self
|
||||||
|
.real_message_sender
|
||||||
.send((messages, transmission_lane))
|
.send((messages, transmission_lane))
|
||||||
.await
|
.await
|
||||||
.expect("real message receiver task (OutQueueControl) has died");
|
{
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("Failed to forward messages to the real message sender: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ use nym_sphinx::addressing::clients::Recipient;
|
|||||||
use nym_sphinx::params::PacketType;
|
use nym_sphinx::params::PacketType;
|
||||||
use nym_statistics_common::clients::ClientStatsSender;
|
use nym_statistics_common::clients::ClientStatsSender;
|
||||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||||
|
use nym_task::TaskClient;
|
||||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -147,6 +148,7 @@ impl RealMessagesController<OsRng> {
|
|||||||
lane_queue_lengths: LaneQueueLengths,
|
lane_queue_lengths: LaneQueueLengths,
|
||||||
client_connection_rx: ConnectionCommandReceiver,
|
client_connection_rx: ConnectionCommandReceiver,
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rng = OsRng;
|
let rng = OsRng;
|
||||||
|
|
||||||
@@ -177,6 +179,7 @@ impl RealMessagesController<OsRng> {
|
|||||||
topology_access.clone(),
|
topology_access.clone(),
|
||||||
reply_storage.key_storage(),
|
reply_storage.key_storage(),
|
||||||
reply_storage.tags_storage(),
|
reply_storage.tags_storage(),
|
||||||
|
task_client.fork("message_handler"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let ack_control = AcknowledgementController::new(
|
let ack_control = AcknowledgementController::new(
|
||||||
@@ -186,6 +189,7 @@ impl RealMessagesController<OsRng> {
|
|||||||
message_handler.clone(),
|
message_handler.clone(),
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
stats_tx.clone(),
|
stats_tx.clone(),
|
||||||
|
task_client.fork("ack_control"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let reply_control = ReplyController::new(
|
let reply_control = ReplyController::new(
|
||||||
@@ -193,6 +197,7 @@ impl RealMessagesController<OsRng> {
|
|||||||
message_handler,
|
message_handler,
|
||||||
reply_storage,
|
reply_storage,
|
||||||
reply_controller_receiver,
|
reply_controller_receiver,
|
||||||
|
task_client.fork("reply_controller"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let out_queue_control = OutQueueControl::new(
|
let out_queue_control = OutQueueControl::new(
|
||||||
@@ -205,6 +210,7 @@ impl RealMessagesController<OsRng> {
|
|||||||
lane_queue_lengths,
|
lane_queue_lengths,
|
||||||
client_connection_rx,
|
client_connection_rx,
|
||||||
stats_tx,
|
stats_tx,
|
||||||
|
task_client.with_suffix("out_queue_control"),
|
||||||
);
|
);
|
||||||
|
|
||||||
RealMessagesController {
|
RealMessagesController {
|
||||||
@@ -214,22 +220,20 @@ impl RealMessagesController<OsRng> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient, packet_type: PacketType) {
|
pub fn start(self, packet_type: PacketType) {
|
||||||
let mut out_queue_control = self.out_queue_control;
|
let mut out_queue_control = self.out_queue_control;
|
||||||
let ack_control = self.ack_control;
|
let ack_control = self.ack_control;
|
||||||
let mut reply_control = self.reply_control;
|
let mut reply_control = self.reply_control;
|
||||||
|
|
||||||
let shutdown_handle = shutdown.fork("out_queue_control");
|
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
out_queue_control.run_with_shutdown(shutdown_handle).await;
|
out_queue_control.run().await;
|
||||||
debug!("The out queue controller has finished execution!");
|
debug!("The out queue controller has finished execution!");
|
||||||
});
|
});
|
||||||
let shutdown_handle = shutdown.fork("reply_control");
|
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
reply_control.run_with_shutdown(shutdown_handle).await;
|
reply_control.run().await;
|
||||||
debug!("The reply controller has finished execution!");
|
debug!("The reply controller has finished execution!");
|
||||||
});
|
});
|
||||||
|
|
||||||
ack_control.start_with_shutdown(shutdown.with_suffix("ack_control"), packet_type);
|
ack_control.start(packet_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
|
|||||||
use nym_task::connections::{
|
use nym_task::connections::{
|
||||||
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
|
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||||
};
|
};
|
||||||
|
use nym_task::TaskClient;
|
||||||
use rand::{CryptoRng, Rng};
|
use rand::{CryptoRng, Rng};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -117,6 +118,8 @@ where
|
|||||||
|
|
||||||
/// Channel used for sending metrics events (specifically `PacketStatistics` events) to the metrics tracker.
|
/// Channel used for sending metrics events (specifically `PacketStatistics` events) to the metrics tracker.
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
|
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -176,6 +179,7 @@ where
|
|||||||
lane_queue_lengths: LaneQueueLengths,
|
lane_queue_lengths: LaneQueueLengths,
|
||||||
client_connection_rx: ConnectionCommandReceiver,
|
client_connection_rx: ConnectionCommandReceiver,
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
OutQueueControl {
|
OutQueueControl {
|
||||||
config,
|
config,
|
||||||
@@ -190,6 +194,7 @@ where
|
|||||||
client_connection_rx,
|
client_connection_rx,
|
||||||
lane_queue_lengths,
|
lane_queue_lengths,
|
||||||
stats_tx,
|
stats_tx,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +203,9 @@ where
|
|||||||
// queues and client load rather than the required delay. So realistically we can treat
|
// queues and client load rather than the required delay. So realistically we can treat
|
||||||
// whatever is about to happen as negligible additional delay.
|
// whatever is about to happen as negligible additional delay.
|
||||||
trace!("{} is about to get sent to the mixnet", frag_id);
|
trace!("{} is about to get sent to the mixnet", frag_id);
|
||||||
self.sent_notifier.unbounded_send(frag_id).unwrap();
|
if let Err(err) = self.sent_notifier.unbounded_send(frag_id) {
|
||||||
|
error!("Failed to notify about sent message: {err}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loop_cover_message_size(&mut self) -> PacketSize {
|
fn loop_cover_message_size(&mut self) -> PacketSize {
|
||||||
@@ -271,7 +278,9 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
|
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
|
||||||
log::error!("Failed to send: {err}");
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
log::error!("Failed to send: {err}");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let event = if fragment_id.is_some() {
|
let event = if fragment_id.is_some() {
|
||||||
PacketStatisticsEvent::RealPacketSent(packet_size)
|
PacketStatisticsEvent::RealPacketSent(packet_size)
|
||||||
@@ -504,21 +513,29 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn log_status(&self, shutdown: &mut nym_task::TaskClient) {
|
fn log_status(&self, shutdown: &mut TaskClient) {
|
||||||
use crate::error::ClientCoreStatusMessage;
|
use crate::error::ClientCoreStatusMessage;
|
||||||
|
|
||||||
let packets = self.transmission_buffer.total_size();
|
let packets = self.transmission_buffer.total_size();
|
||||||
let backlog = self.transmission_buffer.total_size_in_bytes() as f64 / 1024.0;
|
let lanes = self.transmission_buffer.lanes();
|
||||||
let lanes = self.transmission_buffer.num_lanes();
|
|
||||||
let mult = self.sending_delay_controller.current_multiplier();
|
let mult = self.sending_delay_controller.current_multiplier();
|
||||||
let delay = self.current_average_message_sending_delay().as_millis();
|
let delay = self.current_average_message_sending_delay().as_millis();
|
||||||
|
|
||||||
|
let lane_status = lanes
|
||||||
|
.iter()
|
||||||
|
.map(|lane_name| {
|
||||||
|
let lane_length = self.transmission_buffer.lane_length(lane_name).unwrap_or(0);
|
||||||
|
format!("{lane_name:?}: {lane_length}")
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
let status_str = if self.config.traffic.disable_main_poisson_packet_distribution {
|
let status_str = if self.config.traffic.disable_main_poisson_packet_distribution {
|
||||||
format!("Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, no delay")
|
format!("Packet backlog: {lane_status}, no delay")
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!("Packet backlog: {lane_status}, avg delay: {delay}ms ({mult})")
|
||||||
"Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, avg delay: {delay}ms ({mult})"
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if packets > 1000 {
|
if packets > 1000 {
|
||||||
log::warn!("{status_str}");
|
log::warn!("{status_str}");
|
||||||
} else if packets > 0 {
|
} else if packets > 0 {
|
||||||
@@ -535,17 +552,19 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
pub(super) async fn run(&mut self) {
|
||||||
debug!("Started OutQueueControl with graceful shutdown support");
|
debug!("Started OutQueueControl with graceful shutdown support");
|
||||||
|
|
||||||
|
let mut shutdown = self.task_client.fork("select");
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
{
|
{
|
||||||
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
|
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
|
||||||
|
|
||||||
loop {
|
while !shutdown.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
biased;
|
biased;
|
||||||
_ = shutdown.recv_with_delay() => {
|
_ = shutdown.recv() => {
|
||||||
log::trace!("OutQueueControl: Received shutdown");
|
log::trace!("OutQueueControl: Received shutdown");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use nym_sphinx::message::{NymMessage, PlainMessage};
|
|||||||
use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
|
use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||||
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||||
|
use nym_task::TaskClient;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -152,6 +153,7 @@ struct ReceivedMessagesBuffer<R: MessageReceiver> {
|
|||||||
inner: Arc<Mutex<ReceivedMessagesBufferInner<R>>>,
|
inner: Arc<Mutex<ReceivedMessagesBufferInner<R>>>,
|
||||||
reply_key_storage: SentReplyKeys,
|
reply_key_storage: SentReplyKeys,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||||
@@ -160,6 +162,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
|||||||
reply_key_storage: SentReplyKeys,
|
reply_key_storage: SentReplyKeys,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
stats_tx: ClientStatsSender,
|
stats_tx: ClientStatsSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ReceivedMessagesBuffer {
|
ReceivedMessagesBuffer {
|
||||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||||
@@ -172,6 +175,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
|||||||
})),
|
})),
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,11 +261,15 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.reply_controller_sender.send_additional_surbs(
|
if let Err(err) = self.reply_controller_sender.send_additional_surbs(
|
||||||
msg.sender_tag,
|
msg.sender_tag,
|
||||||
reply_surbs,
|
reply_surbs,
|
||||||
from_surb_request,
|
from_surb_request,
|
||||||
)
|
) {
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("{err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
reconstructed
|
reconstructed
|
||||||
}
|
}
|
||||||
@@ -276,8 +284,14 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
|||||||
ReplyMessageContent::Data { message } => reconstructed.push(message.into()),
|
ReplyMessageContent::Data { message } => reconstructed.push(message.into()),
|
||||||
ReplyMessageContent::SurbRequest { recipient, amount } => {
|
ReplyMessageContent::SurbRequest { recipient, amount } => {
|
||||||
debug!("received request for {amount} additional reply SURBs from {recipient}");
|
debug!("received request for {amount} additional reply SURBs from {recipient}");
|
||||||
self.reply_controller_sender
|
if let Err(err) = self
|
||||||
.send_additional_surbs_request(*recipient, amount);
|
.reply_controller_sender
|
||||||
|
.send_additional_surbs_request(*recipient, amount)
|
||||||
|
{
|
||||||
|
if !self.task_client.is_shutdown_poll() {
|
||||||
|
error!("{err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,16 +413,19 @@ pub enum ReceivedBufferMessage {
|
|||||||
struct RequestReceiver<R: MessageReceiver> {
|
struct RequestReceiver<R: MessageReceiver> {
|
||||||
received_buffer: ReceivedMessagesBuffer<R>,
|
received_buffer: ReceivedMessagesBuffer<R>,
|
||||||
query_receiver: ReceivedBufferRequestReceiver,
|
query_receiver: ReceivedBufferRequestReceiver,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: MessageReceiver> RequestReceiver<R> {
|
impl<R: MessageReceiver> RequestReceiver<R> {
|
||||||
fn new(
|
fn new(
|
||||||
received_buffer: ReceivedMessagesBuffer<R>,
|
received_buffer: ReceivedMessagesBuffer<R>,
|
||||||
query_receiver: ReceivedBufferRequestReceiver,
|
query_receiver: ReceivedBufferRequestReceiver,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
RequestReceiver {
|
RequestReceiver {
|
||||||
received_buffer,
|
received_buffer,
|
||||||
query_receiver,
|
query_receiver,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,12 +440,12 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
async fn run(&mut self) {
|
||||||
debug!("Started RequestReceiver with graceful shutdown support");
|
debug!("Started RequestReceiver with graceful shutdown support");
|
||||||
while !shutdown.is_shutdown() {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
biased;
|
biased;
|
||||||
_ = shutdown.recv_with_delay() => {
|
_ = self.task_client.recv() => {
|
||||||
log::trace!("RequestReceiver: Received shutdown");
|
log::trace!("RequestReceiver: Received shutdown");
|
||||||
}
|
}
|
||||||
request = self.query_receiver.next() => {
|
request = self.query_receiver.next() => {
|
||||||
@@ -441,7 +458,7 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shutdown.recv_timeout().await;
|
self.task_client.recv().await;
|
||||||
log::debug!("RequestReceiver: Exiting");
|
log::debug!("RequestReceiver: Exiting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -449,25 +466,25 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
|||||||
struct FragmentedMessageReceiver<R: MessageReceiver> {
|
struct FragmentedMessageReceiver<R: MessageReceiver> {
|
||||||
received_buffer: ReceivedMessagesBuffer<R>,
|
received_buffer: ReceivedMessagesBuffer<R>,
|
||||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
|
impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
|
||||||
fn new(
|
fn new(
|
||||||
received_buffer: ReceivedMessagesBuffer<R>,
|
received_buffer: ReceivedMessagesBuffer<R>,
|
||||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
FragmentedMessageReceiver {
|
FragmentedMessageReceiver {
|
||||||
received_buffer,
|
received_buffer,
|
||||||
mixnet_packet_receiver,
|
mixnet_packet_receiver,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_with_shutdown(
|
async fn run(&mut self) -> Result<(), MessageRecoveryError> {
|
||||||
&mut self,
|
|
||||||
mut shutdown: nym_task::TaskClient,
|
|
||||||
) -> Result<(), MessageRecoveryError> {
|
|
||||||
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
|
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
|
||||||
while !shutdown.is_shutdown() {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
new_messages = self.mixnet_packet_receiver.next() => {
|
new_messages = self.mixnet_packet_receiver.next() => {
|
||||||
if let Some(new_messages) = new_messages {
|
if let Some(new_messages) = new_messages {
|
||||||
@@ -477,12 +494,12 @@ impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = shutdown.recv_with_delay() => {
|
_ = self.task_client.recv_with_delay() => {
|
||||||
log::trace!("FragmentedMessageReceiver: Received shutdown");
|
log::trace!("FragmentedMessageReceiver: Received shutdown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shutdown.recv_timeout().await;
|
self.task_client.recv_timeout().await;
|
||||||
log::debug!("FragmentedMessageReceiver: Exiting");
|
log::debug!("FragmentedMessageReceiver: Exiting");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -501,41 +518,42 @@ impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferControll
|
|||||||
reply_key_storage: SentReplyKeys,
|
reply_key_storage: SentReplyKeys,
|
||||||
reply_controller_sender: ReplyControllerSender,
|
reply_controller_sender: ReplyControllerSender,
|
||||||
metrics_reporter: ClientStatsSender,
|
metrics_reporter: ClientStatsSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let received_buffer = ReceivedMessagesBuffer::new(
|
let received_buffer = ReceivedMessagesBuffer::new(
|
||||||
local_encryption_keypair,
|
local_encryption_keypair,
|
||||||
reply_key_storage,
|
reply_key_storage,
|
||||||
reply_controller_sender,
|
reply_controller_sender,
|
||||||
metrics_reporter,
|
metrics_reporter,
|
||||||
|
task_client.fork("received_messages_buffer"),
|
||||||
);
|
);
|
||||||
|
|
||||||
ReceivedMessagesBufferController {
|
ReceivedMessagesBufferController {
|
||||||
fragmented_message_receiver: FragmentedMessageReceiver::new(
|
fragmented_message_receiver: FragmentedMessageReceiver::new(
|
||||||
received_buffer.clone(),
|
received_buffer.clone(),
|
||||||
mixnet_packet_receiver,
|
mixnet_packet_receiver,
|
||||||
|
task_client.fork("fragmented_message_receiver"),
|
||||||
|
),
|
||||||
|
request_receiver: RequestReceiver::new(
|
||||||
|
received_buffer,
|
||||||
|
query_receiver,
|
||||||
|
task_client.with_suffix("request_receiver"),
|
||||||
),
|
),
|
||||||
request_receiver: RequestReceiver::new(received_buffer, query_receiver),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
|
pub fn start(self) {
|
||||||
let mut fragmented_message_receiver = self.fragmented_message_receiver;
|
let mut fragmented_message_receiver = self.fragmented_message_receiver;
|
||||||
let mut request_receiver = self.request_receiver;
|
let mut request_receiver = self.request_receiver;
|
||||||
|
|
||||||
let shutdown_handle = shutdown.fork("fragmented_message_receiver");
|
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
match fragmented_message_receiver
|
match fragmented_message_receiver.run().await {
|
||||||
.run_with_shutdown(shutdown_handle)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => error!("{e}"),
|
Err(e) => error!("{e}"),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
request_receiver
|
request_receiver.run().await;
|
||||||
.run_with_shutdown(shutdown.with_suffix("request_receiver"))
|
|
||||||
.await;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
|||||||
use nym_sphinx::anonymous_replies::ReplySurb;
|
use nym_sphinx::anonymous_replies::ReplySurb;
|
||||||
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||||
use nym_task::connections::{ConnectionId, TransmissionLane};
|
use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||||
|
use nym_task::TaskClient;
|
||||||
use rand::{CryptoRng, Rng};
|
use rand::{CryptoRng, Rng};
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::collections::btree_map::Entry;
|
use std::collections::btree_map::Entry;
|
||||||
@@ -68,6 +69,9 @@ pub struct ReplyController<R> {
|
|||||||
|
|
||||||
message_handler: MessageHandler<R>,
|
message_handler: MessageHandler<R>,
|
||||||
full_reply_storage: CombinedReplyStorage,
|
full_reply_storage: CombinedReplyStorage,
|
||||||
|
|
||||||
|
// Listen for shutdown signals
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> ReplyController<R>
|
impl<R> ReplyController<R>
|
||||||
@@ -79,6 +83,7 @@ where
|
|||||||
message_handler: MessageHandler<R>,
|
message_handler: MessageHandler<R>,
|
||||||
full_reply_storage: CombinedReplyStorage,
|
full_reply_storage: CombinedReplyStorage,
|
||||||
request_receiver: ReplyControllerReceiver,
|
request_receiver: ReplyControllerReceiver,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
ReplyController {
|
ReplyController {
|
||||||
config,
|
config,
|
||||||
@@ -87,6 +92,7 @@ where
|
|||||||
pending_retransmissions: HashMap::new(),
|
pending_retransmissions: HashMap::new(),
|
||||||
message_handler,
|
message_handler,
|
||||||
full_reply_storage,
|
full_reply_storage,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +102,7 @@ where
|
|||||||
fragments: I,
|
fragments: I,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
) {
|
) {
|
||||||
|
trace!("buffering pending replies for {recipient}");
|
||||||
self.pending_replies
|
self.pending_replies
|
||||||
.entry(*recipient)
|
.entry(*recipient)
|
||||||
.or_insert_with(TransmissionBuffer::new)
|
.or_insert_with(TransmissionBuffer::new)
|
||||||
@@ -107,6 +114,7 @@ where
|
|||||||
recipient: &AnonymousSenderTag,
|
recipient: &AnonymousSenderTag,
|
||||||
fragments: Vec<(TransmissionLane, Fragment)>,
|
fragments: Vec<(TransmissionLane, Fragment)>,
|
||||||
) {
|
) {
|
||||||
|
trace!("re-inserting pending replies for {recipient}");
|
||||||
// the buffer should ALWAYS exist at this point, if it doesn't, it's a bug...
|
// the buffer should ALWAYS exist at this point, if it doesn't, it's a bug...
|
||||||
self.pending_replies
|
self.pending_replies
|
||||||
.entry(*recipient)
|
.entry(*recipient)
|
||||||
@@ -119,6 +127,7 @@ where
|
|||||||
recipient: &AnonymousSenderTag,
|
recipient: &AnonymousSenderTag,
|
||||||
data: Vec<Arc<PendingAcknowledgement>>,
|
data: Vec<Arc<PendingAcknowledgement>>,
|
||||||
) {
|
) {
|
||||||
|
trace!("re-inserting pending retransmissions for {recipient}");
|
||||||
// the underlying entry MUST exist as we've just got data from there
|
// the underlying entry MUST exist as we've just got data from there
|
||||||
let map_entry = self
|
let map_entry = self
|
||||||
.pending_retransmissions
|
.pending_retransmissions
|
||||||
@@ -136,7 +145,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn should_request_more_surbs(&self, target: &AnonymousSenderTag) -> bool {
|
fn should_request_more_surbs(&self, target: &AnonymousSenderTag) -> bool {
|
||||||
trace!("checking if we should request more surbs from {:?}", target);
|
trace!("checking if we should request more surbs from {target}");
|
||||||
|
|
||||||
let pending_queue_size = self
|
let pending_queue_size = self
|
||||||
.pending_replies
|
.pending_replies
|
||||||
@@ -152,11 +161,6 @@ where
|
|||||||
|
|
||||||
let total_queue = pending_queue_size + retransmission_queue;
|
let total_queue = pending_queue_size + retransmission_queue;
|
||||||
|
|
||||||
// simple as that - there's absolutely nothing to retransmit
|
|
||||||
if total_queue == 0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let available_surbs = self
|
let available_surbs = self
|
||||||
.full_reply_storage
|
.full_reply_storage
|
||||||
.surbs_storage_ref()
|
.surbs_storage_ref()
|
||||||
@@ -173,11 +177,27 @@ where
|
|||||||
.full_reply_storage
|
.full_reply_storage
|
||||||
.surbs_storage_ref()
|
.surbs_storage_ref()
|
||||||
.max_surb_threshold();
|
.max_surb_threshold();
|
||||||
|
let min_surbs_threshold_buffer =
|
||||||
|
self.config.reply_surbs.minimum_reply_surb_threshold_buffer;
|
||||||
|
|
||||||
debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..{max_surbs_threshold}");
|
// After clearing the queue, we want to have at least `min_surbs_threshold` surbs available
|
||||||
|
// and reserved for requesting additional surbs, and in addition to that we also want to
|
||||||
|
// have `min_surbs_threshold_buffer` surbs available proactively.
|
||||||
|
let target_surbs_after_clearing_queue = min_surbs_threshold + min_surbs_threshold_buffer;
|
||||||
|
|
||||||
(pending_surbs + available_surbs) < max_surbs_threshold
|
// Check if we have enough surbs to handle the total queue and maintain minimum thresholds
|
||||||
&& (pending_surbs + available_surbs) < (total_queue + min_surbs_threshold)
|
let total_required_surbs = total_queue + target_surbs_after_clearing_queue;
|
||||||
|
let total_available_surbs = pending_surbs + available_surbs;
|
||||||
|
|
||||||
|
debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..+{min_surbs_threshold_buffer}..{max_surbs_threshold}");
|
||||||
|
|
||||||
|
// We should request more surbs if:
|
||||||
|
// 1. We haven't hit the maximum surb threshold, and
|
||||||
|
// 2. We don't have enough surbs to handle the queue plus minimum thresholds
|
||||||
|
let is_below_max_threshold = total_available_surbs < max_surbs_threshold;
|
||||||
|
let is_below_required_surbs = total_available_surbs < total_required_surbs;
|
||||||
|
|
||||||
|
is_below_max_threshold && is_below_required_surbs
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_send_reply(
|
async fn handle_send_reply(
|
||||||
@@ -238,6 +258,10 @@ where
|
|||||||
&recipient_tag,
|
&recipient_tag,
|
||||||
);
|
);
|
||||||
warn!("failed to send reply to {recipient_tag}: {err}");
|
warn!("failed to send reply to {recipient_tag}: {err}");
|
||||||
|
info!(
|
||||||
|
"buffering {no_fragments} fragments for {recipient_tag}",
|
||||||
|
no_fragments = to_send.len()
|
||||||
|
);
|
||||||
self.insert_pending_replies(&recipient_tag, to_send, lane);
|
self.insert_pending_replies(&recipient_tag, to_send, lane);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,6 +269,13 @@ where
|
|||||||
|
|
||||||
// if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it
|
// if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it
|
||||||
if !fragments.is_empty() {
|
if !fragments.is_empty() {
|
||||||
|
// Ideally we should have enough surbs above the minimum threshold to handle sending
|
||||||
|
// new replies without having to first request more surbs. That's why I'd like to log
|
||||||
|
// these cases as they might indicate a problem with the surb management.
|
||||||
|
debug!(
|
||||||
|
"buffering {no_fragments} fragments for {recipient_tag}",
|
||||||
|
no_fragments = fragments.len()
|
||||||
|
);
|
||||||
self.insert_pending_replies(&recipient_tag, fragments, lane);
|
self.insert_pending_replies(&recipient_tag, fragments, lane);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +290,7 @@ where
|
|||||||
target: AnonymousSenderTag,
|
target: AnonymousSenderTag,
|
||||||
amount: u32,
|
amount: u32,
|
||||||
) -> Result<(), PreparationError> {
|
) -> Result<(), PreparationError> {
|
||||||
|
debug!("requesting {amount} additional reply surbs for {target}");
|
||||||
let reply_surb = self
|
let reply_surb = self
|
||||||
.full_reply_storage
|
.full_reply_storage
|
||||||
.surbs_storage_ref()
|
.surbs_storage_ref()
|
||||||
@@ -680,7 +712,7 @@ where
|
|||||||
// it should take into consideration the average latency, sending rate and queue size.
|
// it should take into consideration the average latency, sending rate and queue size.
|
||||||
// it should request as many surbs as it takes to saturate its sending rate before next batch arrives
|
// it should request as many surbs as it takes to saturate its sending rate before next batch arrives
|
||||||
async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) {
|
async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) {
|
||||||
trace!("requesting surbs for queues clearing");
|
trace!("requesting surbs for queue clearing");
|
||||||
|
|
||||||
let pending_queue_size = self
|
let pending_queue_size = self
|
||||||
.pending_replies
|
.pending_replies
|
||||||
@@ -694,17 +726,18 @@ where
|
|||||||
.map(|pending_queue| pending_queue.len())
|
.map(|pending_queue| pending_queue.len())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let min_surbs_buffer = self.config.reply_surbs.minimum_reply_surb_threshold_buffer as u32;
|
||||||
|
|
||||||
let total_queue = (pending_queue_size + retransmission_queue) as u32;
|
let total_queue = (pending_queue_size + retransmission_queue) as u32;
|
||||||
|
|
||||||
if total_queue == 0 {
|
// To proactively request additional surbs, we aim to have a buffer of extra surbs in our
|
||||||
trace!("the pending queues for {:?} are already empty", target);
|
// storage.
|
||||||
return;
|
let total_queue_with_buffer = total_queue + min_surbs_buffer;
|
||||||
}
|
|
||||||
|
|
||||||
let request_size = min(
|
let request_size = min(
|
||||||
self.config.reply_surbs.maximum_reply_surb_request_size,
|
self.config.reply_surbs.maximum_reply_surb_request_size,
|
||||||
max(
|
max(
|
||||||
total_queue,
|
total_queue_with_buffer,
|
||||||
self.config.reply_surbs.minimum_reply_surb_request_size,
|
self.config.reply_surbs.minimum_reply_surb_request_size,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -846,9 +879,11 @@ where
|
|||||||
// todo!()
|
// todo!()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
pub(crate) async fn run(&mut self) {
|
||||||
debug!("Started ReplyController with graceful shutdown support");
|
debug!("Started ReplyController with graceful shutdown support");
|
||||||
|
|
||||||
|
let mut shutdown = self.task_client.fork("select");
|
||||||
|
|
||||||
let polling_rate = Duration::from_secs(5);
|
let polling_rate = Duration::from_secs(5);
|
||||||
let mut stale_inspection = new_interval_stream(polling_rate);
|
let mut stale_inspection = new_interval_stream(polling_rate);
|
||||||
|
|
||||||
@@ -860,7 +895,7 @@ where
|
|||||||
while !shutdown.is_shutdown() {
|
while !shutdown.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
biased;
|
biased;
|
||||||
_ = shutdown.recv_with_delay() => {
|
_ = shutdown.recv() => {
|
||||||
log::trace!("ReplyController: Received shutdown");
|
log::trace!("ReplyController: Received shutdown");
|
||||||
},
|
},
|
||||||
req = self.request_receiver.next() => match req {
|
req = self.request_receiver.next() => match req {
|
||||||
|
|||||||
@@ -15,6 +15,27 @@ pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerR
|
|||||||
(tx.into(), rx)
|
(tx.into(), rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ReplyControllerSenderError {
|
||||||
|
#[error("failed to send retransmission data to reply controller")]
|
||||||
|
SendRetransmissionData(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||||
|
|
||||||
|
#[error("failed to send reply to reply controller")]
|
||||||
|
SendReply(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||||
|
|
||||||
|
#[error("failed to send additional surbs to reply controller")]
|
||||||
|
AdditionalSurbs(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||||
|
|
||||||
|
#[error("failed to send additional surbs request to reply controller")]
|
||||||
|
AdditionalSurbsRequest(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||||
|
|
||||||
|
#[error("failed to request lane queue length from reply controller")]
|
||||||
|
LaneQueueLength(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||||
|
|
||||||
|
#[error("response channel was dropped before we could receive the response")]
|
||||||
|
ResponseChannelDropped(#[source] oneshot::Canceled),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ReplyControllerSender(mpsc::UnboundedSender<ReplyControllerMessage>);
|
pub struct ReplyControllerSender(mpsc::UnboundedSender<ReplyControllerMessage>);
|
||||||
|
|
||||||
@@ -30,14 +51,14 @@ impl ReplyControllerSender {
|
|||||||
recipient: AnonymousSenderTag,
|
recipient: AnonymousSenderTag,
|
||||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||||
extra_surb_request: bool,
|
extra_surb_request: bool,
|
||||||
) {
|
) -> Result<(), ReplyControllerSenderError> {
|
||||||
self.0
|
self.0
|
||||||
.unbounded_send(ReplyControllerMessage::RetransmitReply {
|
.unbounded_send(ReplyControllerMessage::RetransmitReply {
|
||||||
recipient,
|
recipient,
|
||||||
timed_out_ack,
|
timed_out_ack,
|
||||||
extra_surb_request,
|
extra_surb_request,
|
||||||
})
|
})
|
||||||
.expect("ReplyControllerReceiver has died!")
|
.map_err(ReplyControllerSenderError::SendRetransmissionData)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_reply(
|
pub(crate) fn send_reply(
|
||||||
@@ -45,14 +66,14 @@ impl ReplyControllerSender {
|
|||||||
recipient: AnonymousSenderTag,
|
recipient: AnonymousSenderTag,
|
||||||
message: Vec<u8>,
|
message: Vec<u8>,
|
||||||
lane: TransmissionLane,
|
lane: TransmissionLane,
|
||||||
) {
|
) -> Result<(), ReplyControllerSenderError> {
|
||||||
self.0
|
self.0
|
||||||
.unbounded_send(ReplyControllerMessage::SendReply {
|
.unbounded_send(ReplyControllerMessage::SendReply {
|
||||||
recipient,
|
recipient,
|
||||||
message,
|
message,
|
||||||
lane,
|
lane,
|
||||||
})
|
})
|
||||||
.expect("ReplyControllerReceiver has died!")
|
.map_err(ReplyControllerSenderError::SendReply)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_additional_surbs(
|
pub(crate) fn send_additional_surbs(
|
||||||
@@ -60,42 +81,47 @@ impl ReplyControllerSender {
|
|||||||
sender_tag: AnonymousSenderTag,
|
sender_tag: AnonymousSenderTag,
|
||||||
reply_surbs: Vec<ReplySurb>,
|
reply_surbs: Vec<ReplySurb>,
|
||||||
from_surb_request: bool,
|
from_surb_request: bool,
|
||||||
) {
|
) -> Result<(), ReplyControllerSenderError> {
|
||||||
self.0
|
self.0
|
||||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbs {
|
.unbounded_send(ReplyControllerMessage::AdditionalSurbs {
|
||||||
sender_tag,
|
sender_tag,
|
||||||
reply_surbs,
|
reply_surbs,
|
||||||
from_surb_request,
|
from_surb_request,
|
||||||
})
|
})
|
||||||
.expect("ReplyControllerReceiver has died!")
|
.map_err(ReplyControllerSenderError::AdditionalSurbs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) {
|
pub(crate) fn send_additional_surbs_request(
|
||||||
|
&self,
|
||||||
|
recipient: Recipient,
|
||||||
|
amount: u32,
|
||||||
|
) -> Result<(), ReplyControllerSenderError> {
|
||||||
self.0
|
self.0
|
||||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest {
|
.unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest {
|
||||||
recipient: Box::new(recipient),
|
recipient: Box::new(recipient),
|
||||||
amount,
|
amount,
|
||||||
})
|
})
|
||||||
.expect("ReplyControllerReceiver has died!")
|
.map_err(ReplyControllerSenderError::AdditionalSurbsRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize {
|
pub async fn get_lane_queue_length(
|
||||||
|
&self,
|
||||||
|
connection_id: ConnectionId,
|
||||||
|
) -> Result<usize, ReplyControllerSenderError> {
|
||||||
let (response_tx, response_rx) = oneshot::channel();
|
let (response_tx, response_rx) = oneshot::channel();
|
||||||
self.0
|
if let Err(err) = self
|
||||||
|
.0
|
||||||
.unbounded_send(ReplyControllerMessage::LaneQueueLength {
|
.unbounded_send(ReplyControllerMessage::LaneQueueLength {
|
||||||
connection_id,
|
connection_id,
|
||||||
response_channel: response_tx,
|
response_channel: response_tx,
|
||||||
})
|
})
|
||||||
.expect("ReplyControllerReceiver has died!");
|
{
|
||||||
|
return Err(ReplyControllerSenderError::LaneQueueLength(err));
|
||||||
match response_rx.await {
|
|
||||||
Ok(length) => length,
|
|
||||||
Err(_) => {
|
|
||||||
error!("The reply controller has dropped our response channel!");
|
|
||||||
// TODO: should we panic here instead? this message implies something weird and unrecoverable has happened
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
response_rx
|
||||||
|
.await
|
||||||
|
.map_err(ReplyControllerSenderError::ResponseChannelDropped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +136,10 @@ impl ReplyQueueLengths {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize {
|
pub async fn get_lane_queue_length(
|
||||||
|
&self,
|
||||||
|
connection_id: ConnectionId,
|
||||||
|
) -> Result<usize, ReplyControllerSenderError> {
|
||||||
self.reply_controller_sender
|
self.reply_controller_sender
|
||||||
.get_lane_queue_length(connection_id)
|
.get_lane_queue_length(connection_id)
|
||||||
.await
|
.await
|
||||||
@@ -120,7 +149,7 @@ impl ReplyQueueLengths {
|
|||||||
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
|
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum ReplyControllerMessage {
|
pub enum ReplyControllerMessage {
|
||||||
RetransmitReply {
|
RetransmitReply {
|
||||||
recipient: AnonymousSenderTag,
|
recipient: AnonymousSenderTag,
|
||||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use nym_sphinx::addressing::Recipient;
|
|||||||
use nym_statistics_common::clients::{
|
use nym_statistics_common::clients::{
|
||||||
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
||||||
};
|
};
|
||||||
use nym_task::connections::TransmissionLane;
|
use nym_task::{connections::TransmissionLane, TaskClient};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -51,6 +51,9 @@ pub(crate) struct StatisticsControl {
|
|||||||
|
|
||||||
/// Config for stats reporting (enabled, address, interval)
|
/// Config for stats reporting (enabled, address, interval)
|
||||||
reporting_config: StatsReporting,
|
reporting_config: StatsReporting,
|
||||||
|
|
||||||
|
/// Task client for listening for shutdown
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatisticsControl {
|
impl StatisticsControl {
|
||||||
@@ -59,19 +62,24 @@ impl StatisticsControl {
|
|||||||
client_type: String,
|
client_type: String,
|
||||||
client_stats_id: String,
|
client_stats_id: String,
|
||||||
report_tx: InputMessageSender,
|
report_tx: InputMessageSender,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> (Self, ClientStatsSender) {
|
) -> (Self, ClientStatsSender) {
|
||||||
let (stats_tx, stats_rx) = tokio::sync::mpsc::unbounded_channel();
|
let (stats_tx, stats_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
let stats = ClientStatsController::new(client_stats_id, client_type);
|
let stats = ClientStatsController::new(client_stats_id, client_type);
|
||||||
|
|
||||||
|
let mut task_client_stats_sender = task_client.fork("stats_sender");
|
||||||
|
task_client_stats_sender.disarm();
|
||||||
|
|
||||||
(
|
(
|
||||||
StatisticsControl {
|
StatisticsControl {
|
||||||
stats,
|
stats,
|
||||||
stats_rx,
|
stats_rx,
|
||||||
report_tx,
|
report_tx,
|
||||||
reporting_config,
|
reporting_config,
|
||||||
|
task_client,
|
||||||
},
|
},
|
||||||
ClientStatsSender::new(Some(stats_tx)),
|
ClientStatsSender::new(Some(stats_tx), task_client_stats_sender),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +99,7 @@ impl StatisticsControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_with_shutdown(&mut self, mut task_client: nym_task::TaskClient) {
|
async fn run(&mut self) {
|
||||||
log::debug!("Started StatisticsControl with graceful shutdown support");
|
log::debug!("Started StatisticsControl with graceful shutdown support");
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -121,8 +129,13 @@ impl StatisticsControl {
|
|||||||
let mut snapshot_interval =
|
let mut snapshot_interval =
|
||||||
gloo_timers::future::IntervalStream::new(SNAPSHOT_INTERVAL.as_millis() as u32);
|
gloo_timers::future::IntervalStream::new(SNAPSHOT_INTERVAL.as_millis() as u32);
|
||||||
|
|
||||||
loop {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
biased;
|
||||||
|
_ = self.task_client.recv() => {
|
||||||
|
log::trace!("StatisticsControl: Received shutdown");
|
||||||
|
break;
|
||||||
|
},
|
||||||
stats_event = self.stats_rx.recv() => match stats_event {
|
stats_event = self.stats_rx.recv() => match stats_event {
|
||||||
Some(stats_event) => self.stats.handle_event(stats_event),
|
Some(stats_event) => self.stats.handle_event(stats_event),
|
||||||
None => {
|
None => {
|
||||||
@@ -144,34 +157,34 @@ impl StatisticsControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = local_report_interval.next() => {
|
_ = local_report_interval.next() => {
|
||||||
self.stats.local_report(&mut task_client);
|
self.stats.local_report(&mut self.task_client);
|
||||||
}
|
}
|
||||||
_ = task_client.recv_with_delay() => {
|
|
||||||
log::trace!("StatisticsControl: Received shutdown");
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
task_client.recv_timeout().await;
|
|
||||||
log::debug!("StatisticsControl: Exiting");
|
log::debug!("StatisticsControl: Exiting");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn start_with_shutdown(mut self, task_client: nym_task::TaskClient) {
|
pub(crate) fn start(mut self) {
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
self.run_with_shutdown(task_client).await;
|
self.run().await;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn create_and_start_with_shutdown(
|
pub(crate) fn create_and_start(
|
||||||
reporting_config: StatsReporting,
|
reporting_config: StatsReporting,
|
||||||
client_type: String,
|
client_type: String,
|
||||||
client_stats_id: String,
|
client_stats_id: String,
|
||||||
report_tx: InputMessageSender,
|
report_tx: InputMessageSender,
|
||||||
task_client: nym_task::TaskClient,
|
task_client: TaskClient,
|
||||||
) -> ClientStatsSender {
|
) -> ClientStatsSender {
|
||||||
let (controller, sender) =
|
let (controller, sender) = Self::create(
|
||||||
Self::create(reporting_config, client_type, client_stats_id, report_tx);
|
reporting_config,
|
||||||
controller.start_with_shutdown(task_client);
|
client_type,
|
||||||
|
client_stats_id,
|
||||||
|
report_tx,
|
||||||
|
task_client,
|
||||||
|
);
|
||||||
|
controller.start();
|
||||||
sender
|
sender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit};
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use log::*;
|
use log::*;
|
||||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||||
|
use nym_task::TaskClient;
|
||||||
use nym_topology::NymTopologyError;
|
use nym_topology::NymTopologyError;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -43,6 +44,8 @@ pub struct TopologyRefresher {
|
|||||||
|
|
||||||
refresh_rate: Duration,
|
refresh_rate: Duration,
|
||||||
consecutive_failure_count: usize,
|
consecutive_failure_count: usize,
|
||||||
|
|
||||||
|
task_client: TaskClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TopologyRefresher {
|
impl TopologyRefresher {
|
||||||
@@ -50,12 +53,14 @@ impl TopologyRefresher {
|
|||||||
cfg: TopologyRefresherConfig,
|
cfg: TopologyRefresherConfig,
|
||||||
topology_accessor: TopologyAccessor,
|
topology_accessor: TopologyAccessor,
|
||||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||||
|
task_client: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
TopologyRefresher {
|
TopologyRefresher {
|
||||||
topology_provider,
|
topology_provider,
|
||||||
topology_accessor,
|
topology_accessor,
|
||||||
refresh_rate: cfg.refresh_rate,
|
refresh_rate: cfg.refresh_rate,
|
||||||
consecutive_failure_count: 0,
|
consecutive_failure_count: 0,
|
||||||
|
task_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +147,7 @@ impl TopologyRefresher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
pub fn start(mut self) {
|
||||||
spawn_future(async move {
|
spawn_future(async move {
|
||||||
debug!("Started TopologyRefresher with graceful shutdown support");
|
debug!("Started TopologyRefresher with graceful shutdown support");
|
||||||
|
|
||||||
@@ -155,17 +160,17 @@ impl TopologyRefresher {
|
|||||||
let mut interval =
|
let mut interval =
|
||||||
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
|
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
|
||||||
|
|
||||||
while !shutdown.is_shutdown() {
|
while !self.task_client.is_shutdown() {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = interval.next() => {
|
_ = interval.next() => {
|
||||||
self.try_refresh().await;
|
self.try_refresh().await;
|
||||||
},
|
},
|
||||||
_ = shutdown.recv() => {
|
_ = self.task_client.recv() => {
|
||||||
log::trace!("TopologyRefresher: Received shutdown");
|
log::trace!("TopologyRefresher: Received shutdown");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shutdown.recv_timeout().await;
|
self.task_client.recv_timeout().await;
|
||||||
log::debug!("TopologyRefresher: Exiting");
|
log::debug!("TopologyRefresher: Exiting");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ impl<T> TransmissionBuffer<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(crate) fn num_lanes(&self) -> usize {
|
pub(crate) fn lanes(&self) -> Vec<TransmissionLane> {
|
||||||
self.buffer.keys().count()
|
self.buffer.keys().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option<usize> {
|
pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option<usize> {
|
||||||
@@ -83,6 +83,7 @@ impl<T> TransmissionBuffer<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[allow(unused)]
|
||||||
pub(crate) fn total_size_in_bytes(&self) -> usize
|
pub(crate) fn total_size_in_bytes(&self) -> usize
|
||||||
where
|
where
|
||||||
T: SizedData,
|
T: SizedData,
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum ClientCoreError {
|
pub enum ClientCoreError {
|
||||||
|
#[error("could not perform the state migration: {0}")]
|
||||||
|
UnsupportedMigration(String),
|
||||||
|
|
||||||
#[error("I/O error: {0}")]
|
#[error("I/O error: {0}")]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
|
|
||||||
@@ -36,6 +39,13 @@ pub enum ClientCoreError {
|
|||||||
#[error("no gateway with id: {0}")]
|
#[error("no gateway with id: {0}")]
|
||||||
NoGatewayWithId(String),
|
NoGatewayWithId(String),
|
||||||
|
|
||||||
|
#[error("Invalid URL: {0}")]
|
||||||
|
InvalidUrl(String),
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[error("resolution failed: {0}")]
|
||||||
|
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||||
|
|
||||||
#[error("no gateways on network")]
|
#[error("no gateways on network")]
|
||||||
NoGatewaysOnNetwork,
|
NoGatewaysOnNetwork,
|
||||||
|
|
||||||
@@ -96,6 +106,9 @@ pub enum ClientCoreError {
|
|||||||
#[error("timed out while trying to establish gateway connection")]
|
#[error("timed out while trying to establish gateway connection")]
|
||||||
GatewayConnectionTimeout,
|
GatewayConnectionTimeout,
|
||||||
|
|
||||||
|
#[error("failed to forward mix messages to gateway")]
|
||||||
|
GatewayFailedToForwardMessages,
|
||||||
|
|
||||||
#[error("no ping measurements for the gateway ({identity}) performed")]
|
#[error("no ping measurements for the gateway ({identity}) performed")]
|
||||||
NoGatewayMeasurements { identity: String },
|
NoGatewayMeasurements { identity: String },
|
||||||
|
|
||||||
@@ -212,6 +225,9 @@ pub enum ClientCoreError {
|
|||||||
"fresh registration with gateway {gateway_id} somehow requires an additional key upgrade!"
|
"fresh registration with gateway {gateway_id} somehow requires an additional key upgrade!"
|
||||||
)]
|
)]
|
||||||
UnexpectedKeyUpgrade { gateway_id: String },
|
UnexpectedKeyUpgrade { gateway_id: String },
|
||||||
|
|
||||||
|
#[error("failed to derive keys from master key")]
|
||||||
|
HkdfDerivationError {},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set of messages that the client can send to listeners via the task manager
|
/// Set of messages that the client can send to listeners via the task manager
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ use std::{sync::Arc, time::Duration};
|
|||||||
use tungstenite::Message;
|
use tungstenite::Message;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use crate::init::websockets::connect_async;
|
||||||
|
|
||||||
use nym_topology::NodeId;
|
use nym_topology::NodeId;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
@@ -23,8 +26,6 @@ use tokio::time::sleep;
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use tokio_tungstenite::connect_async;
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use wasm_utils::websocket::JSWebsocket;
|
use wasm_utils::websocket::JSWebsocket;
|
||||||
@@ -91,6 +92,7 @@ pub async fn gateways_for_init<R: Rng>(
|
|||||||
nym_apis: &[Url],
|
nym_apis: &[Url],
|
||||||
user_agent: Option<UserAgent>,
|
user_agent: Option<UserAgent>,
|
||||||
minimum_performance: u8,
|
minimum_performance: u8,
|
||||||
|
ignore_epoch_roles: bool,
|
||||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||||
let nym_api = nym_apis
|
let nym_api = nym_apis
|
||||||
.choose(rng)
|
.choose(rng)
|
||||||
@@ -112,7 +114,7 @@ pub async fn gateways_for_init<R: Rng>(
|
|||||||
// (we don't want instability)
|
// (we don't want instability)
|
||||||
let valid_gateways = gateways
|
let valid_gateways = gateways
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|g| !g.supported_roles.mixnode)
|
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
|
||||||
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
||||||
.filter_map(|gateway| gateway.try_into().ok())
|
.filter_map(|gateway| gateway.try_into().ok())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -131,7 +133,7 @@ pub async fn gateways_for_init<R: Rng>(
|
|||||||
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
|
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
|
||||||
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
|
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
|
||||||
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
|
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
|
||||||
Ok(Err(conn_failure)) => Err(conn_failure.into()),
|
Ok(Err(conn_failure)) => Err(conn_failure),
|
||||||
Ok(Ok((stream, _))) => Ok(stream),
|
Ok(Ok((stream, _))) => Ok(stream),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,14 +335,6 @@ pub(super) async fn register_with_gateway(
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// this should NEVER happen, if it did, it means the function was misused,
|
|
||||||
// because for any fresh **registration**, the derived key is always up to date
|
|
||||||
if auth_response.requires_key_upgrade {
|
|
||||||
return Err(ClientCoreError::UnexpectedKeyUpgrade {
|
|
||||||
gateway_id: gateway_id.to_base58_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RegistrationResult {
|
Ok(RegistrationResult {
|
||||||
shared_keys: auth_response.initial_shared_key,
|
shared_keys: auth_response.initial_shared_key,
|
||||||
authenticated_ephemeral_client: gateway_client,
|
authenticated_ephemeral_client: gateway_client,
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ use serde::Serialize;
|
|||||||
|
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(crate) mod websockets;
|
||||||
|
|
||||||
// helpers for error wrapping
|
// helpers for error wrapping
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use nym_client_core_gateways_storage::{
|
|||||||
};
|
};
|
||||||
use nym_crypto::asymmetric::identity;
|
use nym_crypto::asymmetric::identity;
|
||||||
use nym_gateway_client::client::InitGatewayClient;
|
use nym_gateway_client::client::InitGatewayClient;
|
||||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||||
use nym_sphinx::addressing::clients::Recipient;
|
use nym_sphinx::addressing::clients::Recipient;
|
||||||
use nym_topology::node::RoutingNode;
|
use nym_topology::node::RoutingNode;
|
||||||
use nym_validator_client::client::IdentityKey;
|
use nym_validator_client::client::IdentityKey;
|
||||||
@@ -96,7 +96,7 @@ impl SelectedGateway {
|
|||||||
/// - shared keys derived between ourselves and the node
|
/// - shared keys derived between ourselves and the node
|
||||||
/// - an authenticated handle of an ephemeral handle created for the purposes of registration
|
/// - an authenticated handle of an ephemeral handle created for the purposes of registration
|
||||||
pub struct RegistrationResult {
|
pub struct RegistrationResult {
|
||||||
pub shared_keys: Arc<SharedGatewayKey>,
|
pub shared_keys: Arc<SharedSymmetricKey>,
|
||||||
pub authenticated_ephemeral_client: InitGatewayClient,
|
pub authenticated_ephemeral_client: InitGatewayClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use crate::error::ClientCoreError;
|
||||||
|
|
||||||
|
use nym_http_api_client::HickoryDnsResolver;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||||
|
use tungstenite::handshake::client::Response;
|
||||||
|
use url::{Host, Url};
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(crate) async fn connect_async(
|
||||||
|
endpoint: &str,
|
||||||
|
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), ClientCoreError> {
|
||||||
|
let resolver = HickoryDnsResolver::default();
|
||||||
|
let uri = Url::parse(endpoint).map_err(|_| ClientCoreError::InvalidUrl(endpoint.to_owned()))?;
|
||||||
|
let port: u16 = uri.port_or_known_default().unwrap_or(443);
|
||||||
|
|
||||||
|
let host = uri
|
||||||
|
.host()
|
||||||
|
.ok_or(ClientCoreError::InvalidUrl(endpoint.to_owned()))?;
|
||||||
|
|
||||||
|
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
|
||||||
|
// the default std resolve
|
||||||
|
let sock_addrs: Vec<SocketAddr> = match host {
|
||||||
|
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
|
||||||
|
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
|
||||||
|
Host::Domain(domain) => {
|
||||||
|
// Do a DNS lookup for the domain using our custom DNS resolver
|
||||||
|
resolver
|
||||||
|
.resolve_str(domain)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| SocketAddr::new(a, port))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stream = TcpStream::connect(&sock_addrs[..]).await?;
|
||||||
|
|
||||||
|
tokio_tungstenite::client_async_tls(endpoint, stream)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
@@ -33,48 +33,3 @@ where
|
|||||||
{
|
{
|
||||||
tokio::spawn(future);
|
tokio::spawn(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug)]
|
|
||||||
pub struct ForgetMe {
|
|
||||||
client: bool,
|
|
||||||
stats: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ForgetMe {
|
|
||||||
pub fn new_all() -> Self {
|
|
||||||
Self {
|
|
||||||
client: true,
|
|
||||||
stats: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_client() -> Self {
|
|
||||||
Self {
|
|
||||||
client: true,
|
|
||||||
stats: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_stats() -> Self {
|
|
||||||
Self {
|
|
||||||
client: false,
|
|
||||||
stats: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(client: bool, stats: bool) -> Self {
|
|
||||||
Self { client, stats }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn any(&self) -> bool {
|
|
||||||
self.client || self.stats
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn client(&self) -> bool {
|
|
||||||
self.client
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stats(&self) -> bool {
|
|
||||||
self.stats
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ nym-credential-storage = { path = "../../credential-storage" }
|
|||||||
nym-credentials-interface = { path = "../../credentials-interface" }
|
nym-credentials-interface = { path = "../../credentials-interface" }
|
||||||
nym-crypto = { path = "../../crypto" }
|
nym-crypto = { path = "../../crypto" }
|
||||||
nym-gateway-requests = { path = "../../gateway-requests" }
|
nym-gateway-requests = { path = "../../gateway-requests" }
|
||||||
|
nym-http-api-client = { path = "../../http-api-client" }
|
||||||
nym-network-defaults = { path = "../../network-defaults" }
|
nym-network-defaults = { path = "../../network-defaults" }
|
||||||
nym-sphinx = { path = "../../nymsphinx" }
|
nym-sphinx = { path = "../../nymsphinx" }
|
||||||
nym-statistics-common = { path = "../../statistics" }
|
nym-statistics-common = { path = "../../statistics" }
|
||||||
|
|||||||
@@ -20,9 +20,8 @@ use nym_credentials_interface::TicketType;
|
|||||||
use nym_crypto::asymmetric::identity;
|
use nym_crypto::asymmetric::identity;
|
||||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||||
use nym_gateway_requests::{
|
use nym_gateway_requests::{
|
||||||
BinaryRequest, ClientControlRequest, ClientRequest, SensitiveServerResponse, ServerResponse,
|
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt, ServerResponse,
|
||||||
SharedGatewayKey, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
|
||||||
};
|
};
|
||||||
use nym_sphinx::forwarding::packet::MixPacket;
|
use nym_sphinx::forwarding::packet::MixPacket;
|
||||||
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
||||||
@@ -40,8 +39,6 @@ use url::Url;
|
|||||||
use std::os::fd::RawFd;
|
use std::os::fd::RawFd;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use tokio_tungstenite::connect_async;
|
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
use std::os::raw::c_int as RawFd;
|
use std::os::raw::c_int as RawFd;
|
||||||
@@ -49,10 +46,14 @@ use std::os::raw::c_int as RawFd;
|
|||||||
use wasm_utils::websocket::JSWebsocket;
|
use wasm_utils::websocket::JSWebsocket;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use wasmtimer::tokio::sleep;
|
use wasmtimer::tokio::sleep;
|
||||||
use zeroize::Zeroizing;
|
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(crate) mod websockets;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use websockets::connect_async;
|
||||||
|
|
||||||
pub struct GatewayConfig {
|
pub struct GatewayConfig {
|
||||||
pub gateway_identity: identity::PublicKey,
|
pub gateway_identity: identity::PublicKey,
|
||||||
|
|
||||||
@@ -79,8 +80,7 @@ impl GatewayConfig {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AuthenticationResponse {
|
pub struct AuthenticationResponse {
|
||||||
pub initial_shared_key: Arc<SharedGatewayKey>,
|
pub initial_shared_key: Arc<SharedSymmetricKey>,
|
||||||
pub requires_key_upgrade: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this should be refactored into a state machine that keeps track of its authentication state
|
// TODO: this should be refactored into a state machine that keeps track of its authentication state
|
||||||
@@ -92,7 +92,7 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
|||||||
gateway_address: String,
|
gateway_address: String,
|
||||||
gateway_identity: identity::PublicKey,
|
gateway_identity: identity::PublicKey,
|
||||||
local_identity: Arc<identity::KeyPair>,
|
local_identity: Arc<identity::KeyPair>,
|
||||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
shared_key: Option<Arc<SharedSymmetricKey>>,
|
||||||
connection: SocketState,
|
connection: SocketState,
|
||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||||
@@ -116,7 +116,7 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
gateway_config: GatewayConfig,
|
gateway_config: GatewayConfig,
|
||||||
local_identity: Arc<identity::KeyPair>,
|
local_identity: Arc<identity::KeyPair>,
|
||||||
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
|
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
|
||||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
shared_key: Option<Arc<SharedSymmetricKey>>,
|
||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||||
stats_reporter: ClientStatsSender,
|
stats_reporter: ClientStatsSender,
|
||||||
@@ -146,7 +146,7 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
self.gateway_identity
|
self.gateway_identity
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shared_key(&self) -> Option<Arc<SharedGatewayKey>> {
|
pub fn shared_key(&self) -> Option<Arc<SharedSymmetricKey>> {
|
||||||
self.shared_key.clone()
|
self.shared_key.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,23 +201,15 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
"Attemting to establish connection to gateway at: {}",
|
"Attemting to establish connection to gateway at: {}",
|
||||||
self.gateway_address
|
self.gateway_address
|
||||||
);
|
);
|
||||||
let ws_stream = match connect_async(&self.gateway_address).await {
|
let (ws_stream, _) = connect_async(
|
||||||
Ok((ws_stream, _)) => ws_stream,
|
&self.gateway_address,
|
||||||
Err(error) => {
|
#[cfg(unix)]
|
||||||
return Err(GatewayClientError::NetworkConnectionFailed {
|
self.connection_fd_callback.clone(),
|
||||||
address: self.gateway_address.clone(),
|
)
|
||||||
source: error,
|
.await?;
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.connection = SocketState::Available(Box::new(ws_stream));
|
self.connection = SocketState::Available(Box::new(ws_stream));
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
if let (Some(callback), Some(fd)) = (self.connection_fd_callback.as_ref(), self.ws_fd()) {
|
|
||||||
callback.as_ref()(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +263,19 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_client_request(
|
||||||
|
&mut self,
|
||||||
|
message: ClientRequest,
|
||||||
|
) -> Result<(), GatewayClientError> {
|
||||||
|
if let Some(shared_key) = self.shared_key() {
|
||||||
|
let encrypted = message.encrypt(&shared_key)?;
|
||||||
|
Box::pin(self.send_websocket_message(encrypted)).await?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(GatewayClientError::ConnectionInInvalidState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn read_control_response(&mut self) -> Result<ServerResponse, GatewayClientError> {
|
async fn read_control_response(&mut self) -> Result<ServerResponse, GatewayClientError> {
|
||||||
// we use the fact that all request responses are Message::Text and only pushed
|
// we use the fact that all request responses are Message::Text and only pushed
|
||||||
// sphinx packets are Message::Binary
|
// sphinx packets are Message::Binary
|
||||||
@@ -402,49 +407,39 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_gateway_protocol(
|
fn check_gateway_protocol(&self, gateway_protocol: u8) -> Result<(), GatewayClientError> {
|
||||||
&self,
|
|
||||||
gateway_protocol: Option<u8>,
|
|
||||||
) -> Result<(), GatewayClientError> {
|
|
||||||
debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
|
debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||||
|
|
||||||
// right now there are no failure cases here, but this might change in the future
|
// client should reject any gateways that do not indicate they support auth v2 or aes256gcm-siv
|
||||||
match gateway_protocol {
|
if !gateway_protocol.supports_authenticate_v2()
|
||||||
None => {
|
|| !gateway_protocol.supports_aes256_gcm_siv()
|
||||||
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
|
{
|
||||||
// note: in +1.2.0 we will have to return a hard error here
|
return Err(GatewayClientError::IncompatibleProtocol {
|
||||||
Ok(())
|
gateway: gateway_protocol,
|
||||||
}
|
current: CURRENT_PROTOCOL_VERSION,
|
||||||
Some(v) if v > CURRENT_PROTOCOL_VERSION => {
|
});
|
||||||
let err = GatewayClientError::IncompatibleProtocol {
|
}
|
||||||
gateway: Some(v),
|
|
||||||
current: CURRENT_PROTOCOL_VERSION,
|
|
||||||
};
|
|
||||||
error!("{err}");
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(_) => {
|
// we can't handle gateways with higher protocol than ours
|
||||||
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
if gateway_protocol <= CURRENT_PROTOCOL_VERSION {
|
||||||
Ok(())
|
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||||
}
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let err = GatewayClientError::IncompatibleProtocol {
|
||||||
|
gateway: gateway_protocol,
|
||||||
|
current: CURRENT_PROTOCOL_VERSION,
|
||||||
|
};
|
||||||
|
error!("{err}");
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn register(
|
async fn register(&mut self) -> Result<(), GatewayClientError> {
|
||||||
&mut self,
|
|
||||||
derive_aes256_gcm_siv_key: bool,
|
|
||||||
) -> Result<(), GatewayClientError> {
|
|
||||||
if !self.connection.is_established() {
|
if !self.connection.is_established() {
|
||||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(self.connection.is_available());
|
debug_assert!(self.connection.is_available());
|
||||||
log::debug!(
|
|
||||||
"registering with gateway. using legacy key derivation: {}",
|
|
||||||
!derive_aes256_gcm_siv_key
|
|
||||||
);
|
|
||||||
|
|
||||||
// it's fine to instantiate it here as it's only used once (during authentication or registration)
|
// it's fine to instantiate it here as it's only used once (during authentication or registration)
|
||||||
// and putting it into the GatewayClient struct would be a hassle
|
// and putting it into the GatewayClient struct would be a hassle
|
||||||
let mut rng = OsRng;
|
let mut rng = OsRng;
|
||||||
@@ -456,7 +451,6 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
self.local_identity.as_ref(),
|
self.local_identity.as_ref(),
|
||||||
self.gateway_identity,
|
self.gateway_identity,
|
||||||
self.cfg.bandwidth.require_tickets,
|
self.cfg.bandwidth.require_tickets,
|
||||||
derive_aes256_gcm_siv_key,
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
self.task_client.clone(),
|
self.task_client.clone(),
|
||||||
)
|
)
|
||||||
@@ -484,99 +478,15 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// populate the negotiated protocol for future uses
|
// populate the negotiated protocol for future uses
|
||||||
self.negotiated_protocol = gateway_protocol;
|
self.negotiated_protocol = Some(gateway_protocol);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn upgrade_key_authenticated(
|
async fn send_authenticate_request_and_handle_response(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<Zeroizing<SharedSymmetricKey>, GatewayClientError> {
|
msg: ClientControlRequest,
|
||||||
info!("*** STARTING AES128CTR-HMAC KEY UPGRADE INTO AES256GCM-SIV***");
|
) -> Result<(), GatewayClientError> {
|
||||||
|
|
||||||
if !self.connection.is_established() {
|
|
||||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.authenticated {
|
|
||||||
return Err(GatewayClientError::NotAuthenticated);
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
|
||||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
|
||||||
};
|
|
||||||
|
|
||||||
if !shared_key.is_legacy() {
|
|
||||||
return Err(GatewayClientError::KeyAlreadyUpgraded);
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure we have the only reference, so we could safely swap it
|
|
||||||
if Arc::strong_count(shared_key) != 1 {
|
|
||||||
return Err(GatewayClientError::KeyAlreadyInUse);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(shared_key.is_legacy());
|
|
||||||
let legacy_key = shared_key.unwrap_legacy();
|
|
||||||
let (updated_key, hkdf_salt) = legacy_key.upgrade();
|
|
||||||
let derived_key_digest = updated_key.digest();
|
|
||||||
|
|
||||||
let upgrade_request = ClientRequest::UpgradeKey {
|
|
||||||
hkdf_salt,
|
|
||||||
derived_key_digest,
|
|
||||||
}
|
|
||||||
.encrypt(legacy_key)?;
|
|
||||||
|
|
||||||
info!("sending upgrade request and awaiting the acknowledgement back");
|
|
||||||
let (ciphertext, nonce) = match self.send_websocket_message(upgrade_request).await? {
|
|
||||||
ServerResponse::EncryptedResponse { ciphertext, nonce } => (ciphertext, nonce),
|
|
||||||
ServerResponse::Error { message } => {
|
|
||||||
return Err(GatewayClientError::GatewayError(message))
|
|
||||||
}
|
|
||||||
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
|
||||||
};
|
|
||||||
|
|
||||||
// attempt to decrypt it using NEW key
|
|
||||||
let Ok(response) = SensitiveServerResponse::decrypt(&ciphertext, &nonce, &updated_key)
|
|
||||||
else {
|
|
||||||
return Err(GatewayClientError::FatalKeyUpgradeFailure);
|
|
||||||
};
|
|
||||||
|
|
||||||
match response {
|
|
||||||
SensitiveServerResponse::KeyUpgradeAck { .. } => {
|
|
||||||
info!("received key upgrade acknowledgement")
|
|
||||||
}
|
|
||||||
_ => return Err(GatewayClientError::FatalKeyUpgradeFailure),
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform in memory swap and make a copy for updating storage
|
|
||||||
let zeroizing_updated_key = updated_key.zeroizing_clone();
|
|
||||||
self.shared_key = Some(Arc::new(updated_key.into()));
|
|
||||||
|
|
||||||
Ok(zeroizing_updated_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn authenticate(&mut self) -> Result<(), GatewayClientError> {
|
|
||||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
|
||||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
|
||||||
};
|
|
||||||
|
|
||||||
if !self.connection.is_established() {
|
|
||||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
|
||||||
}
|
|
||||||
debug!("authenticating with gateway");
|
|
||||||
|
|
||||||
let self_address = self
|
|
||||||
.local_identity
|
|
||||||
.as_ref()
|
|
||||||
.public_key()
|
|
||||||
.derive_destination_address();
|
|
||||||
|
|
||||||
let msg = ClientControlRequest::new_authenticate(
|
|
||||||
self_address,
|
|
||||||
shared_key,
|
|
||||||
self.cfg.bandwidth.require_tickets,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match self.send_websocket_message(msg).await? {
|
match self.send_websocket_message(msg).await? {
|
||||||
ServerResponse::Authenticate {
|
ServerResponse::Authenticate {
|
||||||
protocol_version,
|
protocol_version,
|
||||||
@@ -587,7 +497,7 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
self.authenticated = status;
|
self.authenticated = status;
|
||||||
self.bandwidth.update_and_maybe_log(bandwidth_remaining);
|
self.bandwidth.update_and_maybe_log(bandwidth_remaining);
|
||||||
|
|
||||||
self.negotiated_protocol = protocol_version;
|
self.negotiated_protocol = Some(protocol_version);
|
||||||
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
|
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
|
||||||
|
|
||||||
self.task_client.send_status_msg(Box::new(
|
self.task_client.send_status_msg(Box::new(
|
||||||
@@ -600,6 +510,26 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> {
|
||||||
|
debug!("using v2 authentication");
|
||||||
|
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||||
|
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||||
|
};
|
||||||
|
|
||||||
|
let msg = ClientControlRequest::new_authenticate_v2(shared_key, &self.local_identity)?;
|
||||||
|
self.send_authenticate_request_and_handle_response(msg)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn authenticate(&mut self) -> Result<(), GatewayClientError> {
|
||||||
|
if !self.connection.is_established() {
|
||||||
|
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||||
|
}
|
||||||
|
debug!("authenticating with gateway");
|
||||||
|
|
||||||
|
self.authenticate_v2().await
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper method to either call register or authenticate based on self.shared_key value
|
/// Helper method to either call register or authenticate based on self.shared_key value
|
||||||
#[instrument(skip_all,
|
#[instrument(skip_all,
|
||||||
fields(
|
fields(
|
||||||
@@ -615,18 +545,26 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. check gateway's protocol version
|
// 1. check gateway's protocol version
|
||||||
let supports_aes_gcm_siv = match self.get_gateway_protocol().await {
|
// if we failed to get this request resolved, it means the gateway is on an old version
|
||||||
Ok(protocol) => protocol >= AES_GCM_SIV_PROTOCOL_VERSION,
|
// that definitely does not support auth v2 or aes256gcm, so we bail
|
||||||
Err(_) => {
|
let gw_protocol = self.get_gateway_protocol().await?;
|
||||||
// if we failed to send the request, it means the gateway is running the old binary,
|
|
||||||
// so it has reset our connection - we have to reconnect
|
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
|
||||||
self.establish_connection().await?;
|
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !supports_aes_gcm_siv {
|
if !supports_aes_gcm_siv {
|
||||||
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
error!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
||||||
|
}
|
||||||
|
if !supports_aes_gcm_siv {
|
||||||
|
error!("this gateway is on an old version that doesn't support authentication v2");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !supports_auth_v2 || !supports_aes_gcm_siv {
|
||||||
|
// we can't continue
|
||||||
|
return Err(GatewayClientError::IncompatibleProtocol {
|
||||||
|
gateway: gw_protocol,
|
||||||
|
current: CURRENT_PROTOCOL_VERSION,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.authenticated {
|
if self.authenticated {
|
||||||
@@ -634,7 +572,6 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
return if let Some(shared_key) = &self.shared_key {
|
return if let Some(shared_key) = &self.shared_key {
|
||||||
Ok(AuthenticationResponse {
|
Ok(AuthenticationResponse {
|
||||||
initial_shared_key: Arc::clone(shared_key),
|
initial_shared_key: Arc::clone(shared_key),
|
||||||
requires_key_upgrade: shared_key.is_legacy() && supports_aes_gcm_siv,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(GatewayClientError::AuthenticationFailureWithPreexistingSharedKey)
|
Err(GatewayClientError::AuthenticationFailureWithPreexistingSharedKey)
|
||||||
@@ -648,17 +585,14 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
// if we are authenticated it means we MUST have an associated shared_key
|
// if we are authenticated it means we MUST have an associated shared_key
|
||||||
let shared_key = self.shared_key.as_ref().unwrap();
|
let shared_key = self.shared_key.as_ref().unwrap();
|
||||||
|
|
||||||
let requires_key_upgrade = shared_key.is_legacy() && supports_aes_gcm_siv;
|
|
||||||
|
|
||||||
Ok(AuthenticationResponse {
|
Ok(AuthenticationResponse {
|
||||||
initial_shared_key: Arc::clone(shared_key),
|
initial_shared_key: Arc::clone(shared_key),
|
||||||
requires_key_upgrade,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(GatewayClientError::AuthenticationFailure)
|
Err(GatewayClientError::AuthenticationFailure)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.register(supports_aes_gcm_siv).await?;
|
self.register().await?;
|
||||||
|
|
||||||
// if registration didn't return an error, we MUST have an associated shared key
|
// if registration didn't return an error, we MUST have an associated shared key
|
||||||
let shared_key = self.shared_key.as_ref().unwrap();
|
let shared_key = self.shared_key.as_ref().unwrap();
|
||||||
@@ -667,7 +601,6 @@ impl<C, St> GatewayClient<C, St> {
|
|||||||
// so no upgrades are required
|
// so no upgrades are required
|
||||||
Ok(AuthenticationResponse {
|
Ok(AuthenticationResponse {
|
||||||
initial_shared_key: Arc::clone(shared_key),
|
initial_shared_key: Arc::clone(shared_key),
|
||||||
requires_key_upgrade: false,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1045,7 +978,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
|||||||
connection: SocketState::NotConnected,
|
connection: SocketState::NotConnected,
|
||||||
packet_router,
|
packet_router,
|
||||||
bandwidth_controller: None,
|
bandwidth_controller: None,
|
||||||
stats_reporter: ClientStatsSender::new(None),
|
stats_reporter: ClientStatsSender::new(None, task_client.clone()),
|
||||||
negotiated_protocol: None,
|
negotiated_protocol: None,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
connection_fd_callback: None,
|
connection_fd_callback: None,
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
use crate::error::GatewayClientError;
|
||||||
|
|
||||||
|
use nym_http_api_client::HickoryDnsResolver;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::{
|
||||||
|
os::fd::{AsRawFd, RawFd},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||||
|
use tungstenite::handshake::client::Response;
|
||||||
|
use url::{Host, Url};
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(crate) async fn connect_async(
|
||||||
|
endpoint: &str,
|
||||||
|
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||||
|
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
|
||||||
|
use tokio::net::TcpSocket;
|
||||||
|
|
||||||
|
let resolver = HickoryDnsResolver::default();
|
||||||
|
let uri =
|
||||||
|
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
|
||||||
|
let port: u16 = uri.port_or_known_default().unwrap_or(443);
|
||||||
|
|
||||||
|
let host = uri
|
||||||
|
.host()
|
||||||
|
.ok_or(GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
|
||||||
|
|
||||||
|
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
|
||||||
|
// the default std resolve
|
||||||
|
let sock_addrs: Vec<SocketAddr> = match host {
|
||||||
|
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
|
||||||
|
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
|
||||||
|
Host::Domain(domain) => {
|
||||||
|
// Do a DNS lookup for the domain using our custom DNS resolver
|
||||||
|
resolver
|
||||||
|
.resolve_str(domain)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| SocketAddr::new(a, port))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stream = Err(GatewayClientError::NoEndpointForConnection {
|
||||||
|
address: endpoint.to_owned(),
|
||||||
|
});
|
||||||
|
for sock_addr in sock_addrs {
|
||||||
|
let socket = if sock_addr.is_ipv4() {
|
||||||
|
TcpSocket::new_v4()
|
||||||
|
} else {
|
||||||
|
TcpSocket::new_v6()
|
||||||
|
}
|
||||||
|
.map_err(|err| GatewayClientError::NetworkConnectionFailed {
|
||||||
|
address: endpoint.to_owned(),
|
||||||
|
source: err.into(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
if let Some(callback) = connection_fd_callback.as_ref() {
|
||||||
|
callback.as_ref()(socket.as_raw_fd());
|
||||||
|
}
|
||||||
|
|
||||||
|
match socket.connect(sock_addr).await {
|
||||||
|
Ok(s) => {
|
||||||
|
stream = Ok(s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
stream = Err(GatewayClientError::NetworkConnectionFailed {
|
||||||
|
address: endpoint.to_owned(),
|
||||||
|
source: err.into(),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio_tungstenite::client_async_tls(endpoint, stream?)
|
||||||
|
.await
|
||||||
|
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
|
||||||
|
address: endpoint.to_owned(),
|
||||||
|
source: error,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -43,8 +43,15 @@ pub enum GatewayClientError {
|
|||||||
#[error("connection failed: {address}: {source}")]
|
#[error("connection failed: {address}: {source}")]
|
||||||
NetworkConnectionFailed { address: String, source: WsError },
|
NetworkConnectionFailed { address: String, source: WsError },
|
||||||
|
|
||||||
|
#[error("no socket address for endpoint: {address}")]
|
||||||
|
NoEndpointForConnection { address: String },
|
||||||
|
|
||||||
#[error("Invalid URL: {0}")]
|
#[error("Invalid URL: {0}")]
|
||||||
InvalidURL(String),
|
InvalidUrl(String),
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[error("resolution failed: {0}")]
|
||||||
|
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||||
|
|
||||||
#[error("No shared key was provided or obtained")]
|
#[error("No shared key was provided or obtained")]
|
||||||
NoSharedKeyAvailable,
|
NoSharedKeyAvailable,
|
||||||
@@ -107,7 +114,7 @@ pub enum GatewayClientError {
|
|||||||
MixnetMsgSenderFailedToSend,
|
MixnetMsgSenderFailedToSend,
|
||||||
|
|
||||||
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway:?}")]
|
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway:?}")]
|
||||||
IncompatibleProtocol { gateway: Option<u8>, current: u8 },
|
IncompatibleProtocol { gateway: u8, current: u8 },
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
"The packet router hasn't been set - are you sure you started up the client correctly?"
|
"The packet router hasn't been set - are you sure you started up the client correctly?"
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ use tracing::{error, warn};
|
|||||||
use tungstenite::{protocol::Message, Error as WsError};
|
use tungstenite::{protocol::Message, Error as WsError};
|
||||||
|
|
||||||
pub use client::{config::GatewayClientConfig, GatewayClient, GatewayConfig};
|
pub use client::{config::GatewayClientConfig, GatewayClient, GatewayConfig};
|
||||||
pub use nym_gateway_requests::shared_key::{
|
pub use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||||
LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey,
|
|
||||||
};
|
|
||||||
pub use packet_router::{
|
pub use packet_router::{
|
||||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||||
PacketRouter,
|
PacketRouter,
|
||||||
@@ -47,7 +45,7 @@ pub(crate) fn cleanup_socket_messages(
|
|||||||
|
|
||||||
pub(crate) fn try_decrypt_binary_message(
|
pub(crate) fn try_decrypt_binary_message(
|
||||||
bin_msg: Vec<u8>,
|
bin_msg: Vec<u8>,
|
||||||
shared_keys: &SharedGatewayKey,
|
shared_keys: &SharedSymmetricKey,
|
||||||
) -> Option<Vec<u8>> {
|
) -> Option<Vec<u8>> {
|
||||||
match BinaryResponse::try_from_encrypted_tagged_bytes(bin_msg, shared_keys) {
|
match BinaryResponse::try_from_encrypted_tagged_bytes(bin_msg, shared_keys) {
|
||||||
Ok(bin_response) => match bin_response {
|
Ok(bin_response) => match bin_response {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{cleanup_socket_messages, try_decrypt_binary_message};
|
|||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use futures::stream::{SplitSink, SplitStream};
|
use futures::stream::{SplitSink, SplitStream};
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||||
use nym_gateway_requests::{ServerResponse, SimpleGatewayRequestsError};
|
use nym_gateway_requests::{ServerResponse, SimpleGatewayRequestsError};
|
||||||
use nym_task::TaskClient;
|
use nym_task::TaskClient;
|
||||||
use si_scale::helpers::bibytes2;
|
use si_scale::helpers::bibytes2;
|
||||||
@@ -63,7 +63,7 @@ pub(crate) struct PartiallyDelegatedHandle {
|
|||||||
|
|
||||||
struct PartiallyDelegatedRouter {
|
struct PartiallyDelegatedRouter {
|
||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
shared_key: Arc<SharedGatewayKey>,
|
shared_key: Arc<SharedSymmetricKey>,
|
||||||
client_bandwidth: ClientBandwidth,
|
client_bandwidth: ClientBandwidth,
|
||||||
|
|
||||||
stream_return: SplitStreamSender,
|
stream_return: SplitStreamSender,
|
||||||
@@ -73,7 +73,7 @@ struct PartiallyDelegatedRouter {
|
|||||||
impl PartiallyDelegatedRouter {
|
impl PartiallyDelegatedRouter {
|
||||||
fn new(
|
fn new(
|
||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
shared_key: Arc<SharedGatewayKey>,
|
shared_key: Arc<SharedSymmetricKey>,
|
||||||
client_bandwidth: ClientBandwidth,
|
client_bandwidth: ClientBandwidth,
|
||||||
stream_return: SplitStreamSender,
|
stream_return: SplitStreamSender,
|
||||||
stream_return_requester: oneshot::Receiver<()>,
|
stream_return_requester: oneshot::Receiver<()>,
|
||||||
@@ -253,7 +253,7 @@ impl PartiallyDelegatedHandle {
|
|||||||
pub(crate) fn split_and_listen_for_mixnet_messages(
|
pub(crate) fn split_and_listen_for_mixnet_messages(
|
||||||
conn: WsConn,
|
conn: WsConn,
|
||||||
packet_router: PacketRouter,
|
packet_router: PacketRouter,
|
||||||
shared_key: Arc<SharedGatewayKey>,
|
shared_key: Arc<SharedSymmetricKey>,
|
||||||
client_bandwidth: ClientBandwidth,
|
client_bandwidth: ClientBandwidth,
|
||||||
shutdown: TaskClient,
|
shutdown: TaskClient,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ use crate::{
|
|||||||
use nym_api_requests::ecash::models::{
|
use nym_api_requests::ecash::models::{
|
||||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||||
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
|
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
|
||||||
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, SpentCredentialsResponse,
|
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, VerifyEcashTicketBody,
|
||||||
VerifyEcashTicketBody,
|
|
||||||
};
|
};
|
||||||
use nym_api_requests::ecash::{
|
use nym_api_requests::ecash::{
|
||||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||||
@@ -24,11 +23,12 @@ use nym_api_requests::models::{
|
|||||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||||
};
|
};
|
||||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SkimmedNode};
|
||||||
use nym_coconut_dkg_common::types::EpochId;
|
use nym_coconut_dkg_common::types::EpochId;
|
||||||
use nym_ecash_contract_common::deposit::DepositId;
|
use nym_ecash_contract_common::deposit::DepositId;
|
||||||
use nym_http_api_client::UserAgent;
|
use nym_http_api_client::UserAgent;
|
||||||
use nym_network_defaults::NymNetworkDetails;
|
use nym_network_defaults::NymNetworkDetails;
|
||||||
|
use std::net::IpAddr;
|
||||||
use time::Date;
|
use time::Date;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@@ -647,13 +647,6 @@ impl NymApiClient {
|
|||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deprecated]
|
|
||||||
pub async fn spent_credentials_filter(
|
|
||||||
&self,
|
|
||||||
) -> Result<SpentCredentialsResponse, ValidatorClientError> {
|
|
||||||
Ok(self.nym_api.double_spending_filter_v1().await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn partial_expiration_date_signatures(
|
pub async fn partial_expiration_date_signatures(
|
||||||
&self,
|
&self,
|
||||||
expiration_date: Option<Date>,
|
expiration_date: Option<Date>,
|
||||||
@@ -718,4 +711,11 @@ impl NymApiClient {
|
|||||||
.issued_ticketbooks_challenge(expiration_date, deposits)
|
.issued_ticketbooks_challenge(expiration_date, deposits)
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn nodes_by_addresses(
|
||||||
|
&self,
|
||||||
|
addresses: Vec<IpAddr>,
|
||||||
|
) -> Result<NodesByAddressesResponse, ValidatorClientError> {
|
||||||
|
Ok(self.nym_api.nodes_by_addresses(addresses).await?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,12 @@ pub enum EcashApiError {
|
|||||||
#[from]
|
#[from]
|
||||||
source: cosmrs::ErrorReport,
|
source: cosmrs::ErrorReport,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("nym api error")]
|
||||||
|
NymApi {
|
||||||
|
#[from]
|
||||||
|
source: crate::ValidatorClientError,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ContractVKShare> for EcashApiClient {
|
impl TryFrom<ContractVKShare> for EcashApiClient {
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ use nym_api_requests::models::{
|
|||||||
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||||
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
||||||
};
|
};
|
||||||
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
|
use nym_api_requests::nym_nodes::{
|
||||||
|
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
|
||||||
|
};
|
||||||
use nym_api_requests::pagination::PaginatedResponse;
|
use nym_api_requests::pagination::PaginatedResponse;
|
||||||
pub use nym_api_requests::{
|
pub use nym_api_requests::{
|
||||||
ecash::{
|
ecash::{
|
||||||
@@ -31,6 +33,7 @@ pub use nym_api_requests::{
|
|||||||
StakeSaturationResponse, UptimeResponse,
|
StakeSaturationResponse, UptimeResponse,
|
||||||
},
|
},
|
||||||
nym_nodes::{CachedNodesResponse, SkimmedNode},
|
nym_nodes::{CachedNodesResponse, SkimmedNode},
|
||||||
|
NymNetworkDetailsResponse,
|
||||||
};
|
};
|
||||||
pub use nym_coconut_dkg_common::types::EpochId;
|
pub use nym_coconut_dkg_common::types::EpochId;
|
||||||
use nym_contracts_common::IdentityKey;
|
use nym_contracts_common::IdentityKey;
|
||||||
@@ -39,6 +42,7 @@ pub use nym_http_api_client::Client;
|
|||||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
|
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
|
||||||
|
use std::net::IpAddr;
|
||||||
use time::format_description::BorrowedFormatItem;
|
use time::format_description::BorrowedFormatItem;
|
||||||
use time::Date;
|
use time::Date;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
@@ -849,20 +853,6 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deprecated]
|
|
||||||
#[instrument(level = "debug", skip(self))]
|
|
||||||
async fn double_spending_filter_v1(&self) -> Result<SpentCredentialsResponse, NymAPIError> {
|
|
||||||
self.get_json(
|
|
||||||
&[
|
|
||||||
routes::API_VERSION,
|
|
||||||
routes::ECASH_ROUTES,
|
|
||||||
routes::DOUBLE_SPENDING_FILTER_V1,
|
|
||||||
],
|
|
||||||
NO_PARAMS,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self))]
|
#[instrument(level = "debug", skip(self))]
|
||||||
async fn partial_expiration_date_signatures(
|
async fn partial_expiration_date_signatures(
|
||||||
&self,
|
&self,
|
||||||
@@ -1027,6 +1017,32 @@ pub trait NymApiClientExt: ApiClient {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn nodes_by_addresses(
|
||||||
|
&self,
|
||||||
|
addresses: Vec<IpAddr>,
|
||||||
|
) -> Result<NodesByAddressesResponse, NymAPIError> {
|
||||||
|
self.post_json(
|
||||||
|
&[
|
||||||
|
routes::API_VERSION,
|
||||||
|
"unstable",
|
||||||
|
routes::NYM_NODES_ROUTES,
|
||||||
|
routes::nym_nodes::BY_ADDRESSES,
|
||||||
|
],
|
||||||
|
NO_PARAMS,
|
||||||
|
&NodesByAddressesRequestBody { addresses },
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "debug", skip(self))]
|
||||||
|
async fn get_network_details(&self) -> Result<NymNetworkDetailsResponse, NymAPIError> {
|
||||||
|
self.get_json(
|
||||||
|
&[routes::API_VERSION, routes::NETWORK, routes::DETAILS],
|
||||||
|
NO_PARAMS,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ pub const DETAILED: &str = "detailed";
|
|||||||
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
|
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
|
||||||
pub const ACTIVE: &str = "active";
|
pub const ACTIVE: &str = "active";
|
||||||
pub const REWARDED: &str = "rewarded";
|
pub const REWARDED: &str = "rewarded";
|
||||||
pub const DOUBLE_SPENDING_FILTER_V1: &str = "double-spending-filter-v1";
|
|
||||||
|
|
||||||
pub const ECASH_ROUTES: &str = "ecash";
|
pub const ECASH_ROUTES: &str = "ecash";
|
||||||
|
|
||||||
pub use ecash::*;
|
pub use ecash::*;
|
||||||
@@ -45,6 +43,7 @@ pub mod nym_nodes {
|
|||||||
pub const NYM_NODES_BONDED: &str = "bonded";
|
pub const NYM_NODES_BONDED: &str = "bonded";
|
||||||
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
|
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
|
||||||
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
|
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
|
||||||
|
pub const BY_ADDRESSES: &str = "by-addresses";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const STATUS_ROUTES: &str = "status";
|
pub const STATUS_ROUTES: &str = "status";
|
||||||
@@ -67,6 +66,8 @@ pub const STAKE_SATURATION: &str = "stake-saturation";
|
|||||||
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
||||||
pub const SUBMIT_GATEWAY: &str = "submit-gateway-monitoring-results";
|
pub const SUBMIT_GATEWAY: &str = "submit-gateway-monitoring-results";
|
||||||
pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
|
pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
|
||||||
pub const PERFORMANCE: &str = "performance";
|
|
||||||
|
|
||||||
pub const SERVICE_PROVIDERS: &str = "services";
|
pub const SERVICE_PROVIDERS: &str = "services";
|
||||||
|
|
||||||
|
pub const DETAILS: &str = "details";
|
||||||
|
pub const NETWORK: &str = "network";
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ use nym_network_defaults::{ChainDetails, NymNetworkDetails};
|
|||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use tendermint_rpc::endpoint::block::Response as BlockResponse;
|
|
||||||
use tendermint_rpc::endpoint::*;
|
use tendermint_rpc::endpoint::*;
|
||||||
use tendermint_rpc::{Error as TendermintRpcError, Order};
|
use tendermint_rpc::{Error as TendermintRpcError, Order};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -63,6 +62,7 @@ pub use cw3;
|
|||||||
pub use cw4;
|
pub use cw4;
|
||||||
pub use cw_controllers;
|
pub use cw_controllers;
|
||||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||||
|
pub use tendermint_rpc::endpoint::block::Response as BlockResponse;
|
||||||
pub use tendermint_rpc::{
|
pub use tendermint_rpc::{
|
||||||
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
|
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
|
||||||
query::Query,
|
query::Query,
|
||||||
|
|||||||
@@ -25,15 +25,6 @@ pub fn in6addr_any_init() -> IpAddr {
|
|||||||
IpAddr::V6(Ipv6Addr::UNSPECIFIED)
|
IpAddr::V6(Ipv6Addr::UNSPECIFIED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper for providing binding warnings if node tries to bind to any of those
|
|
||||||
pub const SPECIAL_ADDRESSES: &[IpAddr] = &[
|
|
||||||
IpAddr::V4(Ipv4Addr::LOCALHOST),
|
|
||||||
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
|
|
||||||
IpAddr::V4(Ipv4Addr::BROADCAST),
|
|
||||||
IpAddr::V6(Ipv6Addr::LOCALHOST),
|
|
||||||
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
|
|
||||||
];
|
|
||||||
|
|
||||||
// TODO: is it really part of 'Config'?
|
// TODO: is it really part of 'Config'?
|
||||||
pub trait OptionalSet {
|
pub trait OptionalSet {
|
||||||
/// If the value is available (i.e. `Some`), the provided closure is applied.
|
/// If the value is available (i.e. `Some`), the provided closure is applied.
|
||||||
|
|||||||
@@ -8,81 +8,81 @@ use thiserror::Error;
|
|||||||
|
|
||||||
#[derive(Error, Debug, PartialEq)]
|
#[derive(Error, Debug, PartialEq)]
|
||||||
pub enum VestingContractError {
|
pub enum VestingContractError {
|
||||||
#[error("VESTING ({}): {0}", line!())]
|
#[error("VESTING ({l}): {0}", l = line!())]
|
||||||
Std(#[from] StdError),
|
Std(#[from] StdError),
|
||||||
|
|
||||||
#[error("VESTING: {0}")]
|
#[error("VESTING: {0}")]
|
||||||
OverflowError(#[from] OverflowError),
|
OverflowError(#[from] OverflowError),
|
||||||
|
|
||||||
#[error("VESTING ({}): Account does not exist - {0}", line!())]
|
#[error("VESTING ({l}): Account does not exist - {0}", l = line!())]
|
||||||
NoAccountForAddress(String),
|
NoAccountForAddress(String),
|
||||||
|
|
||||||
#[error("VESTING ({}): Only admin can perform this action, {0} is not admin", line!())]
|
#[error("VESTING ({l}): Only admin can perform this action, {0} is not admin", l = line!())]
|
||||||
NotAdmin(String),
|
NotAdmin(String),
|
||||||
|
|
||||||
#[error("VESTING ({}): Balance not found for existing account ({0}), this is a bug", line!())]
|
#[error("VESTING ({l}): Balance not found for existing account ({0}), this is a bug", l = line!())]
|
||||||
NoBalanceForAddress(String),
|
NoBalanceForAddress(String),
|
||||||
|
|
||||||
#[error("VESTING ({}): Insufficient balance for address {0} -> {1}", line!())]
|
#[error("VESTING ({l}): Insufficient balance for address {0} -> {1}", l = line!())]
|
||||||
InsufficientBalance(String, u128),
|
InsufficientBalance(String, u128),
|
||||||
|
|
||||||
#[error("VESTING ({}): Insufficient spendable balance for address {0} -> {1}", line!())]
|
#[error("VESTING ({l}): Insufficient spendable balance for address {0} -> {1}", l = line!())]
|
||||||
InsufficientSpendable(String, u128),
|
InsufficientSpendable(String, u128),
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
"VESTING ({}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
|
"VESTING ({l}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
|
||||||
, line!())]
|
, l = line!())]
|
||||||
NotDelegate(String),
|
NotDelegate(String),
|
||||||
|
|
||||||
#[error("VESTING ({}): Total vesting amount is inprobably low -> {0}, this is likely an error", line!())]
|
#[error("VESTING ({l}): Total vesting amount is inprobably low -> {0}, this is likely an error", l = line!())]
|
||||||
ImprobableVestingAmount(u128),
|
ImprobableVestingAmount(u128),
|
||||||
|
|
||||||
#[error("VESTING ({}): Address {0} has already bonded a node", line!())]
|
#[error("VESTING ({l}): Address {0} has already bonded a node", l = line!())]
|
||||||
AlreadyBonded(String),
|
AlreadyBonded(String),
|
||||||
|
|
||||||
#[error("VESTING ({}): Received empty funds vector", line!())]
|
#[error("VESTING ({l}): Received empty funds vector", l = line!())]
|
||||||
EmptyFunds,
|
EmptyFunds,
|
||||||
|
|
||||||
#[error("VESTING ({}): Received wrong denom: {0}, expected {1}", line!())]
|
#[error("VESTING ({l}): Received wrong denom: {0}, expected {1}", l = line!())]
|
||||||
WrongDenom(String, String),
|
WrongDenom(String, String),
|
||||||
|
|
||||||
#[error("VESTING ({}): Received multiple denoms, expected 1", line!())]
|
#[error("VESTING ({l}): Received multiple denoms, expected 1", l = line!())]
|
||||||
MultipleDenoms,
|
MultipleDenoms,
|
||||||
|
|
||||||
#[error("VESTING ({}): No delegations found for account {0}, mix_identity {1}", line!())]
|
#[error("VESTING ({l}): No delegations found for account {0}, mix_identity {1}", l = line!())]
|
||||||
NoSuchDelegation(Addr, NodeId),
|
NoSuchDelegation(Addr, NodeId),
|
||||||
|
|
||||||
#[error("VESTING ({}): Only mixnet contract can perform this operation, got {0}", line!())]
|
#[error("VESTING ({l}): Only mixnet contract can perform this operation, got {0}", l = line!())]
|
||||||
NotMixnetContract(Addr),
|
NotMixnetContract(Addr),
|
||||||
|
|
||||||
#[error("VESTING ({}): Calculation underflowed", line!())]
|
#[error("VESTING ({l}): Calculation underflowed", l = line!())]
|
||||||
Underflow,
|
Underflow,
|
||||||
|
|
||||||
#[error("VESTING ({}): No bond found for account {0}", line!())]
|
#[error("VESTING ({l}): No bond found for account {0}", l = line!())]
|
||||||
NoBondFound(String),
|
NoBondFound(String),
|
||||||
|
|
||||||
#[error("VESTING: Attempted to reduce mixnode bond pledge below zero! The current pledge is {current} and we attempted to reduce it by {decrease_by}.")]
|
#[error("VESTING: Attempted to reduce mixnode bond pledge below zero! The current pledge is {current} and we attempted to reduce it by {decrease_by}.")]
|
||||||
InvalidBondPledgeReduction { current: Coin, decrease_by: Coin },
|
InvalidBondPledgeReduction { current: Coin, decrease_by: Coin },
|
||||||
|
|
||||||
#[error("VESTING ({}): Action can only be executed by account owner -> {0}", line!())]
|
#[error("VESTING ({l}): Action can only be executed by account owner -> {0}", l = line!())]
|
||||||
NotOwner(String),
|
NotOwner(String),
|
||||||
|
|
||||||
#[error("VESTING ({}): Invalid address: {0}", line!())]
|
#[error("VESTING ({l}): Invalid address: {0}", l = line!())]
|
||||||
InvalidAddress(String),
|
InvalidAddress(String),
|
||||||
|
|
||||||
#[error("VESTING ({}): Account already exists: {0}", line!())]
|
#[error("VESTING ({l}): Account already exists: {0}", l = line!())]
|
||||||
AccountAlreadyExists(String),
|
AccountAlreadyExists(String),
|
||||||
|
|
||||||
#[error("VESTING ({}): Staking account already exists: {0}", line!())]
|
#[error("VESTING ({l}): Staking account already exists: {0}", l = line!())]
|
||||||
StakingAccountAlreadyExists(String),
|
StakingAccountAlreadyExists(String),
|
||||||
|
|
||||||
#[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())]
|
#[error("VESTING ({l}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", l = line!())]
|
||||||
MinVestingFunds { sent: u128, need: u128 },
|
MinVestingFunds { sent: u128, need: u128 },
|
||||||
|
|
||||||
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
|
#[error("VESTING ({l}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", l = line!())]
|
||||||
LockedPledgeCapReached { current: Uint128, cap: Uint128 },
|
LockedPledgeCapReached { current: Uint128, cap: Uint128 },
|
||||||
|
|
||||||
#[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())]
|
#[error("VESTING: ({l}: Account owned by {owner} has unpopulated vesting periods!", l = line!())]
|
||||||
UnpopulatedVestingPeriods { owner: Addr },
|
UnpopulatedVestingPeriods { owner: Addr },
|
||||||
|
|
||||||
#[error("VESTING: Vesting account associated with {0} already exists, only addresses with not existing vesting accounts can be added as staking addresses")]
|
#[error("VESTING: Vesting account associated with {0} already exists, only addresses with not existing vesting accounts can be added as staking addresses")]
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
|||||||
nym-credentials = { path = "../credentials" }
|
nym-credentials = { path = "../credentials" }
|
||||||
nym-credentials-interface = { path = "../credentials-interface" }
|
nym-credentials-interface = { path = "../credentials-interface" }
|
||||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||||
nym-ecash-double-spending = { path = "../ecash-double-spending" }
|
|
||||||
nym-gateway-requests = { path = "../gateway-requests" }
|
nym-gateway-requests = { path = "../gateway-requests" }
|
||||||
nym-gateway-storage = { path = "../gateway-storage" }
|
nym-gateway-storage = { path = "../gateway-storage" }
|
||||||
nym-task = { path = "../task" }
|
nym-task = { path = "../task" }
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
|
|||||||
use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody};
|
use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody};
|
||||||
use nym_credentials_interface::Bandwidth;
|
use nym_credentials_interface::Bandwidth;
|
||||||
use nym_credentials_interface::{ClientTicket, TicketType};
|
use nym_credentials_interface::{ClientTicket, TicketType};
|
||||||
|
use nym_validator_client::coconut::EcashApiError;
|
||||||
use nym_validator_client::nym_api::EpochId;
|
use nym_validator_client::nym_api::EpochId;
|
||||||
use nym_validator_client::nyxd::contract_traits::{
|
use nym_validator_client::nyxd::contract_traits::{
|
||||||
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
|
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
|
||||||
@@ -352,7 +353,9 @@ impl CredentialHandler {
|
|||||||
}
|
}
|
||||||
Err(err) => {
|
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");
|
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,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ ed25519-dalek = { workspace = true, features = ["rand_core"], optional = true }
|
|||||||
rand = { workspace = true, optional = true }
|
rand = { workspace = true, optional = true }
|
||||||
serde_bytes = { workspace = true, optional = true }
|
serde_bytes = { workspace = true, optional = true }
|
||||||
serde = { workspace = true, features = ["derive"], optional = true }
|
serde = { workspace = true, features = ["derive"], optional = true }
|
||||||
|
sha2 = { workspace = true, optional = true }
|
||||||
subtle-encoding = { workspace = true, features = ["bech32-preview"] }
|
subtle-encoding = { workspace = true, features = ["bech32-preview"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
|
zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
|
||||||
@@ -36,11 +37,13 @@ nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
|||||||
rand_chacha = { workspace = true }
|
rand_chacha = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["sphinx"]
|
default = []
|
||||||
aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"]
|
aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"]
|
||||||
serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
|
serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
|
||||||
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
||||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"]
|
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
|
||||||
stream_cipher = ["aes", "ctr", "cipher", "generic-array"]
|
stream_cipher = ["aes", "ctr", "cipher", "generic-array"]
|
||||||
sphinx = ["nym-sphinx-types/sphinx"]
|
sphinx = ["nym-sphinx-types/sphinx"]
|
||||||
outfox = ["nym-sphinx-types/outfox"]
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
@@ -202,6 +202,18 @@ impl PemStorableKey for PublicKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<x25519_dalek::PublicKey> for PublicKey {
|
||||||
|
fn from(public_key: x25519_dalek::PublicKey) -> Self {
|
||||||
|
PublicKey(public_key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PublicKey> for x25519_dalek::PublicKey {
|
||||||
|
fn from(public_key: PublicKey) -> Self {
|
||||||
|
public_key.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||||
pub struct PrivateKey(x25519_dalek::StaticSecret);
|
pub struct PrivateKey(x25519_dalek::StaticSecret);
|
||||||
|
|
||||||
@@ -308,109 +320,15 @@ impl PemStorableKey for PrivateKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// compatibility with sphinx keys:
|
impl From<x25519_dalek::StaticSecret> for PrivateKey {
|
||||||
#[cfg(feature = "sphinx")]
|
fn from(secret: x25519_dalek::StaticSecret) -> Self {
|
||||||
impl From<PublicKey> for nym_sphinx_types::PublicKey {
|
PrivateKey(secret)
|
||||||
fn from(key: PublicKey) -> Self {
|
|
||||||
nym_sphinx_types::PublicKey::from(key.to_bytes())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sphinx")]
|
impl AsRef<x25519_dalek::StaticSecret> for PrivateKey {
|
||||||
impl<'a> From<&'a PublicKey> for nym_sphinx_types::PublicKey {
|
fn as_ref(&self) -> &x25519_dalek::StaticSecret {
|
||||||
fn from(key: &'a PublicKey) -> Self {
|
&self.0
|
||||||
nym_sphinx_types::PublicKey::from((*key).to_bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sphinx")]
|
|
||||||
impl From<nym_sphinx_types::PublicKey> for PublicKey {
|
|
||||||
fn from(pub_key: nym_sphinx_types::PublicKey) -> Self {
|
|
||||||
Self(x25519_dalek::PublicKey::from(*pub_key.as_bytes()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sphinx")]
|
|
||||||
impl From<PrivateKey> for nym_sphinx_types::PrivateKey {
|
|
||||||
fn from(key: PrivateKey) -> Self {
|
|
||||||
nym_sphinx_types::PrivateKey::from(key.to_bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sphinx")]
|
|
||||||
impl<'a> From<&'a PrivateKey> for nym_sphinx_types::PrivateKey {
|
|
||||||
fn from(key: &'a PrivateKey) -> Self {
|
|
||||||
nym_sphinx_types::PrivateKey::from(key.to_bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sphinx")]
|
|
||||||
impl From<nym_sphinx_types::PrivateKey> for PrivateKey {
|
|
||||||
fn from(private_key: nym_sphinx_types::PrivateKey) -> Self {
|
|
||||||
let private_key_bytes = private_key.to_bytes();
|
|
||||||
assert_eq!(private_key_bytes.len(), PRIVATE_KEY_SIZE);
|
|
||||||
Self::from_bytes(&private_key_bytes).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod sphinx_key_conversion {
|
|
||||||
use super::*;
|
|
||||||
use rand_chacha::rand_core::SeedableRng;
|
|
||||||
use rand_chacha::ChaCha20Rng;
|
|
||||||
|
|
||||||
pub(super) fn test_rng() -> ChaCha20Rng {
|
|
||||||
let dummy_seed = [42u8; 32];
|
|
||||||
ChaCha20Rng::from_seed(dummy_seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
const NUM_ITERATIONS: usize = 100;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn works_for_forward_conversion() {
|
|
||||||
let mut rng = test_rng();
|
|
||||||
|
|
||||||
for _ in 0..NUM_ITERATIONS {
|
|
||||||
let keys = KeyPair::new(&mut rng);
|
|
||||||
let private = &keys.private_key;
|
|
||||||
let public = &keys.public_key;
|
|
||||||
|
|
||||||
let dummy_remote = KeyPair::new(&mut rng);
|
|
||||||
let dh1 = private.diffie_hellman(&dummy_remote.public_key);
|
|
||||||
|
|
||||||
let public_bytes = public.to_bytes();
|
|
||||||
|
|
||||||
let sphinx_private: nym_sphinx_types::PrivateKey = private.into();
|
|
||||||
let recovered_private = PrivateKey::from(sphinx_private);
|
|
||||||
|
|
||||||
let dh2 = recovered_private.diffie_hellman(&dummy_remote.public_key);
|
|
||||||
|
|
||||||
let sphinx_public: nym_sphinx_types::PublicKey = public.into();
|
|
||||||
let recovered_public = PublicKey::from(sphinx_public);
|
|
||||||
assert_eq!(public_bytes, recovered_public.to_bytes());
|
|
||||||
|
|
||||||
// even though the byte representation of the private key changed, the resultant DH is the same
|
|
||||||
// which is what matters
|
|
||||||
assert_eq!(dh1, dh2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn works_for_backward_conversion() {
|
|
||||||
for _ in 0..NUM_ITERATIONS {
|
|
||||||
let (sphinx_private, sphinx_public) = nym_sphinx_types::crypto::keygen();
|
|
||||||
|
|
||||||
let private_bytes = sphinx_private.to_bytes();
|
|
||||||
let public_bytes = sphinx_public.as_bytes();
|
|
||||||
|
|
||||||
let private: PrivateKey = sphinx_private.into();
|
|
||||||
let recovered_sphinx_private: nym_sphinx_types::PrivateKey = private.into();
|
|
||||||
|
|
||||||
let public: PublicKey = sphinx_public.into();
|
|
||||||
let recovered_sphinx_public: nym_sphinx_types::PublicKey = public.into();
|
|
||||||
assert_eq!(private_bytes, recovered_sphinx_private.to_bytes());
|
|
||||||
assert_eq!(public_bytes, recovered_sphinx_public.as_bytes());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
pub use ed25519_dalek::SignatureError;
|
pub use ed25519_dalek::SignatureError;
|
||||||
use ed25519_dalek::{Signer, SigningKey};
|
use ed25519_dalek::{SecretKey, Signer, SigningKey};
|
||||||
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
|
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||||
use std::fmt::{self, Debug, Display, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
@@ -18,7 +18,7 @@ pub mod serde_helpers;
|
|||||||
use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
|
use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
|
||||||
|
|
||||||
#[cfg(feature = "rand")]
|
#[cfg(feature = "rand")]
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, Rng, RngCore};
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::de::Error as SerdeError;
|
use serde::de::Error as SerdeError;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
@@ -62,16 +62,33 @@ pub struct KeyPair {
|
|||||||
// nothing secret about public key
|
// nothing secret about public key
|
||||||
#[zeroize(skip)]
|
#[zeroize(skip)]
|
||||||
public_key: PublicKey,
|
public_key: PublicKey,
|
||||||
|
|
||||||
|
#[zeroize(skip)]
|
||||||
|
index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All keys will always have an index field populated this is to prevent anyone from figuring out if
|
||||||
|
/// the keys are derived or random, and alter their behaviour based on that.
|
||||||
impl KeyPair {
|
impl KeyPair {
|
||||||
#[cfg(feature = "rand")]
|
#[cfg(feature = "rand")]
|
||||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||||
|
let index = rng.gen();
|
||||||
let ed25519_signing_key = ed25519_dalek::SigningKey::generate(rng);
|
let ed25519_signing_key = ed25519_dalek::SigningKey::generate(rng);
|
||||||
|
|
||||||
KeyPair {
|
KeyPair {
|
||||||
private_key: PrivateKey(ed25519_signing_key.to_bytes()),
|
private_key: PrivateKey(ed25519_signing_key.to_bytes()),
|
||||||
public_key: PublicKey(ed25519_signing_key.verifying_key()),
|
public_key: PublicKey(ed25519_signing_key.verifying_key()),
|
||||||
|
index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_secret(secret: SecretKey, index: u32) -> Self {
|
||||||
|
let ed25519_signing_key = SigningKey::from(secret);
|
||||||
|
|
||||||
|
KeyPair {
|
||||||
|
private_key: PrivateKey(ed25519_signing_key.to_bytes()),
|
||||||
|
public_key: PublicKey(ed25519_signing_key.verifying_key()),
|
||||||
|
index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,15 +104,31 @@ impl KeyPair {
|
|||||||
Ok(KeyPair {
|
Ok(KeyPair {
|
||||||
private_key: PrivateKey::from_bytes(priv_bytes)?,
|
private_key: PrivateKey::from_bytes(priv_bytes)?,
|
||||||
public_key: PublicKey::from_bytes(pub_bytes)?,
|
public_key: PublicKey::from_bytes(pub_bytes)?,
|
||||||
|
index: fake_index(pub_bytes),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reduces a byte slice into a u32 value by XOR-ing all its bytes into a 4-byte accumulator.
|
||||||
|
/// The process iterates over every byte in the input slice, XOR-ing each one into a slot based on its index modulo 4.
|
||||||
|
/// If the input slice contains fewer than 4 bytes, the remaining positions in the accumulator remain zero.
|
||||||
|
/// Finally, the accumulator is interpreted in big-endian order to produce the resulting u32.
|
||||||
|
/// Index is used to verify deterministic identity key, master key and salt are also requried for verification.
|
||||||
|
fn fake_index(input: &[u8]) -> u32 {
|
||||||
|
let mut accumulator = [0u8; 4];
|
||||||
|
for (i, &byte) in input.iter().enumerate() {
|
||||||
|
accumulator[i % 4] ^= byte;
|
||||||
|
}
|
||||||
|
u32::from_be_bytes(accumulator)
|
||||||
|
}
|
||||||
|
|
||||||
impl From<PrivateKey> for KeyPair {
|
impl From<PrivateKey> for KeyPair {
|
||||||
fn from(private_key: PrivateKey) -> Self {
|
fn from(private_key: PrivateKey) -> Self {
|
||||||
|
let public_key = (&private_key).into();
|
||||||
KeyPair {
|
KeyPair {
|
||||||
public_key: (&private_key).into(),
|
public_key,
|
||||||
private_key,
|
private_key,
|
||||||
|
index: fake_index(public_key.to_bytes().as_ref()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,6 +148,7 @@ impl PemStorableKeyPair for KeyPair {
|
|||||||
KeyPair {
|
KeyPair {
|
||||||
private_key,
|
private_key,
|
||||||
public_key,
|
public_key,
|
||||||
|
index: fake_index(public_key.to_bytes().as_ref()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ use hkdf::{
|
|||||||
},
|
},
|
||||||
Hkdf,
|
Hkdf,
|
||||||
};
|
};
|
||||||
|
use sha2::{Sha256, Sha512};
|
||||||
|
|
||||||
|
pub use hkdf::InvalidLength;
|
||||||
|
use zeroize::ZeroizeOnDrop;
|
||||||
|
|
||||||
/// Perform HKDF `extract` then `expand` as a single step.
|
/// Perform HKDF `extract` then `expand` as a single step.
|
||||||
pub fn extract_then_expand<D>(
|
pub fn extract_then_expand<D>(
|
||||||
@@ -28,3 +32,80 @@ where
|
|||||||
|
|
||||||
Ok(okm)
|
Ok(okm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `DerivationMaterial` encapsulates parameters for deterministic key derivation using
|
||||||
|
/// HKDF (SHA-512).
|
||||||
|
///
|
||||||
|
/// It consists of:
|
||||||
|
/// - A master key (`master_key`): the base secret.
|
||||||
|
/// - An index (`index`): ensures unique derivations.
|
||||||
|
/// - A salt (`salt`): adds additional uniqueness, should be application specific.
|
||||||
|
///
|
||||||
|
/// Use the `derive_secret()` method to generate a 32-byte secret. To prepare for a new derivation,
|
||||||
|
/// call the `next()` method, which increments the index. **It is the caller's responsibility to
|
||||||
|
/// track and persist the derivation index if keys need to be rederived.**
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use nym_crypto::hkdf::DerivationMaterial;
|
||||||
|
///
|
||||||
|
/// let master_key = [0u8; 32]; // your secret master key
|
||||||
|
/// let salt = b"unique-salt-value";
|
||||||
|
/// let material = DerivationMaterial::new(master_key, 0, salt);
|
||||||
|
///
|
||||||
|
/// // Derive a secret
|
||||||
|
/// let secret = material.derive_secret().expect("Failed to derive secret");
|
||||||
|
///
|
||||||
|
/// // Prepare for the next derivation
|
||||||
|
/// let next_material = material.next();
|
||||||
|
/// ```
|
||||||
|
#[derive(ZeroizeOnDrop)]
|
||||||
|
pub struct DerivationMaterial {
|
||||||
|
master_key: [u8; 32],
|
||||||
|
index: u32,
|
||||||
|
salt: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerivationMaterial {
|
||||||
|
pub fn index(&self) -> u32 {
|
||||||
|
self.index
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn salt(&self) -> &[u8] {
|
||||||
|
&self.salt
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derives a 32-byte seed from a master seed and an index using HKDF (with SHA-512).
|
||||||
|
///
|
||||||
|
/// The `salt` and the use of the index (as info) bind this derivation to an application/client.
|
||||||
|
pub fn derive_secret(&self) -> Result<[u8; 32], hkdf::InvalidLength> {
|
||||||
|
let salt = &self.salt;
|
||||||
|
let info = self.index.to_be_bytes(); // Use the index as info
|
||||||
|
let hk = Hkdf::<Sha512>::new(Some(salt), &self.master_key);
|
||||||
|
let mut okm = [0u8; 32];
|
||||||
|
hk.expand(&info, &mut okm)?;
|
||||||
|
Ok(okm)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new<T: AsRef<[u8]>>(master_key: T, index: u32, salt: &[u8]) -> Self {
|
||||||
|
// Coerce master_key to [u8; 32]
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(master_key.as_ref());
|
||||||
|
let master_key = hasher.finalize().into();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
master_key,
|
||||||
|
index,
|
||||||
|
salt: salt.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
master_key: self.master_key,
|
||||||
|
index: self.index + 1,
|
||||||
|
salt: self.salt.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,8 +16,11 @@ pub fn compute_keyed_hmac<D>(key: &[u8], data: &[u8]) -> HmacOutput<D>
|
|||||||
where
|
where
|
||||||
D: Digest + BlockSizeUser,
|
D: Digest + BlockSizeUser,
|
||||||
{
|
{
|
||||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
|
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
|
||||||
.expect("HMAC was instantiated with a key of an invalid size!");
|
// digest, they're padded with 0. if they're larger they're hashed and padded
|
||||||
|
// the reason for `Result` return type is due to the trait definition
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let mut hmac = SimpleHmac::<D>::new_from_slice(key).unwrap();
|
||||||
hmac.update(data);
|
hmac.update(data);
|
||||||
hmac.finalize()
|
hmac.finalize()
|
||||||
}
|
}
|
||||||
@@ -27,8 +30,11 @@ pub fn recompute_keyed_hmac_and_verify_tag<D>(key: &[u8], data: &[u8], tag: &[u8
|
|||||||
where
|
where
|
||||||
D: Digest + BlockSizeUser,
|
D: Digest + BlockSizeUser,
|
||||||
{
|
{
|
||||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
|
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
|
||||||
.expect("HMAC was instantiated with a key of an invalid size!");
|
// digest, they're padded with 0. if they're larger they're hashed and padded
|
||||||
|
// the reason for `Result` return type is due to the trait definition
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let mut hmac = SimpleHmac::<D>::new_from_slice(key).unwrap();
|
||||||
hmac.update(data);
|
hmac.update(data);
|
||||||
|
|
||||||
let tag_arr = Output::<D>::from_slice(tag);
|
let tag_arr = Output::<D>::from_slice(tag);
|
||||||
|
|||||||
@@ -27,12 +27,16 @@ where
|
|||||||
// after performing diffie-hellman we don't care about the private component anymore
|
// after performing diffie-hellman we don't care about the private component anymore
|
||||||
let dh_result = ephemeral_keypair.private_key().diffie_hellman(remote_key);
|
let dh_result = ephemeral_keypair.private_key().diffie_hellman(remote_key);
|
||||||
|
|
||||||
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
|
// SAFETY: while this is a relatively weak assumption, it's unlikely that any stream cipher has `C::key_size()`
|
||||||
|
// larger than 255 * chunk_size of the digest (so for example keys larger than 8160 bytes if sh256 is used)
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
||||||
.expect("somehow too long okm was provided");
|
.expect("somehow too long okm was provided");
|
||||||
|
|
||||||
let derived_shared_key =
|
// SAFETY: the generated okm has exactly `C::key_size()` elements,
|
||||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
|
// so this call is safe
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let derived_shared_key = Key::<C>::from_exact_iter(okm).unwrap();
|
||||||
|
|
||||||
(ephemeral_keypair, derived_shared_key)
|
(ephemeral_keypair, derived_shared_key)
|
||||||
}
|
}
|
||||||
@@ -48,9 +52,14 @@ where
|
|||||||
{
|
{
|
||||||
let dh_result = local_key.diffie_hellman(remote_key);
|
let dh_result = local_key.diffie_hellman(remote_key);
|
||||||
|
|
||||||
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
|
// SAFETY: while this is a relatively weak assumption, it's unlikely that any stream cipher has `C::key_size()`
|
||||||
|
// larger than 255 * chunk_size of the digest (so for example keys larger than 8160 bytes if sh256 is used)
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
||||||
.expect("somehow too long okm was provided");
|
.expect("somehow too long okm was provided");
|
||||||
|
|
||||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
|
// SAFETY: the generated okm has exactly `C::key_size()` elements,
|
||||||
|
// so this call is safe
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
Key::<C>::from_exact_iter(okm).unwrap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,20 +60,15 @@ where
|
|||||||
Iv::<C>::default()
|
Iv::<C>::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iv_from_slice<C>(b: &[u8]) -> &IV<C>
|
pub fn try_iv_from_slice<C>(b: &[u8]) -> Option<&IV<C>>
|
||||||
where
|
where
|
||||||
C: IvSizeUser,
|
C: IvSizeUser,
|
||||||
{
|
{
|
||||||
if b.len() != C::iv_size() {
|
if b.len() != C::iv_size() {
|
||||||
// `from_slice` would have caused a panic about this issue anyway.
|
None
|
||||||
// Now we at least have slightly more information
|
} else {
|
||||||
panic!(
|
Some(IV::<C>::from_slice(b))
|
||||||
"Tried to convert {} bytes to IV. Expected {}",
|
|
||||||
b.len(),
|
|
||||||
C::iv_size()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
IV::<C>::from_slice(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: there's really no way to use more parts of the keystream if it was required at some point.
|
// TODO: there's really no way to use more parts of the keystream if it was required at some point.
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "nym-ecash-double-spending"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
homepage.workspace = true
|
|
||||||
documentation.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bit-vec = { workspace = true }
|
|
||||||
bloomfilter = { workspace = true }
|
|
||||||
nym-network-defaults = { path = "../network-defaults" }
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use bit_vec::BitVec;
|
|
||||||
use bloomfilter::Bloom;
|
|
||||||
use nym_network_defaults::{BloomfilterParameters, ECASH_DS_BLOOMFILTER_PARAMS};
|
|
||||||
|
|
||||||
pub struct DoubleSpendingFilter {
|
|
||||||
params: BloomfilterParameters,
|
|
||||||
inner: Bloom<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DoubleSpendingFilter {
|
|
||||||
fn default() -> Self {
|
|
||||||
DoubleSpendingFilter::new_empty_ecash()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bloom_from_params<T>(params: &BloomfilterParameters, bitvec: BitVec) -> Bloom<Vec<T>> {
|
|
||||||
assert_eq!(params.bitmap_size, bitvec.len() as u64);
|
|
||||||
|
|
||||||
Bloom::from_bit_vec(
|
|
||||||
bitvec,
|
|
||||||
params.bitmap_size,
|
|
||||||
params.num_hashes,
|
|
||||||
params.sip_keys,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DoubleSpendingFilter {
|
|
||||||
pub fn new_empty(params: BloomfilterParameters) -> Self {
|
|
||||||
let bitvec = BitVec::from_elem(params.bitmap_size as usize, false);
|
|
||||||
DoubleSpendingFilter {
|
|
||||||
inner: bloom_from_params(¶ms, bitvec),
|
|
||||||
params,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn params(&self) -> BloomfilterParameters {
|
|
||||||
self.params
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rebuild(&self) -> DoubleSpendingFilterBuilder {
|
|
||||||
DoubleSpendingFilterBuilder::new(self.params)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.inner.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_empty_ecash() -> Self {
|
|
||||||
DoubleSpendingFilter::new_empty(ECASH_DS_BLOOMFILTER_PARAMS)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn builder(params: BloomfilterParameters) -> DoubleSpendingFilterBuilder {
|
|
||||||
DoubleSpendingFilterBuilder::new(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_bytes(params: BloomfilterParameters, bitmap: &[u8]) -> Self {
|
|
||||||
DoubleSpendingFilter {
|
|
||||||
inner: bloom_from_params(¶ms, BitVec::from_bytes(bitmap)),
|
|
||||||
params,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace_bitvec(&mut self, new: BitVec) {
|
|
||||||
self.inner = bloom_from_params(&self.params, new)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dump_bitmap(&self) -> Vec<u8> {
|
|
||||||
self.inner.bitmap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&mut self, b: &Vec<u8>) {
|
|
||||||
self.inner.set(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check(&self, b: &Vec<u8>) -> bool {
|
|
||||||
self.inner.check(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DoubleSpendingFilterBuilder {
|
|
||||||
params: BloomfilterParameters,
|
|
||||||
bit_vec_builder: Option<BitVecBuilder>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DoubleSpendingFilterBuilder {
|
|
||||||
pub fn new(params: BloomfilterParameters) -> Self {
|
|
||||||
DoubleSpendingFilterBuilder {
|
|
||||||
params,
|
|
||||||
bit_vec_builder: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_bytes(&mut self, b: &[u8]) -> bool {
|
|
||||||
match &mut self.bit_vec_builder {
|
|
||||||
None => {
|
|
||||||
self.bit_vec_builder = Some(BitVecBuilder::new(b));
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Some(builder) => builder.add_bytes(b),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> DoubleSpendingFilter {
|
|
||||||
match self.bit_vec_builder {
|
|
||||||
None => DoubleSpendingFilter::new_empty(self.params),
|
|
||||||
Some(builder) => DoubleSpendingFilter {
|
|
||||||
inner: bloom_from_params(&self.params, builder.finish()),
|
|
||||||
params: self.params,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BitVecBuilder(BitVec);
|
|
||||||
|
|
||||||
impl BitVecBuilder {
|
|
||||||
pub fn new(initial_bitmap: &[u8]) -> Self {
|
|
||||||
BitVecBuilder(BitVec::from_bytes(initial_bitmap))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_bytes(&mut self, b: &[u8]) -> bool {
|
|
||||||
let add = BitVec::from_bytes(b);
|
|
||||||
if self.0.len() != add.len() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.0.or(&add);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(self) -> BitVec {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,11 +20,14 @@ serde_json = { workspace = true }
|
|||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tracing = { workspace = true, features = ["log"] }
|
tracing = { workspace = true, features = ["log"] }
|
||||||
|
time = { workspace = true }
|
||||||
|
subtle = { workspace = true }
|
||||||
zeroize = { workspace = true }
|
zeroize = { workspace = true }
|
||||||
|
|
||||||
nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
|
nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
|
||||||
nym-pemstore = { path = "../pemstore" }
|
nym-pemstore = { path = "../pemstore" }
|
||||||
nym-sphinx = { path = "../nymsphinx" }
|
nym-sphinx = { path = "../nymsphinx" }
|
||||||
|
nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] }
|
||||||
nym-task = { path = "../task" }
|
nym-task = { path = "../task" }
|
||||||
|
|
||||||
nym-credentials = { path = "../credentials" }
|
nym-credentials = { path = "../credentials" }
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
// Copyright 2020-2024 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
use crate::shared_key::{SharedGatewayKey, SharedKeyUsageError};
|
|
||||||
use nym_sphinx::DestinationAddressBytes;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// Replacement for what used to be an `AuthToken`.
|
|
||||||
///
|
|
||||||
/// Replacement for what used to be an `AuthToken`. We used to be generating an `AuthToken` based on
|
|
||||||
/// local secret and remote address in order to allow for authentication. Due to changes in registration
|
|
||||||
/// and the fact we are deriving a shared key, we are encrypting remote's address with the previously
|
|
||||||
/// derived shared key. If the value is as expected, then authentication is successful.
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
|
||||||
// this is no longer constant size due to the differences in ciphertext between aes128ctr and aes256gcm-siv (inclusion of tag)
|
|
||||||
pub struct EncryptedAddressBytes(Vec<u8>);
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum EncryptedAddressConversionError {
|
|
||||||
#[error("Failed to decode the encrypted address - {0}")]
|
|
||||||
DecodeError(#[from] bs58::decode::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EncryptedAddressBytes {
|
|
||||||
pub fn new(
|
|
||||||
address: &DestinationAddressBytes,
|
|
||||||
key: &SharedGatewayKey,
|
|
||||||
nonce: &[u8],
|
|
||||||
) -> Result<Self, SharedKeyUsageError> {
|
|
||||||
let ciphertext = key.encrypt_naive(address.as_bytes_ref(), Some(nonce))?;
|
|
||||||
|
|
||||||
Ok(EncryptedAddressBytes(ciphertext))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify(
|
|
||||||
&self,
|
|
||||||
address: &DestinationAddressBytes,
|
|
||||||
key: &SharedGatewayKey,
|
|
||||||
nonce: &[u8],
|
|
||||||
) -> bool {
|
|
||||||
let Ok(reconstructed) = Self::new(address, key, nonce) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
self == &reconstructed
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_bytes(&self) -> &[u8] {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_from_base58_string<S: Into<String>>(
|
|
||||||
val: S,
|
|
||||||
) -> Result<Self, EncryptedAddressConversionError> {
|
|
||||||
let decoded = bs58::decode(val.into()).into_vec()?;
|
|
||||||
Ok(EncryptedAddressBytes(decoded))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_base58_string(self) -> String {
|
|
||||||
bs58::encode(self.0).into_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<EncryptedAddressBytes> for String {
|
|
||||||
fn from(val: EncryptedAddressBytes) -> Self {
|
|
||||||
val.to_base58_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
pub mod encrypted_address;
|
|
||||||
@@ -2,24 +2,17 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
pub use nym_crypto::generic_array;
|
pub use nym_crypto::generic_array;
|
||||||
use nym_crypto::OutputSizeUser;
|
|
||||||
use nym_sphinx::params::GatewayIntegrityHmacAlgorithm;
|
|
||||||
|
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
||||||
pub mod authentication;
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod registration;
|
pub mod registration;
|
||||||
pub mod shared_key;
|
pub mod shared_key;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
pub use shared_key::helpers::SymmetricKey;
|
pub use shared_key::{SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey};
|
||||||
pub use shared_key::legacy::{LegacySharedKeySize, LegacySharedKeys};
|
|
||||||
pub use shared_key::{
|
|
||||||
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const CURRENT_PROTOCOL_VERSION: u8 = AES_GCM_SIV_PROTOCOL_VERSION;
|
pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||||
|
|
||||||
/// Defines the current version of the communication protocol between gateway and clients.
|
/// Defines the current version of the communication protocol between gateway and clients.
|
||||||
/// It has to be incremented for any breaking change.
|
/// It has to be incremented for any breaking change.
|
||||||
@@ -27,10 +20,23 @@ pub const CURRENT_PROTOCOL_VERSION: u8 = AES_GCM_SIV_PROTOCOL_VERSION;
|
|||||||
// 1 - initial release
|
// 1 - initial release
|
||||||
// 2 - changes to client credentials structure
|
// 2 - changes to client credentials structure
|
||||||
// 3 - change to AES-GCM-SIV and non-zero IVs
|
// 3 - change to AES-GCM-SIV and non-zero IVs
|
||||||
|
// 4 - introduction of v2 authentication protocol to prevent reply attacks
|
||||||
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
|
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
|
||||||
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
||||||
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
|
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
|
||||||
|
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
|
||||||
|
|
||||||
// TODO: could using `Mac` trait here for OutputSize backfire?
|
pub trait GatewayProtocolVersionExt {
|
||||||
// Should hmac itself be exposed, imported and used instead?
|
fn supports_aes256_gcm_siv(&self) -> bool;
|
||||||
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
|
fn supports_authenticate_v2(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GatewayProtocolVersionExt for u8 {
|
||||||
|
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||||
|
*self >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_authenticate_v2(&self) -> bool {
|
||||||
|
*self >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
use crate::registration::handshake::messages::{Finalization, GatewayMaterialExchange};
|
use crate::registration::handshake::messages::{Finalization, GatewayMaterialExchange};
|
||||||
use crate::registration::handshake::state::State;
|
use crate::registration::handshake::state::State;
|
||||||
use crate::registration::handshake::SharedGatewayKey;
|
use crate::registration::handshake::SharedSymmetricKey;
|
||||||
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||||
use futures::{Sink, Stream};
|
use futures::{Sink, Stream};
|
||||||
use rand::{CryptoRng, RngCore};
|
use rand::{CryptoRng, RngCore};
|
||||||
@@ -15,12 +15,12 @@ impl<S, R> State<'_, S, R> {
|
|||||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||||
R: CryptoRng + RngCore,
|
R: CryptoRng + RngCore,
|
||||||
{
|
{
|
||||||
// 1. if we're using non-legacy, i.e. aes256gcm-siv derivation, generate initiator salt for kdf
|
// 1. generate initiator salt for kdf
|
||||||
let maybe_hkdf_salt = self.maybe_generate_initiator_salt();
|
let hkdf_salt = self.generate_initiator_salt();
|
||||||
|
|
||||||
// 1. send ed25519 pubkey alongside ephemeral x25519 pubkey and a hkdf salt if we're using non-legacy client
|
// 1. send ed25519 pubkey alongside ephemeral x25519 pubkey and a hkdf salt
|
||||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_SALT
|
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_SALT
|
||||||
let init_message = self.init_message(maybe_hkdf_salt.clone());
|
let init_message = self.init_message(hkdf_salt.clone());
|
||||||
self.send_handshake_data(init_message).await?;
|
self.send_handshake_data(init_message).await?;
|
||||||
|
|
||||||
// 2. wait for response with remote x25519 pubkey as well as encrypted signature
|
// 2. wait for response with remote x25519 pubkey as well as encrypted signature
|
||||||
@@ -31,7 +31,7 @@ impl<S, R> State<'_, S, R> {
|
|||||||
|
|
||||||
// 3. derive shared keys locally
|
// 3. derive shared keys locally
|
||||||
// hkdf::<blake3>::(g^xy)
|
// hkdf::<blake3>::(g^xy)
|
||||||
self.derive_shared_key(&mid_res.ephemeral_dh, maybe_hkdf_salt.as_deref());
|
self.derive_shared_key(&mid_res.ephemeral_dh, &hkdf_salt);
|
||||||
|
|
||||||
// 4. verify the received signature using the locally derived keys
|
// 4. verify the received signature using the locally derived keys
|
||||||
self.verify_remote_key_material(&mid_res.materials, &mid_res.ephemeral_dh)?;
|
self.verify_remote_key_material(&mid_res.materials, &mid_res.ephemeral_dh)?;
|
||||||
@@ -49,7 +49,7 @@ impl<S, R> State<'_, S, R> {
|
|||||||
|
|
||||||
pub(crate) async fn perform_client_handshake(
|
pub(crate) async fn perform_client_handshake(
|
||||||
mut self,
|
mut self,
|
||||||
) -> Result<SharedGatewayKey, HandshakeError>
|
) -> Result<SharedSymmetricKey, HandshakeError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||||
R: CryptoRng + RngCore,
|
R: CryptoRng + RngCore,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::registration::handshake::messages::{
|
|||||||
HandshakeMessage, Initialisation, MaterialExchange,
|
HandshakeMessage, Initialisation, MaterialExchange,
|
||||||
};
|
};
|
||||||
use crate::registration::handshake::state::State;
|
use crate::registration::handshake::state::State;
|
||||||
use crate::registration::handshake::SharedGatewayKey;
|
use crate::registration::handshake::SharedSymmetricKey;
|
||||||
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||||
use futures::{Sink, Stream};
|
use futures::{Sink, Stream};
|
||||||
use tungstenite::Message as WsMessage;
|
use tungstenite::Message as WsMessage;
|
||||||
@@ -18,18 +18,14 @@ impl<S, R> State<'_, S, R> {
|
|||||||
where
|
where
|
||||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||||
{
|
{
|
||||||
// 1. receive remote ed25519 pubkey alongside ephemeral x25519 pubkey and maybe a flag indicating non-legacy client
|
// 1. receive remote ed25519 pubkey alongside ephemeral x25519 pubkey and initiator salt
|
||||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_NON_LEGACY
|
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || INITIATOR_SALT
|
||||||
let init_message = Initialisation::try_from_bytes(&raw_init_message)?;
|
let init_message = Initialisation::try_from_bytes(&raw_init_message)?;
|
||||||
self.update_remote_identity(init_message.identity);
|
self.update_remote_identity(init_message.identity);
|
||||||
self.set_aes256_gcm_siv_key_derivation(!init_message.is_legacy());
|
|
||||||
|
|
||||||
// 2. derive shared keys locally
|
// 2. derive shared keys locally
|
||||||
// hkdf::<blake3>::(g^xy)
|
// hkdf::<blake3>::(g^xy)
|
||||||
self.derive_shared_key(
|
self.derive_shared_key(&init_message.ephemeral_dh, &init_message.initiator_salt);
|
||||||
&init_message.ephemeral_dh,
|
|
||||||
init_message.initiator_salt.as_deref(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. send ephemeral x25519 pubkey alongside the encrypted signature
|
// 3. send ephemeral x25519 pubkey alongside the encrypted signature
|
||||||
// g^y || AES(k, sig(gate_priv, (g^y || g^x))
|
// g^y || AES(k, sig(gate_priv, (g^y || g^x))
|
||||||
@@ -54,7 +50,7 @@ impl<S, R> State<'_, S, R> {
|
|||||||
pub(crate) async fn perform_gateway_handshake(
|
pub(crate) async fn perform_gateway_handshake(
|
||||||
mut self,
|
mut self,
|
||||||
raw_init_message: Vec<u8>,
|
raw_init_message: Vec<u8>,
|
||||||
) -> Result<SharedGatewayKey, HandshakeError>
|
) -> Result<SharedSymmetricKey, HandshakeError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
use crate::registration::handshake::error::HandshakeError;
|
use crate::registration::handshake::error::HandshakeError;
|
||||||
use crate::registration::handshake::KDF_SALT_LENGTH;
|
use crate::registration::handshake::KDF_SALT_LENGTH;
|
||||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||||
use nym_crypto::symmetric::aead::{nonce_size, tag_size};
|
use nym_crypto::symmetric::aead::{nonce_size, tag_size, Nonce};
|
||||||
use nym_sphinx::params::GatewayEncryptionAlgorithm;
|
use nym_sphinx::params::GatewayEncryptionAlgorithm;
|
||||||
|
|
||||||
// it is vital nobody changes the serialisation implementation unless you have an EXTREMELY good reason,
|
// it is vital nobody changes the serialisation implementation unless you have an EXTREMELY good reason,
|
||||||
@@ -21,20 +21,13 @@ pub trait HandshakeMessage {
|
|||||||
pub struct Initialisation {
|
pub struct Initialisation {
|
||||||
pub identity: ed25519::PublicKey,
|
pub identity: ed25519::PublicKey,
|
||||||
pub ephemeral_dh: x25519::PublicKey,
|
pub ephemeral_dh: x25519::PublicKey,
|
||||||
pub initiator_salt: Option<Vec<u8>>,
|
pub initiator_salt: Vec<u8>,
|
||||||
}
|
|
||||||
|
|
||||||
impl Initialisation {
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub fn is_legacy(&self) -> bool {
|
|
||||||
self.initiator_salt.is_none()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MaterialExchange {
|
pub struct MaterialExchange {
|
||||||
pub signature_ciphertext: Vec<u8>,
|
pub signature_ciphertext: Vec<u8>,
|
||||||
pub nonce: Option<Vec<u8>>,
|
pub nonce: Nonce<GatewayEncryptionAlgorithm>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaterialExchange {
|
impl MaterialExchange {
|
||||||
@@ -72,17 +65,12 @@ impl HandshakeMessage for Initialisation {
|
|||||||
// Eventually the ID_PUBKEY prefix will get removed and recipient will know
|
// Eventually the ID_PUBKEY prefix will get removed and recipient will know
|
||||||
// initializer's identity from another source.
|
// initializer's identity from another source.
|
||||||
fn into_bytes(self) -> Vec<u8> {
|
fn into_bytes(self) -> Vec<u8> {
|
||||||
let bytes = self
|
self.identity
|
||||||
.identity
|
|
||||||
.to_bytes()
|
.to_bytes()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(self.ephemeral_dh.to_bytes());
|
.chain(self.ephemeral_dh.to_bytes())
|
||||||
|
.chain(self.initiator_salt)
|
||||||
if let Some(salt) = self.initiator_salt {
|
.collect()
|
||||||
bytes.chain(salt).collect()
|
|
||||||
} else {
|
|
||||||
bytes.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this will need to be adjusted when REMOTE_ID_PUBKEY is removed
|
// this will need to be adjusted when REMOTE_ID_PUBKEY is removed
|
||||||
@@ -90,9 +78,8 @@ impl HandshakeMessage for Initialisation {
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
let legacy_len = ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE;
|
let current_len = ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE + KDF_SALT_LENGTH;
|
||||||
let current_len = legacy_len + KDF_SALT_LENGTH;
|
if bytes.len() != current_len {
|
||||||
if bytes.len() != legacy_len && bytes.len() != current_len {
|
|
||||||
return Err(HandshakeError::MalformedRequest);
|
return Err(HandshakeError::MalformedRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,14 +88,13 @@ impl HandshakeMessage for Initialisation {
|
|||||||
|
|
||||||
// this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
|
// this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
|
||||||
// which is impossible
|
// which is impossible
|
||||||
let ephemeral_dh =
|
let ephemeral_dh = x25519::PublicKey::from_bytes(
|
||||||
x25519::PublicKey::from_bytes(&bytes[ed25519::PUBLIC_KEY_LENGTH..legacy_len]).unwrap();
|
&bytes
|
||||||
|
[ed25519::PUBLIC_KEY_LENGTH..ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let initiator_salt = if bytes.len() == legacy_len {
|
let initiator_salt = bytes[ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE..].to_vec();
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(bytes[legacy_len..].to_vec())
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Initialisation {
|
Ok(Initialisation {
|
||||||
identity,
|
identity,
|
||||||
@@ -121,43 +107,31 @@ impl HandshakeMessage for Initialisation {
|
|||||||
impl HandshakeMessage for MaterialExchange {
|
impl HandshakeMessage for MaterialExchange {
|
||||||
// AES(k, SIG(PRIV_GATE, G^y || G^x))
|
// AES(k, SIG(PRIV_GATE, G^y || G^x))
|
||||||
fn into_bytes(self) -> Vec<u8> {
|
fn into_bytes(self) -> Vec<u8> {
|
||||||
if let Some(nonce) = self.nonce {
|
self.signature_ciphertext
|
||||||
self.signature_ciphertext
|
.iter()
|
||||||
.iter()
|
.cloned()
|
||||||
.cloned()
|
.chain(self.nonce)
|
||||||
.chain(nonce)
|
.collect()
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
self.signature_ciphertext.to_vec()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_from_bytes(bytes: &[u8]) -> Result<Self, HandshakeError>
|
fn try_from_bytes(bytes: &[u8]) -> Result<Self, HandshakeError>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
// we expect to receive either:
|
|
||||||
// LEGACY: ed25519 signature ciphertext (64 bytes)
|
|
||||||
// CURRENT: ed25519 signature ciphertext (+ tag) + AES256-GCM-SIV nonce (76 bytes)
|
// CURRENT: ed25519 signature ciphertext (+ tag) + AES256-GCM-SIV nonce (76 bytes)
|
||||||
let legacy_len = ed25519::SIGNATURE_LENGTH;
|
let current_len = ed25519::SIGNATURE_LENGTH
|
||||||
let current_len = legacy_len
|
|
||||||
+ tag_size::<GatewayEncryptionAlgorithm>()
|
+ tag_size::<GatewayEncryptionAlgorithm>()
|
||||||
+ nonce_size::<GatewayEncryptionAlgorithm>();
|
+ nonce_size::<GatewayEncryptionAlgorithm>();
|
||||||
|
|
||||||
if bytes.len() != legacy_len && bytes.len() != current_len {
|
if bytes.len() != current_len {
|
||||||
return Err(HandshakeError::MalformedResponse);
|
return Err(HandshakeError::MalformedResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (signature_ciphertext, nonce) = if bytes.len() == current_len {
|
let ciphertext_len = ed25519::SIGNATURE_LENGTH + tag_size::<GatewayEncryptionAlgorithm>();
|
||||||
let ciphertext_len =
|
let signature_ciphertext = bytes[..ciphertext_len].to_vec();
|
||||||
ed25519::SIGNATURE_LENGTH + tag_size::<GatewayEncryptionAlgorithm>();
|
|
||||||
(
|
// SAFETY: we know the bytes have correct length
|
||||||
bytes[..ciphertext_len].to_vec(),
|
let nonce = Nonce::<GatewayEncryptionAlgorithm>::clone_from_slice(&bytes[ciphertext_len..]);
|
||||||
Some(bytes[ciphertext_len..].to_vec()),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(bytes.to_vec(), None)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(MaterialExchange {
|
Ok(MaterialExchange {
|
||||||
signature_ciphertext,
|
signature_ciphertext,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user