Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02af95c65e | |||
| 85be73401e | |||
| 8385f719e2 |
@@ -26,7 +26,6 @@ jobs:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ jobs:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -8,18 +8,17 @@ on:
|
||||
- 'explorer-api/**'
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-credential-proxy/**'
|
||||
- 'nym-data-observatory/**'
|
||||
- 'nym-network-monitor/**'
|
||||
- 'nym-node/**'
|
||||
- 'nym-node-status-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'nym-validator-rewarder/**'
|
||||
- 'nyx-chain-watcher/**'
|
||||
- 'sdk/ffi/**'
|
||||
- 'sdk/lib/**'
|
||||
- 'sdk/rust/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-browser-extension/storage/**'
|
||||
- 'tools/**'
|
||||
- 'wasm/**'
|
||||
- 'Cargo.toml'
|
||||
@@ -37,7 +36,6 @@ jobs:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
- 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
|
||||
@@ -55,20 +53,6 @@ jobs:
|
||||
override: true
|
||||
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
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
@@ -9,8 +9,6 @@ on:
|
||||
paths:
|
||||
- 'contracts/**'
|
||||
- 'common/**'
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- '.github/workflows/ci-contracts.yml'
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -30,12 +30,6 @@ jobs:
|
||||
override: true
|
||||
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
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
name: ci-sdk-wasm
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'wasm/**'
|
||||
@@ -45,11 +44,6 @@ jobs:
|
||||
- name: 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"
|
||||
run: make sdk-wasm-build
|
||||
|
||||
|
||||
+1
-137
@@ -4,139 +4,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.4-dorina] (2025-03-04)
|
||||
## [2025.2-hu] (2025-01-28)
|
||||
|
||||
- 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])
|
||||
@@ -171,11 +40,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- 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
|
||||
|
||||
Generated
+1134
-1421
File diff suppressed because it is too large
Load Diff
+68
-83
@@ -48,12 +48,13 @@ members = [
|
||||
"common/credentials-interface",
|
||||
"common/crypto",
|
||||
"common/dkg",
|
||||
"common/ecash-double-spending",
|
||||
"common/ecash-time",
|
||||
"common/execute",
|
||||
"common/exit-policy",
|
||||
"common/gateway-requests",
|
||||
"common/gateway-stats-storage",
|
||||
"common/gateway-storage",
|
||||
"common/gateway-stats-storage",
|
||||
"common/http-api-client",
|
||||
"common/http-api-common",
|
||||
"common/inclusion-probability",
|
||||
@@ -66,6 +67,7 @@ members = [
|
||||
"common/nym-id",
|
||||
"common/nym-metrics",
|
||||
"common/nym_offline_compact_ecash",
|
||||
"common/nymcoconut",
|
||||
"common/nymsphinx",
|
||||
"common/nymsphinx/acknowledgements",
|
||||
"common/nymsphinx/addressing",
|
||||
@@ -91,7 +93,6 @@ members = [
|
||||
"common/topology",
|
||||
"common/tun",
|
||||
"common/types",
|
||||
"common/verloc",
|
||||
"common/wasm/client-core",
|
||||
"common/wasm/storage",
|
||||
"common/wasm/utils",
|
||||
@@ -103,22 +104,6 @@ members = [
|
||||
"explorer-api/explorer-client",
|
||||
"gateway",
|
||||
"integrations/bity",
|
||||
"nym-api",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-browser-extension/storage",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-credential-proxy/nym-credential-proxy-requests",
|
||||
"nym-credential-proxy/vpn-api-lib-wasm",
|
||||
"nym-network-monitor",
|
||||
"nym-node",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
"nym-node-status-api/nym-node-status-api",
|
||||
"nym-node-status-api/nym-node-status-client",
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-outfox",
|
||||
"nym-validator-rewarder",
|
||||
"nyx-chain-watcher",
|
||||
"sdk/ffi/cpp",
|
||||
"sdk/ffi/go",
|
||||
"sdk/ffi/shared",
|
||||
@@ -127,16 +112,26 @@ members = [
|
||||
"service-providers/common",
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
"nym-api",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-browser-extension/storage",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-credential-proxy/nym-credential-proxy-requests",
|
||||
"nym-credential-proxy/vpn-api-lib-wasm",
|
||||
"nym-network-monitor",
|
||||
"nyx-chain-watcher",
|
||||
"nym-node",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
"nym-node-status-api/nym-node-status-api",
|
||||
"nym-node-status-api/nym-node-status-client",
|
||||
"nym-outfox",
|
||||
"nym-validator-rewarder",
|
||||
"tools/echo-server",
|
||||
"tools/echo-server",
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
"tools/internal/mixnet-connectivity-check",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
"tools/internal/ssl-inject",
|
||||
# "tools/internal/sdk-version-bump",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-id-cli",
|
||||
@@ -148,6 +143,13 @@ members = [
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
"wasm/zknym-lib",
|
||||
"tools/echo-server",
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"common/verloc",
|
||||
"tools/internal/mixnet-connectivity-check",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -171,6 +173,7 @@ exclude = [
|
||||
"explorer",
|
||||
"contracts",
|
||||
"nym-wallet",
|
||||
"nym-vpn/ui/src-tauri",
|
||||
"cpu-cycles",
|
||||
]
|
||||
|
||||
@@ -186,43 +189,40 @@ readme = "README.md"
|
||||
|
||||
[workspace.dependencies]
|
||||
addr = "0.15.6"
|
||||
aead = "0.5.2"
|
||||
aes = "0.8.1"
|
||||
aes-gcm = "0.10.1"
|
||||
aes-gcm-siv = "0.11.1"
|
||||
ammonia = "4"
|
||||
anyhow = "1.0.97"
|
||||
aead = "0.5.2"
|
||||
anyhow = "1.0.95"
|
||||
arc-swap = "1.7.1"
|
||||
argon2 = "0.5.0"
|
||||
async-trait = "0.1.87"
|
||||
axum = "0.7.5"
|
||||
async-trait = "0.1.85"
|
||||
axum-client-ip = "0.6.1"
|
||||
axum = "0.7.5"
|
||||
axum-extra = "0.9.4"
|
||||
axum-test = "16.2.0"
|
||||
base64 = "0.22.1"
|
||||
base85rs = "0.1.3"
|
||||
bincode = "1.3.3"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
bit-vec = "0.7.0" # can we unify those?
|
||||
bitvec = "1.0.0"
|
||||
blake3 = "1.6.1"
|
||||
blake3 = "1.5.5"
|
||||
bloomfilter = "1.0.14"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
bytes = "1.10.1"
|
||||
bytes = "1.7.2"
|
||||
cargo_metadata = "0.18.1"
|
||||
celes = "2.5.0"
|
||||
celes = "2.4.0"
|
||||
cfg-if = "1.0.0"
|
||||
chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.40"
|
||||
chrono = "0.4.39"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.31"
|
||||
clap = "4.5.26"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.2"
|
||||
comfy-table = "7.1.4"
|
||||
console = "0.15.11"
|
||||
colored = "2.0"
|
||||
comfy-table = "7.1.3"
|
||||
console = "0.15.10"
|
||||
console-subscriber = "0.1.1"
|
||||
console_error_panic_hook = "0.1"
|
||||
const-str = "0.5.6"
|
||||
@@ -241,50 +241,49 @@ doc-comment = "0.3"
|
||||
dotenvy = "0.15.6"
|
||||
ecdsa = "0.16"
|
||||
ed25519-dalek = "2.1"
|
||||
env_logger = "0.11.6"
|
||||
envy = "0.4"
|
||||
etherparse = "0.13.0"
|
||||
envy = "0.4"
|
||||
eyre = "0.6.9"
|
||||
fastrand = "2.1.1"
|
||||
flate2 = "1.1.0"
|
||||
flate2 = "1.0.35"
|
||||
futures = "0.3.31"
|
||||
futures-util = "0.3"
|
||||
generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
getset = "0.1.5"
|
||||
getset = "0.1.3"
|
||||
handlebars = "3.5.5"
|
||||
headers = "0.4.0"
|
||||
hex = "0.4.3"
|
||||
hex-literal = "0.3.3"
|
||||
hickory-resolver = "0.24.4"
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
http = "1"
|
||||
http-body-util = "0.1"
|
||||
httpcodec = "0.2.3"
|
||||
human-repr = "1.1.0"
|
||||
humantime = "2.1.0"
|
||||
humantime-serde = "1.1.1"
|
||||
hyper = "1.6.0"
|
||||
human-repr = "1.1.0"
|
||||
hyper = "1.4.1"
|
||||
hyper-util = "0.1"
|
||||
indicatif = "0.17.11"
|
||||
indicatif = "0.17.9"
|
||||
inquire = "0.6.2"
|
||||
ip_network = "0.4.1"
|
||||
ipnetwork = "0.20"
|
||||
isocountry = "0.3.2"
|
||||
itertools = "0.14.0"
|
||||
itertools = "0.13.0"
|
||||
k256 = "0.13"
|
||||
lazy_static = "1.5.0"
|
||||
ledger-transport = "0.10.0"
|
||||
ledger-transport-hid = "0.10.0"
|
||||
log = "0.4"
|
||||
maxminddb = "0.23.0"
|
||||
rs_merkle = "1.4.2"
|
||||
mime = "0.3.17"
|
||||
moka = { version = "0.12", features = ["future"] }
|
||||
nix = "0.27.1"
|
||||
notify = "5.1.0"
|
||||
okapi = "0.7.0"
|
||||
once_cell = "1.20.3"
|
||||
once_cell = "1.20.2"
|
||||
opentelemetry = "0.19.0"
|
||||
opentelemetry-jaeger = "0.18.0"
|
||||
parking_lot = "0.12.3"
|
||||
@@ -293,7 +292,7 @@ petgraph = "0.6.5"
|
||||
pin-project = "1.1"
|
||||
pin-project-lite = "0.2.16"
|
||||
pretty_env_logger = "0.4.0"
|
||||
publicsuffix = "2.3.0"
|
||||
publicsuffix = "2.2.3"
|
||||
quote = "1"
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3"
|
||||
@@ -307,21 +306,20 @@ reqwest = { version = "0.12.4", default-features = false }
|
||||
rocket = "0.5.0"
|
||||
rocket_cors = "0.6.0"
|
||||
rocket_okapi = "0.8.0"
|
||||
rs_merkle = "1.4.2"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.22"
|
||||
semver = "1.0.26"
|
||||
serde = "1.0.219"
|
||||
serde_bytes = "0.11.17"
|
||||
schemars = "0.8.21"
|
||||
semver = "1.0.24"
|
||||
serde = "1.0.217"
|
||||
serde_bytes = "0.11.15"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.140"
|
||||
serde_json_path = "0.7.2"
|
||||
serde_json = "1.0.135"
|
||||
serde_json_path = "0.7.1"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.8"
|
||||
si-scale = "0.2.3"
|
||||
sphinx-packet = "0.3.1"
|
||||
sphinx-packet = "0.1.1"
|
||||
sqlx = "0.7.4"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
@@ -329,32 +327,29 @@ subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
sysinfo = "0.33.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.44"
|
||||
tar = "0.4.43"
|
||||
tempfile = "3.15"
|
||||
thiserror = "2.0"
|
||||
time = "0.3.39"
|
||||
tokio = "1.43"
|
||||
tokio-postgres = "0.7"
|
||||
thiserror = "1.0.64"
|
||||
time = "0.3.37"
|
||||
tokio = "1.39"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-test = "0.4.4"
|
||||
tokio-tun = "0.11.5"
|
||||
tokio-tungstenite = { version = "0.20.1" }
|
||||
tokio-util = "0.7.13"
|
||||
toml = "0.8.20"
|
||||
tower = "0.5.2"
|
||||
toml = "0.8.19"
|
||||
tower = "0.4.13"
|
||||
tower-http = "0.5.2"
|
||||
tracing = "0.1.41"
|
||||
tracing-log = "0.2"
|
||||
tracing-opentelemetry = "0.19.0"
|
||||
tracing-subscriber = "0.3.19"
|
||||
tracing-tree = "0.2.2"
|
||||
tracing-log = "0.2"
|
||||
ts-rs = "10.1.0"
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
uniffi = "0.29.0"
|
||||
uniffi_build = "0.29.0"
|
||||
url = "2.5"
|
||||
utoipa = "5.2"
|
||||
utoipa-swagger-ui = "8.1"
|
||||
utoipa-swagger-ui = "8.0"
|
||||
utoipauto = "0.2"
|
||||
uuid = "*"
|
||||
vergen = { version = "=8.3.1", default-features = false }
|
||||
@@ -371,7 +366,7 @@ prometheus = { version = "0.13.0" }
|
||||
# plus to make our live easier we need serde support from https://github.com/zkcrypto/bls12_381/pull/125
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect" }
|
||||
group = { version = "0.13.0", default-features = false }
|
||||
ff = { version = "0.13.1", default-features = false }
|
||||
ff = { version = "0.13.0", default-features = false }
|
||||
subtle = "2.5.0"
|
||||
|
||||
# cosmwasm-related
|
||||
@@ -390,10 +385,10 @@ cw4 = { version = "=1.1.2" }
|
||||
cw-controllers = { version = "=1.1.0" }
|
||||
|
||||
# cosmrs-related
|
||||
bip32 = { version = "0.5.3", default-features = false }
|
||||
bip32 = { version = "0.5.2", default-features = false }
|
||||
|
||||
|
||||
cosmrs = { version = "0.21.1" }
|
||||
cosmrs = { version = "0.21.0" }
|
||||
tendermint = "0.40.0"
|
||||
tendermint-rpc = "0.40.0"
|
||||
prost = { version = "0.13", default-features = false }
|
||||
@@ -437,13 +432,3 @@ opt-level = 'z'
|
||||
[profile.release.package.mix-fetch-wasm]
|
||||
# lto = true
|
||||
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"
|
||||
@@ -13,7 +13,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
|
||||
* `nym-client` - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
|
||||
* `nym-socks5-client` - a Socks5 proxy you can run on your machine and use with existing applications.
|
||||
* `nym-explorer` - a (projected) block explorer and (existing) mixnet viewer.
|
||||
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.app)) framework.
|
||||
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
|
||||
* `nym-cli` - a tool for interacting with the network from the CLI.
|
||||
<!-- coming soon
|
||||
* `nym-network-monitor` - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
|
||||
@@ -66,4 +66,4 @@ As a general approach, licensing is as follows this pattern:
|
||||
- libraries and components are Apache 2.0 or MIT
|
||||
- documentation is Apache 2.0 or CC0-1.0
|
||||
|
||||
Nym Node Operators and Validators Terms and Conditions can be found [here](https://nym.com/operators-validators-terms).
|
||||
Nym Node Operators and Validators Temrs and Conditions can be found [here](https://nym.com/terms-and-conditions/operators/v1.0.0).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.49"
|
||||
version = "1.1.47"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -56,7 +56,7 @@ pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
.join(DEFAULT_DATA_DIR)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct Config {
|
||||
#[serde(flatten)]
|
||||
pub base: BaseClientConfig,
|
||||
@@ -94,10 +94,6 @@ impl CliClientConfig for Config {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn base(&self) -> BaseClientConfig {
|
||||
self.base.clone()
|
||||
}
|
||||
|
||||
pub fn new<S: AsRef<str>>(id: S) -> Self {
|
||||
Config {
|
||||
base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")),
|
||||
@@ -213,7 +209,7 @@ impl SocketType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Socket {
|
||||
pub socket_type: SocketType,
|
||||
|
||||
@@ -107,8 +107,5 @@ enabled = {{ debug.stats_reporting.enabled }}
|
||||
provider_address = '{{ debug.stats_reporting.provider_address }}'
|
||||
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;
|
||||
|
||||
type NativeClientBuilder = BaseClientBuilder<QueryHttpRpcNyxdClient, OnDiskPersistent>;
|
||||
type NativeClientBuilder<'a> = BaseClientBuilder<'a, QueryHttpRpcNyxdClient, OnDiskPersistent>;
|
||||
|
||||
pub struct SocketClient {
|
||||
/// Client configuration options, including, among other things, packet sending rates,
|
||||
@@ -32,10 +32,6 @@ pub struct SocketClient {
|
||||
}
|
||||
|
||||
impl SocketClient {
|
||||
pub fn config(&self) -> Config {
|
||||
self.config.clone()
|
||||
}
|
||||
|
||||
pub fn new(config: Config, custom_mixnet: Option<PathBuf>) -> Self {
|
||||
SocketClient {
|
||||
config,
|
||||
@@ -49,7 +45,7 @@ impl SocketClient {
|
||||
client_output: ClientOutput,
|
||||
client_state: ClientState,
|
||||
self_address: &Recipient,
|
||||
task_client: nym_task::TaskClient,
|
||||
shutdown: nym_task::TaskClient,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
info!("Starting websocket listener...");
|
||||
@@ -77,15 +73,10 @@ impl SocketClient {
|
||||
shared_lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
Some(packet_type),
|
||||
task_client.fork("websocket_handler"),
|
||||
);
|
||||
|
||||
websocket::Listener::new(
|
||||
config.socket.host,
|
||||
config.socket.listening_port,
|
||||
task_client.with_suffix("websocket_listener"),
|
||||
)
|
||||
.start(websocket_handler);
|
||||
websocket::Listener::new(config.socket.host, config.socket.listening_port)
|
||||
.start(websocket_handler, shutdown);
|
||||
}
|
||||
|
||||
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
|
||||
@@ -117,9 +108,8 @@ impl SocketClient {
|
||||
let storage = self.initialise_storage().await?;
|
||||
let user_agent = nym_bin_common::bin_info!().into();
|
||||
|
||||
let mut base_client =
|
||||
BaseClientBuilder::new(self.config().base(), storage, dkg_query_client)
|
||||
.with_user_agent(user_agent);
|
||||
let mut base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client)
|
||||
.with_user_agent(user_agent);
|
||||
|
||||
if let Some(custom_mixnet) = &self.custom_mixnet {
|
||||
base_client = base_client.with_stored_topology(custom_mixnet)?;
|
||||
|
||||
@@ -82,7 +82,6 @@ impl From<Init> for OverrideConfig {
|
||||
nyxd_urls: init_config.common_args.nyxd_urls,
|
||||
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
|
||||
stats_reporting_address: init_config.common_args.stats_reporting_address,
|
||||
forget_me: init_config.common_args.forget_me.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use nym_client::client::Recipient;
|
||||
use nym_client_core::cli_helpers::CliClient;
|
||||
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
|
||||
use nym_client_core::config::ForgetMe;
|
||||
use nym_config::OptionalSet;
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
@@ -107,7 +106,6 @@ pub(crate) struct OverrideConfig {
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
stats_reporting_address: Option<Recipient>,
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
@@ -135,7 +133,6 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
args.fastmode,
|
||||
)
|
||||
.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_host, args.host)
|
||||
.with_optional_custom_env_ext(
|
||||
|
||||
@@ -41,7 +41,6 @@ impl From<Run> for OverrideConfig {
|
||||
nyxd_urls: run_config.common_args.nyxd_urls,
|
||||
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
|
||||
stats_reporting_address: run_config.common_args.stats_reporting_address,
|
||||
forget_me: run_config.common_args.forget_me.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_task::connections::{
|
||||
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use std::time::Duration;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::Instant;
|
||||
@@ -44,11 +43,9 @@ pub(crate) struct HandlerBuilder {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl HandlerBuilder {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
msg_input: InputMessageSender,
|
||||
client_connection_tx: ConnectionCommandSender,
|
||||
@@ -57,7 +54,6 @@ impl HandlerBuilder {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
Self {
|
||||
msg_input,
|
||||
@@ -67,14 +63,11 @@ impl HandlerBuilder {
|
||||
lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
packet_type,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make sure we only ever have one active handler
|
||||
pub fn create_active_handler(&self) -> Handler {
|
||||
let mut task_client = self.task_client.fork("active_handler");
|
||||
task_client.disarm();
|
||||
Handler {
|
||||
msg_input: self.msg_input.clone(),
|
||||
client_connection_tx: self.client_connection_tx.clone(),
|
||||
@@ -85,7 +78,6 @@ impl HandlerBuilder {
|
||||
lane_queue_lengths: self.lane_queue_lengths.clone(),
|
||||
reply_controller_sender: self.reply_controller_sender.clone(),
|
||||
packet_type: self.packet_type,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,18 +92,16 @@ pub(crate) struct Handler {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl Drop for Handler {
|
||||
fn drop(&mut self) {
|
||||
if let Err(err) = self
|
||||
if self
|
||||
.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
|
||||
.is_err()
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("failed to disconnect the receiver from the buffer: {err}");
|
||||
}
|
||||
error!("we failed to disconnect the receiver from the buffer! presumably the shutdown procedure has been initiated!")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,23 +125,10 @@ impl Handler {
|
||||
};
|
||||
|
||||
// get the number of pending replies waiting for reply surbs
|
||||
let reply_queue_length = match self
|
||||
let reply_queue_length = self
|
||||
.reply_controller_sender
|
||||
.get_lane_queue_length(connection_id)
|
||||
.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
|
||||
}
|
||||
};
|
||||
.await;
|
||||
|
||||
let queue_length = base_length + reply_queue_length;
|
||||
|
||||
@@ -191,11 +168,10 @@ impl Handler {
|
||||
|
||||
// the ack control is now responsible for chunking, etc.
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send message to the input buffer: {err}");
|
||||
}
|
||||
}
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
@@ -224,11 +200,10 @@ impl Handler {
|
||||
|
||||
let input_msg =
|
||||
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send anonymous message to the input buffer: {err}");
|
||||
}
|
||||
}
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
@@ -252,11 +227,10 @@ impl Handler {
|
||||
});
|
||||
|
||||
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send reply message to the input buffer: {err}");
|
||||
}
|
||||
}
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
@@ -271,14 +245,9 @@ impl Handler {
|
||||
}
|
||||
|
||||
fn handle_closed_connection(&self, connection_id: u64) -> Option<ServerResponse> {
|
||||
if let Err(err) = self
|
||||
.client_connection_tx
|
||||
self.client_connection_tx
|
||||
.unbounded_send(ConnectionCommand::Close(connection_id))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send close connection command: {err}");
|
||||
}
|
||||
}
|
||||
.unwrap();
|
||||
None
|
||||
}
|
||||
|
||||
@@ -393,10 +362,11 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen_for_requests(&mut self, mut msg_receiver: ReconstructedMessagesReceiver) {
|
||||
let mut task_client = self.task_client.fork("select");
|
||||
task_client.disarm();
|
||||
|
||||
async fn listen_for_requests(
|
||||
&mut self,
|
||||
mut msg_receiver: ReconstructedMessagesReceiver,
|
||||
mut task_client: nym_task::TaskClient,
|
||||
) {
|
||||
while !task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
// we can either get a client request from the websocket
|
||||
@@ -445,7 +415,15 @@ impl Handler {
|
||||
}
|
||||
|
||||
// consume self to make sure `drop` is called after this is done
|
||||
pub(crate) async fn handle_connection(mut self, socket: TcpStream) {
|
||||
pub(crate) async fn handle_connection(
|
||||
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 {
|
||||
Ok(ws_stream) => ws_stream,
|
||||
Err(err) => {
|
||||
@@ -458,18 +436,14 @@ impl Handler {
|
||||
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
|
||||
|
||||
// tell the buffer to start sending stuff to us
|
||||
if let Err(err) =
|
||||
self.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
||||
reconstructed_sender,
|
||||
))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("failed to announce the receiver to the buffer: {err}");
|
||||
}
|
||||
}
|
||||
self.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
|
||||
reconstructed_sender,
|
||||
))
|
||||
.expect("the buffer request failed!");
|
||||
|
||||
self.listen_for_requests(reconstructed_receiver).await;
|
||||
self.listen_for_requests(reconstructed_receiver, task_client)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use super::handler::HandlerBuilder;
|
||||
use log::*;
|
||||
use nym_task::TaskClient;
|
||||
use std::net::IpAddr;
|
||||
use std::{net::SocketAddr, process, sync::Arc};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
@@ -23,19 +22,21 @@ impl State {
|
||||
pub(crate) struct Listener {
|
||||
address: SocketAddr,
|
||||
state: State,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
pub(crate) fn new(host: IpAddr, port: u16, task_client: TaskClient) -> Self {
|
||||
pub(crate) fn new(host: IpAddr, port: u16) -> Self {
|
||||
Listener {
|
||||
address: SocketAddr::new(host, port),
|
||||
state: State::AwaitingConnection,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self, handler: HandlerBuilder) {
|
||||
pub(crate) async fn run(
|
||||
&mut self,
|
||||
handler: HandlerBuilder,
|
||||
mut task_client: nym_task::TaskClient,
|
||||
) {
|
||||
let tcp_listener = match tokio::net::TcpListener::bind(self.address).await {
|
||||
Ok(listener) => listener,
|
||||
Err(err) => {
|
||||
@@ -46,11 +47,11 @@ impl Listener {
|
||||
|
||||
let notify = Arc::new(Notify::new());
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
// When the handler finishes we check if shutdown is signalled
|
||||
_ = notify.notified() => {
|
||||
if self.task_client.is_shutdown() {
|
||||
if task_client.is_shutdown() {
|
||||
log::trace!("Websocket listener: detected shutdown after connection closed");
|
||||
break;
|
||||
}
|
||||
@@ -59,7 +60,7 @@ impl Listener {
|
||||
}
|
||||
// ... but when there is no connected client at the time of shutdown being
|
||||
// signalled, we handle it here.
|
||||
_ = self.task_client.recv() => {
|
||||
_ = task_client.recv() => {
|
||||
if !self.state.is_connected() {
|
||||
log::trace!("Not connected: shutting down");
|
||||
break;
|
||||
@@ -87,8 +88,9 @@ impl Listener {
|
||||
// hanging because the executor doesn't come back here
|
||||
let notify_clone = Arc::clone(¬ify);
|
||||
let fresh_handler = handler.create_active_handler();
|
||||
let task_client_handler = task_client.clone();
|
||||
tokio::spawn(async move {
|
||||
fresh_handler.handle_connection(socket).await;
|
||||
fresh_handler.handle_connection(socket, task_client_handler).await;
|
||||
notify_clone.notify_one();
|
||||
});
|
||||
self.state = State::Connected;
|
||||
@@ -102,9 +104,13 @@ impl Listener {
|
||||
log::debug!("Websocket listener: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, handler: HandlerBuilder) -> JoinHandle<()> {
|
||||
pub(crate) fn start(
|
||||
mut self,
|
||||
handler: HandlerBuilder,
|
||||
shutdown: nym_task::TaskClient,
|
||||
) -> JoinHandle<()> {
|
||||
info!("Running websocket on {:?}", self.address.to_string());
|
||||
|
||||
tokio::spawn(async move { self.run(handler).await })
|
||||
tokio::spawn(async move { self.run(handler, shutdown).await })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.49"
|
||||
version = "1.1.47"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -93,7 +93,6 @@ impl From<Init> for OverrideConfig {
|
||||
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
|
||||
outfox: false,
|
||||
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::client::base_client::storage::migration_helpers::v1_1_33;
|
||||
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
|
||||
use nym_client_core::config::{ForgetMe, GroupBy, TopologyStructure};
|
||||
use nym_client_core::config::{GroupBy, TopologyStructure};
|
||||
use nym_config::OptionalSet;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
@@ -113,7 +113,6 @@ pub(crate) struct OverrideConfig {
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
outfox: bool,
|
||||
stats_reporting_address: Option<Recipient>,
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
@@ -180,7 +179,6 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
BaseClientConfig::with_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_port, args.port)
|
||||
.with_optional(Config::with_ip, args.ip)
|
||||
|
||||
@@ -65,7 +65,6 @@ impl From<Run> for OverrideConfig {
|
||||
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
|
||||
outfox: run_config.outfox,
|
||||
stats_reporting_address: run_config.common_args.stats_reporting_address,
|
||||
forget_me: run_config.common_args.forget_me.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,8 +113,4 @@ enabled = {{ core.debug.stats_reporting.enabled }}
|
||||
provider_address = '{{ core.debug.stats_reporting.provider_address }}'
|
||||
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,3 +1,2 @@
|
||||
allow-unwrap-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::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(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
|
||||
|
||||
@@ -105,24 +105,26 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
async fn get_aggregate_verification_key(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||
apis: &mut ApiClientsWrapper,
|
||||
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<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
|
||||
}
|
||||
|
||||
async fn get_coin_index_signatures(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||
apis: &mut ApiClientsWrapper,
|
||||
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<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
|
||||
}
|
||||
|
||||
@@ -130,12 +132,13 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
ecash_apis: &mut ApiClientsWrapper<'_, C>,
|
||||
apis: &mut ApiClientsWrapper,
|
||||
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<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
|
||||
}
|
||||
|
||||
@@ -151,7 +154,7 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
{
|
||||
let epoch_id = retrieved_ticketbook.ticketbook.epoch_id();
|
||||
let expiration_date = retrieved_ticketbook.ticketbook.expiration_date();
|
||||
let mut api_clients = ApiClientsWrapper::new(&self.client, epoch_id);
|
||||
let mut api_clients = Default::default();
|
||||
|
||||
let verification_key = self
|
||||
.get_aggregate_verification_key(epoch_id, &mut api_clients)
|
||||
|
||||
@@ -21,67 +21,30 @@ use rand::thread_rng;
|
||||
use std::fmt::Display;
|
||||
use std::future::Future;
|
||||
|
||||
pub(crate) trait EcashClientsProvider {
|
||||
async fn try_get_ecash_clients(
|
||||
&mut self,
|
||||
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>;
|
||||
}
|
||||
// it really doesn't need the RwLock because it's never moved across tasks,
|
||||
// but we need all the Send/Sync action
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ApiClientsWrapper(Option<Vec<EcashApiClient>>);
|
||||
|
||||
impl EcashClientsProvider for Vec<EcashApiClient> {
|
||||
async fn try_get_ecash_clients(
|
||||
impl ApiClientsWrapper {
|
||||
pub(crate) async fn get_or_init<C>(
|
||||
&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,
|
||||
},
|
||||
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>
|
||||
dkg_client: &C,
|
||||
) -> Result<Vec<EcashApiClient>, BandwidthControllerError>
|
||||
where
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
{
|
||||
match self {
|
||||
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()),
|
||||
if let Some(cached) = &self.0 {
|
||||
return Ok(cached.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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +76,7 @@ where
|
||||
pub(crate) async fn get_aggregate_verification_key<St>(
|
||||
storage: &St,
|
||||
epoch_id: EpochId,
|
||||
mut ecash_apis: impl EcashClientsProvider,
|
||||
ecash_apis: Vec<EcashApiClient>,
|
||||
) -> Result<VerificationKeyAuth, BandwidthControllerError>
|
||||
where
|
||||
St: Storage,
|
||||
@@ -127,8 +90,6 @@ where
|
||||
return Ok(stored);
|
||||
};
|
||||
|
||||
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||
|
||||
let master_vk = query_random_apis_until_success(
|
||||
ecash_apis,
|
||||
|api| async move { api.api_client.master_verification_key(Some(epoch_id)).await },
|
||||
@@ -154,7 +115,7 @@ where
|
||||
pub(crate) async fn get_coin_index_signatures<St>(
|
||||
storage: &St,
|
||||
epoch_id: EpochId,
|
||||
mut ecash_apis: impl EcashClientsProvider,
|
||||
ecash_apis: Vec<EcashApiClient>,
|
||||
) -> Result<Vec<AnnotatedCoinIndexSignature>, BandwidthControllerError>
|
||||
where
|
||||
St: Storage,
|
||||
@@ -168,8 +129,6 @@ where
|
||||
return Ok(stored);
|
||||
};
|
||||
|
||||
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||
|
||||
let index_sigs = query_random_apis_until_success(
|
||||
ecash_apis,
|
||||
|api| async move {
|
||||
@@ -200,7 +159,7 @@ pub(crate) async fn get_expiration_date_signatures<St>(
|
||||
storage: &St,
|
||||
epoch_id: EpochId,
|
||||
expiration_date: Date,
|
||||
mut ecash_apis: impl EcashClientsProvider,
|
||||
ecash_apis: Vec<EcashApiClient>,
|
||||
) -> Result<Vec<AnnotatedExpirationDateSignature>, BandwidthControllerError>
|
||||
where
|
||||
St: Storage,
|
||||
@@ -214,8 +173,6 @@ where
|
||||
return Ok(stored);
|
||||
};
|
||||
|
||||
let ecash_apis = ecash_apis.try_get_ecash_clients().await?;
|
||||
|
||||
let expiration_sigs = query_random_apis_until_success(
|
||||
ecash_apis,
|
||||
|api| async move {
|
||||
|
||||
@@ -40,7 +40,6 @@ nym-crypto = { path = "../crypto" }
|
||||
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
|
||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||
nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-metrics = { path = "../nym-metrics" }
|
||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
|
||||
@@ -45,7 +45,6 @@ const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
|
||||
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
|
||||
|
||||
// define how much to request at once
|
||||
// clients/client-core/src/client/replies/reply_controller.rs
|
||||
@@ -146,11 +145,6 @@ impl Config {
|
||||
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
|
||||
// as of 12.09.23 the below is true (not sure how this comment will rot in the future)
|
||||
// medium_toggle:
|
||||
@@ -622,10 +616,6 @@ pub struct ReplySurbs {
|
||||
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
||||
pub maximum_reply_surb_storage_threshold: usize,
|
||||
|
||||
/// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client
|
||||
/// should proactively request additional reply surbs.
|
||||
pub minimum_reply_surb_threshold_buffer: usize,
|
||||
|
||||
/// Defines the minimum number of reply surbs the client would request.
|
||||
pub minimum_reply_surb_request_size: u32,
|
||||
|
||||
@@ -665,7 +655,6 @@ impl Default for ReplySurbs {
|
||||
ReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
minimum_reply_surb_threshold_buffer: DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER,
|
||||
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
|
||||
@@ -730,9 +719,6 @@ pub struct DebugConfig {
|
||||
|
||||
/// Defines all configuration options related to stats reporting.
|
||||
pub stats_reporting: StatsReporting,
|
||||
|
||||
/// Defines all configuration options related to the forget me flag.
|
||||
pub forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl DebugConfig {
|
||||
@@ -755,69 +741,6 @@ impl Default for DebugConfig {
|
||||
topology: Default::default(),
|
||||
reply_surbs: 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,9 +181,8 @@ impl From<ConfigV5> for Config {
|
||||
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
|
||||
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
|
||||
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
stats_reporting: Default::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ pub mod error;
|
||||
mod manager;
|
||||
mod models;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OnDiskGatewaysDetails {
|
||||
manager: StorageManager,
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ pub enum InMemStorageError {
|
||||
MalformedGateway(#[from] BadGateway),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InMemGatewaysDetails {
|
||||
inner: Arc<RwLock<InMemStorageInner>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Debug, Default)]
|
||||
struct InMemStorageInner {
|
||||
active_gateway: Option<String>,
|
||||
gateways: HashMap<String, GatewayRegistration>,
|
||||
|
||||
@@ -93,10 +93,6 @@ pub struct CommonClientInitArgs {
|
||||
/// Sets the address to report statistics
|
||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||
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> {
|
||||
|
||||
@@ -61,8 +61,4 @@ pub struct CommonClientRunArgs {
|
||||
/// Sets the address to report statistics
|
||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||
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,7 +1,6 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::mix_traffic::ClientRequestSender;
|
||||
use super::received_buffer::ReceivedBufferMessage;
|
||||
use super::statistics_control::StatisticsControl;
|
||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||
@@ -32,15 +31,13 @@ use crate::init::{
|
||||
setup_gateway,
|
||||
types::{GatewaySetup, InitialisationResult},
|
||||
};
|
||||
use crate::{config, spawn_future};
|
||||
use crate::{config, spawn_future, ForgetMe};
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core_config_types::ForgetMe;
|
||||
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_crypto::hkdf::DerivationMaterial;
|
||||
use nym_gateway_client::client::config::GatewayClientConfig;
|
||||
use nym_gateway_client::{
|
||||
AcknowledgementReceiver, GatewayClient, GatewayConfig, MixnetMessageReceiver, PacketRouter,
|
||||
@@ -178,8 +175,8 @@ impl From<bool> for CredentialsToggle {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
config: Config,
|
||||
pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
||||
config: &'a Config,
|
||||
client_store: S,
|
||||
dkg_query_client: Option<C>,
|
||||
|
||||
@@ -194,19 +191,19 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
|
||||
derivation_material: Option<DerivationMaterial>,
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl<C, S> BaseClientBuilder<C, S>
|
||||
impl<'a, C, S> BaseClientBuilder<'a, C, S>
|
||||
where
|
||||
S: MixnetClientStorage + 'static,
|
||||
C: DkgQueryClient + Send + Sync + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
base_config: Config,
|
||||
base_config: &'a Config,
|
||||
client_store: S,
|
||||
dkg_query_client: Option<C>,
|
||||
) -> BaseClientBuilder<C, S> {
|
||||
) -> BaseClientBuilder<'a, C, S> {
|
||||
BaseClientBuilder {
|
||||
config: base_config,
|
||||
client_store,
|
||||
@@ -219,22 +216,13 @@ where
|
||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
derivation_material: None,
|
||||
forget_me: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_derivation_material(
|
||||
mut self,
|
||||
derivation_material: Option<DerivationMaterial>,
|
||||
) -> Self {
|
||||
self.derivation_material = derivation_material;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
||||
self.config.debug.forget_me = *forget_me;
|
||||
self.forget_me = forget_me.clone();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -310,7 +298,7 @@ where
|
||||
topology_accessor: TopologyAccessor,
|
||||
mix_tx: BatchMixMessageSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
|
||||
@@ -323,10 +311,9 @@ where
|
||||
debug_config.traffic,
|
||||
debug_config.cover_traffic,
|
||||
stats_tx,
|
||||
task_client,
|
||||
);
|
||||
|
||||
stream.start();
|
||||
stream.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -341,7 +328,7 @@ where
|
||||
reply_controller_receiver: ReplyControllerReceiver,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
task_client: TaskClient,
|
||||
shutdown: TaskClient,
|
||||
packet_type: PacketType,
|
||||
stats_tx: ClientStatsSender,
|
||||
) {
|
||||
@@ -359,9 +346,8 @@ where
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
stats_tx,
|
||||
task_client,
|
||||
)
|
||||
.start(packet_type);
|
||||
.start_with_shutdown(shutdown, packet_type);
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -384,9 +370,8 @@ where
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
metrics_reporter,
|
||||
shutdown,
|
||||
);
|
||||
controller.start()
|
||||
controller.start_with_shutdown(shutdown)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -575,22 +560,15 @@ where
|
||||
topology_accessor: TopologyAccessor,
|
||||
local_gateway: NodeIdentity,
|
||||
wait_for_gateway: bool,
|
||||
mut task_client: TaskClient,
|
||||
mut shutdown: TaskClient,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let topology_refresher_config =
|
||||
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(
|
||||
topology_refresher_config,
|
||||
topology_accessor,
|
||||
topology_provider,
|
||||
task_client,
|
||||
);
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
@@ -631,11 +609,15 @@ 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.
|
||||
// only use the initial values obtained
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start();
|
||||
topology_refresher.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -646,29 +628,30 @@ where
|
||||
user_agent: Option<UserAgent>,
|
||||
client_stats_id: String,
|
||||
input_sender: Sender<InputMessage>,
|
||||
task_client: TaskClient,
|
||||
shutdown: TaskClient,
|
||||
) -> ClientStatsSender {
|
||||
info!("Starting statistics control...");
|
||||
StatisticsControl::create_and_start(
|
||||
StatisticsControl::create_and_start_with_shutdown(
|
||||
config.debug.stats_reporting,
|
||||
user_agent
|
||||
.map(|u| u.application)
|
||||
.unwrap_or("unknown".to_string()),
|
||||
client_stats_id,
|
||||
input_sender.clone(),
|
||||
task_client,
|
||||
shutdown.with_suffix("controller"),
|
||||
)
|
||||
}
|
||||
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
shutdown: TaskClient,
|
||||
) -> (BatchMixMessageSender, ClientRequestSender) {
|
||||
forget_me: ForgetMe,
|
||||
) -> BatchMixMessageSender {
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx, client_tx) =
|
||||
MixTrafficController::new(gateway_transceiver, shutdown);
|
||||
mix_traffic_controller.start();
|
||||
(mix_tx, client_tx)
|
||||
let (mix_traffic_controller, mix_tx) =
|
||||
MixTrafficController::new(gateway_transceiver, forget_me);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
mix_tx
|
||||
}
|
||||
|
||||
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
|
||||
@@ -703,7 +686,6 @@ where
|
||||
setup_method: GatewaySetup,
|
||||
key_store: &S::KeyStore,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
derivation_material: Option<DerivationMaterial>,
|
||||
) -> Result<InitialisationResult, ClientCoreError>
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
|
||||
@@ -713,12 +695,7 @@ where
|
||||
if key_store.load_keys().await.is_err() {
|
||||
info!("could not find valid client keys - a new set will be generated");
|
||||
let mut rng = OsRng;
|
||||
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)
|
||||
};
|
||||
let keys = ClientKeys::generate_new(&mut rng);
|
||||
store_client_keys(keys, key_store).await?;
|
||||
}
|
||||
|
||||
@@ -740,7 +717,6 @@ where
|
||||
self.setup_method,
|
||||
self.client_store.key_store(),
|
||||
self.client_store.gateway_details_store(),
|
||||
self.derivation_material,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -797,7 +773,7 @@ where
|
||||
);
|
||||
|
||||
let stats_reporter = Self::start_statistics_control(
|
||||
&self.config,
|
||||
self.config,
|
||||
self.user_agent.clone(),
|
||||
generate_client_stats_id(*self_address.identity()),
|
||||
input_sender.clone(),
|
||||
@@ -823,7 +799,7 @@ where
|
||||
|
||||
let gateway_transceiver = Self::setup_gateway_transceiver(
|
||||
self.custom_gateway_transceiver,
|
||||
&self.config,
|
||||
self.config,
|
||||
init_res,
|
||||
bandwidth_controller,
|
||||
&details_store,
|
||||
@@ -857,9 +833,10 @@ where
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
|
||||
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
|
||||
let message_sender = Self::start_mix_traffic_controller(
|
||||
gateway_transceiver,
|
||||
shutdown.fork("mix_traffic_controller"),
|
||||
self.forget_me,
|
||||
);
|
||||
|
||||
// Channels that the websocket listener can use to signal downstream to the real traffic
|
||||
@@ -934,8 +911,6 @@ where
|
||||
},
|
||||
stats_reporter,
|
||||
task_handle: shutdown,
|
||||
client_request_sender,
|
||||
forget_me: self.config.debug.forget_me,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -947,7 +922,6 @@ pub struct BaseClient {
|
||||
pub client_output: ClientOutputStatus,
|
||||
pub client_state: ClientState,
|
||||
pub stats_reporter: ClientStatsSender,
|
||||
pub client_request_sender: ClientRequestSender,
|
||||
|
||||
pub task_handle: TaskHandle,
|
||||
pub forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ pub trait MixnetClientStorage {
|
||||
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ephemeral {
|
||||
key_store: InMemEphemeralKeys,
|
||||
reply_store: reply_storage::Empty,
|
||||
@@ -121,7 +120,6 @@ impl MixnetClientStorage for Ephemeral {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(all(
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "fs-surb-storage",
|
||||
|
||||
@@ -13,7 +13,6 @@ use nym_sphinx::cover::generate_loop_cover_packet;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::utils::sample_poisson_duration;
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_task::TaskClient;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -65,8 +64,6 @@ where
|
||||
packet_type: PacketType,
|
||||
|
||||
stats_tx: ClientStatsSender,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> Stream for LoopCoverTrafficStream<R>
|
||||
@@ -113,7 +110,6 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
traffic_config: config::Traffic,
|
||||
cover_config: config::CoverTraffic,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -132,7 +128,6 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
secondary_packet_size: traffic_config.secondary_packet_size,
|
||||
packet_type: traffic_config.packet_type,
|
||||
stats_tx,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +175,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
}
|
||||
};
|
||||
|
||||
let cover_message = match generate_loop_cover_packet(
|
||||
let cover_message = generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
topology_ref,
|
||||
&self.ack_key,
|
||||
@@ -189,15 +184,8 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
self.cover_traffic.loop_cover_traffic_average_delay,
|
||||
cover_traffic_packet_size,
|
||||
self.packet_type,
|
||||
) {
|
||||
Ok(cover_message) => cover_message,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Somehow failed to generate a loop cover message with a valid topology: {err}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
)
|
||||
.expect("Somehow failed to generate a loop cover message with a valid topology");
|
||||
|
||||
if let Err(err) = self.mix_tx.try_send(vec![cover_message]) {
|
||||
match err {
|
||||
@@ -229,7 +217,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
pub fn start(mut self) {
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
||||
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
|
||||
// so panic and review the code that lead to this branch
|
||||
@@ -243,8 +231,6 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
);
|
||||
self.set_next_delay(sampled);
|
||||
|
||||
let mut shutdown = self.task_client.fork("select");
|
||||
|
||||
spawn_future(async move {
|
||||
debug!("Started LoopCoverTrafficStream with graceful shutdown support");
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use nym_crypto::{
|
||||
asymmetric::{encryption, identity},
|
||||
hkdf::{DerivationMaterial, InvalidLength},
|
||||
};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
@@ -13,7 +10,6 @@ use std::sync::Arc;
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
pub mod persistence;
|
||||
mod test;
|
||||
|
||||
// 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
|
||||
@@ -47,24 +43,6 @@ 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(
|
||||
id_keypair: identity::KeyPair,
|
||||
enc_keypair: encryption::KeyPair,
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::client::key_manager::ClientKeys;
|
||||
use async_trait::async_trait;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -66,7 +65,6 @@ pub enum OnDiskKeysError {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct OnDiskKeys {
|
||||
paths: ClientKeysPaths,
|
||||
@@ -196,9 +194,8 @@ impl KeyStore for OnDiskKeys {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InMemEphemeralKeys {
|
||||
keys: Arc<Mutex<ClientKeys>>,
|
||||
keys: Mutex<ClientKeys>,
|
||||
}
|
||||
|
||||
impl InMemEphemeralKeys {
|
||||
@@ -207,7 +204,7 @@ impl InMemEphemeralKeys {
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
InMemEphemeralKeys {
|
||||
keys: Arc::new(Mutex::new(ClientKeys::generate_new(rng))),
|
||||
keys: Mutex::new(ClientKeys::generate_new(rng)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
#[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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,14 @@
|
||||
|
||||
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::spawn_future;
|
||||
use crate::{spawn_future, ForgetMe};
|
||||
use log::*;
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_task::TaskClient;
|
||||
use transceiver::ErasedGatewayError;
|
||||
|
||||
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<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;
|
||||
|
||||
@@ -28,66 +25,48 @@ pub struct MixTrafficController {
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
|
||||
mix_rx: BatchMixMessageReceiver,
|
||||
client_rx: ClientRequestReceiver,
|
||||
|
||||
// TODO: this is temporary work-around.
|
||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||
consecutive_gateway_failure_count: usize,
|
||||
|
||||
task_client: TaskClient,
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl MixTrafficController {
|
||||
pub fn new<T>(
|
||||
gateway_transceiver: T,
|
||||
task_client: TaskClient,
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
ClientRequestSender,
|
||||
)
|
||||
forget_me: ForgetMe,
|
||||
) -> (MixTrafficController, BatchMixMessageSender)
|
||||
where
|
||||
T: GatewayTransceiver + Send + 'static,
|
||||
{
|
||||
let (message_sender, message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
|
||||
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
(
|
||||
MixTrafficController {
|
||||
gateway_transceiver: Box::new(gateway_transceiver),
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
task_client,
|
||||
forget_me,
|
||||
},
|
||||
message_sender,
|
||||
client_sender,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_dynamic(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
task_client: TaskClient,
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
ClientRequestSender,
|
||||
) {
|
||||
forget_me: ForgetMe,
|
||||
) -> (MixTrafficController, BatchMixMessageSender) {
|
||||
let (message_sender, message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
|
||||
(
|
||||
MixTrafficController {
|
||||
gateway_transceiver,
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
task_client,
|
||||
forget_me,
|
||||
},
|
||||
message_sender,
|
||||
client_sender,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -116,23 +95,31 @@ impl MixTrafficController {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn start(mut self) {
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
debug!("Started MixTrafficController with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
mix_packets = self.mix_rx.recv() => match mix_packets {
|
||||
Some(mix_packets) => {
|
||||
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));
|
||||
tokio::select! {
|
||||
ret = self.on_messages(mix_packets) => {
|
||||
if let Err(err) = ret {
|
||||
error!("Failed to send sphinx packet(s) to the gateway: {err}");
|
||||
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
|
||||
// Disconnect from the gateway. If we should try to re-connect
|
||||
// is handled at a higher layer.
|
||||
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
|
||||
// Do we need to handle the embedded mixnet client case
|
||||
// separately?
|
||||
shutdown.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("MixTrafficController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -142,24 +129,32 @@ impl MixTrafficController {
|
||||
break;
|
||||
}
|
||||
},
|
||||
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() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("MixTrafficController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.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");
|
||||
});
|
||||
|
||||
@@ -86,9 +86,7 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let _ = (**self).send_client_request(message.clone()).await?;
|
||||
log::debug!("Sent client request: {:?}", message);
|
||||
Ok(())
|
||||
(**self).send_client_request(message).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +143,14 @@ where
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
self.gateway_client.send_client_request(message).await
|
||||
if let Some(shared_key) = self.gateway_client.shared_key() {
|
||||
self.gateway_client
|
||||
.send_websocket_message(message.encrypt(&*shared_key)?)
|
||||
.await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(GatewayClientError::ConnectionInInvalidState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-15
@@ -11,7 +11,6 @@ use nym_sphinx::{
|
||||
acknowledgements::{identifier::recover_identifier, AckKey},
|
||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Module responsible for listening for any data resembling acknowledgements from the network
|
||||
@@ -21,7 +20,6 @@ pub(super) struct AcknowledgementListener {
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: AckActionSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl AcknowledgementListener {
|
||||
@@ -30,14 +28,12 @@ impl AcknowledgementListener {
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: AckActionSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
AcknowledgementListener {
|
||||
ack_key,
|
||||
ack_receiver,
|
||||
action_sender,
|
||||
stats_tx,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,14 +64,9 @@ impl AcknowledgementListener {
|
||||
trace!("Received {} from the mix network", frag_id);
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RealAckReceived(ack_content.len()).into());
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_remove(frag_id))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send remove action to action controller: {err}");
|
||||
}
|
||||
}
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn handle_ack_receiver_item(&mut self, item: Vec<Vec<u8>>) {
|
||||
@@ -85,10 +76,10 @@ impl AcknowledgementListener {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started AcknowledgementListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
acks = self.ack_receiver.next() => match acks {
|
||||
Some(acks) => self.handle_ack_receiver_item(acks).await,
|
||||
@@ -97,12 +88,12 @@ impl AcknowledgementListener {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("AcknowledgementListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("AcknowledgementListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+16
-15
@@ -9,7 +9,6 @@ use log::*;
|
||||
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey};
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_sphinx::Delay as SphinxDelay;
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -102,8 +101,6 @@ pub(super) struct ActionController {
|
||||
|
||||
/// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements.
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl ActionController {
|
||||
@@ -111,7 +108,6 @@ impl ActionController {
|
||||
config: Config,
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
incoming_actions: AckActionReceiver,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
ActionController {
|
||||
config,
|
||||
@@ -119,7 +115,6 @@ impl ActionController {
|
||||
pending_acks_timers: NonExhaustiveDelayQueue::new(),
|
||||
incoming_actions,
|
||||
retransmission_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +216,11 @@ impl ActionController {
|
||||
}
|
||||
|
||||
// note: when the entry expires it's automatically removed from pending_acks_timers
|
||||
fn handle_expired_ack_timer(&mut self, expired_ack: Expired<FragmentIdentifier>) {
|
||||
fn handle_expired_ack_timer(
|
||||
&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
|
||||
// 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?
|
||||
@@ -239,13 +238,15 @@ impl ActionController {
|
||||
// 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
|
||||
// resending data (in maybe 1 in 1 million cases, but it's something)
|
||||
if let Err(err) = self
|
||||
if self
|
||||
.retransmission_sender
|
||||
.unbounded_send(Arc::downgrade(pending_ack_data))
|
||||
.is_err()
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
log::error!("Failed to send pending ack for retransmission: {err}");
|
||||
}
|
||||
assert!(
|
||||
task_client.is_shutdown_poll(),
|
||||
"Failed to send pending ack for retransmission"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// this shouldn't cause any issues but shouldn't have happened to begin with!
|
||||
@@ -264,10 +265,10 @@ impl ActionController {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started ActionController with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
action = self.incoming_actions.next() => match action {
|
||||
Some(action) => self.process_action(action),
|
||||
@@ -279,19 +280,19 @@ impl ActionController {
|
||||
}
|
||||
},
|
||||
expired_ack = self.pending_acks_timers.next() => match expired_ack {
|
||||
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack),
|
||||
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack, &mut shutdown),
|
||||
None => {
|
||||
log::trace!("ActionController: Stopping since ack channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("ActionController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("ActionController: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+5
-15
@@ -11,7 +11,6 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
|
||||
@@ -24,7 +23,6 @@ where
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> InputMessageListener<R>
|
||||
@@ -38,13 +36,11 @@ where
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
InputMessageListener {
|
||||
input_receiver,
|
||||
message_handler,
|
||||
reply_controller_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,14 +63,8 @@ where
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
// offload reply handling to the dedicated task
|
||||
if let Err(err) = self
|
||||
.reply_controller_sender
|
||||
self.reply_controller_sender
|
||||
.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(
|
||||
@@ -174,10 +164,10 @@ where
|
||||
};
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started InputMessageListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
input_msg = self.input_receiver.recv() => match input_msg {
|
||||
Some(input_msg) => {
|
||||
@@ -188,12 +178,12 @@ where
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("InputMessageListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("InputMessageListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+27
-18
@@ -24,7 +24,6 @@ use nym_sphinx::{
|
||||
Delay as SphinxDelay,
|
||||
};
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::{
|
||||
sync::{Arc, Weak},
|
||||
@@ -67,7 +66,7 @@ pub(crate) enum PacketDestination {
|
||||
|
||||
/// Structure representing a data `Fragment` that is on-route to the specified `Recipient`
|
||||
#[derive(Debug)]
|
||||
pub struct PendingAcknowledgement {
|
||||
pub(crate) struct PendingAcknowledgement {
|
||||
message_chunk: Fragment,
|
||||
delay: SphinxDelay,
|
||||
destination: PacketDestination,
|
||||
@@ -217,7 +216,6 @@ where
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
||||
|
||||
@@ -227,7 +225,6 @@ where
|
||||
action_config,
|
||||
retransmission_tx,
|
||||
connectors.ack_action_receiver,
|
||||
task_client.fork("action_controller"),
|
||||
);
|
||||
|
||||
// will listen for any acks coming from the network
|
||||
@@ -236,7 +233,6 @@ where
|
||||
connectors.ack_receiver,
|
||||
connectors.ack_action_sender.clone(),
|
||||
stats_tx,
|
||||
task_client.fork("acknowledgement_listener"),
|
||||
);
|
||||
|
||||
// will listen for any new messages from the client
|
||||
@@ -244,7 +240,6 @@ where
|
||||
connectors.input_receiver,
|
||||
message_handler.clone(),
|
||||
reply_controller_sender.clone(),
|
||||
task_client.fork("input_message_listener"),
|
||||
);
|
||||
|
||||
// will listen for any ack timeouts and trigger retransmission
|
||||
@@ -254,16 +249,12 @@ where
|
||||
message_handler,
|
||||
retransmission_rx,
|
||||
reply_controller_sender,
|
||||
task_client.fork("retransmission_request_listener"),
|
||||
);
|
||||
|
||||
// will listen for events indicating the packet was sent through the network so that
|
||||
// the retransmission timer should be started.
|
||||
let sent_notification_listener = SentNotificationListener::new(
|
||||
connectors.sent_notifier,
|
||||
connectors.ack_action_sender,
|
||||
task_client.with_suffix("sent_notification_listener"),
|
||||
);
|
||||
let sent_notification_listener =
|
||||
SentNotificationListener::new(connectors.sent_notifier, connectors.ack_action_sender);
|
||||
|
||||
AcknowledgementController {
|
||||
acknowledgement_listener,
|
||||
@@ -274,35 +265,53 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn start(self, packet_type: PacketType) {
|
||||
pub(super) fn start_with_shutdown(
|
||||
self,
|
||||
shutdown: nym_task::TaskClient,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
let mut acknowledgement_listener = self.acknowledgement_listener;
|
||||
let mut input_message_listener = self.input_message_listener;
|
||||
let mut retransmission_request_listener = self.retransmission_request_listener;
|
||||
let mut sent_notification_listener = self.sent_notification_listener;
|
||||
let mut action_controller = self.action_controller;
|
||||
|
||||
let shutdown_handle = shutdown.fork("acknowledgement_listener");
|
||||
spawn_future(async move {
|
||||
acknowledgement_listener.run().await;
|
||||
acknowledgement_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
debug!("The acknowledgement listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.fork("input_message_listener");
|
||||
spawn_future(async move {
|
||||
input_message_listener.run().await;
|
||||
input_message_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
debug!("The input listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.fork("retransmission_request_listener");
|
||||
spawn_future(async move {
|
||||
retransmission_request_listener.run(packet_type).await;
|
||||
retransmission_request_listener
|
||||
.run_with_shutdown(shutdown_handle, packet_type)
|
||||
.await;
|
||||
debug!("The retransmission request listener has finished execution!");
|
||||
});
|
||||
|
||||
let shutdown_handle = shutdown.fork("sent_notification_listener");
|
||||
spawn_future(async move {
|
||||
sent_notification_listener.run().await;
|
||||
sent_notification_listener
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
debug!("The sent notification listener has finished execution!");
|
||||
});
|
||||
|
||||
spawn_future(async move {
|
||||
action_controller.run().await;
|
||||
action_controller
|
||||
.run_with_shutdown(shutdown.with_suffix("action_controller"))
|
||||
.await;
|
||||
debug!("The controller has finished execution!");
|
||||
});
|
||||
}
|
||||
|
||||
+17
-32
@@ -14,7 +14,7 @@ use log::*;
|
||||
use nym_sphinx::chunking::fragment::Fragment;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
|
||||
use nym_task::{connections::TransmissionLane, TaskClient};
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
@@ -25,7 +25,6 @@ pub(super) struct RetransmissionRequestListener<R> {
|
||||
message_handler: MessageHandler<R>,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> RetransmissionRequestListener<R>
|
||||
@@ -38,7 +37,6 @@ where
|
||||
message_handler: MessageHandler<R>,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
RetransmissionRequestListener {
|
||||
maximum_retransmissions,
|
||||
@@ -46,7 +44,6 @@ where
|
||||
message_handler,
|
||||
request_receiver,
|
||||
reply_controller_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,12 +79,9 @@ where
|
||||
if let Some(limit) = self.maximum_retransmissions {
|
||||
if timed_out_ack.retransmissions >= limit {
|
||||
warn!("reached maximum number of allowed retransmissions for the packet");
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_remove(frag_id))
|
||||
{
|
||||
error!("Failed to send remove action to the controller: {err}");
|
||||
}
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -99,16 +93,11 @@ where
|
||||
} => {
|
||||
// if this is retransmission for reply, offload it to the dedicated task
|
||||
// that deals with all the surbs
|
||||
if let Err(err) = self.reply_controller_sender.send_retransmission_data(
|
||||
return self.reply_controller_sender.send_retransmission_data(
|
||||
*recipient_tag,
|
||||
weak_timed_out_ack,
|
||||
*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) => {
|
||||
self.prepare_normal_retransmission_chunk(
|
||||
@@ -125,12 +114,9 @@ where
|
||||
Err(err) => {
|
||||
warn!("Could not retransmit the packet - {err}");
|
||||
// we NEED to start timer here otherwise we will have this guy permanently stuck in memory
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_start_timer(frag_id))
|
||||
{
|
||||
error!("Failed to send start timer action to the controller: {err}");
|
||||
}
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -155,14 +141,9 @@ where
|
||||
// is sent to the `OutQueueControl` and has gone through its internal queue
|
||||
// with the additional poisson delay.
|
||||
// And since Actions are executed in order `UpdateTimer` will HAVE TO be executed before `StartTimer`
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_update_pending_ack(frag_id, new_delay))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send update pending ack action to the controller: {err}");
|
||||
}
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
// send to `OutQueueControl` to eventually send to the mix network
|
||||
self.message_handler
|
||||
@@ -176,10 +157,14 @@ where
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self, packet_type: PacketType) {
|
||||
pub(super) async fn run_with_shutdown(
|
||||
&mut self,
|
||||
mut shutdown: nym_task::TaskClient,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
debug!("Started RetransmissionRequestListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
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,
|
||||
@@ -188,12 +173,12 @@ where
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("RetransmissionRequestListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("RetransmissionRequestListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+6
-15
@@ -6,7 +6,6 @@ use super::SentPacketNotificationReceiver;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_sphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||
use nym_task::TaskClient;
|
||||
|
||||
/// Module responsible for starting up retransmission timers.
|
||||
/// It is required because when we send our packet to the `real traffic stream` controlled
|
||||
@@ -15,19 +14,16 @@ use nym_task::TaskClient;
|
||||
pub(super) struct SentNotificationListener {
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
action_sender: AckActionSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl SentNotificationListener {
|
||||
pub(super) fn new(
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
action_sender: AckActionSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
SentNotificationListener {
|
||||
sent_notifier,
|
||||
action_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,20 +32,15 @@ impl SentNotificationListener {
|
||||
trace!("sent off a cover message - no need to start retransmission timer!");
|
||||
return;
|
||||
}
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_start_timer(frag_id))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send start timer action to action controller: {err}");
|
||||
}
|
||||
}
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started SentNotificationListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
frag_id = self.sent_notifier.next() => match frag_id {
|
||||
Some(frag_id) => {
|
||||
@@ -60,13 +51,13 @@ impl SentNotificationListener {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("SentNotificationListener: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(self.task_client.is_shutdown_poll());
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("SentNotificationListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
|
||||
use nym_sphinx::Delay;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskClient;
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashMap;
|
||||
@@ -150,14 +149,12 @@ pub(crate) struct MessageHandler<R> {
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
tag_storage: UsedSenderTags,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> MessageHandler<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
rng: R,
|
||||
@@ -166,7 +163,6 @@ where
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
tag_storage: UsedSenderTags,
|
||||
task_client: TaskClient,
|
||||
) -> Self
|
||||
where
|
||||
R: Copy,
|
||||
@@ -187,7 +183,6 @@ where
|
||||
topology_access,
|
||||
reply_key_storage,
|
||||
tag_storage,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,25 +609,15 @@ where
|
||||
}
|
||||
|
||||
pub(crate) fn update_ack_delay(&self, id: FragmentIdentifier, new_delay: Delay) {
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::UpdatePendingAck(id, new_delay))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send update action to the controller: {err}");
|
||||
}
|
||||
}
|
||||
.expect("action control task has died")
|
||||
}
|
||||
|
||||
pub(crate) fn insert_pending_acks(&self, pending_acks: Vec<PendingAcknowledgement>) {
|
||||
if let Err(err) = self
|
||||
.action_sender
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_insert(pending_acks))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send insert action to the controller: {err}");
|
||||
}
|
||||
}
|
||||
.expect("action control task has died")
|
||||
}
|
||||
|
||||
// tells real message sender (with the poisson timer) to send this to the mix network
|
||||
@@ -646,9 +631,9 @@ where
|
||||
.send((messages, transmission_lane))
|
||||
.await
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to forward messages to the real message sender: {err}");
|
||||
}
|
||||
error!(
|
||||
"Failed to forward messages to the real message sender (OutQueueControl): {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||
use nym_task::TaskClient;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -148,7 +147,6 @@ impl RealMessagesController<OsRng> {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -179,7 +177,6 @@ impl RealMessagesController<OsRng> {
|
||||
topology_access.clone(),
|
||||
reply_storage.key_storage(),
|
||||
reply_storage.tags_storage(),
|
||||
task_client.fork("message_handler"),
|
||||
);
|
||||
|
||||
let ack_control = AcknowledgementController::new(
|
||||
@@ -189,7 +186,6 @@ impl RealMessagesController<OsRng> {
|
||||
message_handler.clone(),
|
||||
reply_controller_sender,
|
||||
stats_tx.clone(),
|
||||
task_client.fork("ack_control"),
|
||||
);
|
||||
|
||||
let reply_control = ReplyController::new(
|
||||
@@ -197,7 +193,6 @@ impl RealMessagesController<OsRng> {
|
||||
message_handler,
|
||||
reply_storage,
|
||||
reply_controller_receiver,
|
||||
task_client.fork("reply_controller"),
|
||||
);
|
||||
|
||||
let out_queue_control = OutQueueControl::new(
|
||||
@@ -210,7 +205,6 @@ impl RealMessagesController<OsRng> {
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
stats_tx,
|
||||
task_client.with_suffix("out_queue_control"),
|
||||
);
|
||||
|
||||
RealMessagesController {
|
||||
@@ -220,20 +214,22 @@ impl RealMessagesController<OsRng> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(self, packet_type: PacketType) {
|
||||
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient, packet_type: PacketType) {
|
||||
let mut out_queue_control = self.out_queue_control;
|
||||
let ack_control = self.ack_control;
|
||||
let mut reply_control = self.reply_control;
|
||||
|
||||
let shutdown_handle = shutdown.fork("out_queue_control");
|
||||
spawn_future(async move {
|
||||
out_queue_control.run().await;
|
||||
out_queue_control.run_with_shutdown(shutdown_handle).await;
|
||||
debug!("The out queue controller has finished execution!");
|
||||
});
|
||||
let shutdown_handle = shutdown.fork("reply_control");
|
||||
spawn_future(async move {
|
||||
reply_control.run().await;
|
||||
reply_control.run_with_shutdown(shutdown_handle).await;
|
||||
debug!("The reply controller has finished execution!");
|
||||
});
|
||||
|
||||
ack_control.start(packet_type);
|
||||
ack_control.start_with_shutdown(shutdown.with_suffix("ack_control"), packet_type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
|
||||
use nym_task::connections::{
|
||||
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -118,8 +117,6 @@ where
|
||||
|
||||
/// Channel used for sending metrics events (specifically `PacketStatistics` events) to the metrics tracker.
|
||||
stats_tx: ClientStatsSender,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -179,7 +176,6 @@ where
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
config,
|
||||
@@ -194,7 +190,6 @@ where
|
||||
client_connection_rx,
|
||||
lane_queue_lengths,
|
||||
stats_tx,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,9 +198,7 @@ where
|
||||
// queues and client load rather than the required delay. So realistically we can treat
|
||||
// whatever is about to happen as negligible additional delay.
|
||||
trace!("{} is about to get sent to the mixnet", frag_id);
|
||||
if let Err(err) = self.sent_notifier.unbounded_send(frag_id) {
|
||||
error!("Failed to notify about sent message: {err}");
|
||||
}
|
||||
self.sent_notifier.unbounded_send(frag_id).unwrap();
|
||||
}
|
||||
|
||||
fn loop_cover_message_size(&mut self) -> PacketSize {
|
||||
@@ -278,9 +271,7 @@ where
|
||||
};
|
||||
|
||||
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
log::error!("Failed to send: {err}");
|
||||
}
|
||||
log::error!("Failed to send: {err}");
|
||||
} else {
|
||||
let event = if fragment_id.is_some() {
|
||||
PacketStatisticsEvent::RealPacketSent(packet_size)
|
||||
@@ -513,29 +504,21 @@ where
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn log_status(&self, shutdown: &mut TaskClient) {
|
||||
fn log_status(&self, shutdown: &mut nym_task::TaskClient) {
|
||||
use crate::error::ClientCoreStatusMessage;
|
||||
|
||||
let packets = self.transmission_buffer.total_size();
|
||||
let lanes = self.transmission_buffer.lanes();
|
||||
let backlog = self.transmission_buffer.total_size_in_bytes() as f64 / 1024.0;
|
||||
let lanes = self.transmission_buffer.num_lanes();
|
||||
let mult = self.sending_delay_controller.current_multiplier();
|
||||
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 {
|
||||
format!("Packet backlog: {lane_status}, no delay")
|
||||
format!("Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, no delay")
|
||||
} else {
|
||||
format!("Packet backlog: {lane_status}, avg delay: {delay}ms ({mult})")
|
||||
format!(
|
||||
"Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, avg delay: {delay}ms ({mult})"
|
||||
)
|
||||
};
|
||||
|
||||
if packets > 1000 {
|
||||
log::warn!("{status_str}");
|
||||
} else if packets > 0 {
|
||||
@@ -552,16 +535,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started OutQueueControl with graceful shutdown support");
|
||||
|
||||
let mut shutdown = self.task_client.fork("select");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
|
||||
@@ -20,7 +20,6 @@ use nym_sphinx::message::{NymMessage, PlainMessage};
|
||||
use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -153,7 +152,6 @@ struct ReceivedMessagesBuffer<R: MessageReceiver> {
|
||||
inner: Arc<Mutex<ReceivedMessagesBufferInner<R>>>,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
@@ -162,7 +160,6 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
ReceivedMessagesBuffer {
|
||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||
@@ -175,7 +172,6 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
})),
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,15 +257,11 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = self.reply_controller_sender.send_additional_surbs(
|
||||
self.reply_controller_sender.send_additional_surbs(
|
||||
msg.sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
) {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("{err}");
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
reconstructed
|
||||
}
|
||||
@@ -284,14 +276,8 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
ReplyMessageContent::Data { message } => reconstructed.push(message.into()),
|
||||
ReplyMessageContent::SurbRequest { recipient, amount } => {
|
||||
debug!("received request for {amount} additional reply SURBs from {recipient}");
|
||||
if let Err(err) = self
|
||||
.reply_controller_sender
|
||||
.send_additional_surbs_request(*recipient, amount)
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("{err}");
|
||||
}
|
||||
}
|
||||
self.reply_controller_sender
|
||||
.send_additional_surbs_request(*recipient, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,19 +399,16 @@ pub enum ReceivedBufferMessage {
|
||||
struct RequestReceiver<R: MessageReceiver> {
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
RequestReceiver {
|
||||
received_buffer,
|
||||
query_receiver,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,12 +423,12 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started RequestReceiver with graceful shutdown support");
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("RequestReceiver: Received shutdown");
|
||||
}
|
||||
request = self.query_receiver.next() => {
|
||||
@@ -458,7 +441,7 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
},
|
||||
}
|
||||
}
|
||||
self.task_client.recv().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("RequestReceiver: Exiting");
|
||||
}
|
||||
}
|
||||
@@ -466,25 +449,25 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
struct FragmentedMessageReceiver<R: MessageReceiver> {
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
FragmentedMessageReceiver {
|
||||
received_buffer,
|
||||
mixnet_packet_receiver,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) -> Result<(), MessageRecoveryError> {
|
||||
async fn run_with_shutdown(
|
||||
&mut self,
|
||||
mut shutdown: nym_task::TaskClient,
|
||||
) -> Result<(), MessageRecoveryError> {
|
||||
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
new_messages = self.mixnet_packet_receiver.next() => {
|
||||
if let Some(new_messages) = new_messages {
|
||||
@@ -494,12 +477,12 @@ impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv_with_delay() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("FragmentedMessageReceiver: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("FragmentedMessageReceiver: Exiting");
|
||||
Ok(())
|
||||
}
|
||||
@@ -518,42 +501,41 @@ impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferControll
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
metrics_reporter: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
let received_buffer = ReceivedMessagesBuffer::new(
|
||||
local_encryption_keypair,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
metrics_reporter,
|
||||
task_client.fork("received_messages_buffer"),
|
||||
);
|
||||
|
||||
ReceivedMessagesBufferController {
|
||||
fragmented_message_receiver: FragmentedMessageReceiver::new(
|
||||
received_buffer.clone(),
|
||||
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(self) {
|
||||
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
|
||||
let mut fragmented_message_receiver = self.fragmented_message_receiver;
|
||||
let mut request_receiver = self.request_receiver;
|
||||
|
||||
let shutdown_handle = shutdown.fork("fragmented_message_receiver");
|
||||
spawn_future(async move {
|
||||
match fragmented_message_receiver.run().await {
|
||||
match fragmented_message_receiver
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => error!("{e}"),
|
||||
}
|
||||
});
|
||||
spawn_future(async move {
|
||||
request_receiver.run().await;
|
||||
request_receiver
|
||||
.run_with_shutdown(shutdown.with_suffix("request_receiver"))
|
||||
.await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::anonymous_replies::ReplySurb;
|
||||
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::btree_map::Entry;
|
||||
@@ -69,9 +68,6 @@ pub struct ReplyController<R> {
|
||||
|
||||
message_handler: MessageHandler<R>,
|
||||
full_reply_storage: CombinedReplyStorage,
|
||||
|
||||
// Listen for shutdown signals
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> ReplyController<R>
|
||||
@@ -83,7 +79,6 @@ where
|
||||
message_handler: MessageHandler<R>,
|
||||
full_reply_storage: CombinedReplyStorage,
|
||||
request_receiver: ReplyControllerReceiver,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
ReplyController {
|
||||
config,
|
||||
@@ -92,7 +87,6 @@ where
|
||||
pending_retransmissions: HashMap::new(),
|
||||
message_handler,
|
||||
full_reply_storage,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +96,6 @@ where
|
||||
fragments: I,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
trace!("buffering pending replies for {recipient}");
|
||||
self.pending_replies
|
||||
.entry(*recipient)
|
||||
.or_insert_with(TransmissionBuffer::new)
|
||||
@@ -114,7 +107,6 @@ where
|
||||
recipient: &AnonymousSenderTag,
|
||||
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...
|
||||
self.pending_replies
|
||||
.entry(*recipient)
|
||||
@@ -127,7 +119,6 @@ where
|
||||
recipient: &AnonymousSenderTag,
|
||||
data: Vec<Arc<PendingAcknowledgement>>,
|
||||
) {
|
||||
trace!("re-inserting pending retransmissions for {recipient}");
|
||||
// the underlying entry MUST exist as we've just got data from there
|
||||
let map_entry = self
|
||||
.pending_retransmissions
|
||||
@@ -145,7 +136,7 @@ where
|
||||
}
|
||||
|
||||
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
|
||||
.pending_replies
|
||||
@@ -161,6 +152,11 @@ where
|
||||
|
||||
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
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
@@ -177,27 +173,11 @@ where
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.max_surb_threshold();
|
||||
let min_surbs_threshold_buffer =
|
||||
self.config.reply_surbs.minimum_reply_surb_threshold_buffer;
|
||||
|
||||
// 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;
|
||||
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}");
|
||||
|
||||
// Check if we have enough surbs to handle the total queue and maintain minimum thresholds
|
||||
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
|
||||
(pending_surbs + available_surbs) < max_surbs_threshold
|
||||
&& (pending_surbs + available_surbs) < (total_queue + min_surbs_threshold)
|
||||
}
|
||||
|
||||
async fn handle_send_reply(
|
||||
@@ -258,10 +238,6 @@ where
|
||||
&recipient_tag,
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -269,13 +245,6 @@ where
|
||||
|
||||
// if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -290,7 +259,6 @@ where
|
||||
target: AnonymousSenderTag,
|
||||
amount: u32,
|
||||
) -> Result<(), PreparationError> {
|
||||
debug!("requesting {amount} additional reply surbs for {target}");
|
||||
let reply_surb = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
@@ -712,7 +680,7 @@ where
|
||||
// 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
|
||||
async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) {
|
||||
trace!("requesting surbs for queue clearing");
|
||||
trace!("requesting surbs for queues clearing");
|
||||
|
||||
let pending_queue_size = self
|
||||
.pending_replies
|
||||
@@ -726,18 +694,17 @@ where
|
||||
.map(|pending_queue| pending_queue.len())
|
||||
.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;
|
||||
|
||||
// To proactively request additional surbs, we aim to have a buffer of extra surbs in our
|
||||
// storage.
|
||||
let total_queue_with_buffer = total_queue + min_surbs_buffer;
|
||||
if total_queue == 0 {
|
||||
trace!("the pending queues for {:?} are already empty", target);
|
||||
return;
|
||||
}
|
||||
|
||||
let request_size = min(
|
||||
self.config.reply_surbs.maximum_reply_surb_request_size,
|
||||
max(
|
||||
total_queue_with_buffer,
|
||||
total_queue,
|
||||
self.config.reply_surbs.minimum_reply_surb_request_size,
|
||||
),
|
||||
);
|
||||
@@ -879,11 +846,9 @@ where
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
debug!("Started ReplyController with graceful shutdown support");
|
||||
|
||||
let mut shutdown = self.task_client.fork("select");
|
||||
|
||||
let polling_rate = Duration::from_secs(5);
|
||||
let mut stale_inspection = new_interval_stream(polling_rate);
|
||||
|
||||
@@ -895,7 +860,7 @@ where
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("ReplyController: Received shutdown");
|
||||
},
|
||||
req = self.request_receiver.next() => match req {
|
||||
|
||||
@@ -15,27 +15,6 @@ pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerR
|
||||
(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)]
|
||||
pub struct ReplyControllerSender(mpsc::UnboundedSender<ReplyControllerMessage>);
|
||||
|
||||
@@ -51,14 +30,14 @@ impl ReplyControllerSender {
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
extra_surb_request: bool,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::RetransmitReply {
|
||||
recipient,
|
||||
timed_out_ack,
|
||||
extra_surb_request,
|
||||
})
|
||||
.map_err(ReplyControllerSenderError::SendRetransmissionData)
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_reply(
|
||||
@@ -66,14 +45,14 @@ impl ReplyControllerSender {
|
||||
recipient: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::SendReply {
|
||||
recipient,
|
||||
message,
|
||||
lane,
|
||||
})
|
||||
.map_err(ReplyControllerSenderError::SendReply)
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_additional_surbs(
|
||||
@@ -81,47 +60,42 @@ impl ReplyControllerSender {
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
from_surb_request: bool,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbs {
|
||||
sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
})
|
||||
.map_err(ReplyControllerSenderError::AdditionalSurbs)
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_additional_surbs_request(
|
||||
&self,
|
||||
recipient: Recipient,
|
||||
amount: u32,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest {
|
||||
recipient: Box::new(recipient),
|
||||
amount,
|
||||
})
|
||||
.map_err(ReplyControllerSenderError::AdditionalSurbsRequest)
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub async fn get_lane_queue_length(
|
||||
&self,
|
||||
connection_id: ConnectionId,
|
||||
) -> Result<usize, ReplyControllerSenderError> {
|
||||
pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
if let Err(err) = self
|
||||
.0
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::LaneQueueLength {
|
||||
connection_id,
|
||||
response_channel: response_tx,
|
||||
})
|
||||
{
|
||||
return Err(ReplyControllerSenderError::LaneQueueLength(err));
|
||||
}
|
||||
.expect("ReplyControllerReceiver has died!");
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(ReplyControllerSenderError::ResponseChannelDropped)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,10 +110,7 @@ impl ReplyQueueLengths {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_lane_queue_length(
|
||||
&self,
|
||||
connection_id: ConnectionId,
|
||||
) -> Result<usize, ReplyControllerSenderError> {
|
||||
pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize {
|
||||
self.reply_controller_sender
|
||||
.get_lane_queue_length(connection_id)
|
||||
.await
|
||||
@@ -149,7 +120,7 @@ impl ReplyQueueLengths {
|
||||
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReplyControllerMessage {
|
||||
pub(crate) enum ReplyControllerMessage {
|
||||
RetransmitReply {
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
|
||||
@@ -22,7 +22,7 @@ use nym_sphinx::addressing::Recipient;
|
||||
use nym_statistics_common::clients::{
|
||||
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
||||
};
|
||||
use nym_task::{connections::TransmissionLane, TaskClient};
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
@@ -51,9 +51,6 @@ pub(crate) struct StatisticsControl {
|
||||
|
||||
/// Config for stats reporting (enabled, address, interval)
|
||||
reporting_config: StatsReporting,
|
||||
|
||||
/// Task client for listening for shutdown
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl StatisticsControl {
|
||||
@@ -62,24 +59,19 @@ impl StatisticsControl {
|
||||
client_type: String,
|
||||
client_stats_id: String,
|
||||
report_tx: InputMessageSender,
|
||||
task_client: TaskClient,
|
||||
) -> (Self, ClientStatsSender) {
|
||||
let (stats_tx, stats_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
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 {
|
||||
stats,
|
||||
stats_rx,
|
||||
report_tx,
|
||||
reporting_config,
|
||||
task_client,
|
||||
},
|
||||
ClientStatsSender::new(Some(stats_tx), task_client_stats_sender),
|
||||
ClientStatsSender::new(Some(stats_tx)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,7 +91,7 @@ impl StatisticsControl {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
async fn run_with_shutdown(&mut self, mut task_client: nym_task::TaskClient) {
|
||||
log::debug!("Started StatisticsControl with graceful shutdown support");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -129,13 +121,8 @@ impl StatisticsControl {
|
||||
let mut snapshot_interval =
|
||||
gloo_timers::future::IntervalStream::new(SNAPSHOT_INTERVAL.as_millis() as u32);
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
log::trace!("StatisticsControl: Received shutdown");
|
||||
break;
|
||||
},
|
||||
stats_event = self.stats_rx.recv() => match stats_event {
|
||||
Some(stats_event) => self.stats.handle_event(stats_event),
|
||||
None => {
|
||||
@@ -157,34 +144,34 @@ impl StatisticsControl {
|
||||
}
|
||||
|
||||
_ = local_report_interval.next() => {
|
||||
self.stats.local_report(&mut self.task_client);
|
||||
self.stats.local_report(&mut task_client);
|
||||
}
|
||||
_ = task_client.recv_with_delay() => {
|
||||
log::trace!("StatisticsControl: Received shutdown");
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
task_client.recv_timeout().await;
|
||||
log::debug!("StatisticsControl: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
pub(crate) fn start_with_shutdown(mut self, task_client: nym_task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
self.run().await;
|
||||
self.run_with_shutdown(task_client).await;
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn create_and_start(
|
||||
pub(crate) fn create_and_start_with_shutdown(
|
||||
reporting_config: StatsReporting,
|
||||
client_type: String,
|
||||
client_stats_id: String,
|
||||
report_tx: InputMessageSender,
|
||||
task_client: TaskClient,
|
||||
task_client: nym_task::TaskClient,
|
||||
) -> ClientStatsSender {
|
||||
let (controller, sender) = Self::create(
|
||||
reporting_config,
|
||||
client_type,
|
||||
client_stats_id,
|
||||
report_tx,
|
||||
task_client,
|
||||
);
|
||||
controller.start();
|
||||
let (controller, sender) =
|
||||
Self::create(reporting_config, client_type, client_stats_id, report_tx);
|
||||
controller.start_with_shutdown(task_client);
|
||||
sender
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit};
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_task::TaskClient;
|
||||
use nym_topology::NymTopologyError;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -44,8 +43,6 @@ pub struct TopologyRefresher {
|
||||
|
||||
refresh_rate: Duration,
|
||||
consecutive_failure_count: usize,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl TopologyRefresher {
|
||||
@@ -53,14 +50,12 @@ impl TopologyRefresher {
|
||||
cfg: TopologyRefresherConfig,
|
||||
topology_accessor: TopologyAccessor,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
TopologyRefresher {
|
||||
topology_provider,
|
||||
topology_accessor,
|
||||
refresh_rate: cfg.refresh_rate,
|
||||
consecutive_failure_count: 0,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +142,7 @@ impl TopologyRefresher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(mut self) {
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
debug!("Started TopologyRefresher with graceful shutdown support");
|
||||
|
||||
@@ -160,17 +155,17 @@ impl TopologyRefresher {
|
||||
let mut interval =
|
||||
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = interval.next() => {
|
||||
self.try_refresh().await;
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("TopologyRefresher: Received shutdown");
|
||||
},
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("TopologyRefresher: Exiting");
|
||||
})
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ impl<T> TransmissionBuffer<T> {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn lanes(&self) -> Vec<TransmissionLane> {
|
||||
self.buffer.keys().cloned().collect()
|
||||
pub(crate) fn num_lanes(&self) -> usize {
|
||||
self.buffer.keys().count()
|
||||
}
|
||||
|
||||
pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option<usize> {
|
||||
@@ -83,7 +83,6 @@ impl<T> TransmissionBuffer<T> {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(unused)]
|
||||
pub(crate) fn total_size_in_bytes(&self) -> usize
|
||||
where
|
||||
T: SizedData,
|
||||
|
||||
@@ -36,13 +36,6 @@ pub enum ClientCoreError {
|
||||
#[error("no gateway with id: {0}")]
|
||||
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")]
|
||||
NoGatewaysOnNetwork,
|
||||
|
||||
@@ -222,9 +215,6 @@ pub enum ClientCoreError {
|
||||
"fresh registration with gateway {gateway_id} somehow requires an additional key upgrade!"
|
||||
)]
|
||||
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
|
||||
|
||||
@@ -15,9 +15,6 @@ use std::{sync::Arc, time::Duration};
|
||||
use tungstenite::Message;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::init::websockets::connect_async;
|
||||
|
||||
use nym_topology::NodeId;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::net::TcpStream;
|
||||
@@ -26,6 +23,8 @@ use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::Instant;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::connect_async;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
@@ -133,7 +132,7 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
|
||||
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
|
||||
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
|
||||
Ok(Err(conn_failure)) => Err(conn_failure),
|
||||
Ok(Err(conn_failure)) => Err(conn_failure.into()),
|
||||
Ok(Ok((stream, _))) => Ok(stream),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@ use serde::Serialize;
|
||||
|
||||
pub mod helpers;
|
||||
pub mod types;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) mod websockets;
|
||||
|
||||
// helpers for error wrapping
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
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,3 +33,48 @@ where
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ mod error;
|
||||
mod manager;
|
||||
mod models;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct Backend {
|
||||
temporary_old_path: Option<PathBuf>,
|
||||
database_path: PathBuf,
|
||||
|
||||
@@ -19,7 +19,7 @@ pub mod fs_backend;
|
||||
#[error("no information provided")]
|
||||
pub struct UndefinedError;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct Empty {
|
||||
// we need to keep 'basic' metadata here to "load" the CombinedReplyStorage
|
||||
pub min_surb_threshold: usize,
|
||||
|
||||
@@ -27,7 +27,6 @@ nym-credential-storage = { path = "../../credential-storage" }
|
||||
nym-credentials-interface = { path = "../../credentials-interface" }
|
||||
nym-crypto = { path = "../../crypto" }
|
||||
nym-gateway-requests = { path = "../../gateway-requests" }
|
||||
nym-http-api-client = { path = "../../http-api-client" }
|
||||
nym-network-defaults = { path = "../../network-defaults" }
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
nym-statistics-common = { path = "../../statistics" }
|
||||
|
||||
@@ -20,8 +20,8 @@ use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||
use nym_gateway_requests::{
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
|
||||
SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey,
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, SensitiveServerResponse, ServerResponse,
|
||||
SharedGatewayKey, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
@@ -40,6 +40,8 @@ use url::Url;
|
||||
use std::os::fd::RawFd;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::connect_async;
|
||||
|
||||
#[cfg(not(unix))]
|
||||
use std::os::raw::c_int as RawFd;
|
||||
@@ -51,11 +53,6 @@ use zeroize::Zeroizing;
|
||||
|
||||
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 gateway_identity: identity::PublicKey,
|
||||
|
||||
@@ -204,15 +201,23 @@ impl<C, St> GatewayClient<C, St> {
|
||||
"Attemting to establish connection to gateway at: {}",
|
||||
self.gateway_address
|
||||
);
|
||||
let (ws_stream, _) = connect_async(
|
||||
&self.gateway_address,
|
||||
#[cfg(unix)]
|
||||
self.connection_fd_callback.clone(),
|
||||
)
|
||||
.await?;
|
||||
let ws_stream = match connect_async(&self.gateway_address).await {
|
||||
Ok((ws_stream, _)) => ws_stream,
|
||||
Err(error) => {
|
||||
return Err(GatewayClientError::NetworkConnectionFailed {
|
||||
address: self.gateway_address.clone(),
|
||||
source: error,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -266,19 +271,6 @@ 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> {
|
||||
// we use the fact that all request responses are Message::Text and only pushed
|
||||
// sphinx packets are Message::Binary
|
||||
@@ -563,10 +555,28 @@ impl<C, St> GatewayClient<C, St> {
|
||||
Ok(zeroizing_updated_key)
|
||||
}
|
||||
|
||||
async fn send_authenticate_request_and_handle_response(
|
||||
&mut self,
|
||||
msg: ClientControlRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
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? {
|
||||
ServerResponse::Authenticate {
|
||||
protocol_version,
|
||||
@@ -590,51 +600,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn authenticate_v1(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!("using v1 authentication");
|
||||
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
let self_address = self
|
||||
.local_identity
|
||||
.public_key()
|
||||
.derive_destination_address();
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate(
|
||||
self_address,
|
||||
shared_key,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
)?;
|
||||
self.send_authenticate_request_and_handle_response(msg)
|
||||
.await
|
||||
}
|
||||
|
||||
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, use_v2: bool) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
debug!("authenticating with gateway");
|
||||
|
||||
if use_v2 {
|
||||
self.authenticate_v2().await
|
||||
} else {
|
||||
self.authenticate_v1().await
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method to either call register or authenticate based on self.shared_key value
|
||||
#[instrument(skip_all,
|
||||
fields(
|
||||
@@ -650,25 +615,19 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// 1. check gateway's protocol version
|
||||
let gw_protocol = match self.get_gateway_protocol().await {
|
||||
Ok(protocol) => Some(protocol),
|
||||
let supports_aes_gcm_siv = match self.get_gateway_protocol().await {
|
||||
Ok(protocol) => protocol >= AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
Err(_) => {
|
||||
// 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
|
||||
self.establish_connection().await?;
|
||||
None
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
|
||||
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
|
||||
|
||||
if !supports_aes_gcm_siv {
|
||||
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
||||
}
|
||||
if !supports_auth_v2 {
|
||||
warn!("this gateway is on an old version that doesn't support authentication v2")
|
||||
}
|
||||
|
||||
if self.authenticated {
|
||||
debug!("Already authenticated");
|
||||
@@ -683,7 +642,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
if self.shared_key.is_some() {
|
||||
self.authenticate(supports_auth_v2).await?;
|
||||
self.authenticate().await?;
|
||||
|
||||
if self.authenticated {
|
||||
// if we are authenticated it means we MUST have an associated shared_key
|
||||
@@ -1016,8 +975,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
|
||||
self.authenticate(self.negotiated_protocol.supports_authenticate_v2())
|
||||
.await?;
|
||||
self.authenticate().await?;
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
@@ -1087,7 +1045,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
bandwidth_controller: None,
|
||||
stats_reporter: ClientStatsSender::new(None, task_client.clone()),
|
||||
stats_reporter: ClientStatsSender::new(None),
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
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,15 +43,8 @@ pub enum GatewayClientError {
|
||||
#[error("connection failed: {address}: {source}")]
|
||||
NetworkConnectionFailed { address: String, source: WsError },
|
||||
|
||||
#[error("no socket address for endpoint: {address}")]
|
||||
NoEndpointForConnection { address: String },
|
||||
|
||||
#[error("Invalid URL: {0}")]
|
||||
InvalidUrl(String),
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
InvalidURL(String),
|
||||
|
||||
#[error("No shared key was provided or obtained")]
|
||||
NoSharedKeyAvailable,
|
||||
@@ -125,6 +118,9 @@ pub enum GatewayClientError {
|
||||
"this operation couldn't be completed as the program is in the process of shutting down"
|
||||
)]
|
||||
ShutdownInProgress,
|
||||
|
||||
#[error("Timed out on sending message(s)")]
|
||||
TimeoutOnSink,
|
||||
}
|
||||
|
||||
impl GatewayClientError {
|
||||
|
||||
@@ -291,7 +291,10 @@ impl PartiallyDelegatedHandle {
|
||||
&mut self,
|
||||
msg: Message,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
Ok(self.sink_half.send(msg).await?)
|
||||
tokio::time::timeout(std::time::Duration::from_secs(1), self.sink_half.send(msg))
|
||||
.await
|
||||
.map_err(|_| GatewayClientError::TimeoutOnSink)??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn batch_send_without_response(
|
||||
@@ -300,7 +303,13 @@ impl PartiallyDelegatedHandle {
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let stream_messages: Vec<_> = messages.into_iter().map(Ok).collect();
|
||||
let mut send_stream = futures::stream::iter(stream_messages);
|
||||
Ok(self.sink_half.send_all(&mut send_stream).await?)
|
||||
tokio::time::timeout(
|
||||
std::time::Duration::from_secs(1),
|
||||
self.sink_half.send_all(&mut send_stream),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| GatewayClientError::TimeoutOnSink)??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn merge(self) -> Result<WsConn, GatewayClientError> {
|
||||
|
||||
@@ -11,7 +11,8 @@ use crate::{
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
|
||||
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, VerifyEcashTicketBody,
|
||||
IssuedTicketbooksChallengeResponse, IssuedTicketbooksForResponse, SpentCredentialsResponse,
|
||||
VerifyEcashTicketBody,
|
||||
};
|
||||
use nym_api_requests::ecash::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||
@@ -23,12 +24,11 @@ use nym_api_requests::models::{
|
||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SkimmedNode};
|
||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use std::net::IpAddr;
|
||||
use time::Date;
|
||||
use url::Url;
|
||||
|
||||
@@ -647,6 +647,13 @@ impl NymApiClient {
|
||||
.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(
|
||||
&self,
|
||||
expiration_date: Option<Date>,
|
||||
@@ -711,11 +718,4 @@ impl NymApiClient {
|
||||
.issued_ticketbooks_challenge(expiration_date, deposits)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn nodes_by_addresses(
|
||||
&self,
|
||||
addresses: Vec<IpAddr>,
|
||||
) -> Result<NodesByAddressesResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.nodes_by_addresses(addresses).await?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ use nym_api_requests::models::{
|
||||
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
pub use nym_api_requests::{
|
||||
ecash::{
|
||||
@@ -33,7 +31,6 @@ pub use nym_api_requests::{
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
},
|
||||
nym_nodes::{CachedNodesResponse, SkimmedNode},
|
||||
NymNetworkDetailsResponse,
|
||||
};
|
||||
pub use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_contracts_common::IdentityKey;
|
||||
@@ -42,7 +39,6 @@ pub use nym_http_api_client::Client;
|
||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
|
||||
use std::net::IpAddr;
|
||||
use time::format_description::BorrowedFormatItem;
|
||||
use time::Date;
|
||||
use tracing::instrument;
|
||||
@@ -853,6 +849,20 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.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))]
|
||||
async fn partial_expiration_date_signatures(
|
||||
&self,
|
||||
@@ -1017,32 +1027,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)
|
||||
.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))]
|
||||
|
||||
@@ -13,6 +13,8 @@ pub const DETAILED: &str = "detailed";
|
||||
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
|
||||
pub const ACTIVE: &str = "active";
|
||||
pub const REWARDED: &str = "rewarded";
|
||||
pub const DOUBLE_SPENDING_FILTER_V1: &str = "double-spending-filter-v1";
|
||||
|
||||
pub const ECASH_ROUTES: &str = "ecash";
|
||||
|
||||
pub use ecash::*;
|
||||
@@ -43,7 +45,6 @@ pub mod nym_nodes {
|
||||
pub const NYM_NODES_BONDED: &str = "bonded";
|
||||
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
|
||||
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
|
||||
pub const BY_ADDRESSES: &str = "by-addresses";
|
||||
}
|
||||
|
||||
pub const STATUS_ROUTES: &str = "status";
|
||||
@@ -66,8 +67,6 @@ pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
||||
pub const SUBMIT_GATEWAY: &str = "submit-gateway-monitoring-results";
|
||||
pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
|
||||
pub const PERFORMANCE: &str = "performance";
|
||||
|
||||
pub const SERVICE_PROVIDERS: &str = "services";
|
||||
|
||||
pub const DETAILS: &str = "details";
|
||||
pub const NETWORK: &str = "network";
|
||||
|
||||
@@ -28,6 +28,7 @@ use nym_network_defaults::{ChainDetails, NymNetworkDetails};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::time::SystemTime;
|
||||
use tendermint_rpc::endpoint::block::Response as BlockResponse;
|
||||
use tendermint_rpc::endpoint::*;
|
||||
use tendermint_rpc::{Error as TendermintRpcError, Order};
|
||||
use url::Url;
|
||||
@@ -62,7 +63,6 @@ pub use cw3;
|
||||
pub use cw4;
|
||||
pub use cw_controllers;
|
||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
pub use tendermint_rpc::endpoint::block::Response as BlockResponse;
|
||||
pub use tendermint_rpc::{
|
||||
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
|
||||
query::Query,
|
||||
|
||||
@@ -25,6 +25,15 @@ pub fn in6addr_any_init() -> IpAddr {
|
||||
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'?
|
||||
pub trait OptionalSet {
|
||||
/// If the value is available (i.e. `Some`), the provided closure is applied.
|
||||
|
||||
@@ -8,81 +8,81 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum VestingContractError {
|
||||
#[error("VESTING ({l}): {0}", l = line!())]
|
||||
#[error("VESTING ({}): {0}", line!())]
|
||||
Std(#[from] StdError),
|
||||
|
||||
#[error("VESTING: {0}")]
|
||||
OverflowError(#[from] OverflowError),
|
||||
|
||||
#[error("VESTING ({l}): Account does not exist - {0}", l = line!())]
|
||||
#[error("VESTING ({}): Account does not exist - {0}", line!())]
|
||||
NoAccountForAddress(String),
|
||||
|
||||
#[error("VESTING ({l}): Only admin can perform this action, {0} is not admin", l = line!())]
|
||||
#[error("VESTING ({}): Only admin can perform this action, {0} is not admin", line!())]
|
||||
NotAdmin(String),
|
||||
|
||||
#[error("VESTING ({l}): Balance not found for existing account ({0}), this is a bug", l = line!())]
|
||||
#[error("VESTING ({}): Balance not found for existing account ({0}), this is a bug", line!())]
|
||||
NoBalanceForAddress(String),
|
||||
|
||||
#[error("VESTING ({l}): Insufficient balance for address {0} -> {1}", l = line!())]
|
||||
#[error("VESTING ({}): Insufficient balance for address {0} -> {1}", line!())]
|
||||
InsufficientBalance(String, u128),
|
||||
|
||||
#[error("VESTING ({l}): Insufficient spendable balance for address {0} -> {1}", l = line!())]
|
||||
#[error("VESTING ({}): Insufficient spendable balance for address {0} -> {1}", line!())]
|
||||
InsufficientSpendable(String, u128),
|
||||
|
||||
#[error(
|
||||
"VESTING ({l}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
|
||||
, l = line!())]
|
||||
"VESTING ({}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
|
||||
, line!())]
|
||||
NotDelegate(String),
|
||||
|
||||
#[error("VESTING ({l}): Total vesting amount is inprobably low -> {0}, this is likely an error", l = line!())]
|
||||
#[error("VESTING ({}): Total vesting amount is inprobably low -> {0}, this is likely an error", line!())]
|
||||
ImprobableVestingAmount(u128),
|
||||
|
||||
#[error("VESTING ({l}): Address {0} has already bonded a node", l = line!())]
|
||||
#[error("VESTING ({}): Address {0} has already bonded a node", line!())]
|
||||
AlreadyBonded(String),
|
||||
|
||||
#[error("VESTING ({l}): Received empty funds vector", l = line!())]
|
||||
#[error("VESTING ({}): Received empty funds vector", line!())]
|
||||
EmptyFunds,
|
||||
|
||||
#[error("VESTING ({l}): Received wrong denom: {0}, expected {1}", l = line!())]
|
||||
#[error("VESTING ({}): Received wrong denom: {0}, expected {1}", line!())]
|
||||
WrongDenom(String, String),
|
||||
|
||||
#[error("VESTING ({l}): Received multiple denoms, expected 1", l = line!())]
|
||||
#[error("VESTING ({}): Received multiple denoms, expected 1", line!())]
|
||||
MultipleDenoms,
|
||||
|
||||
#[error("VESTING ({l}): No delegations found for account {0}, mix_identity {1}", l = line!())]
|
||||
#[error("VESTING ({}): No delegations found for account {0}, mix_identity {1}", line!())]
|
||||
NoSuchDelegation(Addr, NodeId),
|
||||
|
||||
#[error("VESTING ({l}): Only mixnet contract can perform this operation, got {0}", l = line!())]
|
||||
#[error("VESTING ({}): Only mixnet contract can perform this operation, got {0}", line!())]
|
||||
NotMixnetContract(Addr),
|
||||
|
||||
#[error("VESTING ({l}): Calculation underflowed", l = line!())]
|
||||
#[error("VESTING ({}): Calculation underflowed", line!())]
|
||||
Underflow,
|
||||
|
||||
#[error("VESTING ({l}): No bond found for account {0}", l = line!())]
|
||||
#[error("VESTING ({}): No bond found for account {0}", line!())]
|
||||
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}.")]
|
||||
InvalidBondPledgeReduction { current: Coin, decrease_by: Coin },
|
||||
|
||||
#[error("VESTING ({l}): Action can only be executed by account owner -> {0}", l = line!())]
|
||||
#[error("VESTING ({}): Action can only be executed by account owner -> {0}", line!())]
|
||||
NotOwner(String),
|
||||
|
||||
#[error("VESTING ({l}): Invalid address: {0}", l = line!())]
|
||||
#[error("VESTING ({}): Invalid address: {0}", line!())]
|
||||
InvalidAddress(String),
|
||||
|
||||
#[error("VESTING ({l}): Account already exists: {0}", l = line!())]
|
||||
#[error("VESTING ({}): Account already exists: {0}", line!())]
|
||||
AccountAlreadyExists(String),
|
||||
|
||||
#[error("VESTING ({l}): Staking account already exists: {0}", l = line!())]
|
||||
#[error("VESTING ({}): Staking account already exists: {0}", line!())]
|
||||
StakingAccountAlreadyExists(String),
|
||||
|
||||
#[error("VESTING ({l}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", l = line!())]
|
||||
#[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())]
|
||||
MinVestingFunds { sent: u128, need: u128 },
|
||||
|
||||
#[error("VESTING ({l}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", l = line!())]
|
||||
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
|
||||
LockedPledgeCapReached { current: Uint128, cap: Uint128 },
|
||||
|
||||
#[error("VESTING: ({l}: Account owned by {owner} has unpopulated vesting periods!", l = line!())]
|
||||
#[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())]
|
||||
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")]
|
||||
|
||||
@@ -19,7 +19,7 @@ use std::error::Error;
|
||||
// `SELECT total_tickets, used_tickets FROM ecash_ticketbook WHERE expiration_date >= ?`, today_date
|
||||
// then for each calculate the diff total_tickets - used_tickets and multiply the result by the size of the ticket
|
||||
#[async_trait]
|
||||
pub trait Storage: Clone + Send + Sync {
|
||||
pub trait Storage: Send + Sync {
|
||||
type StorageError: Error;
|
||||
|
||||
async fn close(&self);
|
||||
|
||||
@@ -26,6 +26,7 @@ nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
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-storage = { path = "../gateway-storage" }
|
||||
nym-task = { path = "../task" }
|
||||
|
||||
@@ -24,7 +24,6 @@ ed25519-dalek = { workspace = true, features = ["rand_core"], optional = true }
|
||||
rand = { workspace = true, optional = true }
|
||||
serde_bytes = { workspace = true, optional = true }
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
sha2 = { workspace = true, optional = true }
|
||||
subtle-encoding = { workspace = true, features = ["bech32-preview"] }
|
||||
thiserror = { workspace = true }
|
||||
zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
|
||||
@@ -37,13 +36,11 @@ nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
||||
rand_chacha = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["sphinx"]
|
||||
aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"]
|
||||
serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
|
||||
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"]
|
||||
stream_cipher = ["aes", "ctr", "cipher", "generic-array"]
|
||||
sphinx = ["nym-sphinx-types/sphinx"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
outfox = ["nym-sphinx-types/outfox"]
|
||||
|
||||
@@ -202,18 +202,6 @@ 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)]
|
||||
pub struct PrivateKey(x25519_dalek::StaticSecret);
|
||||
|
||||
@@ -320,15 +308,109 @@ impl PemStorableKey for PrivateKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<x25519_dalek::StaticSecret> for PrivateKey {
|
||||
fn from(secret: x25519_dalek::StaticSecret) -> Self {
|
||||
PrivateKey(secret)
|
||||
// compatibility with sphinx keys:
|
||||
#[cfg(feature = "sphinx")]
|
||||
impl From<PublicKey> for nym_sphinx_types::PublicKey {
|
||||
fn from(key: PublicKey) -> Self {
|
||||
nym_sphinx_types::PublicKey::from(key.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<x25519_dalek::StaticSecret> for PrivateKey {
|
||||
fn as_ref(&self) -> &x25519_dalek::StaticSecret {
|
||||
&self.0
|
||||
#[cfg(feature = "sphinx")]
|
||||
impl<'a> From<&'a PublicKey> for nym_sphinx_types::PublicKey {
|
||||
fn from(key: &'a PublicKey) -> Self {
|
||||
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
|
||||
|
||||
pub use ed25519_dalek::SignatureError;
|
||||
use ed25519_dalek::{SecretKey, Signer, SigningKey};
|
||||
use ed25519_dalek::{Signer, SigningKey};
|
||||
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
@@ -18,7 +18,7 @@ pub mod serde_helpers;
|
||||
use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
|
||||
|
||||
#[cfg(feature = "rand")]
|
||||
use rand::{CryptoRng, Rng, RngCore};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::de::Error as SerdeError;
|
||||
#[cfg(feature = "serde")]
|
||||
@@ -62,33 +62,16 @@ pub struct KeyPair {
|
||||
// nothing secret about public key
|
||||
#[zeroize(skip)]
|
||||
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 {
|
||||
#[cfg(feature = "rand")]
|
||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||
let index = rng.gen();
|
||||
let ed25519_signing_key = ed25519_dalek::SigningKey::generate(rng);
|
||||
|
||||
KeyPair {
|
||||
private_key: PrivateKey(ed25519_signing_key.to_bytes()),
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,31 +87,15 @@ impl KeyPair {
|
||||
Ok(KeyPair {
|
||||
private_key: PrivateKey::from_bytes(priv_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 {
|
||||
fn from(private_key: PrivateKey) -> Self {
|
||||
let public_key = (&private_key).into();
|
||||
KeyPair {
|
||||
public_key,
|
||||
public_key: (&private_key).into(),
|
||||
private_key,
|
||||
index: fake_index(public_key.to_bytes().as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,7 +115,6 @@ impl PemStorableKeyPair for KeyPair {
|
||||
KeyPair {
|
||||
private_key,
|
||||
public_key,
|
||||
index: fake_index(public_key.to_bytes().as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,6 @@ use hkdf::{
|
||||
},
|
||||
Hkdf,
|
||||
};
|
||||
use sha2::{Sha256, Sha512};
|
||||
|
||||
pub use hkdf::InvalidLength;
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
/// Perform HKDF `extract` then `expand` as a single step.
|
||||
pub fn extract_then_expand<D>(
|
||||
@@ -32,80 +28,3 @@ where
|
||||
|
||||
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,11 +16,8 @@ pub fn compute_keyed_hmac<D>(key: &[u8], data: &[u8]) -> HmacOutput<D>
|
||||
where
|
||||
D: Digest + BlockSizeUser,
|
||||
{
|
||||
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
|
||||
// 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();
|
||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
|
||||
.expect("HMAC was instantiated with a key of an invalid size!");
|
||||
hmac.update(data);
|
||||
hmac.finalize()
|
||||
}
|
||||
@@ -30,11 +27,8 @@ pub fn recompute_keyed_hmac_and_verify_tag<D>(key: &[u8], data: &[u8], tag: &[u8
|
||||
where
|
||||
D: Digest + BlockSizeUser,
|
||||
{
|
||||
// SAFETY: hmac is fine with keys of any size; if they're smaller than the block size of the underlying
|
||||
// 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();
|
||||
let mut hmac = SimpleHmac::<D>::new_from_slice(key)
|
||||
.expect("HMAC was instantiated with a key of an invalid size!");
|
||||
hmac.update(data);
|
||||
|
||||
let tag_arr = Output::<D>::from_slice(tag);
|
||||
|
||||
@@ -27,16 +27,12 @@ where
|
||||
// after performing diffie-hellman we don't care about the private component anymore
|
||||
let dh_result = ephemeral_keypair.private_key().diffie_hellman(remote_key);
|
||||
|
||||
// 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)]
|
||||
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
|
||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
// SAFETY: the generated okm has exactly `C::key_size()` elements,
|
||||
// so this call is safe
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let derived_shared_key = Key::<C>::from_exact_iter(okm).unwrap();
|
||||
let derived_shared_key =
|
||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
|
||||
|
||||
(ephemeral_keypair, derived_shared_key)
|
||||
}
|
||||
@@ -52,14 +48,9 @@ where
|
||||
{
|
||||
let dh_result = local_key.diffie_hellman(remote_key);
|
||||
|
||||
// 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)]
|
||||
// there is no reason for this to fail as our okm is expected to be only C::KeySize bytes
|
||||
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::key_size())
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
// 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()
|
||||
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
|
||||
}
|
||||
|
||||
@@ -60,15 +60,20 @@ where
|
||||
Iv::<C>::default()
|
||||
}
|
||||
|
||||
pub fn try_iv_from_slice<C>(b: &[u8]) -> Option<&IV<C>>
|
||||
pub fn iv_from_slice<C>(b: &[u8]) -> &IV<C>
|
||||
where
|
||||
C: IvSizeUser,
|
||||
{
|
||||
if b.len() != C::iv_size() {
|
||||
None
|
||||
} else {
|
||||
Some(IV::<C>::from_slice(b))
|
||||
// `from_slice` would have caused a panic about this issue anyway.
|
||||
// Now we at least have slightly more information
|
||||
panic!(
|
||||
"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.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[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" }
|
||||
@@ -0,0 +1,136 @@
|
||||
// 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,14 +20,11 @@ serde_json = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
time = { workspace = true }
|
||||
subtle = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] }
|
||||
nym-task = { path = "../task" }
|
||||
|
||||
nym-credentials = { path = "../credentials" }
|
||||
|
||||
@@ -15,12 +15,6 @@ use thiserror::Error;
|
||||
// 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>);
|
||||
|
||||
impl From<Vec<u8>> for EncryptedAddressBytes {
|
||||
fn from(encrypted_address: Vec<u8>) -> Self {
|
||||
EncryptedAddressBytes(encrypted_address)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EncryptedAddressConversionError {
|
||||
#[error("Failed to decode the encrypted address - {0}")]
|
||||
|
||||
@@ -19,7 +19,7 @@ pub use shared_key::{
|
||||
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
|
||||
};
|
||||
|
||||
pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
pub const CURRENT_PROTOCOL_VERSION: u8 = AES_GCM_SIV_PROTOCOL_VERSION;
|
||||
|
||||
/// Defines the current version of the communication protocol between gateway and clients.
|
||||
/// It has to be incremented for any breaking change.
|
||||
@@ -27,29 +27,10 @@ pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
// 1 - initial release
|
||||
// 2 - changes to client credentials structure
|
||||
// 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 CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
||||
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?
|
||||
// Should hmac itself be exposed, imported and used instead?
|
||||
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
|
||||
|
||||
pub trait GatewayProtocolVersionExt {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool;
|
||||
fn supports_authenticate_v2(&self) -> bool;
|
||||
}
|
||||
|
||||
impl GatewayProtocolVersionExt for Option<u8> {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_authenticate_v2(&self) -> bool {
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::SharedKeyUsageError;
|
||||
use nym_credentials_interface::CompactEcashError;
|
||||
use nym_crypto::asymmetric::ed25519::SignatureError;
|
||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddressError;
|
||||
use nym_sphinx::forwarding::packet::MixPacketFormattingError;
|
||||
use nym_sphinx::params::packet_sizes::PacketSize;
|
||||
@@ -60,12 +59,12 @@ pub enum GatewayRequestsError {
|
||||
source: NymNodeRoutingAddressError,
|
||||
},
|
||||
|
||||
#[error("received request had invalid size. (actual: {0}, but expected one of: {a} (ACK), {r} (REGULAR), {e8}, {e16}, {e32} (EXTENDED))",
|
||||
a = PacketSize::AckPacket.size(),
|
||||
r = PacketSize::RegularPacket.size(),
|
||||
e8 = PacketSize::ExtendedPacket8.size(),
|
||||
e16 = PacketSize::ExtendedPacket16.size(),
|
||||
e32 = PacketSize::ExtendedPacket32.size())
|
||||
#[error("received request had invalid size. (actual: {0}, but expected one of: {} (ACK), {} (REGULAR), {}, {}, {} (EXTENDED))",
|
||||
PacketSize::AckPacket.size(),
|
||||
PacketSize::RegularPacket.size(),
|
||||
PacketSize::ExtendedPacket8.size(),
|
||||
PacketSize::ExtendedPacket16.size(),
|
||||
PacketSize::ExtendedPacket32.size())
|
||||
]
|
||||
RequestOfInvalidSize(usize),
|
||||
|
||||
@@ -93,34 +92,7 @@ pub enum GatewayRequestsError {
|
||||
#[error("the provided [v1] credential has invalid number of parameters - {0}")]
|
||||
InvalidNumberOfEmbededParameters(u32),
|
||||
|
||||
#[error("failed to authenticate the client: {0}")]
|
||||
Authentication(#[from] AuthenticationFailure),
|
||||
|
||||
// variant to catch legacy errors
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AuthenticationFailure {
|
||||
#[error(transparent)]
|
||||
KeyUsageFailure(#[from] SharedKeyUsageError),
|
||||
|
||||
#[error("failed to verify provided address ciphertext")]
|
||||
MalformedCiphertext,
|
||||
|
||||
#[error("failed to verify request signature")]
|
||||
InvalidSignature(#[from] SignatureError),
|
||||
|
||||
#[error("provided request timestamp is in the future")]
|
||||
RequestTimestampInFuture,
|
||||
|
||||
#[error("the client is not registered")]
|
||||
NotRegistered,
|
||||
|
||||
#[error("the provided request is too stale to process")]
|
||||
StaleRequest,
|
||||
|
||||
#[error("the provided request timestamp is smaller or equal to a one previously used")]
|
||||
RequestReuse,
|
||||
}
|
||||
|
||||
+2
-23
@@ -2,23 +2,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::CredentialSpendingRequest;
|
||||
use crate::text_request::authenticate::AuthenticateRequest;
|
||||
use crate::{
|
||||
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
INITIAL_PROTOCOL_VERSION,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use tungstenite::Message;
|
||||
|
||||
pub mod authenticate;
|
||||
|
||||
// wrapper for all encrypted requests for ease of use
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ClientRequest {
|
||||
UpgradeKey {
|
||||
@@ -73,9 +68,6 @@ pub enum ClientControlRequest {
|
||||
enc_address: String,
|
||||
iv: String,
|
||||
},
|
||||
|
||||
AuthenticateV2(Box<AuthenticateRequest>),
|
||||
|
||||
#[serde(alias = "handshakePayload")]
|
||||
RegisterHandshakeInitRequest {
|
||||
#[serde(default)]
|
||||
@@ -131,22 +123,9 @@ impl ClientControlRequest {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_authenticate_v2(
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're using v2 authentication, we must announce at least that protocol version
|
||||
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
|
||||
Ok(ClientControlRequest::AuthenticateV2(Box::new(
|
||||
AuthenticateRequest::new(protocol_version, shared_key, identity_keys)?,
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
ClientControlRequest::Authenticate { .. } => "Authenticate".to_string(),
|
||||
ClientControlRequest::AuthenticateV2(..) => "AuthenticateV2".to_string(),
|
||||
ClientControlRequest::RegisterHandshakeInitRequest { .. } => {
|
||||
"RegisterHandshakeInitRequest".to_string()
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::iter;
|
||||
use std::time::Duration;
|
||||
use subtle::ConstantTimeEq;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthenticateRequest {
|
||||
#[serde(flatten)]
|
||||
pub content: AuthenticateRequestContent,
|
||||
|
||||
pub request_signature: ed25519::Signature,
|
||||
}
|
||||
|
||||
impl AuthenticateRequest {
|
||||
pub fn new(
|
||||
protocol_version: u8,
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
) -> Result<AuthenticateRequest, GatewayRequestsError> {
|
||||
let content = AuthenticateRequestContent::new(
|
||||
protocol_version,
|
||||
shared_key,
|
||||
*identity_keys.public_key(),
|
||||
)?;
|
||||
let plaintext = content.plaintext();
|
||||
let request_signature = identity_keys.private_key().sign(&plaintext);
|
||||
|
||||
Ok(AuthenticateRequest {
|
||||
content,
|
||||
request_signature,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify_timestamp(&self, max_request_age: Duration) -> Result<(), AuthenticationFailure> {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
if self.content.request_timestamp() + max_request_age < now {
|
||||
return Err(AuthenticationFailure::StaleRequest);
|
||||
}
|
||||
if self.content.request_timestamp() > now {
|
||||
return Err(AuthenticationFailure::RequestTimestampInFuture);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ensure_timestamp_not_reused(
|
||||
&self,
|
||||
previous: OffsetDateTime,
|
||||
) -> Result<(), AuthenticationFailure> {
|
||||
if self.content.request_timestamp() <= previous {
|
||||
return Err(AuthenticationFailure::RequestReuse);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_ciphertext(
|
||||
&self,
|
||||
shared_key: &SharedGatewayKey,
|
||||
) -> Result<(), AuthenticationFailure> {
|
||||
let expected = shared_key.encrypt(
|
||||
self.content
|
||||
.client_identity
|
||||
.derive_destination_address()
|
||||
.as_bytes_ref(),
|
||||
Some(&self.content.nonce),
|
||||
)?;
|
||||
|
||||
if !bool::from(expected.ct_eq(&self.content.address_ciphertext)) {
|
||||
return Err(AuthenticationFailure::MalformedCiphertext);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_signature(&self) -> Result<(), AuthenticationFailure> {
|
||||
let plaintext = self.content.plaintext();
|
||||
self.content
|
||||
.client_identity
|
||||
.verify(plaintext, &self.request_signature)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthenticateRequestContent {
|
||||
pub protocol_version: u8,
|
||||
|
||||
// this is identical to the client's address
|
||||
pub client_identity: ed25519::PublicKey,
|
||||
|
||||
#[serde(with = "nym_serde_helpers::base64")]
|
||||
pub address_ciphertext: Vec<u8>,
|
||||
|
||||
#[serde(with = "nym_serde_helpers::base64")]
|
||||
pub nonce: Vec<u8>,
|
||||
|
||||
pub request_unix_timestamp: u64,
|
||||
}
|
||||
|
||||
impl AuthenticateRequestContent {
|
||||
fn new(
|
||||
protocol_version: u8,
|
||||
shared_key: &SharedGatewayKey,
|
||||
client_identity: ed25519::PublicKey,
|
||||
) -> Result<AuthenticateRequestContent, GatewayRequestsError> {
|
||||
let nonce = shared_key.random_nonce_or_iv();
|
||||
let destination_address = client_identity.derive_destination_address();
|
||||
|
||||
let address_ciphertext =
|
||||
shared_key.encrypt(destination_address.as_bytes_ref(), Some(&nonce))?;
|
||||
let now = OffsetDateTime::now_utc();
|
||||
Ok(AuthenticateRequestContent {
|
||||
protocol_version,
|
||||
client_identity,
|
||||
address_ciphertext,
|
||||
nonce,
|
||||
request_unix_timestamp: now.unix_timestamp() as u64, // SAFETY: we're running this in post 1970...
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthenticateRequestContent {
|
||||
pub fn plaintext(&self) -> Vec<u8> {
|
||||
iter::once(self.protocol_version)
|
||||
.chain(self.client_identity.to_bytes())
|
||||
.chain(self.address_ciphertext.iter().copied())
|
||||
.chain(self.nonce.iter().copied())
|
||||
.chain(self.request_unix_timestamp.to_be_bytes())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn request_timestamp(&self) -> OffsetDateTime {
|
||||
OffsetDateTime::from_unix_timestamp(self.request_unix_timestamp as i64)
|
||||
.unwrap_or(OffsetDateTime::UNIX_EPOCH)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
ALTER TABLE shared_keys
|
||||
ADD COLUMN last_used_authentication TIMESTAMP WITHOUT TIME ZONE;
|
||||
@@ -200,20 +200,6 @@ impl GatewayStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_last_used_authentication_timestamp(
|
||||
&self,
|
||||
client_id: i64,
|
||||
last_used_authentication_timestamp: OffsetDateTime,
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
self.shared_key_manager
|
||||
.update_last_used_authentication_timestamp(
|
||||
client_id,
|
||||
last_used_authentication_timestamp,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_client(&self, client_id: i64) -> Result<Option<Client>, GatewayStorageError> {
|
||||
let client = self.client_manager.get_client(client_id).await?;
|
||||
Ok(client)
|
||||
|
||||
@@ -14,13 +14,13 @@ pub struct Client {
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub struct PersistedSharedKeys {
|
||||
#[allow(dead_code)]
|
||||
pub client_id: i64,
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub client_address_bs58: String,
|
||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||
pub last_used_authentication: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::models::PersistedSharedKeys;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SharedKeysManager {
|
||||
@@ -69,22 +68,6 @@ impl SharedKeysManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_last_used_authentication_timestamp(
|
||||
&self,
|
||||
client_id: i64,
|
||||
last_used: OffsetDateTime,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE shared_keys SET last_used_authentication = ? WHERE client_id = ?;",
|
||||
last_used,
|
||||
client_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to retrieve shared keys stored for the particular client.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -94,10 +77,13 @@ impl SharedKeysManager {
|
||||
&self,
|
||||
client_address_bs58: &str,
|
||||
) -> Result<Option<PersistedSharedKeys>, sqlx::Error> {
|
||||
sqlx::query_as("SELECT * FROM shared_keys WHERE client_address_bs58 = ?")
|
||||
.bind(client_address_bs58)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
sqlx::query_as!(
|
||||
PersistedSharedKeys,
|
||||
"SELECT * FROM shared_keys WHERE client_address_bs58 = ?",
|
||||
client_address_bs58
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Removes from the database shared keys derived with the particular client.
|
||||
|
||||
@@ -12,10 +12,9 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "gzip"] }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
http.workspace = true
|
||||
url = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
@@ -23,13 +22,7 @@ tracing = { workspace = true }
|
||||
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "webpki-roots"] }
|
||||
|
||||
# for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||
workspace = true
|
||||
features = ["tokio"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features=["rt", "macros"] }
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
//! DNS resolver configuration for internal lookups.
|
||||
//!
|
||||
//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints
|
||||
//! supporting DoH and DoT.
|
||||
//!
|
||||
//! This resolver implements a fallback mechanism where, should the DNS-over-TLS resolution fail, a
|
||||
//! followup resolution will be done using the hosts configured default (e.g. `/etc/resolve.conf` on
|
||||
//! linux).
|
||||
//!
|
||||
//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the
|
||||
//! `hickory-resolver` crate
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use crate::ClientBuilder;
|
||||
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
|
||||
use hickory_resolver::{
|
||||
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||
error::ResolveError,
|
||||
lookup_ip::LookupIpIntoIter,
|
||||
TokioAsyncResolver,
|
||||
};
|
||||
use hickory_resolver::{error::ResolveErrorKind, lookup_ip::LookupIp};
|
||||
use once_cell::sync::OnceCell;
|
||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||
use tracing::warn;
|
||||
|
||||
impl ClientBuilder {
|
||||
/// Override the DNS resolver implementation used by the underlying http client.
|
||||
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
|
||||
self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct SocketAddrs {
|
||||
iter: LookupIpIntoIter,
|
||||
}
|
||||
|
||||
// n.b. static items do not call [`Drop`] on program termination, so this won't be deallocated.
|
||||
// this is fine, as the OS can deallocate the terminated program faster than we can free memory
|
||||
// but tools like valgrind might report "memory leaks" as it isn't obvious this is intentional.
|
||||
static SHARED_RESOLVER: LazyLock<HickoryDnsResolver> = LazyLock::new(|| {
|
||||
tracing::debug!("Initializing shared DNS resolver");
|
||||
HickoryDnsResolver::default()
|
||||
});
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("hickory-dns resolver error: {hickory_error}")]
|
||||
/// Error occurring while resolving a hostname into an IP address.
|
||||
pub struct HickoryDnsError {
|
||||
#[from]
|
||||
hickory_error: ResolveError,
|
||||
}
|
||||
|
||||
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
|
||||
///
|
||||
/// Typical use involves instantiating using the `Default` implementation and then resolving using
|
||||
/// methods or trait implementations.
|
||||
///
|
||||
/// The default initialization uses a shared underlying `AsyncResolver`. If a thread local resolver
|
||||
/// is required use `thread_resolver()` to build a resolver with an independently instantiated
|
||||
/// internal `AsyncResolver`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct HickoryDnsResolver {
|
||||
// Since we might not have been called in the context of a
|
||||
// Tokio Runtime in initialization, so we must delay the actual
|
||||
// construction of the resolver.
|
||||
state: Arc<OnceCell<TokioAsyncResolver>>,
|
||||
fallback: Arc<OnceCell<TokioAsyncResolver>>,
|
||||
dont_use_shared: bool,
|
||||
}
|
||||
|
||||
impl Resolve for HickoryDnsResolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
let resolver = self.state.clone();
|
||||
let fallback = self.fallback.clone();
|
||||
let independent = self.dont_use_shared;
|
||||
Box::pin(async move {
|
||||
let resolver = resolver.get_or_try_init(|| {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if independent {
|
||||
new_resolver()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
|
||||
}
|
||||
})?;
|
||||
|
||||
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||
let lookup = match resolver.lookup_ip(name.as_str()).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
match e.kind() {
|
||||
ResolveErrorKind::NoRecordsFound { .. } => {}
|
||||
_ => {
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
}
|
||||
}
|
||||
let resolver = fallback.get_or_try_init(|| {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if independent {
|
||||
new_resolver_system()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER
|
||||
.fallback
|
||||
.get_or_try_init(new_resolver_system)?
|
||||
.clone())
|
||||
}
|
||||
})?;
|
||||
resolver.lookup_ip(name.as_str()).await?
|
||||
}
|
||||
};
|
||||
|
||||
let addrs: Addrs = Box::new(SocketAddrs {
|
||||
iter: lookup.into_iter(),
|
||||
});
|
||||
Ok(addrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SocketAddrs {
|
||||
type Item = SocketAddr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
|
||||
}
|
||||
}
|
||||
|
||||
impl HickoryDnsResolver {
|
||||
/// Attempt to resolve a domain name to a set of ['IpAddr']s
|
||||
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
|
||||
let resolver = self.state.get_or_try_init(|| self.new_resolver())?;
|
||||
|
||||
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||
let lookup = match resolver.lookup_ip(name).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
match e.kind() {
|
||||
ResolveErrorKind::NoRecordsFound { .. } => {}
|
||||
_ => {
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
}
|
||||
}
|
||||
let resolver = self
|
||||
.fallback
|
||||
.get_or_try_init(|| self.new_resolver_system())?;
|
||||
resolver.lookup_ip(name).await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(lookup)
|
||||
}
|
||||
|
||||
/// Create a (lazy-initialized) resolver that is not shared across threads.
|
||||
pub fn thread_resolver() -> Self {
|
||||
Self {
|
||||
dont_use_shared: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn new_resolver(&self) -> Result<TokioAsyncResolver, HickoryDnsError> {
|
||||
if self.dont_use_shared {
|
||||
new_resolver()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn new_resolver_system(&self) -> Result<TokioAsyncResolver, HickoryDnsError> {
|
||||
if self.dont_use_shared {
|
||||
new_resolver_system()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER
|
||||
.fallback
|
||||
.get_or_try_init(new_resolver_system)?
|
||||
.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
|
||||
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
|
||||
fn new_resolver() -> Result<TokioAsyncResolver, HickoryDnsError> {
|
||||
let mut name_servers = NameServerConfigGroup::google_tls();
|
||||
name_servers.merge(NameServerConfigGroup::google_https());
|
||||
// name_servers.merge(NameServerConfigGroup::google_h3());
|
||||
name_servers.merge(NameServerConfigGroup::quad9_tls());
|
||||
name_servers.merge(NameServerConfigGroup::quad9_https());
|
||||
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
|
||||
name_servers.merge(NameServerConfigGroup::cloudflare_https());
|
||||
|
||||
let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
|
||||
|
||||
let mut opts = ResolverOpts::default();
|
||||
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
||||
// Would like to enable this when 0.25 stabilizes
|
||||
// opts.server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
|
||||
|
||||
Ok(TokioAsyncResolver::tokio(config, opts))
|
||||
}
|
||||
|
||||
/// Create a new resolver with the default configuration, which reads from the system DNS config
|
||||
/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6
|
||||
/// addresses to work with "happy eyeballs" algorithm.
|
||||
fn new_resolver_system() -> Result<TokioAsyncResolver, HickoryDnsError> {
|
||||
let (config, mut opts) = hickory_resolver::system_conf::read_system_conf()?;
|
||||
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
||||
Ok(TokioAsyncResolver::tokio(config, opts))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn reqwest_hickory_doh() {
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
let client = reqwest::ClientBuilder::new()
|
||||
.dns_resolver(resolver.into())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let resp = client
|
||||
.get("http://ifconfig.me:80")
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.bytes()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(!resp.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dns_lookup() -> Result<(), HickoryDnsError> {
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
|
||||
let domain = "ifconfig.me";
|
||||
let addrs = resolver.resolve_str(domain).await?;
|
||||
|
||||
assert!(addrs.into_iter().next().is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+346
-448
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user