Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f1baa1db7 | |||
| 6d4eaea1cc | |||
| 865b668e12 | |||
| c4409e3c1a | |||
| 3934c556ee | |||
| f0da36df7c | |||
| 95989dbb67 | |||
| 33f2e2ca7d | |||
| fce494af97 | |||
| 671ce9a399 | |||
| 1303d404f7 | |||
| 7618ebf694 | |||
| 13e64da2ad | |||
| 901b88f98b | |||
| b60f07730b |
@@ -31,5 +31,3 @@ updates:
|
||||
update-types:
|
||||
- "patch"
|
||||
open-pull-requests-limit: 10
|
||||
assignees:
|
||||
- "octol"
|
||||
|
||||
@@ -9,16 +9,15 @@ on:
|
||||
- 'gateway/**'
|
||||
- 'integrations/**'
|
||||
- 'mixnode/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-data-observatory/**'
|
||||
- 'nym-network-monitor/**'
|
||||
- 'nym-node/**'
|
||||
- 'nym-node-status-api/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'nym-validator-rewarder/**'
|
||||
- 'sdk/lib/**'
|
||||
- 'sdk/rust/**'
|
||||
- 'sdk/lib/**'
|
||||
- 'service-providers/**'
|
||||
- 'nym-network-monitor/**'
|
||||
- 'nym-api/**'
|
||||
- 'nym-node/**'
|
||||
- 'nym-outfox/**'
|
||||
- 'nym-data-observatory/**'
|
||||
- 'nym-validator-rewarder/**'
|
||||
- 'tools/**'
|
||||
- 'wasm/**'
|
||||
- 'Cargo.toml'
|
||||
@@ -31,7 +30,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ arc-ubuntu-20.04, custom-windows-11, custom-runner-mac-m1 ]
|
||||
os: [ arc-ubuntu-20.04, custom-runner-mac-m1 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
@@ -15,7 +15,6 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
|
||||
+1
-8
@@ -51,11 +51,4 @@ ppa-private-key.b64
|
||||
ppa-private-key.asc
|
||||
nym-network-monitor/topology.json
|
||||
nym-network-monitor/__pycache__
|
||||
nym-network-monitor/*.key
|
||||
nym-network-monitor/.envrc
|
||||
nym-network-monitor/.envrc
|
||||
nym-api/redocly/formatted-openapi.json
|
||||
|
||||
|
||||
*.sqlite
|
||||
.build
|
||||
nym-network-monitor/*.key
|
||||
@@ -2,18 +2,22 @@
|
||||
# See https://redocly.com/docs/cli/ for more information.
|
||||
formatted-openapi.json:
|
||||
path-parameters-defined:
|
||||
- >-
|
||||
#/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/0/name
|
||||
- >-
|
||||
#/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/1/name
|
||||
- >-
|
||||
#/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/2/name
|
||||
- >-
|
||||
#/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/3/name
|
||||
- >-
|
||||
#/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/4/name
|
||||
- >-
|
||||
#/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/5/name
|
||||
# - >-
|
||||
# #/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/0/name
|
||||
# - >-
|
||||
# #/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/1/name
|
||||
# - >-
|
||||
# #/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/2/name
|
||||
# - >-
|
||||
# #/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/3/name
|
||||
# - >-
|
||||
# #/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/4/name
|
||||
# - >-
|
||||
# #/paths/~1v1~1status~1mixnode~1{mix_id}~1compute-reward-estimation/post/parameters/5/name
|
||||
# - '#/paths/~1v1~1unstable~1nym-nodes~1skimmed~1active/get/parameters/0/name'
|
||||
# - '#/paths/~1v1~1unstable~1nym-nodes~1skimmed~1active/get/parameters/1/name'
|
||||
# - '#/paths/~1v1~1unstable~1nym-nodes~1skimmed~1active/get/parameters/2/name'
|
||||
# - '#/paths/~1v1~1unstable~1nym-nodes~1skimmed~1active/get/parameters/3/name'
|
||||
operation-operationId-unique:
|
||||
- >-
|
||||
#/paths/~1v1~1status~1mixnodes~1active~1detailed/get/get_active_set_detailed
|
||||
@@ -26,7 +30,6 @@ formatted-openapi.json:
|
||||
- '#/components/schemas/EcashTicketVerificationRejection'
|
||||
- '#/components/schemas/ExpirationDatePathParam'
|
||||
- '#/components/schemas/FullFatNode'
|
||||
- '#/components/schemas/G2ProjectiveSchema'
|
||||
- '#/components/schemas/HistoricalPerformanceResponse'
|
||||
- '#/components/schemas/HistoricalUptimeResponse'
|
||||
- '#/components/schemas/MasterVerificationKeyResponse'
|
||||
@@ -38,7 +41,6 @@ formatted-openapi.json:
|
||||
- '#/components/schemas/NymNodeDetails'
|
||||
- '#/components/schemas/PaginationRequest'
|
||||
- '#/components/schemas/PartialCoinIndicesSignatureResponse'
|
||||
- '#/components/schemas/PayInfo'
|
||||
- '#/components/schemas/SpentCredentialsResponse'
|
||||
- '#/components/schemas/UptimeHistoryResponse'
|
||||
- '#/components/schemas/VerifyEcashCredentialBody'
|
||||
@@ -0,0 +1,83 @@
|
||||
extends:
|
||||
- minimal
|
||||
apis:
|
||||
nym-api:
|
||||
root: ./formatted-openapi.json
|
||||
rules:
|
||||
# https://redocly.com/docs/cli/rules/oas/operation-summary
|
||||
operation-summary: off
|
||||
# https://redocly.com/docs/cli/rules/oas/security-defined
|
||||
security-defined: off
|
||||
struct: off
|
||||
# https://redocly.com/docs/cli/rules/oas/operation-2xx-response
|
||||
operation-2xx-response: off
|
||||
# rules:
|
||||
# skip-warnings: true
|
||||
# ignore:
|
||||
# - path: /v1/gateways
|
||||
# method: get
|
||||
# - path: /v1/gateways/blacklisted
|
||||
# method: get
|
||||
# - path: /v1/mixnodes
|
||||
# method: get
|
||||
# - path: /v1/mixnodes/active
|
||||
# method: get
|
||||
# - path: /v1/mixnodes/active/detailed
|
||||
# method: get
|
||||
# - path: /v1/mixnodes/blacklisted
|
||||
# method: get
|
||||
# - path: /v1/mixnodes/detailed
|
||||
# method: get
|
||||
# - path: /v1/mixnodes/rewarded
|
||||
# method: get
|
||||
# - path: /v1/mixnodes/rewarded/detailed
|
||||
# method: get
|
||||
# - path: /v1/gateways/described
|
||||
# method: get
|
||||
# # network-monitor-status (deprecated)
|
||||
# - path: /v1/status/gateway/{identity}/avg_uptime
|
||||
# method: GET
|
||||
# - path: /v1/status/gateway/{identity}/core-status-count
|
||||
# method: GET
|
||||
# - path: /v1/status/gateway/{identity}/history
|
||||
# method: GET
|
||||
# - path: /v1/status/gateway/{identity}/report
|
||||
# method: GET
|
||||
# - path: /v1/status/gateways/detailed
|
||||
# method: GET
|
||||
# - path: /v1/status/gateways/detailed-unfiltered
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnode/{mix_id}/avg_uptime
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnode/{mix_id}/compute-reward-estimation
|
||||
# method: POST
|
||||
# - path: /v1/status/mixnode/{mix_id}/core-status-count
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnode/{mix_id}/history
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnode/{mix_id}/report
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnode/{mix_id}/reward-estimation
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnodes/detailed-unfiltered
|
||||
# method: GET
|
||||
# # status
|
||||
# - path: /v1/status/mixnode/{mix_id}/inclusion-probability
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnode/{mix_id}/stake-saturation
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnode/{mix_id}/status
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnodes/active/detailed
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnodes/detailed
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnodes/inclusion-probability
|
||||
# method: GET
|
||||
# - path: /v1/status/mixnodes/rewarded/detailed
|
||||
# method: GET
|
||||
# # unstable nym nodes
|
||||
# - path: /v1/unstable/nym-nodes/gateways/skimmed
|
||||
# method: get
|
||||
# - path: /v1/unstable/nym-nodes/mixnodes/skimmed
|
||||
# method: get
|
||||
@@ -4,11 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.14-crunch-patched] (2024-12-17)
|
||||
|
||||
- Fixes an issue to allow previously registred clients to connect to latest nym-nodes
|
||||
- Fixes compatibility issues between nym-nodes and older clients
|
||||
|
||||
## [2024.14-crunch] (2024-12-11)
|
||||
|
||||
- Merge/release/2024.14-crunch ([#5242])
|
||||
|
||||
Generated
+301
-747
File diff suppressed because it is too large
Load Diff
+29
-31
@@ -118,8 +118,8 @@ members = [
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-credential-proxy/nym-credential-proxy-requests",
|
||||
"nym-credential-proxy/vpn-api-lib-wasm",
|
||||
"nym-data-observatory",
|
||||
"nym-network-monitor",
|
||||
"nyx-chain-watcher",
|
||||
"nym-node",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-node/nym-node-metrics",
|
||||
@@ -147,9 +147,7 @@ members = [
|
||||
"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",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract", "common/verloc", "tools/internal/mixnet-connectivity-check",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -158,11 +156,11 @@ default-members = [
|
||||
"explorer-api",
|
||||
"nym-api",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-data-observatory",
|
||||
"nym-node",
|
||||
"nym-node-status-api/nym-node-status-agent",
|
||||
"nym-node-status-api/nym-node-status-api",
|
||||
"nym-validator-rewarder",
|
||||
"nyx-chain-watcher",
|
||||
"service-providers/authenticator",
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
@@ -193,9 +191,9 @@ aes = "0.8.1"
|
||||
aes-gcm = "0.10.1"
|
||||
aes-gcm-siv = "0.11.1"
|
||||
aead = "0.5.2"
|
||||
anyhow = "1.0.95"
|
||||
anyhow = "1.0.90"
|
||||
argon2 = "0.5.0"
|
||||
async-trait = "0.1.84"
|
||||
async-trait = "0.1.83"
|
||||
axum-client-ip = "0.6.1"
|
||||
axum = "0.7.5"
|
||||
axum-extra = "0.9.4"
|
||||
@@ -204,7 +202,7 @@ 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.5.5"
|
||||
blake3 = "1.5.4"
|
||||
bloomfilter = "1.0.14"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
@@ -214,20 +212,20 @@ celes = "2.4.0"
|
||||
cfg-if = "1.0.0"
|
||||
chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.39"
|
||||
chrono = "0.4.31"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.23"
|
||||
clap = "4.5.20"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.0"
|
||||
comfy-table = "7.1.3"
|
||||
console = "0.15.10"
|
||||
comfy-table = "7.1.1"
|
||||
console = "0.15.8"
|
||||
console-subscriber = "0.1.1"
|
||||
console_error_panic_hook = "0.1"
|
||||
const-str = "0.5.6"
|
||||
const_format = "0.2.34"
|
||||
const_format = "0.2.33"
|
||||
criterion = "0.4"
|
||||
csv = "1.3.1"
|
||||
csv = "1.3.0"
|
||||
ctr = "0.9.1"
|
||||
cupid = "0.6.1"
|
||||
curve25519-dalek = "4.1"
|
||||
@@ -244,7 +242,7 @@ etherparse = "0.13.0"
|
||||
envy = "0.4"
|
||||
eyre = "0.6.9"
|
||||
fastrand = "2.1.1"
|
||||
flate2 = "1.0.35"
|
||||
flate2 = "1.0.34"
|
||||
futures = "0.3.28"
|
||||
futures-util = "0.3"
|
||||
generic-array = "0.14.7"
|
||||
@@ -264,7 +262,7 @@ humantime-serde = "1.1.1"
|
||||
human-repr = "1.1.0"
|
||||
hyper = "1.4.1"
|
||||
hyper-util = "0.1"
|
||||
indicatif = "0.17.9"
|
||||
indicatif = "0.17.8"
|
||||
inquire = "0.6.2"
|
||||
ip_network = "0.4.1"
|
||||
ipnetwork = "0.20"
|
||||
@@ -289,7 +287,7 @@ parking_lot = "0.12.3"
|
||||
pem = "0.8"
|
||||
petgraph = "0.6.5"
|
||||
pin-project = "1.1"
|
||||
pin-project-lite = "0.2.15"
|
||||
pin-project-lite = "0.2.14"
|
||||
pretty_env_logger = "0.4.0"
|
||||
publicsuffix = "2.2.3"
|
||||
quote = "1"
|
||||
@@ -307,11 +305,11 @@ rocket_cors = "0.6.0"
|
||||
rocket_okapi = "0.8.0"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.21"
|
||||
semver = "1.0.24"
|
||||
serde = "1.0.217"
|
||||
semver = "1.0.23"
|
||||
serde = "1.0.211"
|
||||
serde_bytes = "0.11.15"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.134"
|
||||
serde_json = "1.0.132"
|
||||
serde_json_path = "0.7.1"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
@@ -326,22 +324,22 @@ subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
sysinfo = "0.30.13"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.43"
|
||||
tar = "0.4.42"
|
||||
tempfile = "3.14"
|
||||
thiserror = "1.0.64"
|
||||
time = "0.3.37"
|
||||
time = "0.3.30"
|
||||
tokio = "1.39"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-stream = "0.1.16"
|
||||
tokio-test = "0.4.4"
|
||||
tokio-tun = "0.11.5"
|
||||
tokio-tungstenite = { version = "0.20.1" }
|
||||
tokio-util = "0.7.13"
|
||||
toml = "0.8.19"
|
||||
tokio-util = "0.7.12"
|
||||
toml = "0.8.14"
|
||||
tower = "0.4.13"
|
||||
tower-http = "0.5.2"
|
||||
tracing = "0.1.41"
|
||||
tracing = "0.1.37"
|
||||
tracing-opentelemetry = "0.19.0"
|
||||
tracing-subscriber = "0.3.19"
|
||||
tracing-subscriber = "0.3.16"
|
||||
tracing-tree = "0.2.2"
|
||||
tracing-log = "0.2"
|
||||
ts-rs = "10.0.0"
|
||||
@@ -353,7 +351,7 @@ utoipauto = "0.2"
|
||||
uuid = "*"
|
||||
vergen = { version = "=8.3.1", default-features = false }
|
||||
walkdir = "2"
|
||||
wasm-bindgen-test = "0.3.45"
|
||||
wasm-bindgen-test = "0.3.43"
|
||||
x25519-dalek = "2.0.0"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
@@ -402,11 +400,11 @@ gloo-net = "0.5.0"
|
||||
# this is blocked until the upstream removes outdates `wasm_bindgen` feature usage
|
||||
# indexed_db_futures = "0.4.1"
|
||||
indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", branch = "update-uuid" }
|
||||
js-sys = "0.3.76"
|
||||
js-sys = "0.3.70"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
tsify = "0.4.5"
|
||||
wasm-bindgen = "0.2.99"
|
||||
wasm-bindgen-futures = "0.4.49"
|
||||
wasm-bindgen = "0.2.95"
|
||||
wasm-bindgen-futures = "0.4.45"
|
||||
wasmtimer = "0.2.0"
|
||||
web-sys = "0.3.72"
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -14,7 +14,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
|
||||
* `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.studio/en/docs/about/intro) framework.
|
||||
* `nym-cli` - a tool for interacting with the network from the CLI.
|
||||
* `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").
|
||||
-->
|
||||
@@ -42,10 +42,10 @@ client ───► Gateway ──┘ mix │ mix ┌─►mix ───►
|
||||
|
||||
References for developers:
|
||||
|
||||
* [Dev Docs](https://nym.com/docs/developers)
|
||||
* [SDKs](https://nym.com/docs/developers/rust)
|
||||
* [Network Docs](https://nym.com/docs/network)
|
||||
* [Release Cycle - git flow](https://nym.com/docs/operators/release-cycle)
|
||||
* [Dev Docs](https://nymtech.net/docs/developers)
|
||||
* [SDKs](https://nymtech.net/docs/developers/rust)
|
||||
* [Network Docs](https://nymtech.net/docs/network)
|
||||
* [Release Cycle - git flow](https://nymtech.net/docs/operators/release-cycle)
|
||||
|
||||
### Developer chat
|
||||
|
||||
@@ -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 Temrs and Conditions can be found [here](https://nym.com/terms-and-conditions/operators/v1.0.0).
|
||||
Nym Node Operators and Validators Temrs and Conditions can be found [here](https://nymtech.net/terms-and-conditions/operators/v1.0.0).
|
||||
|
||||
@@ -3,18 +3,11 @@ name = "nym-client-core"
|
||||
version = "1.1.15"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
rust-version = "1.70"
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
nym-client-core-config-types = { path = "./config-types", features = [
|
||||
"disk-persistence",
|
||||
] }
|
||||
toml.workspace = true
|
||||
uneval = "0.2.4"
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
@@ -50,10 +43,9 @@ nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-metrics = { path = "../nym-metrics" }
|
||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-sphinx-params = { path = "../nymsphinx/params" }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
nym-topology = { path = "../topology", features = ["persistence"] }
|
||||
nym-topology = { path = "../topology", features = ["serializable"] }
|
||||
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-task = { path = "../task" }
|
||||
@@ -125,15 +117,12 @@ features = ["wasm-bindgen"]
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
toml.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["enable-cfg"]
|
||||
default = []
|
||||
cli = ["clap", "comfy-table"]
|
||||
fs-credentials-storage = ["nym-credential-storage/persistent-storage"]
|
||||
fs-surb-storage = ["nym-client-core-surb-storage/fs-surb-storage"]
|
||||
fs-gateways-storage = ["nym-client-core-gateways-storage/fs-gateways-storage"]
|
||||
wasm = ["nym-gateway-client/wasm"]
|
||||
metrics-server = []
|
||||
|
||||
enable-cfg = []
|
||||
@@ -1,109 +0,0 @@
|
||||
use nym_client_core_config_types::Config;
|
||||
|
||||
use std::io::{BufWriter, Write};
|
||||
|
||||
fn main() {
|
||||
write_conditional_default();
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
const DEFAULT_CONFIG_FILE_NAME: &str = "nymvpn-config.toml";
|
||||
|
||||
const CONFIG_INIT_FN_PREAMBLE: &[u8] = br#"
|
||||
pub fn new_bootstrapped<S1, S2>(id: S1, version: S2) -> Config
|
||||
where
|
||||
S1: Into<String>,
|
||||
S2: Into<String>,
|
||||
{
|
||||
use nym_sphinx_params::{PacketSize, PacketType};
|
||||
|
||||
|
||||
let mut cfg: Config = "#;
|
||||
|
||||
const CONFIG_INIT_FN_EPILOGUE: &[u8] = br#";
|
||||
cfg.client.id = id.into();
|
||||
cfg.client.version = version.into();
|
||||
cfg
|
||||
}"#;
|
||||
|
||||
// #[cfg(feature = "enable-cfg")]
|
||||
// const CUSTOM_BREAK: &[u8] = br#"
|
||||
// #[cfg(feature="enable-cfg")]
|
||||
// "#;
|
||||
|
||||
// #[cfg(not(feature = "enable-cfg"))]
|
||||
// const DEFAULT_BREAK: &[u8] = br#"
|
||||
// #[cfg(not(feature="enable-cfg"))]
|
||||
// "#;
|
||||
|
||||
// const CONFIG_INIT_FN_PREAMBLE: &[u8] = br#"
|
||||
// impl Default for BaseClientConfig {
|
||||
// fn default() -> Self {
|
||||
// use config_types::Keys;
|
||||
|
||||
// Self("#;
|
||||
|
||||
// const DEFAULT_CONFIG_EPILOGUE: &[u8] = br#"
|
||||
// )
|
||||
// }
|
||||
// }"#;
|
||||
|
||||
// #[cfg(feature = "enable-cfg")]
|
||||
// const CUSTOM_BREAK: &[u8] = br#"
|
||||
// #[cfg(feature="enable-cfg")]
|
||||
// "#;
|
||||
|
||||
// #[cfg(not(feature = "enable-cfg"))]
|
||||
// const DEFAULT_BREAK: &[u8] = br#"
|
||||
// #[cfg(not(feature="enable-cfg"))]
|
||||
// "#;
|
||||
|
||||
fn write_conditional_default() {
|
||||
// creating a string with the values from our default Config object
|
||||
let mut array_string = BufWriter::new(Vec::new());
|
||||
array_string.write(CONFIG_INIT_FN_PREAMBLE).unwrap();
|
||||
|
||||
#[cfg(feature = "enable-cfg")]
|
||||
write_custom_config(&mut array_string);
|
||||
|
||||
#[cfg(not(feature = "enable-cfg"))]
|
||||
{
|
||||
let default_config = Config::new("", "");
|
||||
uneval::write(default_config, &mut array_string).unwrap();
|
||||
}
|
||||
|
||||
array_string.write(CONFIG_INIT_FN_EPILOGUE).unwrap();
|
||||
|
||||
// write the string to a file. OUT_DIR environment variable is defined by cargo
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
let dest_path = std::path::Path::new(&out_dir).join("default.rs");
|
||||
|
||||
let out_str = String::from_utf8(array_string.into_inner().unwrap()).unwrap();
|
||||
std::fs::write(&dest_path, out_str).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "enable-cfg")]
|
||||
pub(crate) fn write_custom_config(out: impl Write) {
|
||||
// allow the name of the file we draw hardcoded values from to be set by an
|
||||
// environment variable at compile time.
|
||||
let cfg_file_name = option_env!("NYMVPN_CONFIG_PATH").unwrap_or(DEFAULT_CONFIG_FILE_NAME);
|
||||
|
||||
// set reasons to rebuild
|
||||
println!("cargo:rerun-if-changed={cfg_file_name}");
|
||||
println!("cargo:rerun-if-env-changed=NYMVPN_CONFIG_PATH");
|
||||
|
||||
let path = std::path::PathBuf::from(cfg_file_name);
|
||||
let cfg_file_path = if path.is_absolute() {
|
||||
path
|
||||
} else {
|
||||
let workspace_path = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
std::path::Path::new(&workspace_path).join(cfg_file_name)
|
||||
};
|
||||
|
||||
let config_str: String = std::fs::read_to_string(cfg_file_path).unwrap();
|
||||
let config: Config = toml::from_str(&config_str).unwrap();
|
||||
|
||||
uneval::write(config, out).unwrap();
|
||||
}
|
||||
@@ -550,15 +550,6 @@ pub struct Topology {
|
||||
/// Specifies a minimum performance of a gateway that is used on route construction.
|
||||
/// This setting is only applicable when `NymApi` topology is used.
|
||||
pub minimum_gateway_performance: u8,
|
||||
|
||||
/// Specifies whether this client should attempt to retrieve all available network nodes
|
||||
/// as opposed to just active mixnodes/gateways.
|
||||
/// Useless without `ignore_epoch_roles = true`
|
||||
pub use_extended_topology: bool,
|
||||
|
||||
/// Specifies whether this client should ignore the current epoch role of the target egress node
|
||||
/// when constructing the final hop packets.
|
||||
pub ignore_egress_epoch_role: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
@@ -595,8 +586,6 @@ impl Default for Topology {
|
||||
topology_structure: TopologyStructure::default(),
|
||||
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
|
||||
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
|
||||
use_extended_topology: false,
|
||||
ignore_egress_epoch_role: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use log::{debug, error};
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -33,9 +30,6 @@ impl StorageManager {
|
||||
}
|
||||
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
@@ -116,7 +110,7 @@ impl StorageManager {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type)
|
||||
INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type)
|
||||
VALUES (?, ?, ?)
|
||||
"#,
|
||||
registered_gateway.gateway_id_bs58,
|
||||
@@ -230,7 +224,7 @@ impl StorageManager {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO custom_gateway_details(gateway_id_bs58, data)
|
||||
INSERT INTO custom_gateway_details(gateway_id_bs58, data)
|
||||
VALUES (?, ?)
|
||||
"#,
|
||||
custom.gateway_id_bs58,
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
|
||||
[client]
|
||||
version = ""
|
||||
id = ""
|
||||
disabled_credentials_mode = true
|
||||
nyxd_urls = ["https://rpc.nymtech.net/"]
|
||||
nym_api_urls = ["https://validator.nymtech.net/api/"]
|
||||
|
||||
[debug.traffic]
|
||||
average_packet_delay = "50ms"
|
||||
message_sending_average_delay = "20ms"
|
||||
disable_main_poisson_packet_distribution = false
|
||||
deterministic_route_selection = false
|
||||
primary_packet_size = "regular"
|
||||
packet_type = "mix"
|
||||
|
||||
[debug.cover_traffic]
|
||||
loop_cover_traffic_average_delay = "200ms"
|
||||
cover_traffic_primary_size_ratio = 0.7
|
||||
disable_loop_cover_traffic_stream = false
|
||||
|
||||
[debug.gateway_connection]
|
||||
gateway_response_timeout = "5m"
|
||||
|
||||
[debug.acknowledgements]
|
||||
average_ack_delay = "50ms"
|
||||
ack_wait_multiplier = 1.5
|
||||
ack_wait_addition = "1s 500ms"
|
||||
|
||||
[debug.topology]
|
||||
topology_refresh_rate = "5m"
|
||||
topology_resolution_timeout = "5s"
|
||||
disable_refreshing = false
|
||||
max_startup_gateway_waiting_period = "1h 10m"
|
||||
topology_structure = "NymApi"
|
||||
minimum_mixnode_performance = 50
|
||||
minimum_gateway_performance = 50
|
||||
use_extended_topology = false
|
||||
ignore_egress_epoch_role = false
|
||||
|
||||
[debug.reply_surbs]
|
||||
minimum_reply_surb_storage_threshold = 10
|
||||
maximum_reply_surb_storage_threshold = 200
|
||||
minimum_reply_surb_request_size = 10
|
||||
maximum_reply_surb_request_size = 100
|
||||
maximum_allowed_reply_surb_request_size = 500
|
||||
maximum_reply_surb_rerequest_waiting_period = "10s"
|
||||
maximum_reply_surb_drop_waiting_period = "5m"
|
||||
maximum_reply_surb_age = "12h"
|
||||
maximum_reply_key_age = "1day"
|
||||
|
||||
[debug.stats_reporting]
|
||||
enabled = true
|
||||
reporting_interval = "5m"
|
||||
@@ -112,7 +112,7 @@ where
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
hardcoded_topology.get_gateways()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::current_gateways(
|
||||
@@ -128,7 +128,7 @@ where
|
||||
// make sure the list of available gateways doesn't overlap the list of known gateways
|
||||
let available_gateways = available_gateways
|
||||
.into_iter()
|
||||
.filter(|g| !registered_gateways.contains(&g.identity()))
|
||||
.filter(|g| !registered_gateways.contains(g.identity()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if available_gateways.is_empty() {
|
||||
|
||||
@@ -167,7 +167,7 @@ where
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
hardcoded_topology.get_gateways()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::current_gateways(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use super::received_buffer::ReceivedBufferMessage;
|
||||
use super::statistics_control::StatisticsControl;
|
||||
use super::topology_control::geo_aware_provider::GeoAwareTopologyProvider;
|
||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||
use crate::client::base_client::storage::MixnetClientStorage;
|
||||
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
@@ -23,7 +24,7 @@ use crate::client::replies::reply_storage::{
|
||||
};
|
||||
use crate::client::topology_control::nym_api_provider::NymApiTopologyProvider;
|
||||
use crate::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
nym_api_provider, TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use crate::config::{Config, DebugConfig};
|
||||
use crate::error::ClientCoreError;
|
||||
@@ -31,7 +32,7 @@ use crate::init::{
|
||||
setup_gateway,
|
||||
types::{GatewaySetup, InitialisationResult},
|
||||
};
|
||||
use crate::{config, spawn_future, ForgetMe};
|
||||
use crate::{config, spawn_future};
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
@@ -187,11 +188,6 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
||||
user_agent: Option<UserAgent>,
|
||||
|
||||
setup_method: GatewaySetup,
|
||||
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl<'a, C, S> BaseClientBuilder<'a, C, S>
|
||||
@@ -214,18 +210,9 @@ where
|
||||
shutdown: None,
|
||||
user_agent: None,
|
||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
forget_me: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
||||
self.forget_me = forget_me.clone();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
|
||||
self.setup_method = setup;
|
||||
@@ -274,15 +261,6 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn with_connection_fd_callback(
|
||||
mut self,
|
||||
callback: Arc<dyn Fn(RawFd) + Send + Sync>,
|
||||
) -> Self {
|
||||
self.connection_fd_callback = Some(callback);
|
||||
self
|
||||
}
|
||||
|
||||
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
|
||||
// because it relies on the crypto keys being already loaded
|
||||
fn mix_address(details: &InitialisationResult) -> Recipient {
|
||||
@@ -374,7 +352,6 @@ where
|
||||
controller.start_with_shutdown(shutdown)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn start_gateway_client(
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
@@ -382,7 +359,6 @@ where
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
|
||||
where
|
||||
@@ -425,8 +401,6 @@ where
|
||||
packet_router,
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
shutdown,
|
||||
)
|
||||
};
|
||||
@@ -463,8 +437,8 @@ where
|
||||
details_store
|
||||
.upgrade_stored_remote_gateway_key(gateway_client.gateway_identity(), &updated_key)
|
||||
.await.map_err(|err| {
|
||||
error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
|
||||
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
|
||||
error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
|
||||
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
|
||||
})?
|
||||
}
|
||||
|
||||
@@ -488,7 +462,6 @@ where
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
mut shutdown: TaskClient,
|
||||
) -> Result<Box<dyn GatewayTransceiver + Send>, ClientCoreError>
|
||||
where
|
||||
@@ -520,8 +493,6 @@ where
|
||||
details_store,
|
||||
packet_router,
|
||||
stats_reporter,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
shutdown,
|
||||
)
|
||||
.await?;
|
||||
@@ -538,15 +509,15 @@ where
|
||||
// if no custom provider was ... provided ..., create one using nym-api
|
||||
custom_provider.unwrap_or_else(|| match config_topology.topology_structure {
|
||||
config::TopologyStructure::NymApi => Box::new(NymApiTopologyProvider::new(
|
||||
config_topology,
|
||||
nym_api_provider::Config {
|
||||
min_mixnode_performance: config_topology.minimum_mixnode_performance,
|
||||
min_gateway_performance: config_topology.minimum_gateway_performance,
|
||||
},
|
||||
nym_api_urls,
|
||||
user_agent,
|
||||
)),
|
||||
config::TopologyStructure::GeoAware(group_by) => {
|
||||
warn!("using deprecated 'GeoAware' topology provider - this option will be removed very soon");
|
||||
|
||||
#[allow(deprecated)]
|
||||
Box::new(crate::client::topology_control::GeoAwareTopologyProvider::new(nym_api_urls, group_by))
|
||||
Box::new(GeoAwareTopologyProvider::new(nym_api_urls, group_by))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -557,7 +528,7 @@ where
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
topology_config: config::Topology,
|
||||
topology_accessor: TopologyAccessor,
|
||||
local_gateway: NodeIdentity,
|
||||
local_gateway: &NodeIdentity,
|
||||
wait_for_gateway: bool,
|
||||
mut shutdown: TaskClient,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
@@ -589,7 +560,7 @@ where
|
||||
};
|
||||
|
||||
if let Err(err) = topology_refresher
|
||||
.ensure_contains_routable_egress(local_gateway)
|
||||
.ensure_contains_gateway(local_gateway)
|
||||
.await
|
||||
{
|
||||
if let Some(waiting_timeout) = gateway_wait_timeout {
|
||||
@@ -644,11 +615,9 @@ where
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
shutdown: TaskClient,
|
||||
forget_me: ForgetMe,
|
||||
) -> BatchMixMessageSender {
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx) =
|
||||
MixTrafficController::new(gateway_transceiver, forget_me);
|
||||
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_transceiver);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
mix_tx
|
||||
}
|
||||
@@ -739,8 +708,7 @@ where
|
||||
|
||||
// channels responsible for controlling ack messages
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
let shared_topology_accessor =
|
||||
TopologyAccessor::new(self.config.debug.topology.ignore_egress_epoch_role);
|
||||
let shared_topology_accessor = TopologyAccessor::new();
|
||||
|
||||
// Shutdown notifier for signalling tasks to stop
|
||||
let shutdown = self
|
||||
@@ -804,8 +772,6 @@ where
|
||||
&details_store,
|
||||
gateway_packet_router,
|
||||
stats_reporter.clone(),
|
||||
#[cfg(unix)]
|
||||
self.connection_fd_callback,
|
||||
shutdown.fork("gateway_transceiver"),
|
||||
)
|
||||
.await?;
|
||||
@@ -831,11 +797,9 @@ where
|
||||
// that are to be sent to the mixnet. They are used by cover traffic stream and real
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
|
||||
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
|
||||
|
||||
@@ -163,7 +163,6 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
// poisson delay, but is it really a problem?
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
// the ack is sent back to ourselves (and then ignored)
|
||||
|
||||
let topology_ref = match topology_permit.try_get_valid_topology_ref(
|
||||
&self.our_full_destination,
|
||||
Some(&self.our_full_destination),
|
||||
|
||||
@@ -28,6 +28,7 @@ pub enum InputMessage {
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
mix_hops: Option<u8>,
|
||||
},
|
||||
|
||||
/// Creates a message used for a duplex anonymous communication where the recipient
|
||||
@@ -43,6 +44,7 @@ pub enum InputMessage {
|
||||
data: Vec<u8>,
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
mix_hops: Option<u8>,
|
||||
},
|
||||
|
||||
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
|
||||
@@ -92,6 +94,29 @@ impl InputMessage {
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
mix_hops: None,
|
||||
};
|
||||
if let Some(packet_type) = packet_type {
|
||||
InputMessage::new_wrapper(message, packet_type)
|
||||
} else {
|
||||
message
|
||||
}
|
||||
}
|
||||
|
||||
// IMHO `new_regular` should take `mix_hops: Option<u8>` as an argument instead of creating
|
||||
// this function, but that would potentially break backwards compatibility with the current API
|
||||
pub fn new_regular_with_custom_hops(
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
packet_type: Option<PacketType>,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Self {
|
||||
let message = InputMessage::Regular {
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
mix_hops,
|
||||
};
|
||||
if let Some(packet_type) = packet_type {
|
||||
InputMessage::new_wrapper(message, packet_type)
|
||||
@@ -112,6 +137,7 @@ impl InputMessage {
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
mix_hops: None,
|
||||
};
|
||||
if let Some(packet_type) = packet_type {
|
||||
InputMessage::new_wrapper(message, packet_type)
|
||||
@@ -128,12 +154,14 @@ impl InputMessage {
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
packet_type: Option<PacketType>,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Self {
|
||||
let message = InputMessage::Anonymous {
|
||||
recipient,
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
mix_hops,
|
||||
};
|
||||
if let Some(packet_type) = packet_type {
|
||||
InputMessage::new_wrapper(message, packet_type)
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
||||
use crate::{spawn_future, ForgetMe};
|
||||
use crate::spawn_future;
|
||||
use log::*;
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
|
||||
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
|
||||
@@ -27,14 +26,10 @@ pub struct MixTrafficController {
|
||||
// TODO: this is temporary work-around.
|
||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||
consecutive_gateway_failure_count: usize,
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl MixTrafficController {
|
||||
pub fn new<T>(
|
||||
gateway_transceiver: T,
|
||||
forget_me: ForgetMe,
|
||||
) -> (MixTrafficController, BatchMixMessageSender)
|
||||
pub fn new<T>(gateway_transceiver: T) -> (MixTrafficController, BatchMixMessageSender)
|
||||
where
|
||||
T: GatewayTransceiver + Send + 'static,
|
||||
{
|
||||
@@ -45,7 +40,6 @@ impl MixTrafficController {
|
||||
gateway_transceiver: Box::new(gateway_transceiver),
|
||||
mix_rx: message_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
forget_me,
|
||||
},
|
||||
message_sender,
|
||||
)
|
||||
@@ -53,7 +47,6 @@ impl MixTrafficController {
|
||||
|
||||
pub fn new_dynamic(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
forget_me: ForgetMe,
|
||||
) -> (MixTrafficController, BatchMixMessageSender) {
|
||||
let (message_sender, message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
@@ -62,7 +55,6 @@ impl MixTrafficController {
|
||||
gateway_transceiver,
|
||||
mix_rx: message_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
forget_me,
|
||||
},
|
||||
message_sender,
|
||||
)
|
||||
@@ -119,27 +111,7 @@ impl MixTrafficController {
|
||||
}
|
||||
}
|
||||
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");
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@ use async_trait::async_trait;
|
||||
use log::{debug, error};
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use std::fmt::Debug;
|
||||
@@ -28,14 +26,9 @@ fn erase_err<E: std::error::Error + Send + Sync + 'static>(err: E) -> ErasedGate
|
||||
}
|
||||
|
||||
/// This combines combines the functionalities of being able to send and receive mix packets.
|
||||
#[async_trait]
|
||||
pub trait GatewayTransceiver: GatewaySender + GatewayReceiver {
|
||||
fn gateway_identity(&self) -> identity::PublicKey;
|
||||
fn ws_fd(&self) -> Option<RawFd>;
|
||||
async fn send_client_request(
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError>;
|
||||
}
|
||||
|
||||
/// This trait defines the functionality of sending `MixPacket` into the mixnet,
|
||||
@@ -72,7 +65,6 @@ pub trait GatewayReceiver {
|
||||
}
|
||||
|
||||
// to allow for dynamic dispatch
|
||||
#[async_trait]
|
||||
impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
||||
#[inline]
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
@@ -81,13 +73,6 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
(**self).ws_fd()
|
||||
}
|
||||
|
||||
async fn send_client_request(
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
(**self).send_client_request(message).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -106,6 +91,7 @@ impl<G: GatewaySender + ?Sized + Send> GatewaySender for Box<G> {
|
||||
(**self).batch_send_mix_packets(packets).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: GatewayReceiver + ?Sized> GatewayReceiver for Box<G> {
|
||||
#[inline]
|
||||
fn set_packet_router(&mut self, packet_router: PacketRouter) -> Result<(), ErasedGatewayError> {
|
||||
@@ -125,7 +111,6 @@ impl<C, St> RemoteGateway<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, St> GatewayTransceiver for RemoteGateway<C, St>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
@@ -138,20 +123,6 @@ where
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
self.gateway_client.ws_fd()
|
||||
}
|
||||
|
||||
async fn send_client_request(
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -224,7 +195,6 @@ impl LocalGateway {
|
||||
mod nonwasm_sealed {
|
||||
use super::*;
|
||||
|
||||
#[async_trait]
|
||||
impl GatewayTransceiver for LocalGateway {
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.local_identity
|
||||
@@ -232,13 +202,6 @@ mod nonwasm_sealed {
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn send_client_request(
|
||||
&mut self,
|
||||
_message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -306,7 +269,6 @@ impl GatewaySender for MockGateway {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GatewayTransceiver for MockGateway {
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.dummy_identity
|
||||
@@ -314,11 +276,4 @@ impl GatewayTransceiver for MockGateway {
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn send_client_request(
|
||||
&mut self,
|
||||
_message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+35
-8
@@ -73,10 +73,11 @@ where
|
||||
content: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
mix_hops: Option<u8>,
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_plain_message(recipient, content, lane, packet_type)
|
||||
.try_send_plain_message(recipient, content, lane, packet_type, mix_hops)
|
||||
.await
|
||||
{
|
||||
warn!("failed to send a plain message - {err}")
|
||||
@@ -90,10 +91,18 @@ where
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
mix_hops: Option<u8>,
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type)
|
||||
.try_send_message_with_reply_surbs(
|
||||
recipient,
|
||||
content,
|
||||
reply_surbs,
|
||||
lane,
|
||||
packet_type,
|
||||
mix_hops,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("failed to send a repliable message - {err}")
|
||||
@@ -106,8 +115,9 @@ where
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
mix_hops,
|
||||
} => {
|
||||
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
|
||||
self.handle_plain_message(recipient, data, lane, PacketType::Mix, mix_hops)
|
||||
.await
|
||||
}
|
||||
InputMessage::Anonymous {
|
||||
@@ -115,9 +125,17 @@ where
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
mix_hops,
|
||||
} => {
|
||||
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
|
||||
.await
|
||||
self.handle_repliable_message(
|
||||
recipient,
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
PacketType::Mix,
|
||||
mix_hops,
|
||||
)
|
||||
.await
|
||||
}
|
||||
InputMessage::Reply {
|
||||
recipient_tag,
|
||||
@@ -135,8 +153,9 @@ where
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
mix_hops,
|
||||
} => {
|
||||
self.handle_plain_message(recipient, data, lane, packet_type)
|
||||
self.handle_plain_message(recipient, data, lane, packet_type, mix_hops)
|
||||
.await
|
||||
}
|
||||
InputMessage::Anonymous {
|
||||
@@ -144,9 +163,17 @@ where
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
mix_hops,
|
||||
} => {
|
||||
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
|
||||
.await
|
||||
self.handle_repliable_message(
|
||||
recipient,
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
packet_type,
|
||||
mix_hops,
|
||||
)
|
||||
.await
|
||||
}
|
||||
InputMessage::Reply {
|
||||
recipient_tag,
|
||||
|
||||
@@ -70,6 +70,7 @@ pub(crate) struct PendingAcknowledgement {
|
||||
message_chunk: Fragment,
|
||||
delay: SphinxDelay,
|
||||
destination: PacketDestination,
|
||||
mix_hops: Option<u8>,
|
||||
retransmissions: u32,
|
||||
}
|
||||
|
||||
@@ -79,11 +80,13 @@ impl PendingAcknowledgement {
|
||||
message_chunk: Fragment,
|
||||
delay: SphinxDelay,
|
||||
recipient: Recipient,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Self {
|
||||
PendingAcknowledgement {
|
||||
message_chunk,
|
||||
delay,
|
||||
destination: PacketDestination::KnownRecipient(recipient.into()),
|
||||
mix_hops,
|
||||
retransmissions: 0,
|
||||
}
|
||||
}
|
||||
@@ -101,6 +104,9 @@ impl PendingAcknowledgement {
|
||||
recipient_tag,
|
||||
extra_surb_request,
|
||||
},
|
||||
// Messages sent using SURBs are using the number of mix hops set by the recipient when
|
||||
// they provided the SURBs, so it doesn't make sense to include it here.
|
||||
mix_hops: None,
|
||||
retransmissions: 0,
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -52,12 +52,18 @@ where
|
||||
packet_recipient: Recipient,
|
||||
chunk_data: Fragment,
|
||||
packet_type: PacketType,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Result<PreparedFragment, PreparationError> {
|
||||
debug!("retransmitting normal packet...");
|
||||
|
||||
// TODO: Figure out retransmission packet type signaling
|
||||
self.message_handler
|
||||
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type)
|
||||
.try_prepare_single_chunk_for_sending(
|
||||
packet_recipient,
|
||||
chunk_data,
|
||||
packet_type,
|
||||
mix_hops,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -104,6 +110,7 @@ where
|
||||
**recipient,
|
||||
timed_out_ack.message_chunk.clone(),
|
||||
packet_type,
|
||||
timed_out_ack.mix_hops,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessa
|
||||
use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey};
|
||||
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use nym_sphinx::message::NymMessage;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::params::{PacketSize, PacketType, DEFAULT_NUM_MIX_HOPS};
|
||||
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
|
||||
use nym_sphinx::Delay;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use nym_topology::{NymTopology, NymTopologyError};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@@ -100,6 +100,10 @@ pub(crate) struct Config {
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
|
||||
/// Note that it does not include gateway hops.
|
||||
num_mix_hops: u8,
|
||||
|
||||
/// Primary predefined packet size used for the encapsulated messages.
|
||||
primary_packet_size: PacketSize,
|
||||
|
||||
@@ -121,11 +125,19 @@ impl Config {
|
||||
deterministic_route_selection,
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
|
||||
primary_packet_size: PacketSize::default(),
|
||||
secondary_packet_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows setting non-default number of expected mix hops in the network.
|
||||
#[allow(dead_code)]
|
||||
pub fn with_mix_hops(mut self, hops: u8) -> Self {
|
||||
self.num_mix_hops = hops;
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows setting non-default size of the sphinx packets sent out.
|
||||
pub fn with_custom_primary_packet_size(mut self, packet_size: PacketSize) -> Self {
|
||||
self.primary_packet_size = packet_size;
|
||||
@@ -173,7 +185,9 @@ where
|
||||
config.sender_address,
|
||||
config.average_packet_delay,
|
||||
config.average_ack_delay,
|
||||
);
|
||||
)
|
||||
.with_mix_hops(config.num_mix_hops);
|
||||
|
||||
MessageHandler {
|
||||
config,
|
||||
rng,
|
||||
@@ -202,7 +216,7 @@ where
|
||||
fn get_topology<'a>(
|
||||
&self,
|
||||
permit: &'a TopologyReadPermit<'a>,
|
||||
) -> Result<&'a NymRouteProvider, PreparationError> {
|
||||
) -> Result<&'a NymTopology, PreparationError> {
|
||||
match permit.try_get_valid_topology_ref(&self.config.sender_address, None) {
|
||||
Ok(topology_ref) => Ok(topology_ref),
|
||||
Err(err) => {
|
||||
@@ -219,8 +233,9 @@ where
|
||||
return self.config.primary_packet_size;
|
||||
};
|
||||
|
||||
let primary_count = msg.required_packets(self.config.primary_packet_size);
|
||||
let secondary_count = msg.required_packets(secondary_packet);
|
||||
let primary_count =
|
||||
msg.required_packets(self.config.primary_packet_size, self.config.num_mix_hops);
|
||||
let secondary_count = msg.required_packets(secondary_packet, self.config.num_mix_hops);
|
||||
|
||||
trace!("This message would require: {primary_count} primary packets or {secondary_count} secondary packets...");
|
||||
// if there would be no benefit in using the secondary packet - use the primary (duh)
|
||||
@@ -409,9 +424,10 @@ where
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Result<(), PreparationError> {
|
||||
let message = NymMessage::new_plain(message);
|
||||
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
|
||||
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type, mix_hops)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -421,6 +437,7 @@ where
|
||||
recipient: Recipient,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Result<(), PreparationError> {
|
||||
debug!("Sending non-reply message with packet type {packet_type}");
|
||||
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
|
||||
@@ -453,6 +470,7 @@ where
|
||||
&self.config.ack_key,
|
||||
&recipient,
|
||||
packet_type,
|
||||
mix_hops,
|
||||
)?;
|
||||
|
||||
let real_message = RealMessage::new(
|
||||
@@ -460,7 +478,8 @@ where
|
||||
Some(fragment.fragment_identifier()),
|
||||
);
|
||||
let delay = prepared_fragment.total_delay;
|
||||
let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient);
|
||||
let pending_ack =
|
||||
PendingAcknowledgement::new_known(fragment, delay, recipient, mix_hops);
|
||||
|
||||
real_messages.push(real_message);
|
||||
pending_acks.push(pending_ack);
|
||||
@@ -477,6 +496,7 @@ where
|
||||
recipient: Recipient,
|
||||
amount: u32,
|
||||
packet_type: PacketType,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Result<(), PreparationError> {
|
||||
debug!("Sending additional reply SURBs with packet type {packet_type}");
|
||||
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
||||
@@ -493,6 +513,7 @@ where
|
||||
recipient,
|
||||
TransmissionLane::AdditionalReplySurbs,
|
||||
packet_type,
|
||||
mix_hops,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -509,6 +530,7 @@ where
|
||||
num_reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
debug!("Sending message with reply SURBs with packet type {packet_type}");
|
||||
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
||||
@@ -519,7 +541,7 @@ where
|
||||
let message =
|
||||
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
|
||||
|
||||
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
|
||||
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type, mix_hops)
|
||||
.await?;
|
||||
|
||||
log::trace!("storing {} reply keys", reply_keys.len());
|
||||
@@ -533,18 +555,23 @@ where
|
||||
recipient: Recipient,
|
||||
chunk: Fragment,
|
||||
packet_type: PacketType,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Result<PreparedFragment, PreparationError> {
|
||||
debug!("Sending single chunk with packet type {packet_type}");
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = self.get_topology(&topology_permit)?;
|
||||
|
||||
let prepared_fragment = self.message_preparer.prepare_chunk_for_sending(
|
||||
chunk,
|
||||
topology,
|
||||
&self.config.ack_key,
|
||||
&recipient,
|
||||
packet_type,
|
||||
)?;
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_chunk_for_sending(
|
||||
chunk,
|
||||
topology,
|
||||
&self.config.ack_key,
|
||||
&recipient,
|
||||
packet_type,
|
||||
mix_hops,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(prepared_fragment)
|
||||
}
|
||||
@@ -597,13 +624,16 @@ where
|
||||
Err(err) => return Err(err.return_surbs(vec![reply_surb])),
|
||||
};
|
||||
|
||||
let prepared_fragment = self.message_preparer.prepare_reply_chunk_for_sending(
|
||||
chunk,
|
||||
topology,
|
||||
&self.config.ack_key,
|
||||
reply_surb,
|
||||
PacketType::Mix,
|
||||
)?;
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_reply_chunk_for_sending(
|
||||
chunk,
|
||||
topology,
|
||||
&self.config.ack_key,
|
||||
reply_surb,
|
||||
PacketType::Mix,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(prepared_fragment)
|
||||
}
|
||||
|
||||
@@ -230,7 +230,6 @@ where
|
||||
// poisson delay, but is it really a problem?
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
// the ack is sent back to ourselves (and then ignored)
|
||||
|
||||
let topology_ref = match topology_permit.try_get_valid_topology_ref(
|
||||
&self.config.our_full_destination,
|
||||
Some(&self.config.our_full_destination),
|
||||
|
||||
@@ -516,6 +516,7 @@ where
|
||||
recipient,
|
||||
to_send,
|
||||
nym_sphinx::params::PacketType::Mix,
|
||||
self.config.reply_surbs.surb_mix_hops,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::{NymRouteProvider, NymTopology, NymTopologyError};
|
||||
use nym_sphinx::params::DEFAULT_NUM_MIX_HOPS;
|
||||
use nym_topology::{NymTopology, NymTopologyError};
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
@@ -16,36 +17,29 @@ pub struct TopologyAccessorInner {
|
||||
// few seconds, while reads are needed every single packet generated.
|
||||
// However, proper benchmarks will be needed to determine if `RwLock` is indeed a better
|
||||
// approach than a `Mutex`
|
||||
topology: RwLock<NymRouteProvider>,
|
||||
topology: RwLock<Option<NymTopology>>,
|
||||
}
|
||||
|
||||
impl TopologyAccessorInner {
|
||||
fn new(initial: NymRouteProvider) -> Self {
|
||||
fn new() -> Self {
|
||||
TopologyAccessorInner {
|
||||
controlled_manually: AtomicBool::new(false),
|
||||
released_manual_control: Notify::new(),
|
||||
topology: RwLock::new(initial),
|
||||
topology: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
async fn update(&self, new: Option<NymTopology>) {
|
||||
let mut guard = self.topology.write().await;
|
||||
|
||||
match new {
|
||||
Some(updated) => {
|
||||
guard.update(updated);
|
||||
}
|
||||
None => guard.clear_topology(),
|
||||
}
|
||||
*self.topology.write().await = new;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TopologyReadPermit<'a> {
|
||||
permit: RwLockReadGuard<'a, NymRouteProvider>,
|
||||
permit: RwLockReadGuard<'a, Option<NymTopology>>,
|
||||
}
|
||||
|
||||
impl Deref for TopologyReadPermit<'_> {
|
||||
type Target = NymRouteProvider;
|
||||
type Target = Option<NymTopology>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.permit
|
||||
@@ -59,31 +53,43 @@ impl<'a> TopologyReadPermit<'a> {
|
||||
&'a self,
|
||||
ack_recipient: &Recipient,
|
||||
packet_recipient: Option<&Recipient>,
|
||||
) -> Result<&'a NymRouteProvider, NymTopologyError> {
|
||||
let route_provider = self.permit.deref();
|
||||
let topology = &route_provider.topology;
|
||||
|
||||
) -> Result<&'a NymTopology, NymTopologyError> {
|
||||
// 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through?
|
||||
topology.ensure_not_empty()?;
|
||||
let topology = self
|
||||
.permit
|
||||
.as_ref()
|
||||
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
|
||||
|
||||
// 2. does the topology have a node on each mixing layer?
|
||||
topology.ensure_minimally_routable()?;
|
||||
// 2. does it have any mixnode at all?
|
||||
// 3. does it have any gateways at all?
|
||||
// 4. does it have a mixnode on each layer?
|
||||
topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?;
|
||||
|
||||
// 3. does it contain OUR gateway (so that we could create an ack packet)?
|
||||
let _ = route_provider.egress_by_identity(ack_recipient.gateway())?;
|
||||
|
||||
// 4. for our target recipient, does it contain THEIR gateway (so that we send anything over?)
|
||||
if let Some(recipient) = packet_recipient {
|
||||
let _ = route_provider.egress_by_identity(recipient.gateway())?;
|
||||
// 5. does it contain OUR gateway (so that we could create an ack packet)?
|
||||
if !topology.gateway_exists(ack_recipient.gateway()) {
|
||||
return Err(NymTopologyError::NonExistentGatewayError {
|
||||
identity_key: ack_recipient.gateway().to_base58_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(route_provider)
|
||||
// 6. for our target recipient, does it contain THEIR gateway (so that we could create
|
||||
if let Some(recipient) = packet_recipient {
|
||||
if !topology.gateway_exists(recipient.gateway()) {
|
||||
return Err(NymTopologyError::NonExistentGatewayError {
|
||||
identity_key: recipient.gateway().to_base58_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(topology)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<RwLockReadGuard<'a, NymRouteProvider>> for TopologyReadPermit<'a> {
|
||||
fn from(permit: RwLockReadGuard<'a, NymRouteProvider>) -> Self {
|
||||
TopologyReadPermit { permit }
|
||||
impl<'a> From<RwLockReadGuard<'a, Option<NymTopology>>> for TopologyReadPermit<'a> {
|
||||
fn from(read_permit: RwLockReadGuard<'a, Option<NymTopology>>) -> Self {
|
||||
TopologyReadPermit {
|
||||
permit: read_permit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,11 +99,9 @@ pub struct TopologyAccessor {
|
||||
}
|
||||
|
||||
impl TopologyAccessor {
|
||||
pub fn new(ignore_egress_epoch_roles: bool) -> Self {
|
||||
pub fn new() -> Self {
|
||||
TopologyAccessor {
|
||||
inner: Arc::new(TopologyAccessorInner::new(NymRouteProvider::new_empty(
|
||||
ignore_egress_epoch_roles,
|
||||
))),
|
||||
inner: Arc::new(TopologyAccessorInner::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,21 +121,8 @@ impl TopologyAccessor {
|
||||
self.inner.released_manual_control.notified().await
|
||||
}
|
||||
|
||||
#[deprecated(note = "use .current_route_provider instead")]
|
||||
pub async fn current_topology(&self) -> Option<NymTopology> {
|
||||
self.current_route_provider()
|
||||
.await
|
||||
.as_ref()
|
||||
.map(|p| p.topology.clone())
|
||||
}
|
||||
|
||||
pub async fn current_route_provider(&self) -> Option<RwLockReadGuard<NymRouteProvider>> {
|
||||
let provider = self.inner.topology.read().await;
|
||||
if provider.topology.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(provider)
|
||||
}
|
||||
self.inner.topology.read().await.clone()
|
||||
}
|
||||
|
||||
pub async fn manually_change_topology(&self, new_topology: NymTopology) {
|
||||
@@ -149,11 +140,15 @@ impl TopologyAccessor {
|
||||
// only used by the client at startup to get a slightly more reasonable error message
|
||||
// (currently displays as unused because health checker is disabled due to required changes)
|
||||
pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> {
|
||||
self.inner
|
||||
.topology
|
||||
.read()
|
||||
.await
|
||||
.topology
|
||||
.ensure_minimally_routable()
|
||||
match self.inner.topology.read().await.deref() {
|
||||
None => Err(NymTopologyError::EmptyNetworkTopology),
|
||||
Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TopologyAccessor {
|
||||
fn default() -> Self {
|
||||
TopologyAccessor::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use log::{debug, error};
|
||||
use nym_explorer_client::{ExplorerClient, PrettyDetailedMixNodeBond};
|
||||
use nym_network_defaults::var_names::EXPLORER_API;
|
||||
use nym_topology::{
|
||||
nym_topology_from_basic_info,
|
||||
provider_trait::{async_trait, TopologyProvider},
|
||||
NymTopology,
|
||||
};
|
||||
@@ -14,6 +15,8 @@ use url::Url;
|
||||
|
||||
pub use nym_country_group::CountryGroup;
|
||||
|
||||
const MIN_NODES_PER_LAYER: usize = 1;
|
||||
|
||||
fn create_explorer_client() -> Option<ExplorerClient> {
|
||||
let Ok(explorer_api_url) = std::env::var(EXPLORER_API) else {
|
||||
error!("Missing EXPLORER_API");
|
||||
@@ -60,20 +63,30 @@ fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<NodeId>>) {
|
||||
}
|
||||
|
||||
fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
|
||||
if topology.ensure_minimally_routable().is_err() {
|
||||
let mixes = topology.mixes();
|
||||
if mixes.keys().len() < 3 {
|
||||
error!("Layer is missing in topology!");
|
||||
return Err(());
|
||||
}
|
||||
for (layer, mixnodes) in mixes {
|
||||
debug!("Layer {:?} has {} mixnodes", layer, mixnodes.len());
|
||||
if mixnodes.len() < MIN_NODES_PER_LAYER {
|
||||
error!(
|
||||
"There are only {} mixnodes in layer {:?}",
|
||||
mixnodes.len(),
|
||||
layer
|
||||
);
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[deprecated(note = "use NymApiTopologyProvider instead as explorer API will soon be removed")]
|
||||
pub struct GeoAwareTopologyProvider {
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
filter_on: GroupBy,
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl GeoAwareTopologyProvider {
|
||||
pub fn new(mut nym_api_urls: Vec<Url>, filter_on: GroupBy) -> GeoAwareTopologyProvider {
|
||||
log::info!(
|
||||
@@ -91,15 +104,6 @@ impl GeoAwareTopologyProvider {
|
||||
}
|
||||
|
||||
async fn get_topology(&self) -> Option<NymTopology> {
|
||||
let rewarded_set = self
|
||||
.validator_client
|
||||
.get_current_rewarded_set()
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to get current rewarded set: {err}"))
|
||||
.ok()?;
|
||||
|
||||
let mut topology = NymTopology::new_empty(rewarded_set);
|
||||
|
||||
let mixnodes = match self
|
||||
.validator_client
|
||||
.get_all_basic_active_mixing_assigned_nodes()
|
||||
@@ -183,8 +187,7 @@ impl GeoAwareTopologyProvider {
|
||||
.filter(|m| filtered_mixnode_ids.contains(&m.node_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
topology.add_skimmed_nodes(&mixnodes);
|
||||
topology.add_skimmed_nodes(&gateways);
|
||||
let topology = nym_topology_from_basic_info(&mixnodes, &gateways);
|
||||
|
||||
// TODO: return real error type
|
||||
check_layer_integrity(topology.clone()).ok()?;
|
||||
@@ -193,7 +196,6 @@ impl GeoAwareTopologyProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[async_trait]
|
||||
impl TopologyProvider for GeoAwareTopologyProvider {
|
||||
@@ -203,7 +205,6 @@ impl TopologyProvider for GeoAwareTopologyProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[async_trait(?Send)]
|
||||
impl TopologyProvider for GeoAwareTopologyProvider {
|
||||
|
||||
@@ -19,7 +19,6 @@ mod accessor;
|
||||
pub mod geo_aware_provider;
|
||||
pub mod nym_api_provider;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub use geo_aware_provider::GeoAwareTopologyProvider;
|
||||
pub use nym_api_provider::{Config as NymApiTopologyProviderConfig, NymApiTopologyProvider};
|
||||
pub use nym_topology::provider_trait::TopologyProvider;
|
||||
@@ -28,7 +27,7 @@ pub use nym_topology::provider_trait::TopologyProvider;
|
||||
const MAX_FAILURE_COUNT: usize = 10;
|
||||
|
||||
pub struct TopologyRefresherConfig {
|
||||
pub refresh_rate: Duration,
|
||||
refresh_rate: Duration,
|
||||
}
|
||||
|
||||
impl TopologyRefresherConfig {
|
||||
@@ -97,24 +96,28 @@ impl TopologyRefresher {
|
||||
self.topology_accessor.ensure_is_routable().await
|
||||
}
|
||||
|
||||
pub async fn ensure_contains_routable_egress(
|
||||
pub async fn ensure_contains_gateway(
|
||||
&self,
|
||||
egress: NodeIdentity,
|
||||
gateway: &NodeIdentity,
|
||||
) -> Result<(), NymTopologyError> {
|
||||
let topology = self
|
||||
.topology_accessor
|
||||
.current_route_provider()
|
||||
.current_topology()
|
||||
.await
|
||||
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
|
||||
|
||||
let _ = topology.egress_by_identity(egress)?;
|
||||
if !topology.gateway_exists(gateway) {
|
||||
return Err(NymTopologyError::NonExistentGatewayError {
|
||||
identity_key: gateway.to_base58_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn wait_for_gateway(
|
||||
&mut self,
|
||||
gateway: NodeIdentity,
|
||||
gateway: &NodeIdentity,
|
||||
timeout_duration: Duration,
|
||||
) -> Result<(), NymTopologyError> {
|
||||
info!(
|
||||
@@ -132,7 +135,7 @@ impl TopologyRefresher {
|
||||
})
|
||||
}
|
||||
_ = self.try_refresh() => {
|
||||
if self.ensure_contains_routable_egress(gateway).await.is_ok() {
|
||||
if self.ensure_contains_gateway(gateway).await.is_ok() {
|
||||
return Ok(())
|
||||
}
|
||||
info!("gateway '{gateway}' is still not online...");
|
||||
|
||||
@@ -4,39 +4,32 @@
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error, warn};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::NymTopology;
|
||||
use nym_topology::{NymTopology, NymTopologyError};
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::cmp::min;
|
||||
use url::Url;
|
||||
|
||||
// the same values as our current (10.06.24) blacklist
|
||||
pub const DEFAULT_MIN_MIXNODE_PERFORMANCE: u8 = 50;
|
||||
pub const DEFAULT_MIN_GATEWAY_PERFORMANCE: u8 = 50;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub min_mixnode_performance: u8,
|
||||
pub min_gateway_performance: u8,
|
||||
pub use_extended_topology: bool,
|
||||
pub ignore_egress_epoch_role: bool,
|
||||
}
|
||||
|
||||
impl From<nym_client_core_config_types::Topology> for Config {
|
||||
fn from(value: nym_client_core_config_types::Topology) -> Self {
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
// old values that decided on blacklist membership
|
||||
Config {
|
||||
min_mixnode_performance: value.minimum_mixnode_performance,
|
||||
min_gateway_performance: value.minimum_gateway_performance,
|
||||
use_extended_topology: value.use_extended_topology,
|
||||
ignore_egress_epoch_role: value.ignore_egress_epoch_role,
|
||||
min_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
|
||||
min_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// if we're using 'extended' topology, filter the nodes based on the lowest set performance
|
||||
fn min_node_performance(&self) -> u8 {
|
||||
min(self.min_mixnode_performance, self.min_gateway_performance)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NymApiTopologyProvider {
|
||||
config: Config,
|
||||
|
||||
@@ -46,11 +39,7 @@ pub struct NymApiTopologyProvider {
|
||||
}
|
||||
|
||||
impl NymApiTopologyProvider {
|
||||
pub fn new(
|
||||
config: impl Into<Config>,
|
||||
mut nym_api_urls: Vec<Url>,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Self {
|
||||
pub fn new(config: Config, mut nym_api_urls: Vec<Url>, user_agent: Option<UserAgent>) -> Self {
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
let validator_client = if let Some(user_agent) = user_agent {
|
||||
@@ -63,7 +52,7 @@ impl NymApiTopologyProvider {
|
||||
};
|
||||
|
||||
NymApiTopologyProvider {
|
||||
config: config.into(),
|
||||
config,
|
||||
validator_client,
|
||||
nym_api_urls,
|
||||
currently_used_api: 0,
|
||||
@@ -81,69 +70,70 @@ impl NymApiTopologyProvider {
|
||||
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
|
||||
}
|
||||
|
||||
/// Verifies whether nodes a reasonably distributed among all mix layers.
|
||||
///
|
||||
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
|
||||
/// However, this is a rather unrealistic expectation, instead we check whether there exists
|
||||
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `topology`: active topology constructed from validator api data
|
||||
fn check_layer_distribution(
|
||||
&self,
|
||||
active_topology: &NymTopology,
|
||||
) -> Result<(), NymTopologyError> {
|
||||
let lower_threshold = 0.15;
|
||||
let upper_threshold = 0.66;
|
||||
active_topology.ensure_even_layer_distribution(lower_threshold, upper_threshold)
|
||||
}
|
||||
|
||||
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
|
||||
let rewarded_set = self
|
||||
let mixnodes = match self
|
||||
.validator_client
|
||||
.get_current_rewarded_set()
|
||||
.get_all_basic_active_mixing_assigned_nodes()
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to get current rewarded set: {err}"))
|
||||
.ok()?;
|
||||
|
||||
let mut topology = NymTopology::new_empty(rewarded_set);
|
||||
|
||||
if self.config.use_extended_topology {
|
||||
let all_nodes = self
|
||||
.validator_client
|
||||
.get_all_basic_nodes()
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to get network nodes: {err}"))
|
||||
.ok()?;
|
||||
|
||||
debug!(
|
||||
"there are {} nodes on the network (before filtering)",
|
||||
all_nodes.len()
|
||||
);
|
||||
topology.add_additional_nodes(all_nodes.iter().filter(|n| {
|
||||
n.performance.round_to_integer() >= self.config.min_node_performance()
|
||||
}));
|
||||
} else {
|
||||
// if we're not using extended topology, we're only getting active set mixnodes and gateways
|
||||
|
||||
let mixnodes = self
|
||||
.validator_client
|
||||
.get_all_basic_active_mixing_assigned_nodes()
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to get network mixnodes: {err}"))
|
||||
.ok()?;
|
||||
|
||||
// TODO: we really should be getting ACTIVE gateways only
|
||||
let gateways = self
|
||||
.validator_client
|
||||
.get_all_basic_entry_assigned_nodes()
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to get network gateways: {err}"))
|
||||
.ok()?;
|
||||
|
||||
debug!(
|
||||
"there are {} mixnodes and {} gateways in total (before performance filtering)",
|
||||
mixnodes.len(),
|
||||
gateways.len()
|
||||
);
|
||||
|
||||
topology.add_additional_nodes(mixnodes.iter().filter(|m| {
|
||||
m.performance.round_to_integer() >= self.config.min_mixnode_performance
|
||||
}));
|
||||
topology.add_additional_nodes(gateways.iter().filter(|m| {
|
||||
m.performance.round_to_integer() >= self.config.min_gateway_performance
|
||||
}));
|
||||
{
|
||||
Err(err) => {
|
||||
error!("failed to get network mixnodes - {err}");
|
||||
return None;
|
||||
}
|
||||
Ok(mixes) => mixes,
|
||||
};
|
||||
|
||||
if !topology.is_minimally_routable() {
|
||||
error!("the current filtered active topology can't be used to construct any packets");
|
||||
return None;
|
||||
}
|
||||
let gateways = match self
|
||||
.validator_client
|
||||
.get_all_basic_entry_assigned_nodes()
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
error!("failed to get network gateways - {err}");
|
||||
return None;
|
||||
}
|
||||
Ok(gateways) => gateways,
|
||||
};
|
||||
|
||||
Some(topology)
|
||||
debug!(
|
||||
"there are {} mixnodes and {} gateways in total (before performance filtering)",
|
||||
mixnodes.len(),
|
||||
gateways.len()
|
||||
);
|
||||
|
||||
let topology = NymTopology::from_unordered(
|
||||
mixnodes.iter().filter(|m| {
|
||||
m.performance.round_to_integer() >= self.config.min_mixnode_performance
|
||||
}),
|
||||
gateways.iter().filter(|g| {
|
||||
g.performance.round_to_integer() >= self.config.min_gateway_performance
|
||||
}),
|
||||
);
|
||||
if let Err(err) = self.check_layer_distribution(&topology) {
|
||||
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used: {err}");
|
||||
self.use_next_nym_api();
|
||||
None
|
||||
} else {
|
||||
Some(topology)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,11 +142,7 @@ impl NymApiTopologyProvider {
|
||||
#[async_trait]
|
||||
impl TopologyProvider for NymApiTopologyProvider {
|
||||
async fn get_new_topology(&mut self) -> Option<NymTopology> {
|
||||
let Some(topology) = self.get_current_compatible_topology().await else {
|
||||
self.use_next_nym_api();
|
||||
return None;
|
||||
};
|
||||
Some(topology)
|
||||
self.get_current_compatible_topology().await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,10 +150,6 @@ impl TopologyProvider for NymApiTopologyProvider {
|
||||
#[async_trait(?Send)]
|
||||
impl TopologyProvider for NymApiTopologyProvider {
|
||||
async fn get_new_topology(&mut self) -> Option<NymTopology> {
|
||||
let Some(topology) = self.get_current_compatible_topology().await else {
|
||||
self.use_next_nym_api();
|
||||
return None;
|
||||
};
|
||||
Some(topology)
|
||||
self.get_current_compatible_topology().await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,40 +7,3 @@ pub use nym_client_core_config_types::old::{
|
||||
old_config_v1_1_33,
|
||||
};
|
||||
pub use nym_client_core_config_types::*;
|
||||
|
||||
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/default.rs"));
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn dump_default() {
|
||||
let cfg = Config::new("","");
|
||||
println!("{}", toml::to_string(&cfg).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bootstrapped_config() {
|
||||
#[cfg(not(feature = "enable-cfg"))]
|
||||
{
|
||||
let config = new_bootstrapped("id1", "v0.0.0");
|
||||
assert_eq!(config.client.id, "id1");
|
||||
assert_eq!(config.client.version, "v0.0.0");
|
||||
assert_eq!(config.client.disabled_credentials_mode, true);
|
||||
|
||||
assert_eq!(config.debug.topology.use_extended_topology, false);
|
||||
assert_eq!(config.debug.stats_reporting.enabled, true);
|
||||
}
|
||||
#[cfg(feature = "enable-cfg")]
|
||||
{
|
||||
let config = new_bootstrapped("id2", "v0.0.0-beta");
|
||||
assert_eq!(config.client.id, "id2");
|
||||
assert_eq!(config.client.version, "v0.0.0-beta");
|
||||
assert_eq!(config.client.disabled_credentials_mode, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
use crate::client::mix_traffic::transceiver::ErasedGatewayError;
|
||||
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_topology::node::RoutingNodeError;
|
||||
use nym_topology::{NodeId, NymTopologyError};
|
||||
use nym_topology::gateway::GatewayConversionError;
|
||||
use nym_topology::NymTopologyError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
@@ -74,10 +74,10 @@ pub enum ClientCoreError {
|
||||
#[error("the gateway id is invalid - {0}")]
|
||||
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
|
||||
|
||||
#[error("the node is malformed: {source}")]
|
||||
#[error("The gateway is malformed: {source}")]
|
||||
MalformedGateway {
|
||||
#[from]
|
||||
source: Box<RoutingNodeError>,
|
||||
source: GatewayConversionError,
|
||||
},
|
||||
|
||||
#[error("failed to establish connection to gateway: {source}")]
|
||||
@@ -159,9 +159,6 @@ pub enum ClientCoreError {
|
||||
#[error("the specified gateway '{gateway}' does not support the wss protocol")]
|
||||
UnsupportedWssProtocol { gateway: String },
|
||||
|
||||
#[error("node {id} ({identity}) does not support mixnet entry mode")]
|
||||
UnsupportedEntry { id: NodeId, identity: String },
|
||||
|
||||
#[error(
|
||||
"failed to load custom topology using path '{}'. detailed message: {source}", file_path.display()
|
||||
)]
|
||||
|
||||
@@ -7,7 +7,7 @@ use futures::{SinkExt, StreamExt};
|
||||
use log::{debug, info, trace, warn};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_topology::gateway;
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
@@ -15,7 +15,6 @@ use std::{sync::Arc, time::Duration};
|
||||
use tungstenite::Message;
|
||||
use url::Url;
|
||||
|
||||
use nym_topology::NodeId;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::net::TcpStream;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -26,6 +25,7 @@ use tokio::time::Instant;
|
||||
use tokio_tungstenite::connect_async;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -48,30 +48,22 @@ const PING_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
|
||||
// The abstraction that some of these helpers use
|
||||
pub trait ConnectableGateway {
|
||||
fn node_id(&self) -> NodeId;
|
||||
fn identity(&self) -> identity::PublicKey;
|
||||
fn clients_address(&self, prefer_ipv6: bool) -> Option<String>;
|
||||
fn identity(&self) -> &identity::PublicKey;
|
||||
fn clients_address(&self) -> String;
|
||||
fn is_wss(&self) -> bool;
|
||||
}
|
||||
|
||||
impl ConnectableGateway for RoutingNode {
|
||||
fn node_id(&self) -> NodeId {
|
||||
self.node_id
|
||||
impl ConnectableGateway for gateway::LegacyNode {
|
||||
fn identity(&self) -> &identity::PublicKey {
|
||||
self.identity()
|
||||
}
|
||||
|
||||
fn identity(&self) -> identity::PublicKey {
|
||||
self.identity_key
|
||||
}
|
||||
|
||||
fn clients_address(&self, prefer_ipv6: bool) -> Option<String> {
|
||||
self.ws_entry_address(prefer_ipv6)
|
||||
fn clients_address(&self) -> String {
|
||||
self.clients_address()
|
||||
}
|
||||
|
||||
fn is_wss(&self) -> bool {
|
||||
self.entry
|
||||
.as_ref()
|
||||
.map(|e| e.clients_wss_port.is_some())
|
||||
.unwrap_or_default()
|
||||
self.clients_wss_port.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +83,7 @@ pub async fn current_gateways<R: Rng>(
|
||||
nym_apis: &[Url],
|
||||
user_agent: Option<UserAgent>,
|
||||
minimum_performance: u8,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
) -> Result<Vec<gateway::LegacyNode>, ClientCoreError> {
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
@@ -112,7 +104,7 @@ pub async fn current_gateways<R: Rng>(
|
||||
.iter()
|
||||
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
||||
.filter_map(|gateway| gateway.try_into().ok())
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<Vec<gateway::LegacyNode>>();
|
||||
log::debug!("After checking validity: {}", valid_gateways.len());
|
||||
log::trace!("Valid gateways: {:#?}", valid_gateways);
|
||||
|
||||
@@ -142,12 +134,7 @@ async fn measure_latency<G>(gateway: &G) -> Result<GatewayWithLatency<G>, Client
|
||||
where
|
||||
G: ConnectableGateway,
|
||||
{
|
||||
let Some(addr) = gateway.clients_address(false) else {
|
||||
return Err(ClientCoreError::UnsupportedEntry {
|
||||
id: gateway.node_id(),
|
||||
identity: gateway.identity().to_string(),
|
||||
});
|
||||
};
|
||||
let addr = gateway.clients_address();
|
||||
trace!(
|
||||
"establishing connection to {} ({addr})...",
|
||||
gateway.identity(),
|
||||
@@ -203,7 +190,7 @@ where
|
||||
Ok(GatewayWithLatency::new(gateway, avg))
|
||||
}
|
||||
|
||||
pub async fn choose_gateway_by_latency<R: Rng, G: ConnectableGateway + Clone>(
|
||||
pub async fn choose_gateway_by_latency<'a, R: Rng, G: ConnectableGateway + Clone>(
|
||||
rng: &mut R,
|
||||
gateways: &[G],
|
||||
must_use_tls: bool,
|
||||
@@ -218,7 +205,7 @@ pub async fn choose_gateway_by_latency<R: Rng, G: ConnectableGateway + Clone>(
|
||||
let gateways_with_latency = Arc::new(tokio::sync::Mutex::new(Vec::new()));
|
||||
futures::stream::iter(gateways)
|
||||
.for_each_concurrent(CONCURRENT_GATEWAYS_MEASURED, |gateway| async {
|
||||
let id = gateway.identity();
|
||||
let id = *gateway.identity();
|
||||
trace!("measuring latency to {id}...");
|
||||
match measure_latency(gateway).await {
|
||||
Ok(with_latency) => {
|
||||
@@ -265,9 +252,9 @@ fn filter_by_tls<G: ConnectableGateway>(
|
||||
|
||||
pub(super) fn uniformly_random_gateway<R: Rng>(
|
||||
rng: &mut R,
|
||||
gateways: &[RoutingNode],
|
||||
gateways: &[gateway::LegacyNode],
|
||||
must_use_tls: bool,
|
||||
) -> Result<RoutingNode, ClientCoreError> {
|
||||
) -> Result<gateway::LegacyNode, ClientCoreError> {
|
||||
filter_by_tls(gateways, must_use_tls)?
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
|
||||
@@ -276,9 +263,9 @@ pub(super) fn uniformly_random_gateway<R: Rng>(
|
||||
|
||||
pub(super) fn get_specified_gateway(
|
||||
gateway_identity: IdentityKeyRef,
|
||||
gateways: &[RoutingNode],
|
||||
gateways: &[gateway::LegacyNode],
|
||||
must_use_tls: bool,
|
||||
) -> Result<RoutingNode, ClientCoreError> {
|
||||
) -> Result<gateway::LegacyNode, ClientCoreError> {
|
||||
log::debug!("Requesting specified gateway: {}", gateway_identity);
|
||||
let user_gateway = identity::PublicKey::from_base58_string(gateway_identity)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
@@ -288,14 +275,7 @@ pub(super) fn get_specified_gateway(
|
||||
.find(|gateway| gateway.identity_key == user_gateway)
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))?;
|
||||
|
||||
let Some(entry_details) = gateway.entry.as_ref() else {
|
||||
return Err(ClientCoreError::UnsupportedEntry {
|
||||
id: gateway.node_id,
|
||||
identity: gateway.identity().to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
if must_use_tls && entry_details.clients_wss_port.is_none() {
|
||||
if must_use_tls && gateway.clients_wss_port.is_none() {
|
||||
return Err(ClientCoreError::UnsupportedWssProtocol {
|
||||
gateway: gateway_identity.to_string(),
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::init::types::{
|
||||
use nym_client_core_gateways_storage::GatewaysDetailsStore;
|
||||
use nym_client_core_gateways_storage::{GatewayDetails, GatewayRegistration};
|
||||
use nym_gateway_client::client::InitGatewayClient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_topology::gateway;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use serde::Serialize;
|
||||
@@ -50,7 +50,7 @@ async fn setup_new_gateway<K, D>(
|
||||
key_store: &K,
|
||||
details_store: &D,
|
||||
selection_specification: GatewaySelectionSpecification,
|
||||
available_gateways: Vec<RoutingNode>,
|
||||
available_gateways: Vec<gateway::LegacyNode>,
|
||||
) -> Result<InitialisationResult, ClientCoreError>
|
||||
where
|
||||
K: KeyStore,
|
||||
|
||||
@@ -13,7 +13,7 @@ use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::client::InitGatewayClient;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_topology::gateway;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use serde::Serialize;
|
||||
@@ -38,23 +38,16 @@ pub enum SelectedGateway {
|
||||
|
||||
impl SelectedGateway {
|
||||
pub fn from_topology_node(
|
||||
node: RoutingNode,
|
||||
node: gateway::LegacyNode,
|
||||
must_use_tls: bool,
|
||||
) -> Result<Self, ClientCoreError> {
|
||||
// for now, let's use 'old' behaviour, if you want to change it, you can pass it up the enum stack yourself : )
|
||||
let prefer_ipv6 = false;
|
||||
|
||||
let gateway_listener = if must_use_tls {
|
||||
node.ws_entry_address_tls()
|
||||
node.clients_address_tls()
|
||||
.ok_or(ClientCoreError::UnsupportedWssProtocol {
|
||||
gateway: node.identity_key.to_base58_string(),
|
||||
})?
|
||||
} else {
|
||||
node.ws_entry_address(prefer_ipv6)
|
||||
.ok_or(ClientCoreError::UnsupportedEntry {
|
||||
id: node.node_id,
|
||||
identity: node.identity_key.to_base58_string(),
|
||||
})?
|
||||
node.clients_address()
|
||||
};
|
||||
|
||||
let gateway_listener =
|
||||
@@ -207,7 +200,7 @@ pub enum GatewaySetup {
|
||||
specification: GatewaySelectionSpecification,
|
||||
|
||||
// TODO: seems to be a bit inefficient to pass them by value
|
||||
available_gateways: Vec<RoutingNode>,
|
||||
available_gateways: Vec<gateway::LegacyNode>,
|
||||
},
|
||||
|
||||
ReuseConnection {
|
||||
|
||||
@@ -14,7 +14,8 @@ pub mod error;
|
||||
pub mod init;
|
||||
|
||||
pub use nym_topology::{
|
||||
HardcodedTopologyProvider, NymRouteProvider, NymTopology, NymTopologyError, TopologyProvider,
|
||||
HardcodedTopologyProvider, NymTopology, NymTopologyError, SerializableNymTopology,
|
||||
SerializableTopologyError, TopologyProvider,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -33,48 +34,3 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,7 @@ use crate::backend::fs_backend::{
|
||||
},
|
||||
};
|
||||
use log::{error, info};
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -34,9 +31,6 @@ impl StorageManager {
|
||||
}
|
||||
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(fresh)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -101,10 +101,6 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
// currently unused (but populated)
|
||||
negotiated_protocol: Option<u8>,
|
||||
|
||||
// Callback on the fd as soon as the connection has been established
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
|
||||
/// Listen to shutdown messages and send notifications back to the task manager
|
||||
task_client: TaskClient,
|
||||
}
|
||||
@@ -120,7 +116,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
@@ -136,8 +131,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
@@ -212,12 +205,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
};
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -324,7 +311,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
// If we want to send a message (with response), we need to have a full control over the socket,
|
||||
// as we need to be able to write the request and read the subsequent response
|
||||
pub async fn send_websocket_message(
|
||||
async fn send_websocket_message(
|
||||
&mut self,
|
||||
msg: impl Into<Message>,
|
||||
) -> Result<ServerResponse, GatewayClientError> {
|
||||
@@ -1047,8 +1034,6 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
bandwidth_controller: None,
|
||||
stats_reporter: ClientStatsSender::new(None),
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
@@ -1079,8 +1064,6 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
negotiated_protocol: self.negotiated_protocol,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: self.connection_fd_callback,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,10 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dashmap = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tokio = { workspace = true, features = ["time", "sync"] }
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
tokio-util = { workspace = true, features = ["codec"], optional = true }
|
||||
tokio-stream = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-sphinx = { path = "../../nymsphinx" }
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use dashmap::DashMap;
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_sphinx::framing::codec::NymCodec;
|
||||
use nym_sphinx::framing::packet::FramedNymPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::NymPacket;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::error::TrySendError;
|
||||
use tokio::time::sleep;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tokio_util::codec::Framed;
|
||||
use tracing::*;
|
||||
|
||||
@@ -58,37 +55,11 @@ pub trait SendWithoutResponse {
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
active_connections: ActiveConnections,
|
||||
connections_count: Arc<AtomicUsize>,
|
||||
conn_new: HashMap<NymNodeRoutingAddress, ConnectionSender>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ActiveConnections {
|
||||
inner: Arc<DashMap<NymNodeRoutingAddress, ConnectionSender>>,
|
||||
}
|
||||
|
||||
impl ActiveConnections {
|
||||
pub fn pending_packets(&self) -> usize {
|
||||
self.inner
|
||||
.iter()
|
||||
.map(|sender| {
|
||||
let max_capacity = sender.channel.max_capacity();
|
||||
let capacity = sender.channel.capacity();
|
||||
max_capacity - capacity
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ActiveConnections {
|
||||
type Target = DashMap<NymNodeRoutingAddress, ConnectionSender>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConnectionSender {
|
||||
struct ConnectionSender {
|
||||
channel: mpsc::Sender<FramedNymPacket>,
|
||||
current_reconnection_attempt: Arc<AtomicU32>,
|
||||
}
|
||||
@@ -102,53 +73,46 @@ impl ConnectionSender {
|
||||
}
|
||||
}
|
||||
|
||||
struct ManagedConnection {
|
||||
address: SocketAddr,
|
||||
message_receiver: ReceiverStream<FramedNymPacket>,
|
||||
connection_timeout: Duration,
|
||||
current_reconnection: Arc<AtomicU32>,
|
||||
}
|
||||
|
||||
impl ManagedConnection {
|
||||
fn new(
|
||||
address: SocketAddr,
|
||||
message_receiver: mpsc::Receiver<FramedNymPacket>,
|
||||
connection_timeout: Duration,
|
||||
current_reconnection: Arc<AtomicU32>,
|
||||
) -> Self {
|
||||
ManagedConnection {
|
||||
address,
|
||||
message_receiver: ReceiverStream::new(message_receiver),
|
||||
connection_timeout,
|
||||
current_reconnection,
|
||||
impl Client {
|
||||
pub fn new(config: Config) -> Client {
|
||||
Client {
|
||||
conn_new: HashMap::new(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(self) {
|
||||
let address = self.address;
|
||||
async fn manage_connection(
|
||||
address: SocketAddr,
|
||||
receiver: mpsc::Receiver<FramedNymPacket>,
|
||||
connection_timeout: Duration,
|
||||
current_reconnection: &AtomicU32,
|
||||
) {
|
||||
let connection_fut = TcpStream::connect(address);
|
||||
|
||||
let conn = match tokio::time::timeout(self.connection_timeout, connection_fut).await {
|
||||
let conn = match tokio::time::timeout(connection_timeout, connection_fut).await {
|
||||
Ok(stream_res) => match stream_res {
|
||||
Ok(stream) => {
|
||||
debug!("Managed to establish connection to {}", self.address);
|
||||
debug!("Managed to establish connection to {}", address);
|
||||
// if we managed to connect, reset the reconnection count (whatever it might have been)
|
||||
self.current_reconnection.store(0, Ordering::Release);
|
||||
current_reconnection.store(0, Ordering::Release);
|
||||
Framed::new(stream, NymCodec)
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("failed to establish connection to {address} (err: {err})",);
|
||||
debug!(
|
||||
"failed to establish connection to {} (err: {})",
|
||||
address, err
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
debug!(
|
||||
"failed to connect to {address} within {:?}",
|
||||
self.connection_timeout
|
||||
"failed to connect to {} within {:?}",
|
||||
address, connection_timeout
|
||||
);
|
||||
|
||||
// we failed to connect - increase reconnection attempt
|
||||
self.current_reconnection.fetch_add(1, Ordering::SeqCst);
|
||||
current_reconnection.fetch_add(1, Ordering::SeqCst);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -156,28 +120,15 @@ impl ManagedConnection {
|
||||
// Take whatever the receiver channel produces and put it on the connection.
|
||||
// We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care
|
||||
// about neither receiver nor the connection, it doesn't matter which one gets consumed
|
||||
if let Err(err) = self.message_receiver.map(Ok).forward(conn).await {
|
||||
warn!("Failed to forward packets to {address}: {err}");
|
||||
if let Err(err) = receiver.map(Ok).forward(conn).await {
|
||||
warn!("Failed to forward packets to {} - {err}", address);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"connection manager to {address} is finished. Either the connection failed or mixnet client got dropped",
|
||||
"connection manager to {} is finished. Either the connection failed or mixnet client got dropped",
|
||||
address
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(config: Config, connections_count: Arc<AtomicUsize>) -> Client {
|
||||
Client {
|
||||
active_connections: Default::default(),
|
||||
connections_count,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active_connections(&self) -> ActiveConnections {
|
||||
self.active_connections.clone()
|
||||
}
|
||||
|
||||
/// If we're trying to reconnect, determine how long we should wait.
|
||||
fn determine_backoff(&self, current_attempt: u32) -> Option<Duration> {
|
||||
@@ -197,7 +148,7 @@ impl Client {
|
||||
}
|
||||
|
||||
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
|
||||
let (sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
|
||||
let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
|
||||
|
||||
// this CAN'T fail because we just created the channel which has a non-zero capacity
|
||||
if self.config.maximum_connection_buffer_size > 0 {
|
||||
@@ -205,16 +156,15 @@ impl Client {
|
||||
}
|
||||
|
||||
// if we already tried to connect to `address` before, grab the current attempt count
|
||||
let current_reconnection_attempt =
|
||||
if let Some(mut existing) = self.active_connections.get_mut(&address) {
|
||||
existing.channel = sender;
|
||||
Arc::clone(&existing.current_reconnection_attempt)
|
||||
} else {
|
||||
let new_entry = ConnectionSender::new(sender);
|
||||
let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt);
|
||||
self.active_connections.insert(address, new_entry);
|
||||
current_attempt
|
||||
};
|
||||
let current_reconnection_attempt = if let Some(existing) = self.conn_new.get_mut(&address) {
|
||||
existing.channel = sender;
|
||||
Arc::clone(&existing.current_reconnection_attempt)
|
||||
} else {
|
||||
let new_entry = ConnectionSender::new(sender);
|
||||
let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt);
|
||||
self.conn_new.insert(address, new_entry);
|
||||
current_attempt
|
||||
};
|
||||
|
||||
// load the actual value.
|
||||
let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire);
|
||||
@@ -223,7 +173,6 @@ impl Client {
|
||||
// copy the value before moving into another task
|
||||
let initial_connection_timeout = self.config.initial_connection_timeout;
|
||||
|
||||
let connections_count = self.connections_count.clone();
|
||||
tokio::spawn(async move {
|
||||
// before executing the manager, wait for what was specified, if anything
|
||||
if let Some(backoff) = backoff {
|
||||
@@ -231,16 +180,13 @@ impl Client {
|
||||
sleep(backoff).await;
|
||||
}
|
||||
|
||||
connections_count.fetch_add(1, Ordering::SeqCst);
|
||||
ManagedConnection::new(
|
||||
Self::manage_connection(
|
||||
address.into(),
|
||||
receiver,
|
||||
initial_connection_timeout,
|
||||
current_reconnection_attempt,
|
||||
¤t_reconnection_attempt,
|
||||
)
|
||||
.run()
|
||||
.await;
|
||||
connections_count.fetch_sub(1, Ordering::SeqCst);
|
||||
.await
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -255,47 +201,49 @@ impl SendWithoutResponse for Client {
|
||||
trace!("Sending packet to {address:?}");
|
||||
let framed_packet = FramedNymPacket::new(packet, packet_type);
|
||||
|
||||
let Some(sender) = self.active_connections.get_mut(&address) else {
|
||||
if let Some(sender) = self.conn_new.get_mut(&address) {
|
||||
if let Err(err) = sender.channel.try_send(framed_packet) {
|
||||
if err.is_full() {
|
||||
debug!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address);
|
||||
// it's not a 'big' error, but we did not manage to send the packet
|
||||
// if the queue is full, we can't really do anything but to drop the packet
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::WouldBlock,
|
||||
"connection queue is full",
|
||||
))
|
||||
} else if err.is_disconnected() {
|
||||
debug!(
|
||||
"Connection to {} seems to be dead. attempting to re-establish it...",
|
||||
address
|
||||
);
|
||||
// it's not a 'big' error, but we did not manage to send the packet, but queue
|
||||
// it up to send it as soon as the connection is re-established
|
||||
self.make_connection(address, err.into_inner());
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::ConnectionAborted,
|
||||
"reconnection attempt is in progress",
|
||||
))
|
||||
} else {
|
||||
// this can't really happen, but let's safe-guard against it in case something changes in futures library
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"unknown connection buffer error",
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
// there was never a connection to begin with
|
||||
debug!("establishing initial connection to {}", address);
|
||||
// it's not a 'big' error, but we did not manage to send the packet, but queue the packet
|
||||
// for sending for as soon as the connection is created
|
||||
self.make_connection(address, framed_packet);
|
||||
return Err(io::Error::new(
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::NotConnected,
|
||||
"connection is in progress",
|
||||
));
|
||||
};
|
||||
|
||||
let sending_res = sender.channel.try_send(framed_packet);
|
||||
drop(sender);
|
||||
|
||||
sending_res.map_err(|err| {
|
||||
match err {
|
||||
TrySendError::Full(_) => {
|
||||
debug!("Connection to {address} seems to not be able to handle all the traffic - dropping the current packet");
|
||||
// it's not a 'big' error, but we did not manage to send the packet
|
||||
// if the queue is full, we can't really do anything but to drop the packet
|
||||
io::Error::new(
|
||||
io::ErrorKind::WouldBlock,
|
||||
"connection queue is full",
|
||||
)
|
||||
}
|
||||
TrySendError::Closed(dropped) => {
|
||||
debug!(
|
||||
"Connection to {address} seems to be dead. attempting to re-establish it...",
|
||||
);
|
||||
|
||||
// it's not a 'big' error, but we did not manage to send the packet, but queue
|
||||
// it up to send it as soon as the connection is re-established
|
||||
self.make_connection(address, dropped);
|
||||
io::Error::new(
|
||||
io::ErrorKind::ConnectionAborted,
|
||||
"reconnection attempt is in progress",
|
||||
)
|
||||
}
|
||||
}
|
||||
} )
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,15 +252,12 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn dummy_client() -> Client {
|
||||
Client::new(
|
||||
Config {
|
||||
initial_reconnection_backoff: Duration::from_millis(10_000),
|
||||
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
},
|
||||
Default::default(),
|
||||
)
|
||||
Client::new(Config {
|
||||
initial_reconnection_backoff: Duration::from_millis(10_000),
|
||||
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -32,10 +32,10 @@ use time::Date;
|
||||
use url::Url;
|
||||
|
||||
pub use crate::nym_api::NymApiClientExt;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
pub use nym_mixnet_contract_common::{
|
||||
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId, NymNodeDetails,
|
||||
};
|
||||
|
||||
// re-export the type to not break existing imports
|
||||
pub use crate::coconut::EcashApiClient;
|
||||
|
||||
@@ -367,10 +367,6 @@ impl NymApiClient {
|
||||
Ok(self.nym_api.get_basic_gateways().await?.nodes)
|
||||
}
|
||||
|
||||
pub async fn get_current_rewarded_set(&self) -> Result<EpochRewardedSet, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_rewarded_set().await?.into())
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as an entry gateway
|
||||
/// this includes legacy gateways and nym-nodes
|
||||
pub async fn get_all_basic_entry_assigned_nodes(
|
||||
|
||||
@@ -13,7 +13,7 @@ use nym_api_requests::ecash::models::{
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||
NodeRefreshBody, NymNodeDescription, RewardedSetResponse,
|
||||
NodeRefreshBody, NymNodeDescription,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
@@ -235,15 +235,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, "nym-nodes", "rewarded-set"],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as an entry gateway
|
||||
/// this includes legacy gateways and nym-nodes
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
@@ -921,7 +912,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn force_refresh_describe_cache(
|
||||
&self,
|
||||
request: &NodeRefreshBody,
|
||||
@@ -934,7 +924,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn issued_ticketbooks_for(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
@@ -951,7 +940,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn issued_ticketbooks_challenge(
|
||||
&self,
|
||||
expiration_date: Date,
|
||||
|
||||
+12
-15
@@ -26,10 +26,10 @@ use nym_mixnet_contract_common::{
|
||||
reward_params::{Performance, RewardingParams},
|
||||
rewarding::{EstimatedCurrentEpochRewardResponse, PendingRewardResponse},
|
||||
ContractBuildInformation, ContractState, ContractStateParams, CurrentIntervalResponse,
|
||||
CurrentNymNodeVersionResponse, Delegation, EpochEventId, EpochRewardedSet, EpochStatus,
|
||||
GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, HistoricalNymNodeVersionEntry,
|
||||
IdentityKey, IdentityKeyRef, IntervalEventId, MixNodeBond, MixNodeDetails,
|
||||
MixOwnershipResponse, MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse, NodeId,
|
||||
CurrentNymNodeVersionResponse, Delegation, EpochEventId, EpochStatus, GatewayBond,
|
||||
GatewayBondResponse, GatewayOwnershipResponse, HistoricalNymNodeVersionEntry, IdentityKey,
|
||||
IdentityKeyRef, IntervalEventId, MixNodeBond, MixNodeDetails, MixOwnershipResponse,
|
||||
MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse, NodeId,
|
||||
NumberOfPendingEventsResponse, NymNodeBond, NymNodeDetails, NymNodeVersionHistoryResponse,
|
||||
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse,
|
||||
PagedMixnodeBondsResponse, PagedNodeDelegationsResponse, PendingEpochEvent,
|
||||
@@ -670,7 +670,7 @@ impl<T> PagedMixnetQueryClient for T where T: MixnetQueryClient {}
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait MixnetQueryClientExt: MixnetQueryClient {
|
||||
async fn get_rewarded_set(&self) -> Result<EpochRewardedSet, NyxdError> {
|
||||
async fn get_rewarded_set(&self) -> Result<RewardedSet, NyxdError> {
|
||||
let error_response = |message| Err(NyxdError::extension_query_failure("mixnet", message));
|
||||
|
||||
let metadata = self.get_rewarded_set_metadata().await?;
|
||||
@@ -711,16 +711,13 @@ pub trait MixnetQueryClientExt: MixnetQueryClient {
|
||||
return error_response("the nodes assigned for 'standby' returned unexpected epoch_id");
|
||||
}
|
||||
|
||||
Ok(EpochRewardedSet {
|
||||
epoch_id: expected_epoch_id,
|
||||
assignment: RewardedSet {
|
||||
entry_gateways: entry.nodes,
|
||||
exit_gateways: exit.nodes,
|
||||
layer1: layer1.nodes,
|
||||
layer2: layer2.nodes,
|
||||
layer3: layer3.nodes,
|
||||
standby: standby.nodes,
|
||||
},
|
||||
Ok(RewardedSet {
|
||||
entry_gateways: entry.nodes,
|
||||
exit_gateways: exit.nodes,
|
||||
layer1: layer1.nodes,
|
||||
layer2: layer2.nodes,
|
||||
layer3: layer3.nodes,
|
||||
standby: standby.nodes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@ dirs = { workspace = true, optional = true }
|
||||
handlebars = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true, features = ["display"] }
|
||||
toml = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
nym-network-defaults = { path = "../network-defaults", features = ["utoipa"] }
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NymConfigTomlError {
|
||||
#[error(transparent)]
|
||||
FileIoFailure(#[from] io::Error),
|
||||
#[error(transparent)]
|
||||
TomlSerializeFailure(#[from] toml::ser::Error),
|
||||
}
|
||||
@@ -13,7 +13,6 @@ pub use helpers::{parse_urls, OptionalSet};
|
||||
pub use toml::de::Error as TomlDeError;
|
||||
|
||||
pub mod defaults;
|
||||
pub mod error;
|
||||
pub mod helpers;
|
||||
pub mod legacy_helpers;
|
||||
pub mod serde_helpers;
|
||||
@@ -96,42 +95,6 @@ where
|
||||
config.format_to_writer(file)
|
||||
}
|
||||
|
||||
pub fn save_unformatted_config_to_file<C, P>(
|
||||
config: &C,
|
||||
path: P,
|
||||
) -> Result<(), error::NymConfigTomlError>
|
||||
where
|
||||
C: Serialize + ?Sized,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
log::info!("saving config file to {}", path.display());
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let mut file = File::create(path)?;
|
||||
|
||||
// TODO: check for whether any of our configs store anything sensitive
|
||||
// and change that to 0o644 instead
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let mut perms = fs::metadata(path)?.permissions();
|
||||
perms.set_mode(0o600);
|
||||
fs::set_permissions(path, perms)?;
|
||||
}
|
||||
|
||||
// let serde format the TOML in whatever ugly way it chooses
|
||||
// TODO: in https://docs.rs/toml/latest/toml/fn.to_string_pretty.html it recommends using
|
||||
// https://docs.rs/toml_edit/latest/toml_edit/struct.DocumentMut.html to preserve formatting
|
||||
let toml_string = toml::to_string_pretty(config)?;
|
||||
|
||||
Ok(file.write_all(toml_string.as_bytes())?)
|
||||
}
|
||||
|
||||
pub fn deserialize_config_from_toml_str<C>(raw: &str) -> Result<C, TomlDeError>
|
||||
where
|
||||
C: DeserializeOwned,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
use crate::constants::{TOKEN_SUPPLY, UNIT_DELEGATION_BASE};
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::nym_node::Role;
|
||||
use crate::reward_params::{NodeRewardingParameters, RewardingParams};
|
||||
use crate::rewarding::helpers::truncate_reward;
|
||||
use crate::rewarding::RewardDistribution;
|
||||
@@ -625,16 +624,6 @@ pub enum LegacyMixLayer {
|
||||
Three = 3,
|
||||
}
|
||||
|
||||
impl From<LegacyMixLayer> for Role {
|
||||
fn from(layer: LegacyMixLayer) -> Self {
|
||||
match layer {
|
||||
LegacyMixLayer::One => Role::Layer1,
|
||||
LegacyMixLayer::Two => Role::Layer2,
|
||||
LegacyMixLayer::Three => Role::Layer3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LegacyMixLayer> for String {
|
||||
fn from(layer: LegacyMixLayer) -> Self {
|
||||
(layer as u8).to_string()
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::config_score::{ConfigScoreParams, OutdatedVersionWeights, VersionScoreFormulaParams};
|
||||
use crate::nym_node::Role;
|
||||
use crate::EpochId;
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Coin;
|
||||
@@ -33,23 +32,6 @@ impl RoleAssignment {
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Default)]
|
||||
pub struct EpochRewardedSet {
|
||||
pub epoch_id: EpochId,
|
||||
|
||||
pub assignment: RewardedSet,
|
||||
}
|
||||
|
||||
impl From<(EpochId, RewardedSet)> for EpochRewardedSet {
|
||||
fn from((epoch_id, assignment): (EpochId, RewardedSet)) -> Self {
|
||||
EpochRewardedSet {
|
||||
epoch_id,
|
||||
assignment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Default)]
|
||||
pub struct RewardedSet {
|
||||
@@ -87,29 +69,6 @@ impl RewardedSet {
|
||||
pub fn rewarded_set_size(&self) -> usize {
|
||||
self.active_set_size() + self.standby.len()
|
||||
}
|
||||
|
||||
pub fn get_role(&self, node_id: NodeId) -> Option<Role> {
|
||||
// given each role has ~100 entries in them, doing linear lookup with vec should be fine
|
||||
if self.entry_gateways.contains(&node_id) {
|
||||
return Some(Role::EntryGateway);
|
||||
}
|
||||
if self.exit_gateways.contains(&node_id) {
|
||||
return Some(Role::ExitGateway);
|
||||
}
|
||||
if self.layer1.contains(&node_id) {
|
||||
return Some(Role::Layer1);
|
||||
}
|
||||
if self.layer2.contains(&node_id) {
|
||||
return Some(Role::Layer2);
|
||||
}
|
||||
if self.layer3.contains(&node_id) {
|
||||
return Some(Role::Layer3);
|
||||
}
|
||||
if self.standby.contains(&node_id) {
|
||||
return Some(Role::Standby);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
|
||||
@@ -23,11 +23,6 @@ impl SqliteEcashTicketbookManager {
|
||||
SqliteEcashTicketbookManager { connection_pool }
|
||||
}
|
||||
|
||||
/// Closes the connection pool.
|
||||
pub async fn close(&self) {
|
||||
self.connection_pool.close().await
|
||||
}
|
||||
|
||||
pub(crate) async fn cleanup_expired(&self, deadline: Date) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM ecash_ticketbook WHERE expiration_date <= ?",
|
||||
|
||||
@@ -43,10 +43,6 @@ impl Debug for EphemeralStorage {
|
||||
impl Storage for EphemeralStorage {
|
||||
type StorageError = StorageError;
|
||||
|
||||
async fn close(&self) {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
async fn cleanup_expired(&self) -> Result<(), Self::StorageError> {
|
||||
self.storage_manager.cleanup_expired().await;
|
||||
Ok(())
|
||||
|
||||
@@ -33,10 +33,7 @@ use nym_credentials::{
|
||||
IssuanceTicketBook, IssuedTicketBook,
|
||||
};
|
||||
use nym_ecash_time::{ecash_today, Date, EcashTime};
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
@@ -59,9 +56,6 @@ impl PersistentStorage {
|
||||
);
|
||||
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
@@ -89,10 +83,6 @@ impl PersistentStorage {
|
||||
impl Storage for PersistentStorage {
|
||||
type StorageError = StorageError;
|
||||
|
||||
async fn close(&self) {
|
||||
self.storage_manager.close().await
|
||||
}
|
||||
|
||||
/// remove all expired ticketbooks and expiration date signatures
|
||||
async fn cleanup_expired(&self) -> Result<(), Self::StorageError> {
|
||||
let ecash_yesterday = ecash_today().date().previous_day().unwrap();
|
||||
|
||||
@@ -22,8 +22,6 @@ use std::error::Error;
|
||||
pub trait Storage: Send + Sync {
|
||||
type StorageError: Error;
|
||||
|
||||
async fn close(&self);
|
||||
|
||||
/// remove all expired ticketbooks and expiration date signatures
|
||||
async fn cleanup_expired(&self) -> Result<(), Self::StorageError>;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use futures::{Sink, Stream};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use tungstenite::Message as WsMessage;
|
||||
|
||||
impl<S, R> State<'_, S, R> {
|
||||
impl<'a, S, R> State<'a, S, R> {
|
||||
async fn client_handshake_inner(&mut self) -> Result<(), HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||
use futures::{Sink, Stream};
|
||||
use tungstenite::Message as WsMessage;
|
||||
|
||||
impl<S, R> State<'_, S, R> {
|
||||
impl<'a, S, R> State<'a, S, R> {
|
||||
async fn gateway_handshake_inner(
|
||||
&mut self,
|
||||
raw_init_message: Vec<u8>,
|
||||
|
||||
@@ -20,10 +20,6 @@ pub enum ClientRequest {
|
||||
hkdf_salt: Vec<u8>,
|
||||
derived_key_digest: Vec<u8>,
|
||||
},
|
||||
ForgetMe {
|
||||
client: bool,
|
||||
stats: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl ClientRequest {
|
||||
|
||||
@@ -11,7 +11,6 @@ use tungstenite::Message;
|
||||
#[non_exhaustive]
|
||||
pub enum SensitiveServerResponse {
|
||||
KeyUpgradeAck {},
|
||||
ForgetMeAck {},
|
||||
}
|
||||
|
||||
impl SensitiveServerResponse {
|
||||
|
||||
@@ -6,10 +6,7 @@ use models::StoredFinishedSession;
|
||||
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use sessions::SessionManager;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
use time::Date;
|
||||
use tracing::{debug, error};
|
||||
@@ -39,9 +36,6 @@ impl PersistentStatsStorage {
|
||||
// TODO: we can inject here more stuff based on our gateway global config
|
||||
// struct. Maybe different pool size or timeout intervals?
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
@@ -122,16 +116,6 @@ impl PersistentStatsStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn delete_unique_user(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.delete_unique_user(client_address.as_base58_string())
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn insert_active_session(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
|
||||
@@ -71,16 +71,6 @@ impl SessionManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_unique_user(&self, client_address_b58: String) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM sessions_unique_users WHERE client_address = ?",
|
||||
client_address_b58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_unique_users(&self, date: Date) -> Result<Vec<String>> {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT client_address as count FROM sessions_unique_users WHERE day = ?",
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "DELETE FROM message_store WHERE client_address_bs58 = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734"
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "DELETE FROM available_bandwidth WHERE client_id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118"
|
||||
}
|
||||
@@ -11,6 +11,7 @@ license.workspace = true
|
||||
[dependencies]
|
||||
bincode = { workspace = true }
|
||||
defguard_wireguard_rs = { workspace = true }
|
||||
log = { workspace = true }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
ALTER TABLE message_store
|
||||
ADD COLUMN timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
@@ -49,16 +49,6 @@ impl BandwidthManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_client(&self, client_id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM available_bandwidth WHERE client_id = ?",
|
||||
client_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the expiration date of the particular client to the provided date.
|
||||
pub(crate) async fn set_expiration(
|
||||
&self,
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::models::StoredMessage;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InboxManager {
|
||||
pub(crate) struct InboxManager {
|
||||
connection_pool: sqlx::SqlitePool,
|
||||
/// Maximum number of messages that can be obtained from the database per operation.
|
||||
/// It is used to prevent out of memory errors in the case of client receiving a lot of data while
|
||||
@@ -73,22 +71,44 @@ impl InboxManager {
|
||||
// get 1 additional message to check whether there will be more to grab
|
||||
// next time
|
||||
let limit = self.retrieval_limit + 1;
|
||||
let start_after = start_after.unwrap_or(-1);
|
||||
|
||||
let mut res: Vec<StoredMessage> = sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, client_address_bs58, content, timestamp
|
||||
FROM message_store
|
||||
WHERE client_address_bs58 = ? AND id > ?
|
||||
ORDER BY id ASC
|
||||
LIMIT ?;
|
||||
"#,
|
||||
)
|
||||
.bind(client_address_bs58)
|
||||
.bind(start_after)
|
||||
.bind(limit)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?;
|
||||
let mut res = if let Some(start_after) = start_after {
|
||||
sqlx::query_as!(
|
||||
StoredMessage,
|
||||
r#"
|
||||
SELECT
|
||||
id as "id!",
|
||||
client_address_bs58 as "client_address_bs58!",
|
||||
content as "content!"
|
||||
FROM message_store
|
||||
WHERE client_address_bs58 = ? AND id > ?
|
||||
ORDER BY id ASC
|
||||
LIMIT ?;
|
||||
"#,
|
||||
client_address_bs58,
|
||||
start_after,
|
||||
limit
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?
|
||||
} else {
|
||||
sqlx::query_as!(
|
||||
StoredMessage,
|
||||
r#"
|
||||
SELECT
|
||||
id as "id!",
|
||||
client_address_bs58 as "client_address_bs58!",
|
||||
content as "content!"
|
||||
FROM message_store
|
||||
WHERE client_address_bs58 = ?
|
||||
ORDER BY id ASC
|
||||
LIMIT ?;
|
||||
"#,
|
||||
client_address_bs58,
|
||||
limit
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await?
|
||||
};
|
||||
|
||||
if res.len() > self.retrieval_limit as usize {
|
||||
res.truncate(self.retrieval_limit as usize);
|
||||
@@ -113,26 +133,4 @@ impl InboxManager {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_messages_for_client(
|
||||
&self,
|
||||
client_address_bs58: &str,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM message_store WHERE client_address_bs58 = ?",
|
||||
client_address_bs58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_stale(&self, cutoff: OffsetDateTime) -> Result<(), sqlx::Error> {
|
||||
let affected = sqlx::query!("DELETE FROM message_store WHERE timestamp < ?", cutoff)
|
||||
.execute(&self.connection_pool)
|
||||
.await?
|
||||
.rows_affected();
|
||||
debug!("Removed {affected} stale messages");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use bandwidth::BandwidthManager;
|
||||
use clients::{ClientManager, ClientType};
|
||||
use inboxes::InboxManager;
|
||||
use models::{
|
||||
Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage,
|
||||
VerifiedTicket, WireguardPeer,
|
||||
@@ -11,10 +12,7 @@ use nym_credentials_interface::ClientTicket;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use shared_keys::SharedKeysManager;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
use tickets::TicketStorageManager;
|
||||
use time::OffsetDateTime;
|
||||
@@ -30,7 +28,6 @@ mod tickets;
|
||||
mod wireguard_peers;
|
||||
|
||||
pub use error::GatewayStorageError;
|
||||
pub use inboxes::InboxManager;
|
||||
|
||||
// note that clone here is fine as upon cloning the same underlying pool will be used
|
||||
#[derive(Clone)]
|
||||
@@ -44,33 +41,6 @@ pub struct GatewayStorage {
|
||||
}
|
||||
|
||||
impl GatewayStorage {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn client_manager(&self) -> &ClientManager {
|
||||
&self.client_manager
|
||||
}
|
||||
|
||||
pub(crate) fn shared_key_manager(&self) -> &SharedKeysManager {
|
||||
&self.shared_key_manager
|
||||
}
|
||||
|
||||
pub fn inbox_manager(&self) -> &InboxManager {
|
||||
&self.inbox_manager
|
||||
}
|
||||
|
||||
pub(crate) fn bandwidth_manager(&self) -> &BandwidthManager {
|
||||
&self.bandwidth_manager
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn ticket_manager(&self) -> &TicketStorageManager {
|
||||
&self.ticket_manager
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn wireguard_peer_manager(&self) -> &wireguard_peers::WgPeerManager {
|
||||
&self.wireguard_peer_manager
|
||||
}
|
||||
|
||||
/// Initialises `PersistentStorage` using the provided path.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -89,9 +59,6 @@ impl GatewayStorage {
|
||||
// TODO: we can inject here more stuff based on our gateway global config
|
||||
// struct. Maybe different pool size or timeout intervals?
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
@@ -134,21 +101,6 @@ impl GatewayStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn handle_forget_me(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
let client_id = self.get_mixnet_client_id(client_address).await?;
|
||||
self.inbox_manager()
|
||||
.remove_messages_for_client(&client_address.as_base58_string())
|
||||
.await?;
|
||||
self.bandwidth_manager().remove_client(client_id).await?;
|
||||
self.shared_key_manager()
|
||||
.remove_shared_keys(&client_address.as_base58_string())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn insert_shared_keys(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
|
||||
@@ -48,13 +48,11 @@ impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow)]
|
||||
pub struct StoredMessage {
|
||||
pub id: i64,
|
||||
#[allow(dead_code)]
|
||||
pub client_address_bs58: String,
|
||||
pub content: Vec<u8>,
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
|
||||
+138
-241
@@ -192,28 +192,6 @@ impl Client {
|
||||
&self.base_url
|
||||
}
|
||||
|
||||
pub fn create_request<B, K, V>(
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: Option<&B>,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
let mut request = self.reqwest_client.request(method.clone(), url);
|
||||
|
||||
if let Some(body) = json_body {
|
||||
request = request.json(body);
|
||||
}
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
pub fn create_get_request<K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -227,6 +205,38 @@ impl Client {
|
||||
self.reqwest_client.get(url)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
async fn send_get_request<K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
tracing::trace!("Sending GET request");
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
Ok(
|
||||
wasmtimer::tokio::timeout(
|
||||
self.request_timeout,
|
||||
self.reqwest_client.get(url).send(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
Ok(self.reqwest_client.get(url).send().await?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_post_request<B, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -242,6 +252,36 @@ impl Client {
|
||||
self.reqwest_client.post(url).json(json_body)
|
||||
}
|
||||
|
||||
async fn send_post_request<B, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
Ok(wasmtimer::tokio::timeout(
|
||||
self.request_timeout,
|
||||
self.reqwest_client.post(url).json(json_body).send(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
Ok(self.reqwest_client.post(url).json(json_body).send().await?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_delete_request<K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -255,88 +295,6 @@ impl Client {
|
||||
self.reqwest_client.delete(url)
|
||||
}
|
||||
|
||||
pub fn create_patch_request<B, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
self.reqwest_client.patch(url).json(json_body)
|
||||
}
|
||||
|
||||
async fn send_request<B, K, V, E>(
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: Option<&B>,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
|
||||
let mut request = self.reqwest_client.request(method.clone(), url);
|
||||
|
||||
if let Some(body) = json_body {
|
||||
request = request.json(body);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
Ok(
|
||||
wasmtimer::tokio::timeout(self.request_timeout, request.send())
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
Ok(request.send().await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
async fn send_get_request<K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
self.send_request(reqwest::Method::GET, path, params, None::<&()>)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn send_post_request<B, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
self.send_request(reqwest::Method::POST, path, params, Some(json_body))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_delete_request<K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
@@ -347,24 +305,23 @@ impl Client {
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
self.send_request(reqwest::Method::DELETE, path, params, None::<&()>)
|
||||
.await
|
||||
}
|
||||
tracing::trace!("Sending DELETE request");
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
|
||||
pub async fn send_patch_request<B, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
self.send_request(reqwest::Method::PATCH, path, params, Some(json_body))
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
Ok(wasmtimer::tokio::timeout(
|
||||
self.request_timeout,
|
||||
self.reqwest_client.delete(url).send(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
Ok(self.reqwest_client.delete(url).send().await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
@@ -415,56 +372,6 @@ impl Client {
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
pub async fn patch_json<B, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
let res = self.send_patch_request(path, params, json_body).await?;
|
||||
parse_response(res, true).await
|
||||
}
|
||||
|
||||
async fn call_json_endpoint<B, T, S, E>(
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
endpoint: S,
|
||||
json_body: Option<&B>,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
let mut request = self
|
||||
.reqwest_client
|
||||
.request(method.clone(), self.base_url.join(endpoint.as_ref())?);
|
||||
|
||||
if let Some(body) = json_body {
|
||||
request = request.json(body);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = {
|
||||
wasmtimer::tokio::timeout(self.request_timeout, request.send())
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = { request.send().await? };
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
@@ -472,8 +379,27 @@ impl Client {
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
self.call_json_endpoint(reqwest::Method::GET, endpoint, None::<&()>)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = {
|
||||
wasmtimer::tokio::timeout(
|
||||
self.request_timeout,
|
||||
self.reqwest_client
|
||||
.get(self.base_url.join(endpoint.as_ref())?)
|
||||
.send(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = {
|
||||
self.reqwest_client
|
||||
.get(self.base_url.join(endpoint.as_ref())?)
|
||||
.send()
|
||||
.await?
|
||||
};
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
pub async fn post_json_endpoint<B, T, S, E>(
|
||||
@@ -487,8 +413,29 @@ impl Client {
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
self.call_json_endpoint(reqwest::Method::POST, endpoint, Some(json_body))
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = {
|
||||
wasmtimer::tokio::timeout(
|
||||
self.request_timeout,
|
||||
self.reqwest_client
|
||||
.post(self.base_url.join(endpoint.as_ref())?)
|
||||
.json(json_body)
|
||||
.send(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = {
|
||||
self.reqwest_client
|
||||
.post(self.base_url.join(endpoint.as_ref())?)
|
||||
.json(json_body)
|
||||
.send()
|
||||
.await?
|
||||
};
|
||||
|
||||
parse_response(res, true).await
|
||||
}
|
||||
|
||||
pub async fn delete_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
@@ -497,23 +444,27 @@ impl Client {
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
self.call_json_endpoint(reqwest::Method::DELETE, endpoint, None::<&()>)
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = {
|
||||
wasmtimer::tokio::timeout(
|
||||
self.request_timeout,
|
||||
self.reqwest_client
|
||||
.delete(self.base_url.join(endpoint.as_ref())?)
|
||||
.send(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??
|
||||
};
|
||||
|
||||
pub async fn patch_json_endpoint<B, T, S, E>(
|
||||
&self,
|
||||
endpoint: S,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
self.call_json_endpoint(reqwest::Method::PATCH, endpoint, Some(json_body))
|
||||
.await
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = {
|
||||
self.reqwest_client
|
||||
.delete(self.base_url.join(endpoint.as_ref())?)
|
||||
.send()
|
||||
.await?
|
||||
};
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,19 +509,6 @@ pub trait ApiClient {
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned;
|
||||
|
||||
async fn patch_json<B, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned;
|
||||
|
||||
/// `get` json data from the provided absolute endpoint, i.e. for example `"/api/v1/mixnodes?since=12345"`
|
||||
async fn get_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
@@ -594,17 +532,6 @@ pub trait ApiClient {
|
||||
for<'a> T: Deserialize<'a>,
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str> + Sync + Send;
|
||||
|
||||
async fn patch_json_data_at<B, T, S, E>(
|
||||
&self,
|
||||
endpoint: S,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str> + Sync + Send;
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -654,22 +581,6 @@ impl ApiClient for Client {
|
||||
self.delete_json(path, params).await
|
||||
}
|
||||
|
||||
async fn patch_json<B, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
self.patch_json(path, params, json_body).await
|
||||
}
|
||||
|
||||
async fn get_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
@@ -701,20 +612,6 @@ impl ApiClient for Client {
|
||||
{
|
||||
self.delete_json_endpoint(endpoint).await
|
||||
}
|
||||
|
||||
async fn patch_json_data_at<B, T, S, E>(
|
||||
&self,
|
||||
endpoint: S,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str> + Sync + Send,
|
||||
{
|
||||
self.patch_json_endpoint(endpoint, json_body).await
|
||||
}
|
||||
}
|
||||
|
||||
// utility function that should solve the double slash problem in API urls forever.
|
||||
|
||||
@@ -63,7 +63,6 @@ impl From<v6::request::StaticConnectRequest> for v7::request::StaticConnectReque
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl From<v6::request::DynamicConnectRequest> for v7::request::DynamicConnectRequest {
|
||||
fn from(dynamic_connect_request: v6::request::DynamicConnectRequest) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -51,7 +51,6 @@ impl IpPacketRequest {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub fn new_dynamic_connect_request(
|
||||
reply_to: Recipient,
|
||||
reply_to_hops: Option<u8>,
|
||||
@@ -286,9 +285,6 @@ pub struct DynamicConnectRequest {
|
||||
|
||||
// The number of mix node hops that responses should take, in addition to the entry and exit
|
||||
// node. Zero means only client -> entry -> exit -> client.
|
||||
#[deprecated(
|
||||
note = "clients can no longer control number of hops to use. this field is scheduled for removal in V8"
|
||||
)]
|
||||
pub reply_to_hops: Option<u8>,
|
||||
|
||||
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
|
||||
|
||||
@@ -57,14 +57,3 @@ pub mod wireguard {
|
||||
pub const WG_TUN_DEVICE_IP_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc01, 0, 0, 0, 0, 0, 0, 0x1); // fc01::1
|
||||
pub const WG_TUN_DEVICE_NETMASK_V6: u8 = 112;
|
||||
}
|
||||
|
||||
pub mod mixnet_vpn {
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
// The interface used to route traffic
|
||||
pub const NYM_TUN_BASE_NAME: &str = "nymtun";
|
||||
pub const NYM_TUN_DEVICE_ADDRESS_V4: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1);
|
||||
pub const NYM_TUN_DEVICE_NETMASK_V4: Ipv4Addr = Ipv4Addr::new(255, 255, 0, 0);
|
||||
pub const NYM_TUN_DEVICE_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0x1); // fc00::1
|
||||
pub const NYM_TUN_DEVICE_NETMASK_V6: &str = "112";
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::NetworkTestingError;
|
||||
use crate::node::{NodeType, TestableNode};
|
||||
use crate::node::TestableNode;
|
||||
use crate::NodeId;
|
||||
use nym_sphinx::message::NymMessage;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_topology::{gateway, mix};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -25,76 +26,73 @@ pub struct TestMessage<T = Empty> {
|
||||
}
|
||||
|
||||
impl<T> TestMessage<T> {
|
||||
pub fn new(tested_node: TestableNode, msg_id: u32, total_msgs: u32, ext: T) -> Self {
|
||||
pub fn new<N: Into<TestableNode>>(node: N, msg_id: u32, total_msgs: u32, ext: T) -> Self {
|
||||
TestMessage {
|
||||
tested_node,
|
||||
tested_node: node.into(),
|
||||
msg_id,
|
||||
total_msgs,
|
||||
ext,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mix(node: &RoutingNode, msg_id: u32, total_msgs: u32, ext: T) -> Self {
|
||||
Self::new(
|
||||
TestableNode::new_routing(node, NodeType::Mixnode),
|
||||
msg_id,
|
||||
total_msgs,
|
||||
ext,
|
||||
)
|
||||
pub fn new_mix(node: &mix::LegacyNode, msg_id: u32, total_msgs: u32, ext: T) -> Self {
|
||||
Self::new(node, msg_id, total_msgs, ext)
|
||||
}
|
||||
|
||||
pub fn new_gateway(node: &RoutingNode, msg_id: u32, total_msgs: u32, ext: T) -> Self {
|
||||
Self::new(
|
||||
TestableNode::new_routing(node, NodeType::Gateway),
|
||||
msg_id,
|
||||
total_msgs,
|
||||
ext,
|
||||
)
|
||||
// pub fn new_gateway(node: &gateway::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
|
||||
// Self::new(node, msg_id, total_msgs, ext)
|
||||
// }
|
||||
|
||||
pub fn new_serialized<N>(
|
||||
node: N,
|
||||
msg_id: u32,
|
||||
total_msgs: u32,
|
||||
ext: T,
|
||||
) -> Result<Vec<u8>, NetworkTestingError>
|
||||
where
|
||||
N: Into<TestableNode>,
|
||||
T: Serialize,
|
||||
{
|
||||
Self::new(node, msg_id, total_msgs, ext).as_bytes()
|
||||
}
|
||||
|
||||
pub fn new_plaintexts(
|
||||
node: TestableNode,
|
||||
pub fn new_plaintexts<N>(
|
||||
node: &N,
|
||||
total_msgs: u32,
|
||||
ext: T,
|
||||
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
|
||||
where
|
||||
for<'a> &'a N: Into<TestableNode>,
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let mut msgs = Vec::with_capacity(total_msgs as usize);
|
||||
for msg_id in 1..=total_msgs {
|
||||
msgs.push(Self::new(node.clone(), msg_id, total_msgs, ext.clone()).as_bytes()?)
|
||||
msgs.push(Self::new(node, msg_id, total_msgs, ext.clone()).as_bytes()?)
|
||||
}
|
||||
Ok(msgs)
|
||||
}
|
||||
|
||||
pub fn mix_plaintexts(
|
||||
node: &RoutingNode,
|
||||
node: &mix::LegacyNode,
|
||||
total_msgs: u32,
|
||||
ext: T,
|
||||
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
Self::new_plaintexts(
|
||||
TestableNode::new_routing(node, NodeType::Mixnode),
|
||||
total_msgs,
|
||||
ext,
|
||||
)
|
||||
Self::new_plaintexts(node, total_msgs, ext)
|
||||
}
|
||||
|
||||
pub fn legacy_gateway_plaintexts(
|
||||
node: &RoutingNode,
|
||||
node: &gateway::LegacyNode,
|
||||
node_id: NodeId,
|
||||
total_msgs: u32,
|
||||
ext: T,
|
||||
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
Self::new_plaintexts(
|
||||
TestableNode::new_routing(node, NodeType::Gateway),
|
||||
total_msgs,
|
||||
ext,
|
||||
)
|
||||
Self::new_plaintexts(&(node, node_id), total_msgs, ext)
|
||||
}
|
||||
|
||||
pub fn as_json_string(&self) -> Result<String, NetworkTestingError>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::NodeId;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_topology::{gateway, mix};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
@@ -24,14 +24,6 @@ impl TestableNode {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_routing(routing_node: &RoutingNode, typ: NodeType) -> Self {
|
||||
TestableNode::new(
|
||||
routing_node.identity_key.to_base58_string(),
|
||||
typ,
|
||||
routing_node.node_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_mixnode(encoded_identity: String, node_id: NodeId) -> Self {
|
||||
TestableNode::new(encoded_identity, NodeType::Mixnode, node_id)
|
||||
}
|
||||
@@ -45,6 +37,38 @@ impl TestableNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a mix::LegacyNode> for TestableNode {
|
||||
fn from(value: &'a mix::LegacyNode) -> Self {
|
||||
TestableNode {
|
||||
encoded_identity: value.identity_key.to_base58_string(),
|
||||
typ: NodeType::Mixnode,
|
||||
node_id: value.mix_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<(&'a gateway::LegacyNode, NodeId)> for TestableNode {
|
||||
fn from((gateway, node_id): (&'a gateway::LegacyNode, NodeId)) -> Self {
|
||||
(&(gateway, node_id)).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a (gateway::LegacyNode, NodeId)> for TestableNode {
|
||||
fn from((gateway, node_id): &'a (gateway::LegacyNode, NodeId)) -> Self {
|
||||
(gateway, *node_id).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> From<&'a (&'b gateway::LegacyNode, NodeId)> for TestableNode {
|
||||
fn from((gateway, node_id): &'a (&'b gateway::LegacyNode, NodeId)) -> Self {
|
||||
TestableNode {
|
||||
encoded_identity: gateway.identity_key.to_base58_string(),
|
||||
typ: NodeType::Gateway,
|
||||
node_id: *node_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TestableNode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
|
||||
@@ -2,22 +2,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::NetworkTestingError;
|
||||
use crate::Empty;
|
||||
use crate::NodeId;
|
||||
use crate::TestMessage;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::message::NymMessage;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
|
||||
use nym_sphinx::preparer::{FragmentPreparer, PreparedFragment};
|
||||
use nym_sphinx_params::PacketType;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_topology::{NymRouteProvider, NymTopology, Role};
|
||||
use nym_topology::{gateway, mix, NymTopology};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
pub use nym_topology::node::LegacyMixLayer;
|
||||
|
||||
pub struct NodeTester<R> {
|
||||
rng: R,
|
||||
|
||||
@@ -39,6 +38,10 @@ pub struct NodeTester<R> {
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
|
||||
/// Note that it does not include gateway hops.
|
||||
num_mix_hops: u8,
|
||||
|
||||
// while acks are going to be ignored they still need to be constructed
|
||||
// so that the gateway would be able to correctly process and forward the message
|
||||
ack_key: Arc<AckKey>,
|
||||
@@ -67,27 +70,41 @@ where
|
||||
deterministic_route_selection,
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
|
||||
ack_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn testable_mix_topology(&self, layer: LegacyMixLayer, node: &RoutingNode) -> NymTopology {
|
||||
/// Allows setting non-default number of expected mix hops in the network.
|
||||
#[allow(dead_code)]
|
||||
pub fn with_mix_hops(mut self, hops: u8) -> Self {
|
||||
self.num_mix_hops = hops;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn testable_mix_topology(&self, node: &mix::LegacyNode) -> NymTopology {
|
||||
let mut topology = self.base_topology.clone();
|
||||
topology.set_testable_node(layer.into(), node.clone());
|
||||
topology.set_mixes_in_layer(node.layer as u8, vec![node.clone()]);
|
||||
topology
|
||||
}
|
||||
|
||||
pub fn testable_gateway_topology(&self, node: &RoutingNode) -> NymTopology {
|
||||
pub fn testable_gateway_topology(&self, gateway: &gateway::LegacyNode) -> NymTopology {
|
||||
let mut topology = self.base_topology.clone();
|
||||
topology.set_testable_node(Role::EntryGateway, node.clone());
|
||||
topology.set_testable_node(Role::ExitGateway, node.clone());
|
||||
topology.set_gateways(vec![gateway.clone()]);
|
||||
topology
|
||||
}
|
||||
|
||||
pub fn simple_mixnode_test_packets(
|
||||
&mut self,
|
||||
mix: &mix::LegacyNode,
|
||||
test_packets: u32,
|
||||
) -> Result<Vec<PreparedFragment>, NetworkTestingError> {
|
||||
self.mixnode_test_packets(mix, Empty, test_packets, None)
|
||||
}
|
||||
|
||||
pub fn mixnode_test_packets<T>(
|
||||
&mut self,
|
||||
mix: &RoutingNode,
|
||||
legacy_mix_layer: LegacyMixLayer,
|
||||
mix: &mix::LegacyNode,
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
@@ -95,9 +112,7 @@ where
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let ephemeral_topology =
|
||||
NymRouteProvider::from(self.testable_mix_topology(legacy_mix_layer, mix))
|
||||
.with_ignore_egress_epoch_roles(true);
|
||||
let ephemeral_topology = self.testable_mix_topology(mix);
|
||||
|
||||
let mut packets = Vec::with_capacity(test_packets as usize);
|
||||
for plaintext in TestMessage::mix_plaintexts(mix, test_packets, msg_ext)? {
|
||||
@@ -113,7 +128,7 @@ where
|
||||
|
||||
pub fn mixnodes_test_packets<T>(
|
||||
&mut self,
|
||||
nodes: &[(LegacyMixLayer, RoutingNode)],
|
||||
nodes: &[mix::LegacyNode],
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
@@ -122,10 +137,9 @@ where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let mut packets = Vec::new();
|
||||
for (layer, node) in nodes {
|
||||
for node in nodes {
|
||||
packets.append(&mut self.mixnode_test_packets(
|
||||
node,
|
||||
*layer,
|
||||
msg_ext.clone(),
|
||||
test_packets,
|
||||
custom_recipient,
|
||||
@@ -135,10 +149,9 @@ where
|
||||
Ok(packets)
|
||||
}
|
||||
|
||||
pub fn existing_identity_mixnode_test_packets<T>(
|
||||
pub fn existing_mixnode_test_packets<T>(
|
||||
&mut self,
|
||||
encoded_mix_identity: String,
|
||||
layer: LegacyMixLayer,
|
||||
mix_id: NodeId,
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
@@ -146,30 +159,39 @@ where
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let Ok(identity) = encoded_mix_identity.parse() else {
|
||||
let Some(node) = self.base_topology.find_mix(mix_id) else {
|
||||
return Err(NetworkTestingError::NonExistentMixnode { mix_id });
|
||||
};
|
||||
|
||||
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
|
||||
}
|
||||
|
||||
pub fn existing_identity_mixnode_test_packets<T>(
|
||||
&mut self,
|
||||
encoded_mix_identity: String,
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let Some(node) = self
|
||||
.base_topology
|
||||
.find_mix_by_identity(&encoded_mix_identity)
|
||||
else {
|
||||
return Err(NetworkTestingError::NonExistentMixnodeIdentity {
|
||||
mix_identity: encoded_mix_identity,
|
||||
});
|
||||
};
|
||||
|
||||
let Some(node) = self.base_topology.find_node_by_identity(identity) else {
|
||||
return Err(NetworkTestingError::NonExistentMixnodeIdentity {
|
||||
mix_identity: encoded_mix_identity,
|
||||
});
|
||||
};
|
||||
|
||||
self.mixnode_test_packets(
|
||||
&node.clone(),
|
||||
layer,
|
||||
msg_ext,
|
||||
test_packets,
|
||||
custom_recipient,
|
||||
)
|
||||
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
|
||||
}
|
||||
|
||||
pub fn legacy_gateway_test_packets<T>(
|
||||
&mut self,
|
||||
gateway: &RoutingNode,
|
||||
gateway: &gateway::LegacyNode,
|
||||
node_id: NodeId,
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
@@ -177,11 +199,12 @@ where
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let ephemeral_topology = NymRouteProvider::from(self.testable_gateway_topology(gateway))
|
||||
.with_ignore_egress_epoch_roles(true);
|
||||
let ephemeral_topology = self.testable_gateway_topology(gateway);
|
||||
|
||||
let mut packets = Vec::with_capacity(test_packets as usize);
|
||||
for plaintext in TestMessage::legacy_gateway_plaintexts(gateway, test_packets, msg_ext)? {
|
||||
for plaintext in
|
||||
TestMessage::legacy_gateway_plaintexts(gateway, node_id, test_packets, msg_ext)?
|
||||
{
|
||||
packets.push(self.wrap_plaintext_data(
|
||||
plaintext,
|
||||
&ephemeral_topology,
|
||||
@@ -192,10 +215,36 @@ where
|
||||
Ok(packets)
|
||||
}
|
||||
|
||||
pub fn existing_gateway_test_packets<T>(
|
||||
&mut self,
|
||||
node_id: NodeId,
|
||||
encoded_gateway_identity: String,
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let Some(node) = self.base_topology.find_gateway(&encoded_gateway_identity) else {
|
||||
return Err(NetworkTestingError::NonExistentGateway {
|
||||
gateway_identity: encoded_gateway_identity,
|
||||
});
|
||||
};
|
||||
|
||||
self.legacy_gateway_test_packets(
|
||||
&node.clone(),
|
||||
node_id,
|
||||
msg_ext,
|
||||
test_packets,
|
||||
custom_recipient,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn wrap_plaintext_data(
|
||||
&mut self,
|
||||
plaintext: Vec<u8>,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<PreparedFragment, NetworkTestingError> {
|
||||
let message = NymMessage::new_plain(plaintext);
|
||||
@@ -225,13 +274,14 @@ where
|
||||
&address,
|
||||
&address,
|
||||
PacketType::Mix,
|
||||
None,
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn create_test_packet<T>(
|
||||
&mut self,
|
||||
message: &TestMessage<T>,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<PreparedFragment, NetworkTestingError>
|
||||
where
|
||||
@@ -257,6 +307,10 @@ impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
|
||||
1
|
||||
}
|
||||
|
||||
fn num_mix_hops(&self) -> u8 {
|
||||
self.num_mix_hops
|
||||
}
|
||||
|
||||
fn average_packet_delay(&self) -> Duration {
|
||||
self.average_packet_delay
|
||||
}
|
||||
|
||||
@@ -65,14 +65,6 @@ impl<T> NonExhaustiveDelayQueue<T> {
|
||||
pub fn remove(&mut self, key: &QueueKey) -> Expired<T> {
|
||||
self.inner.remove(key)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for NonExhaustiveDelayQueue<T> {
|
||||
|
||||
@@ -12,6 +12,6 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
prometheus = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
log = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
|
||||
+81
-390
@@ -1,18 +1,14 @@
|
||||
use dashmap::DashMap;
|
||||
pub use log::error;
|
||||
use log::{debug, warn};
|
||||
use std::fmt;
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use prometheus::{
|
||||
core::Collector, Encoder as _, Gauge, Histogram, HistogramOpts, IntCounter, IntGauge, Registry,
|
||||
TextEncoder,
|
||||
};
|
||||
|
||||
pub use prometheus::HistogramTimer;
|
||||
pub use std::time::Instant;
|
||||
|
||||
use prometheus::{core::Collector, Encoder as _, IntCounter, IntGauge, Registry, TextEncoder};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! prepend_package_name {
|
||||
($name: tt) => {
|
||||
($name: literal) => {
|
||||
&format!(
|
||||
"{}_{}",
|
||||
std::module_path!()
|
||||
@@ -27,29 +23,15 @@ macro_rules! prepend_package_name {
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! inc_by {
|
||||
($name:literal, $x:expr, $help: expr) => {
|
||||
$crate::REGISTRY.maybe_register_and_inc_by(
|
||||
$crate::prepend_package_name!($name),
|
||||
$x as i64,
|
||||
$help,
|
||||
);
|
||||
};
|
||||
($name:literal, $x:expr) => {
|
||||
$crate::REGISTRY.maybe_register_and_inc_by(
|
||||
$crate::prepend_package_name!($name),
|
||||
$x as i64,
|
||||
None,
|
||||
);
|
||||
$crate::REGISTRY.inc_by($crate::prepend_package_name!($name), $x as i64);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! inc {
|
||||
($name:literal, $help: expr) => {
|
||||
$crate::REGISTRY.maybe_register_and_inc($crate::prepend_package_name!($name), $help);
|
||||
};
|
||||
($name:literal) => {
|
||||
$crate::REGISTRY.maybe_register_and_inc($crate::prepend_package_name!($name), None);
|
||||
$crate::REGISTRY.inc($crate::prepend_package_name!($name));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,71 +42,6 @@ macro_rules! metrics {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! set_metric {
|
||||
($name:literal, $x:expr, $help: expr) => {
|
||||
$crate::REGISTRY.maybe_register_and_set(
|
||||
$crate::prepend_package_name!($name),
|
||||
$x as i64,
|
||||
$help,
|
||||
);
|
||||
};
|
||||
($name:literal, $x:expr) => {
|
||||
$crate::REGISTRY.maybe_register_and_set(
|
||||
$crate::prepend_package_name!($name),
|
||||
$x as i64,
|
||||
None,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! set_metric_float {
|
||||
($name:literal, $x:expr, $help: expr) => {
|
||||
$crate::REGISTRY.maybe_register_and_set_float(
|
||||
$crate::prepend_package_name!($name),
|
||||
$x as f64,
|
||||
$help,
|
||||
);
|
||||
};
|
||||
($name:literal, $x:expr) => {
|
||||
$crate::REGISTRY.maybe_register_and_set_float(
|
||||
$crate::prepend_package_name!($name),
|
||||
$x as f64,
|
||||
None,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! add_histogram_obs {
|
||||
($name:expr, $x:expr, $b:expr, $help:expr) => {
|
||||
$crate::REGISTRY.maybe_register_and_add_to_histogram(
|
||||
$crate::prepend_package_name!($name),
|
||||
$x as f64,
|
||||
Some($b),
|
||||
$help,
|
||||
);
|
||||
};
|
||||
|
||||
($name:expr, $x:expr, $b:expr) => {
|
||||
$crate::REGISTRY.maybe_register_and_add_to_histogram(
|
||||
$crate::prepend_package_name!($name),
|
||||
$x as f64,
|
||||
Some($b),
|
||||
None,
|
||||
);
|
||||
};
|
||||
($name:expr, $x:expr) => {
|
||||
$crate::REGISTRY.maybe_register_and_add_to_histogram(
|
||||
$crate::prepend_package_name!($name),
|
||||
$x as f64,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! nanos {
|
||||
( $name:literal, $x:expr ) => {{
|
||||
@@ -133,7 +50,7 @@ macro_rules! nanos {
|
||||
let r = $x;
|
||||
let duration = start.elapsed().as_nanos() as i64;
|
||||
let name = $crate::prepend_package_name!($name);
|
||||
$crate::REGISTRY.maybe_register_and_inc_by(&format!("{}_nanos", $name), duration, None);
|
||||
$crate::REGISTRY.inc_by(&format!("{}_nanos", $name), duration);
|
||||
r
|
||||
}};
|
||||
}
|
||||
@@ -142,100 +59,15 @@ lazy_static::lazy_static! {
|
||||
pub static ref REGISTRY: MetricsController = MetricsController::default();
|
||||
}
|
||||
|
||||
pub fn metrics_registry() -> &'static MetricsController {
|
||||
®ISTRY
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MetricsController {
|
||||
registry: Registry,
|
||||
registry_index: DashMap<String, Metric>,
|
||||
}
|
||||
|
||||
pub enum Metric {
|
||||
IntCounter(Box<IntCounter>),
|
||||
IntGauge(Box<IntGauge>),
|
||||
FloatGauge(Box<Gauge>),
|
||||
Histogram(Box<Histogram>),
|
||||
}
|
||||
|
||||
impl Metric {
|
||||
pub fn new_int_counter(name: &str, help: &str) -> Option<Self> {
|
||||
match IntCounter::new(sanitize_metric_name(name), help) {
|
||||
Ok(c) => Some(c.into()),
|
||||
Err(err) => {
|
||||
error!("Failed to create counter {name:?}: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_int_gauge(name: &str, help: &str) -> Option<Self> {
|
||||
match IntGauge::new(sanitize_metric_name(name), help) {
|
||||
Ok(g) => Some(g.into()),
|
||||
Err(err) => {
|
||||
error!("Failed to create gauge {name:?}: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_float_gauge(name: &str, help: &str) -> Option<Self> {
|
||||
match Gauge::new(sanitize_metric_name(name), help) {
|
||||
Ok(g) => Some(g.into()),
|
||||
Err(err) => {
|
||||
error!("Failed to create gauge {name:?}: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_histogram(name: &str, help: &str, buckets: Option<&[f64]>) -> Option<Self> {
|
||||
let mut opts = HistogramOpts::new(sanitize_metric_name(name), help);
|
||||
if let Some(buckets) = buckets {
|
||||
opts = opts.buckets(buckets.to_vec())
|
||||
}
|
||||
match Histogram::with_opts(opts) {
|
||||
Ok(h) => Some(Metric::Histogram(Box::new(h))),
|
||||
Err(err) => {
|
||||
error!("failed to create histogram {name:?}: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_collector(&self) -> Box<dyn Collector> {
|
||||
match self {
|
||||
Metric::IntCounter(c) => c.clone(),
|
||||
Metric::IntGauge(g) => g.clone(),
|
||||
Metric::FloatGauge(g) => g.clone(),
|
||||
Metric::Histogram(h) => h.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IntCounter> for Metric {
|
||||
fn from(v: IntCounter) -> Self {
|
||||
Metric::IntCounter(Box::new(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IntGauge> for Metric {
|
||||
fn from(v: IntGauge) -> Self {
|
||||
Metric::IntGauge(Box::new(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Gauge> for Metric {
|
||||
fn from(v: Gauge) -> Self {
|
||||
Metric::FloatGauge(Box::new(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Histogram> for Metric {
|
||||
fn from(v: Histogram) -> Self {
|
||||
Metric::Histogram(Box::new(v))
|
||||
}
|
||||
enum Metric {
|
||||
C(Box<IntCounter>),
|
||||
G(Box<IntGauge>),
|
||||
}
|
||||
|
||||
fn fq_name(c: &dyn Collector) -> String {
|
||||
@@ -249,92 +81,34 @@ impl Metric {
|
||||
#[inline(always)]
|
||||
fn fq_name(&self) -> String {
|
||||
match self {
|
||||
Metric::IntCounter(c) => fq_name(c.as_ref()),
|
||||
Metric::IntGauge(g) => fq_name(g.as_ref()),
|
||||
Metric::FloatGauge(g) => fq_name(g.as_ref()),
|
||||
Metric::Histogram(h) => fq_name(h.as_ref()),
|
||||
Metric::C(c) => fq_name(c.as_ref()),
|
||||
Metric::G(g) => fq_name(g.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn inc(&self) {
|
||||
match self {
|
||||
Metric::IntCounter(c) => c.inc(),
|
||||
Metric::IntGauge(g) => g.inc(),
|
||||
Metric::FloatGauge(g) => g.inc(),
|
||||
Metric::Histogram(_) => {
|
||||
warn!("invalid operation: attempted to call increment on a histogram")
|
||||
}
|
||||
Metric::C(c) => c.inc(),
|
||||
Metric::G(g) => g.inc(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn inc_by(&self, value: i64) {
|
||||
match self {
|
||||
Metric::IntCounter(c) => c.inc_by(value as u64),
|
||||
Metric::IntGauge(g) => g.add(value),
|
||||
Metric::FloatGauge(g) => {
|
||||
warn!("attempted to increment a float gauge ('{}') by an integer - this is most likely a bug", self.fq_name());
|
||||
g.add(value as f64)
|
||||
}
|
||||
Metric::Histogram(_) => {
|
||||
warn!("invalid operation: attempted to call increment on a histogram")
|
||||
}
|
||||
Metric::C(c) => c.inc_by(value as u64),
|
||||
Metric::G(g) => g.add(value),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn set(&self, value: i64) {
|
||||
match self {
|
||||
Metric::IntCounter(_c) => {
|
||||
Metric::C(_c) => {
|
||||
warn!("Cannot set value for counter {:?}", self.fq_name());
|
||||
}
|
||||
Metric::IntGauge(g) => g.set(value),
|
||||
Metric::FloatGauge(g) => {
|
||||
warn!("attempted to set a float gauge ('{}') to an integer value - this is most likely a bug", self.fq_name());
|
||||
g.set(value as f64)
|
||||
}
|
||||
Metric::Histogram(_) => {
|
||||
warn!("invalid operation: attempted to call set on a histogram")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn set_float(&self, value: f64) {
|
||||
match self {
|
||||
Metric::IntCounter(_c) => {
|
||||
warn!("Cannot set value for counter {:?}", self.fq_name());
|
||||
}
|
||||
Metric::IntGauge(g) => {
|
||||
warn!("attempted to set a integer gauge ('{}') to a float value - this is most likely a bug", self.fq_name());
|
||||
g.set(value as i64)
|
||||
}
|
||||
Metric::FloatGauge(g) => g.set(value),
|
||||
Metric::Histogram(_) => {
|
||||
warn!("invalid operation: attempted to call increment on a histogram")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_histogram_observation(&self, value: f64) {
|
||||
match self {
|
||||
Metric::Histogram(h) => {
|
||||
h.observe(value);
|
||||
}
|
||||
_ => warn!("attempted to add histogram observation on a non-histogram metric"),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn start_timer(&self) -> Option<HistogramTimer> {
|
||||
match self {
|
||||
Metric::Histogram(h) => Some(h.start_timer()),
|
||||
_ => {
|
||||
warn!("attempted to start histogram observation on a non-histogram metric");
|
||||
None
|
||||
}
|
||||
Metric::G(g) => g.set(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,165 +145,93 @@ impl MetricsController {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_int_gauge<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
|
||||
let Some(metric) = Metric::new_int_gauge(name, help.into().unwrap_or(name)) else {
|
||||
return;
|
||||
};
|
||||
self.register_metric(metric);
|
||||
}
|
||||
|
||||
pub fn register_float_gauge<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
|
||||
let Some(metric) = Metric::new_float_gauge(name, help.into().unwrap_or(name)) else {
|
||||
return;
|
||||
};
|
||||
self.register_metric(metric);
|
||||
}
|
||||
|
||||
pub fn register_int_counter<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
|
||||
let Some(metric) = Metric::new_int_counter(name, help.into().unwrap_or(name)) else {
|
||||
return;
|
||||
};
|
||||
self.register_metric(metric);
|
||||
}
|
||||
|
||||
pub fn register_histogram<'a>(
|
||||
&self,
|
||||
name: &str,
|
||||
help: impl Into<Option<&'a str>>,
|
||||
buckets: Option<&[f64]>,
|
||||
) {
|
||||
let Some(metric) = Metric::new_histogram(name, help.into().unwrap_or(name), buckets) else {
|
||||
return;
|
||||
};
|
||||
self.register_metric(metric);
|
||||
}
|
||||
|
||||
pub fn set(&self, name: &str, value: i64) -> bool {
|
||||
pub fn set(&self, name: &str, value: i64) {
|
||||
if let Some(metric) = self.registry_index.get(name) {
|
||||
metric.set(value);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
let gauge = match IntGauge::new(sanitize_metric_name(name), name) {
|
||||
Ok(g) => g,
|
||||
Err(e) => {
|
||||
debug!("Failed to create gauge {:?}:\n{}", name, e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.register_gauge(Box::new(gauge));
|
||||
self.set(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_float(&self, name: &str, value: f64) -> bool {
|
||||
if let Some(metric) = self.registry_index.get(name) {
|
||||
metric.set_float(value);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_to_histogram(&self, name: &str, value: f64) -> bool {
|
||||
if let Some(metric) = self.registry_index.get(name) {
|
||||
metric.add_histogram_observation(value);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_timer(&self, name: &str) -> Option<HistogramTimer> {
|
||||
self.registry_index
|
||||
.get(name)
|
||||
.and_then(|metric| metric.start_timer())
|
||||
}
|
||||
|
||||
pub fn inc(&self, name: &str) -> bool {
|
||||
pub fn inc(&self, name: &str) {
|
||||
if let Some(metric) = self.registry_index.get(name) {
|
||||
metric.inc();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
let counter = match IntCounter::new(sanitize_metric_name(name), name) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
debug!("Failed to create counter {:?}:\n{}", name, e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.register_counter(Box::new(counter));
|
||||
self.inc(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inc_by(&self, name: &str, value: i64) -> bool {
|
||||
pub fn inc_by(&self, name: &str, value: i64) {
|
||||
if let Some(metric) = self.registry_index.get(name) {
|
||||
metric.inc_by(value);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
let counter = match IntCounter::new(sanitize_metric_name(name), name) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
debug!("Failed to create counter {:?}:\n{}", name, e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.register_counter(Box::new(counter));
|
||||
self.inc_by(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_register_and_set<'a>(
|
||||
&self,
|
||||
name: &str,
|
||||
value: i64,
|
||||
help: impl Into<Option<&'a str>>,
|
||||
) {
|
||||
if !self.set(name, value) {
|
||||
let help = help.into();
|
||||
self.register_int_gauge(name, help);
|
||||
self.set(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_register_and_set_float<'a>(
|
||||
&self,
|
||||
name: &str,
|
||||
value: f64,
|
||||
help: impl Into<Option<&'a str>>,
|
||||
) {
|
||||
if !self.set_float(name, value) {
|
||||
let help = help.into();
|
||||
self.register_float_gauge(name, help);
|
||||
self.set_float(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_register_and_add_to_histogram<'a>(
|
||||
&self,
|
||||
name: &str,
|
||||
value: f64,
|
||||
buckets: Option<&[f64]>,
|
||||
help: impl Into<Option<&'a str>>,
|
||||
) {
|
||||
if !self.add_to_histogram(name, value) {
|
||||
let help = help.into();
|
||||
self.register_histogram(name, help, buckets);
|
||||
self.add_to_histogram(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_register_and_inc<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
|
||||
if !self.inc(name) {
|
||||
let help = help.into();
|
||||
self.register_int_counter(name, help);
|
||||
self.inc(name);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_register_and_inc_by<'a>(
|
||||
&self,
|
||||
name: &str,
|
||||
value: i64,
|
||||
help: impl Into<Option<&'a str>>,
|
||||
) {
|
||||
if !self.inc_by(name, value) {
|
||||
let help = help.into();
|
||||
self.register_int_counter(name, help);
|
||||
self.inc_by(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_metric(&self, metric: impl Into<Metric>) {
|
||||
let m = metric.into();
|
||||
let fq_name = m.fq_name();
|
||||
fn register_gauge(&self, metric: Box<IntGauge>) {
|
||||
let fq_name = metric
|
||||
.desc()
|
||||
.first()
|
||||
.map(|d| d.fq_name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
if self.registry_index.contains_key(&fq_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.registry.register(m.as_collector()) {
|
||||
match self.registry.register(metric.clone()) {
|
||||
Ok(_) => {
|
||||
self.registry_index.insert(fq_name, m);
|
||||
self.registry_index
|
||||
.insert(fq_name, Metric::G(metric.clone()));
|
||||
}
|
||||
Err(err) => {
|
||||
debug!("Failed to register '{fq_name}': {err}")
|
||||
Err(e) => {
|
||||
debug!("Failed to register {:?}:\n{}", fq_name, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register_counter(&self, metric: Box<IntCounter>) {
|
||||
let fq_name = metric
|
||||
.desc()
|
||||
.first()
|
||||
.map(|d| d.fq_name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
if self.registry_index.contains_key(&fq_name) {
|
||||
return;
|
||||
}
|
||||
match self.registry.register(metric.clone()) {
|
||||
Ok(_) => {
|
||||
self.registry_index
|
||||
.insert(fq_name, Metric::C(metric.clone()));
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to register {:?}:\n{}", fq_name, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,15 +275,4 @@ mod tests {
|
||||
"packets_sent_34_242_65_133:1789"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepend_package_name() {
|
||||
let literal = prepend_package_name!("foo");
|
||||
assert_eq!(literal, "nym_metrics_foo");
|
||||
|
||||
let bar = "bar";
|
||||
let format = format!("foomp_{}", bar);
|
||||
let formatted = prepend_package_name!(format);
|
||||
assert_eq!(formatted, "nym_metrics_foomp_bar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ rand = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
bs58 = { workspace = true }
|
||||
utoipa = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
rayon = { workspace = true, optional = true }
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
|
||||
@@ -115,7 +115,10 @@ pub fn aggregate_signatures(
|
||||
let params = ecash_group_parameters();
|
||||
// aggregate the signature
|
||||
|
||||
let signature = Aggregatable::aggregate(signatures, indices)?;
|
||||
let signature = match Aggregatable::aggregate(signatures, indices) {
|
||||
Ok(res) => res,
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
// Ensure the aggregated signature is not an infinity point
|
||||
if bool::from(signature.is_at_infinity()) {
|
||||
|
||||
@@ -16,6 +16,7 @@ use group::{Curve, Group};
|
||||
use itertools::Itertools;
|
||||
use std::borrow::Borrow;
|
||||
use std::ops::Neg;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub struct Polynomial {
|
||||
coefficients: Vec<Scalar>,
|
||||
@@ -51,6 +52,17 @@ impl Polynomial {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ToSchema)]
|
||||
#[schema(title = "G2Projective")]
|
||||
pub struct G2ProjectiveSchema {
|
||||
#[schema(content_encoding = "base16")]
|
||||
pub x: [u64; 6],
|
||||
#[schema(content_encoding = "base16")]
|
||||
pub y: [u64; 6],
|
||||
#[schema(content_encoding = "base16")]
|
||||
pub z: [u64; 6],
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn generate_lagrangian_coefficients_at_origin(points: &[u64]) -> Vec<Scalar> {
|
||||
let x = Scalar::zero();
|
||||
|
||||
@@ -8,10 +8,10 @@ use nym_sphinx_addressing::nodes::{
|
||||
NymNodeRoutingAddress, NymNodeRoutingAddressError, MAX_NODE_ADDRESS_UNPADDED_LEN,
|
||||
};
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::PacketType;
|
||||
use nym_sphinx_params::{PacketType, DEFAULT_NUM_MIX_HOPS};
|
||||
use nym_sphinx_types::delays::Delay;
|
||||
use nym_sphinx_types::{NymPacket, NymPacketError, MIN_PACKET_SIZE};
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use nym_topology::{NymTopology, NymTopologyError};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
use std::time;
|
||||
@@ -43,13 +43,14 @@ impl SurbAck {
|
||||
ack_key: &AckKey,
|
||||
marshaled_fragment_id: [u8; 5],
|
||||
average_delay: time::Duration,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
packet_type: PacketType,
|
||||
) -> Result<Self, NymTopologyError>
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let route = topology.random_route_to_egress(rng, recipient.gateway())?;
|
||||
let route =
|
||||
topology.random_route_to_gateway(rng, DEFAULT_NUM_MIX_HOPS, recipient.gateway())?;
|
||||
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
|
||||
let destination = recipient.as_sphinx_destination();
|
||||
|
||||
|
||||
@@ -131,8 +131,8 @@ impl Recipient {
|
||||
&self.client_encryption_key
|
||||
}
|
||||
|
||||
pub fn gateway(&self) -> NodeIdentity {
|
||||
self.gateway
|
||||
pub fn gateway(&self) -> &NodeIdentity {
|
||||
&self.gateway
|
||||
}
|
||||
|
||||
pub fn to_bytes(self) -> RecipientBytes {
|
||||
|
||||
@@ -6,9 +6,9 @@ use nym_crypto::{generic_array::typenum::Unsigned, Digest};
|
||||
use nym_sphinx_addressing::clients::Recipient;
|
||||
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
|
||||
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
|
||||
use nym_sphinx_types::{NymPacket, SURBMaterial, SphinxError, SURB};
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use nym_topology::{NymTopology, NymTopologyError};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use serde::de::{Error as SerdeError, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
@@ -89,12 +89,13 @@ impl ReplySurb {
|
||||
rng: &mut R,
|
||||
recipient: &Recipient,
|
||||
average_delay: time::Duration,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
) -> Result<Self, NymTopologyError>
|
||||
where
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
let route = topology.random_route_to_egress(rng, recipient.gateway())?;
|
||||
let route =
|
||||
topology.random_route_to_gateway(rng, DEFAULT_NUM_MIX_HOPS, recipient.gateway())?;
|
||||
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
|
||||
let destination = recipient.as_sphinx_destination();
|
||||
|
||||
@@ -109,12 +110,15 @@ impl ReplySurb {
|
||||
|
||||
/// Returns the expected number of bytes the [`ReplySURB`] will take after serialization.
|
||||
/// Useful for deserialization from a bytes stream.
|
||||
pub fn serialized_len() -> usize {
|
||||
pub fn serialized_len(mix_hops: u8) -> usize {
|
||||
use nym_sphinx_types::{HEADER_SIZE, NODE_ADDRESS_LENGTH, PAYLOAD_KEY_SIZE};
|
||||
|
||||
// the SURB itself consists of SURB_header, first hop address and set of payload keys
|
||||
// for each hop (3x mix + egress)
|
||||
SurbEncryptionKeySize::USIZE + HEADER_SIZE + NODE_ADDRESS_LENGTH + 4 * PAYLOAD_KEY_SIZE
|
||||
// (note extra 1 for the gateway)
|
||||
SurbEncryptionKeySize::USIZE
|
||||
+ HEADER_SIZE
|
||||
+ NODE_ADDRESS_LENGTH
|
||||
+ (1 + mix_hops as usize) * PAYLOAD_KEY_SIZE
|
||||
}
|
||||
|
||||
pub fn encryption_key(&self) -> &SurbEncryptionKey {
|
||||
|
||||
@@ -169,7 +169,10 @@ impl RepliableMessage {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
|
||||
pub fn try_from_bytes(
|
||||
bytes: &[u8],
|
||||
num_mix_hops: u8,
|
||||
) -> Result<Self, InvalidReplyRequestError> {
|
||||
if bytes.len() < SENDER_TAG_SIZE + 1 {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
@@ -177,8 +180,11 @@ impl RepliableMessage {
|
||||
AnonymousSenderTag::from_bytes(bytes[..SENDER_TAG_SIZE].try_into().unwrap());
|
||||
let content_tag = RepliableMessageContentTag::try_from(bytes[SENDER_TAG_SIZE])?;
|
||||
|
||||
let content =
|
||||
RepliableMessageContent::try_from_bytes(&bytes[SENDER_TAG_SIZE + 1..], content_tag)?;
|
||||
let content = RepliableMessageContent::try_from_bytes(
|
||||
&bytes[SENDER_TAG_SIZE + 1..],
|
||||
num_mix_hops,
|
||||
content_tag,
|
||||
)?;
|
||||
|
||||
Ok(RepliableMessage {
|
||||
sender_tag,
|
||||
@@ -186,20 +192,23 @@ impl RepliableMessage {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serialized_size(&self) -> usize {
|
||||
pub fn serialized_size(&self, num_mix_hops: u8) -> usize {
|
||||
let content_type_size = 1;
|
||||
SENDER_TAG_SIZE + content_type_size + self.content.serialized_size()
|
||||
SENDER_TAG_SIZE + content_type_size + self.content.serialized_size(num_mix_hops)
|
||||
}
|
||||
}
|
||||
|
||||
// this recovery code is shared between all variants containing reply surbs
|
||||
fn recover_reply_surbs(bytes: &[u8]) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
|
||||
fn recover_reply_surbs(
|
||||
bytes: &[u8],
|
||||
num_mix_hops: u8,
|
||||
) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
|
||||
let mut consumed = mem::size_of::<u32>();
|
||||
if bytes.len() < consumed {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
let num_surbs = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
|
||||
let surb_size = ReplySurb::serialized_len();
|
||||
let surb_size = ReplySurb::serialized_len(num_mix_hops);
|
||||
if bytes[consumed..].len() < num_surbs as usize * surb_size {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
@@ -298,13 +307,14 @@ impl RepliableMessageContent {
|
||||
|
||||
fn try_from_bytes(
|
||||
bytes: &[u8],
|
||||
num_mix_hops: u8,
|
||||
tag: RepliableMessageContentTag,
|
||||
) -> Result<Self, InvalidReplyRequestError> {
|
||||
if bytes.is_empty() {
|
||||
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
|
||||
}
|
||||
|
||||
let (reply_surbs, n) = recover_reply_surbs(bytes)?;
|
||||
let (reply_surbs, n) = recover_reply_surbs(bytes, num_mix_hops)?;
|
||||
|
||||
match tag {
|
||||
RepliableMessageContentTag::Data => Ok(RepliableMessageContent::Data {
|
||||
@@ -330,7 +340,7 @@ impl RepliableMessageContent {
|
||||
}
|
||||
}
|
||||
|
||||
fn serialized_size(&self) -> usize {
|
||||
fn serialized_size(&self, num_mix_hops: u8) -> usize {
|
||||
match self {
|
||||
RepliableMessageContent::Data {
|
||||
message,
|
||||
@@ -338,18 +348,19 @@ impl RepliableMessageContent {
|
||||
} => {
|
||||
let num_reply_surbs_tag = mem::size_of::<u32>();
|
||||
num_reply_surbs_tag
|
||||
+ reply_surbs.len() * ReplySurb::serialized_len()
|
||||
+ reply_surbs.len() * ReplySurb::serialized_len(num_mix_hops)
|
||||
+ message.len()
|
||||
}
|
||||
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
|
||||
let num_reply_surbs_tag = mem::size_of::<u32>();
|
||||
num_reply_surbs_tag + reply_surbs.len() * ReplySurb::serialized_len()
|
||||
num_reply_surbs_tag + reply_surbs.len() * ReplySurb::serialized_len(num_mix_hops)
|
||||
}
|
||||
RepliableMessageContent::Heartbeat {
|
||||
additional_reply_surbs,
|
||||
} => {
|
||||
let num_reply_surbs_tag = mem::size_of::<u32>();
|
||||
num_reply_surbs_tag + additional_reply_surbs.len() * ReplySurb::serialized_len()
|
||||
num_reply_surbs_tag
|
||||
+ additional_reply_surbs.len() * ReplySurb::serialized_len(num_mix_hops)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,11 +578,11 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn reply_surb(rng: &mut ChaCha20Rng) -> ReplySurb {
|
||||
pub(super) fn reply_surb(rng: &mut ChaCha20Rng, num_mix_hops: u8) -> ReplySurb {
|
||||
// due to gateway
|
||||
const HOPS: u8 = 4;
|
||||
let route = (0..HOPS).map(|_| node(rng)).collect();
|
||||
let delays = (0..HOPS)
|
||||
let num_hops = num_mix_hops + 1;
|
||||
let route = (0..num_hops).map(|_| node(rng)).collect();
|
||||
let delays = (0..num_hops)
|
||||
.map(|_| Delay::new_from_nanos(rng.next_u64()))
|
||||
.collect();
|
||||
let mut destination_bytes = [0u8; 32];
|
||||
@@ -594,40 +605,47 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn reply_surbs(rng: &mut ChaCha20Rng, n: usize) -> Vec<ReplySurb> {
|
||||
pub(super) fn reply_surbs(
|
||||
rng: &mut ChaCha20Rng,
|
||||
num_mix_hops: u8,
|
||||
n: usize,
|
||||
) -> Vec<ReplySurb> {
|
||||
let mut surbs = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
surbs.push(reply_surb(rng))
|
||||
surbs.push(reply_surb(rng, num_mix_hops))
|
||||
}
|
||||
surbs
|
||||
}
|
||||
|
||||
pub(super) fn repliable_content_data(
|
||||
rng: &mut ChaCha20Rng,
|
||||
num_mix_hops: u8,
|
||||
msg_len: usize,
|
||||
surbs: usize,
|
||||
) -> RepliableMessageContent {
|
||||
RepliableMessageContent::Data {
|
||||
message: random_vec_u8(rng, msg_len),
|
||||
reply_surbs: reply_surbs(rng, surbs),
|
||||
reply_surbs: reply_surbs(rng, num_mix_hops, surbs),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn repliable_content_surbs(
|
||||
rng: &mut ChaCha20Rng,
|
||||
num_mix_hops: u8,
|
||||
surbs: usize,
|
||||
) -> RepliableMessageContent {
|
||||
RepliableMessageContent::AdditionalSurbs {
|
||||
reply_surbs: reply_surbs(rng, surbs),
|
||||
reply_surbs: reply_surbs(rng, num_mix_hops, surbs),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn repliable_content_heartbeat(
|
||||
rng: &mut ChaCha20Rng,
|
||||
num_mix_hops: u8,
|
||||
surbs: usize,
|
||||
) -> RepliableMessageContent {
|
||||
RepliableMessageContent::Heartbeat {
|
||||
additional_reply_surbs: reply_surbs(rng, surbs),
|
||||
additional_reply_surbs: reply_surbs(rng, num_mix_hops, surbs),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,54 +676,70 @@ mod tests {
|
||||
#[test]
|
||||
fn serialized_size_matches_actual_serialization() {
|
||||
let mut rng = fixtures::test_rng();
|
||||
let num_mix_hops = 3;
|
||||
|
||||
let data1 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_data(&mut rng, 10000, 0),
|
||||
content: fixtures::repliable_content_data(&mut rng, num_mix_hops, 10000, 0),
|
||||
};
|
||||
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
|
||||
assert_eq!(
|
||||
data1.serialized_size(num_mix_hops),
|
||||
data1.into_bytes().len()
|
||||
);
|
||||
|
||||
let data2 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_data(&mut rng, 10, 100),
|
||||
content: fixtures::repliable_content_data(&mut rng, num_mix_hops, 10, 100),
|
||||
};
|
||||
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
|
||||
assert_eq!(
|
||||
data2.serialized_size(num_mix_hops),
|
||||
data2.into_bytes().len()
|
||||
);
|
||||
|
||||
let data3 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_data(&mut rng, 100000, 1000),
|
||||
content: fixtures::repliable_content_data(&mut rng, num_mix_hops, 100000, 1000),
|
||||
};
|
||||
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
|
||||
assert_eq!(
|
||||
data3.serialized_size(num_mix_hops),
|
||||
data3.into_bytes().len()
|
||||
);
|
||||
|
||||
let additional_surbs1 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_surbs(&mut rng, 1),
|
||||
content: fixtures::repliable_content_surbs(&mut rng, num_mix_hops, 1),
|
||||
};
|
||||
assert_eq!(
|
||||
additional_surbs1.serialized_size(),
|
||||
additional_surbs1.serialized_size(num_mix_hops),
|
||||
additional_surbs1.into_bytes().len()
|
||||
);
|
||||
|
||||
let additional_surbs2 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_surbs(&mut rng, 1000),
|
||||
content: fixtures::repliable_content_surbs(&mut rng, num_mix_hops, 1000),
|
||||
};
|
||||
assert_eq!(
|
||||
additional_surbs2.serialized_size(),
|
||||
additional_surbs2.serialized_size(num_mix_hops),
|
||||
additional_surbs2.into_bytes().len()
|
||||
);
|
||||
|
||||
let heartbeat1 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_heartbeat(&mut rng, 1),
|
||||
content: fixtures::repliable_content_heartbeat(&mut rng, num_mix_hops, 1),
|
||||
};
|
||||
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
|
||||
assert_eq!(
|
||||
heartbeat1.serialized_size(num_mix_hops),
|
||||
heartbeat1.into_bytes().len()
|
||||
);
|
||||
|
||||
let heartbeat2 = RepliableMessage {
|
||||
sender_tag: fixtures::sender_tag(&mut rng),
|
||||
content: fixtures::repliable_content_heartbeat(&mut rng, 1000),
|
||||
content: fixtures::repliable_content_heartbeat(&mut rng, num_mix_hops, 1000),
|
||||
};
|
||||
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
|
||||
assert_eq!(
|
||||
heartbeat2.serialized_size(num_mix_hops),
|
||||
heartbeat2.into_bytes().len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -716,33 +750,49 @@ mod tests {
|
||||
#[test]
|
||||
fn serialized_size_matches_actual_serialization() {
|
||||
let mut rng = fixtures::test_rng();
|
||||
let num_mix_hops = 3;
|
||||
|
||||
let data1 = fixtures::repliable_content_data(&mut rng, 10000, 0);
|
||||
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
|
||||
|
||||
let data2 = fixtures::repliable_content_data(&mut rng, 10, 100);
|
||||
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
|
||||
|
||||
let data3 = fixtures::repliable_content_data(&mut rng, 100000, 1000);
|
||||
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
|
||||
|
||||
let additional_surbs1 = fixtures::repliable_content_surbs(&mut rng, 1);
|
||||
let data1 = fixtures::repliable_content_data(&mut rng, num_mix_hops, 10000, 0);
|
||||
assert_eq!(
|
||||
additional_surbs1.serialized_size(),
|
||||
data1.serialized_size(num_mix_hops),
|
||||
data1.into_bytes().len()
|
||||
);
|
||||
|
||||
let data2 = fixtures::repliable_content_data(&mut rng, num_mix_hops, 10, 100);
|
||||
assert_eq!(
|
||||
data2.serialized_size(num_mix_hops),
|
||||
data2.into_bytes().len()
|
||||
);
|
||||
|
||||
let data3 = fixtures::repliable_content_data(&mut rng, num_mix_hops, 100000, 1000);
|
||||
assert_eq!(
|
||||
data3.serialized_size(num_mix_hops),
|
||||
data3.into_bytes().len()
|
||||
);
|
||||
|
||||
let additional_surbs1 = fixtures::repliable_content_surbs(&mut rng, num_mix_hops, 1);
|
||||
assert_eq!(
|
||||
additional_surbs1.serialized_size(num_mix_hops),
|
||||
additional_surbs1.into_bytes().len()
|
||||
);
|
||||
|
||||
let additional_surbs2 = fixtures::repliable_content_surbs(&mut rng, 1000);
|
||||
let additional_surbs2 = fixtures::repliable_content_surbs(&mut rng, num_mix_hops, 1000);
|
||||
assert_eq!(
|
||||
additional_surbs2.serialized_size(),
|
||||
additional_surbs2.serialized_size(num_mix_hops),
|
||||
additional_surbs2.into_bytes().len()
|
||||
);
|
||||
|
||||
let heartbeat1 = fixtures::repliable_content_heartbeat(&mut rng, 1);
|
||||
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
|
||||
let heartbeat1 = fixtures::repliable_content_heartbeat(&mut rng, num_mix_hops, 1);
|
||||
assert_eq!(
|
||||
heartbeat1.serialized_size(num_mix_hops),
|
||||
heartbeat1.into_bytes().len()
|
||||
);
|
||||
|
||||
let heartbeat2 = fixtures::repliable_content_heartbeat(&mut rng, 1000);
|
||||
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
|
||||
let heartbeat2 = fixtures::repliable_content_heartbeat(&mut rng, num_mix_hops, 1000);
|
||||
assert_eq!(
|
||||
heartbeat2.serialized_size(num_mix_hops),
|
||||
heartbeat2.into_bytes().len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,11 +69,11 @@ pub mod monitoring {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fragment_sent(fragment: &Fragment, client_nonce: i32, destination: PublicKey) {
|
||||
pub fn fragment_sent(fragment: &Fragment, client_nonce: i32, destination: PublicKey, hops: u8) {
|
||||
if enabled() {
|
||||
let id = fragment.fragment_identifier().set_id();
|
||||
let mut entry = FRAGMENTS_SENT.entry(id).or_default();
|
||||
let s = SentFragment::new(fragment.header(), now!(), client_nonce, destination);
|
||||
let s = SentFragment::new(fragment.header(), now!(), client_nonce, destination, hops);
|
||||
entry.push(s);
|
||||
}
|
||||
}
|
||||
@@ -82,11 +82,16 @@ pub mod monitoring {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FragmentMixParams {
|
||||
destination: PublicKey,
|
||||
hops: u8,
|
||||
}
|
||||
|
||||
impl FragmentMixParams {
|
||||
pub fn destination(&self) -> PublicKey {
|
||||
self.destination
|
||||
pub fn destination(&self) -> &PublicKey {
|
||||
&self.destination
|
||||
}
|
||||
|
||||
pub fn hops(&self) -> u8 {
|
||||
self.hops
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,8 +105,14 @@ pub struct SentFragment {
|
||||
}
|
||||
|
||||
impl SentFragment {
|
||||
fn new(header: FragmentHeader, at: u64, client_nonce: i32, destination: PublicKey) -> Self {
|
||||
let mixnet_params = FragmentMixParams { destination };
|
||||
fn new(
|
||||
header: FragmentHeader,
|
||||
at: u64,
|
||||
client_nonce: i32,
|
||||
destination: PublicKey,
|
||||
hops: u8,
|
||||
) -> Self {
|
||||
let mixnet_params = FragmentMixParams { destination, hops };
|
||||
SentFragment {
|
||||
header,
|
||||
at,
|
||||
|
||||
@@ -10,9 +10,11 @@ use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
|
||||
use nym_sphinx_chunking::fragment::COVER_FRAG_ID;
|
||||
use nym_sphinx_forwarding::packet::MixPacket;
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::{PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketType};
|
||||
use nym_sphinx_params::{
|
||||
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketType, DEFAULT_NUM_MIX_HOPS,
|
||||
};
|
||||
use nym_sphinx_types::NymPacket;
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use nym_topology::{NymTopology, NymTopologyError};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
use std::time;
|
||||
@@ -34,7 +36,7 @@ pub enum CoverMessageError {
|
||||
|
||||
pub fn generate_loop_cover_surb_ack<R>(
|
||||
rng: &mut R,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
ack_key: &AckKey,
|
||||
full_address: &Recipient,
|
||||
average_ack_delay: time::Duration,
|
||||
@@ -57,7 +59,7 @@ where
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn generate_loop_cover_packet<R>(
|
||||
rng: &mut R,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
ack_key: &AckKey,
|
||||
full_address: &Recipient,
|
||||
average_ack_delay: time::Duration,
|
||||
@@ -116,7 +118,8 @@ where
|
||||
.chain(cover_content)
|
||||
.collect();
|
||||
|
||||
let route = topology.random_route_to_egress(rng, full_address.gateway())?;
|
||||
let route =
|
||||
topology.random_route_to_gateway(rng, DEFAULT_NUM_MIX_HOPS, full_address.gateway())?;
|
||||
let delays = nym_sphinx_routing::generate_hop_delays(average_packet_delay, route.len());
|
||||
let destination = full_address.as_sphinx_destination();
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ pub mod packet_sizes;
|
||||
pub mod packet_types;
|
||||
pub mod packet_version;
|
||||
|
||||
// If somebody can provide an argument why it might be reasonable to have more than 255 mix hops,
|
||||
// I will change this to [`usize`]
|
||||
pub const DEFAULT_NUM_MIX_HOPS: u8 = 3;
|
||||
|
||||
// TODO: not entirely sure how to feel about those being defined here, ideally it'd be where [`Fragment`]
|
||||
// is defined, but that'd introduce circular dependencies as the acknowledgements crate also needs
|
||||
// access to that
|
||||
|
||||
@@ -59,30 +59,30 @@ pub enum InvalidPacketSize {
|
||||
pub enum PacketSize {
|
||||
// for example instant messaging use case
|
||||
#[default]
|
||||
#[serde(alias = "regular")]
|
||||
#[serde(rename = "regular")]
|
||||
RegularPacket = 1,
|
||||
|
||||
// for sending SURB-ACKs
|
||||
#[serde(alias = "ack")]
|
||||
#[serde(rename = "ack")]
|
||||
AckPacket = 2,
|
||||
|
||||
// for example for streaming fast and furious in uncompressed 10bit 4K HDR quality
|
||||
#[serde(alias = "extended32")]
|
||||
#[serde(rename = "extended32")]
|
||||
ExtendedPacket32 = 3,
|
||||
|
||||
// for example for streaming fast and furious in heavily compressed lossy RealPlayer quality
|
||||
#[serde(alias = "extended8")]
|
||||
#[serde(rename = "extended8")]
|
||||
ExtendedPacket8 = 4,
|
||||
|
||||
// for example for streaming fast and furious in compressed XviD quality
|
||||
#[serde(alias = "extended16")]
|
||||
#[serde(rename = "extended16")]
|
||||
ExtendedPacket16 = 5,
|
||||
|
||||
#[serde(alias = "outfox_regular")]
|
||||
#[serde(rename = "outfox_regular")]
|
||||
OutfoxRegularPacket = 6,
|
||||
|
||||
// for sending SURB-ACKs
|
||||
#[serde(alias = "outfox_ack")]
|
||||
#[serde(rename = "outfox_ack")]
|
||||
OutfoxAckPacket = 7,
|
||||
}
|
||||
|
||||
|
||||
@@ -23,17 +23,17 @@ pub enum PacketType {
|
||||
/// Represents 'normal' packet sent through the network that should be delayed by an appropriate
|
||||
/// value at each hop.
|
||||
#[default]
|
||||
#[serde(alias = "mix")]
|
||||
#[serde(rename = "mix")]
|
||||
#[serde(alias = "sphinx")]
|
||||
Mix = 0,
|
||||
|
||||
/// Represents a packet that should be sent through the network as fast as possible.
|
||||
#[deprecated]
|
||||
#[serde(alias = "unsupported-mix-vpn")]
|
||||
#[serde(rename = "unsupported-mix-vpn")]
|
||||
Vpn = 1,
|
||||
|
||||
/// Abusing this to add Outfox support
|
||||
#[serde(alias = "outfox")]
|
||||
#[serde(rename = "outfox")]
|
||||
Outfox = 2,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx_types::{delays, Delay};
|
||||
use std::time::Duration;
|
||||
|
||||
use nym_sphinx_addressing::clients::Recipient;
|
||||
use nym_sphinx_types::{delays, Delay, Node};
|
||||
use thiserror::Error;
|
||||
|
||||
pub trait SphinxRouteMaker {
|
||||
type Error;
|
||||
|
||||
fn sphinx_route(&mut self, hops: u8, destination: &Recipient)
|
||||
-> Result<Vec<Node>, Self::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Clone, Copy)]
|
||||
#[error("the route vector contains {available} nodes while {requested} hops are required")]
|
||||
pub struct InvalidNumberOfHops {
|
||||
@@ -12,6 +21,29 @@ pub struct InvalidNumberOfHops {
|
||||
requested: u8,
|
||||
}
|
||||
|
||||
// if one wants to provide a hardcoded route, they can
|
||||
impl SphinxRouteMaker for Vec<Node> {
|
||||
type Error = InvalidNumberOfHops;
|
||||
|
||||
fn sphinx_route(
|
||||
&mut self,
|
||||
hops: u8,
|
||||
_destination: &Recipient,
|
||||
) -> Result<Vec<Node>, InvalidNumberOfHops> {
|
||||
// it's the responsibility of the caller to ensure the hardcoded route has correct number of hops
|
||||
// and that it's final hop include the recipient's gateway.
|
||||
|
||||
if self.len() != hops as usize {
|
||||
Err(InvalidNumberOfHops {
|
||||
available: self.len(),
|
||||
requested: hops,
|
||||
})
|
||||
} else {
|
||||
Ok(self.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_hop_delays(average_packet_delay: Duration, num_hops: usize) -> Vec<Delay> {
|
||||
if average_packet_delay.is_zero() {
|
||||
vec![nym_sphinx_types::Delay::new_from_millis(0); num_hops]
|
||||
|
||||
@@ -149,7 +149,7 @@ impl NymMessage {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn try_from_bytes(bytes: &[u8]) -> Result<Self, NymMessageError> {
|
||||
fn try_from_bytes(bytes: &[u8], num_mix_hops: u8) -> Result<Self, NymMessageError> {
|
||||
if bytes.is_empty() {
|
||||
return Err(NymMessageError::EmptyMessage);
|
||||
}
|
||||
@@ -158,7 +158,7 @@ impl NymMessage {
|
||||
match typ_tag {
|
||||
NymMessageType::Plain => Ok(NymMessage::Plain(bytes[1..].to_vec())),
|
||||
NymMessageType::Repliable => Ok(NymMessage::Repliable(
|
||||
RepliableMessage::try_from_bytes(&bytes[1..])?,
|
||||
RepliableMessage::try_from_bytes(&bytes[1..], num_mix_hops)?,
|
||||
)),
|
||||
NymMessageType::Reply => Ok(NymMessage::Reply(ReplyMessage::try_from_bytes(
|
||||
&bytes[1..],
|
||||
@@ -166,10 +166,10 @@ impl NymMessage {
|
||||
}
|
||||
}
|
||||
|
||||
fn serialized_size(&self) -> usize {
|
||||
fn serialized_size(&self, num_mix_hops: u8) -> usize {
|
||||
let inner_size = match self {
|
||||
NymMessage::Plain(msg) => msg.len(),
|
||||
NymMessage::Repliable(msg) => msg.serialized_size(),
|
||||
NymMessage::Repliable(msg) => msg.serialized_size(num_mix_hops),
|
||||
NymMessage::Reply(msg) => msg.serialized_size(),
|
||||
};
|
||||
let message_type_size = 1;
|
||||
@@ -207,9 +207,9 @@ impl NymMessage {
|
||||
}
|
||||
|
||||
/// Determines the number of required packets of the provided size for the split message.
|
||||
pub fn required_packets(&self, packet_size: PacketSize) -> usize {
|
||||
pub fn required_packets(&self, packet_size: PacketSize, num_mix_hops: u8) -> usize {
|
||||
let plaintext_per_packet = self.true_available_plaintext_per_packet(packet_size);
|
||||
let serialized_len = self.serialized_size();
|
||||
let serialized_len = self.serialized_size(num_mix_hops);
|
||||
|
||||
let (num_fragments, _) =
|
||||
chunking::number_of_required_fragments(serialized_len, plaintext_per_packet);
|
||||
@@ -279,11 +279,11 @@ impl PaddedMessage {
|
||||
}
|
||||
|
||||
// reverse of NymMessage::pad_to_full_packet_lengths
|
||||
pub fn remove_padding(self) -> Result<NymMessage, NymMessageError> {
|
||||
pub fn remove_padding(self, num_mix_hops: u8) -> Result<NymMessage, NymMessageError> {
|
||||
// we are looking for first occurrence of 1 in the tail and we get its index
|
||||
if let Some(padding_end) = self.0.iter().rposition(|b| *b == 1) {
|
||||
// and now we only take bytes until that point (but not including it)
|
||||
NymMessage::try_from_bytes(&self.0[..padding_end])
|
||||
NymMessage::try_from_bytes(&self.0[..padding_end], num_mix_hops)
|
||||
} else {
|
||||
Err(NymMessageError::InvalidMessagePadding)
|
||||
}
|
||||
@@ -304,7 +304,7 @@ mod tests {
|
||||
fn serialized_size_matches_actual_serialization() {
|
||||
// plain
|
||||
let plain = NymMessage::new_plain(vec![1, 2, 3, 4, 5]);
|
||||
assert_eq!(plain.serialized_size(), plain.into_bytes().len());
|
||||
assert_eq!(plain.serialized_size(3), plain.into_bytes().len());
|
||||
|
||||
// a single variant for each repliable and reply is enough as they are more thoroughly tested
|
||||
// internally
|
||||
@@ -313,9 +313,9 @@ mod tests {
|
||||
[42u8; 16].into(),
|
||||
vec![],
|
||||
));
|
||||
assert_eq!(repliable.serialized_size(), repliable.into_bytes().len());
|
||||
assert_eq!(repliable.serialized_size(3), repliable.into_bytes().len());
|
||||
|
||||
let reply = NymMessage::new_reply(ReplyMessage::new_data_message(vec![1, 2, 3, 4, 5]));
|
||||
assert_eq!(reply.serialized_size(), reply.into_bytes().len());
|
||||
assert_eq!(reply.serialized_size(3), reply.into_bytes().len());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ use nym_sphinx_anonymous_replies::reply_surb::ReplySurb;
|
||||
use nym_sphinx_chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use nym_sphinx_forwarding::packet::MixPacket;
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
|
||||
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
|
||||
use nym_sphinx_types::{Delay, NymPacket};
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use nym_topology::{NymTopology, NymTopologyError};
|
||||
use rand::{CryptoRng, Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
@@ -54,13 +54,14 @@ pub trait FragmentPreparer {
|
||||
fn deterministic_route_selection(&self) -> bool;
|
||||
fn rng(&mut self) -> &mut Self::Rng;
|
||||
fn nonce(&self) -> i32;
|
||||
fn num_mix_hops(&self) -> u8;
|
||||
fn average_packet_delay(&self) -> Duration;
|
||||
fn average_ack_delay(&self) -> Duration;
|
||||
|
||||
fn generate_reply_surbs(
|
||||
&mut self,
|
||||
amount: usize,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
reply_recipient: &Recipient,
|
||||
) -> Result<Vec<ReplySurb>, NymTopologyError> {
|
||||
let mut reply_surbs = Vec::with_capacity(amount);
|
||||
@@ -78,7 +79,7 @@ pub trait FragmentPreparer {
|
||||
&mut self,
|
||||
recipient: &Recipient,
|
||||
fragment_id: FragmentIdentifier,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
ack_key: &AckKey,
|
||||
packet_type: PacketType,
|
||||
) -> Result<SurbAck, NymTopologyError> {
|
||||
@@ -108,7 +109,7 @@ pub trait FragmentPreparer {
|
||||
fn prepare_reply_chunk_for_sending(
|
||||
&mut self,
|
||||
fragment: Fragment,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
ack_key: &AckKey,
|
||||
reply_surb: ReplySurb,
|
||||
packet_sender: &Recipient,
|
||||
@@ -129,8 +130,9 @@ pub trait FragmentPreparer {
|
||||
.expect("the message has been incorrectly fragmented");
|
||||
|
||||
// this is not going to be accurate by any means. but that's the best estimation we can do
|
||||
let expected_forward_delay =
|
||||
Delay::new_from_millis((self.average_packet_delay().as_millis() * 3) as u64);
|
||||
let expected_forward_delay = Delay::new_from_millis(
|
||||
(self.average_packet_delay().as_millis() * self.num_mix_hops() as u128) as u64,
|
||||
);
|
||||
|
||||
let fragment_identifier = fragment.fragment_identifier();
|
||||
|
||||
@@ -188,11 +190,12 @@ pub trait FragmentPreparer {
|
||||
fn prepare_chunk_for_sending(
|
||||
&mut self,
|
||||
fragment: Fragment,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
ack_key: &AckKey,
|
||||
packet_sender: &Recipient,
|
||||
packet_recipient: &Recipient,
|
||||
packet_type: PacketType,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Result<PreparedFragment, NymTopologyError> {
|
||||
debug!("Preparing chunk for sending");
|
||||
// each plain or repliable packet (i.e. not a reply) attaches an ephemeral public key so that the recipient
|
||||
@@ -201,7 +204,8 @@ pub trait FragmentPreparer {
|
||||
|
||||
let fragment_header = fragment.header();
|
||||
let destination = packet_recipient.gateway();
|
||||
monitoring::fragment_sent(&fragment, self.nonce(), destination);
|
||||
let hops = mix_hops.unwrap_or(self.num_mix_hops());
|
||||
monitoring::fragment_sent(&fragment, self.nonce(), *destination, hops);
|
||||
|
||||
let non_reply_overhead = encryption::PUBLIC_KEY_SIZE;
|
||||
let expected_plaintext = match packet_type {
|
||||
@@ -236,16 +240,16 @@ pub trait FragmentPreparer {
|
||||
};
|
||||
|
||||
// generate pseudorandom route for the packet
|
||||
log::trace!("Preparing chunk for sending");
|
||||
log::trace!("Preparing chunk for sending with {hops} mix hops");
|
||||
let route = if self.deterministic_route_selection() {
|
||||
log::trace!("using deterministic route selection");
|
||||
let seed = fragment_header.seed().wrapping_mul(self.nonce());
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed as u64);
|
||||
topology.random_route_to_egress(&mut rng, destination)?
|
||||
topology.random_route_to_gateway(&mut rng, hops, destination)?
|
||||
} else {
|
||||
log::trace!("using pseudorandom route selection");
|
||||
let mut rng = self.rng();
|
||||
topology.random_route_to_egress(&mut rng, destination)?
|
||||
topology.random_route_to_gateway(&mut rng, hops, destination)?
|
||||
};
|
||||
|
||||
let destination = packet_recipient.as_sphinx_destination();
|
||||
@@ -331,6 +335,10 @@ pub struct MessagePreparer<R> {
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
|
||||
/// Note that it does not include gateway hops.
|
||||
num_mix_hops: u8,
|
||||
|
||||
nonce: i32,
|
||||
}
|
||||
|
||||
@@ -353,10 +361,17 @@ where
|
||||
sender_address,
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
|
||||
nonce,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows setting non-default number of expected mix hops in the network.
|
||||
pub fn with_mix_hops(mut self, hops: u8) -> Self {
|
||||
self.num_mix_hops = hops;
|
||||
self
|
||||
}
|
||||
|
||||
/// Overwrites existing sender address with the provided value.
|
||||
pub fn set_sender_address(&mut self, sender_address: Recipient) {
|
||||
self.sender_address = sender_address;
|
||||
@@ -365,7 +380,7 @@ where
|
||||
pub fn generate_reply_surbs(
|
||||
&mut self,
|
||||
amount: usize,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
) -> Result<Vec<ReplySurb>, NymTopologyError> {
|
||||
let mut reply_surbs = Vec::with_capacity(amount);
|
||||
for _ in 0..amount {
|
||||
@@ -384,7 +399,7 @@ where
|
||||
pub fn prepare_reply_chunk_for_sending(
|
||||
&mut self,
|
||||
fragment: Fragment,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
ack_key: &AckKey,
|
||||
reply_surb: ReplySurb,
|
||||
packet_type: PacketType,
|
||||
@@ -405,10 +420,11 @@ where
|
||||
pub fn prepare_chunk_for_sending(
|
||||
&mut self,
|
||||
fragment: Fragment,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
ack_key: &AckKey,
|
||||
packet_recipient: &Recipient,
|
||||
packet_type: PacketType,
|
||||
mix_hops: Option<u8>,
|
||||
) -> Result<PreparedFragment, NymTopologyError> {
|
||||
let sender = self.sender_address;
|
||||
|
||||
@@ -420,6 +436,7 @@ where
|
||||
&sender,
|
||||
packet_recipient,
|
||||
packet_type,
|
||||
mix_hops,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -427,7 +444,7 @@ where
|
||||
pub fn generate_surb_ack(
|
||||
&mut self,
|
||||
fragment_id: FragmentIdentifier,
|
||||
topology: &NymRouteProvider,
|
||||
topology: &NymTopology,
|
||||
ack_key: &AckKey,
|
||||
packet_type: PacketType,
|
||||
) -> Result<SurbAck, NymTopologyError> {
|
||||
@@ -466,6 +483,10 @@ impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
|
||||
self.nonce
|
||||
}
|
||||
|
||||
fn num_mix_hops(&self) -> u8 {
|
||||
self.num_mix_hops
|
||||
}
|
||||
|
||||
fn average_packet_delay(&self) -> Duration {
|
||||
self.average_packet_delay
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use nym_sphinx_chunking::reconstruction::MessageReconstructor;
|
||||
use nym_sphinx_chunking::ChunkingError;
|
||||
use nym_sphinx_params::{
|
||||
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySurbEncryptionAlgorithm,
|
||||
DEFAULT_NUM_MIX_HOPS,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -78,6 +79,7 @@ pub enum MessageRecoveryError {
|
||||
pub trait MessageReceiver {
|
||||
fn new() -> Self;
|
||||
fn reconstructor(&mut self) -> &mut MessageReconstructor;
|
||||
fn num_mix_hops(&self) -> u8;
|
||||
|
||||
fn decrypt_raw_message<C>(
|
||||
&self,
|
||||
@@ -141,7 +143,7 @@ pub trait MessageReceiver {
|
||||
fragment: Fragment,
|
||||
) -> Result<Option<(NymMessage, Vec<i32>)>, MessageRecoveryError> {
|
||||
if let Some((message, used_sets)) = self.reconstructor().insert_new_fragment(fragment) {
|
||||
match PaddedMessage::new_reconstructed(message).remove_padding() {
|
||||
match PaddedMessage::new_reconstructed(message).remove_padding(self.num_mix_hops()) {
|
||||
Ok(message) => Ok(Some((message, used_sets))),
|
||||
Err(err) => Err(MessageRecoveryError::MalformedReconstructedMessage {
|
||||
source: err,
|
||||
@@ -154,11 +156,28 @@ pub trait MessageReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct SphinxMessageReceiver {
|
||||
/// High level public structure used to buffer all received data [`Fragment`]s and eventually
|
||||
/// returning original messages that they encapsulate.
|
||||
reconstructor: MessageReconstructor,
|
||||
|
||||
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
|
||||
/// Note that it does not include gateway hops.
|
||||
num_mix_hops: u8,
|
||||
}
|
||||
|
||||
impl SphinxMessageReceiver {
|
||||
/// Allows setting non-default number of expected mix hops in the network.
|
||||
// IMPORTANT NOTE: this is among others used to deserialize SURBs. Meaning that this is a
|
||||
// global setting and currently always set to the default value. The implication is that it is
|
||||
// not currently possible to have different number of hops for different SURB messages. So,
|
||||
// don't try to use <3 mix hops for SURBs until this is refactored.
|
||||
#[must_use]
|
||||
pub fn with_mix_hops(mut self, hops: u8) -> Self {
|
||||
self.num_mix_hops = hops;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageReceiver for SphinxMessageReceiver {
|
||||
@@ -182,4 +201,112 @@ impl MessageReceiver for SphinxMessageReceiver {
|
||||
fn reconstructor(&mut self) -> &mut MessageReconstructor {
|
||||
&mut self.reconstructor
|
||||
}
|
||||
|
||||
fn num_mix_hops(&self) -> u8 {
|
||||
self.num_mix_hops
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SphinxMessageReceiver {
|
||||
fn default() -> Self {
|
||||
SphinxMessageReceiver {
|
||||
reconstructor: Default::default(),
|
||||
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod message_receiver {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_contract_common::LegacyMixLayer;
|
||||
use nym_topology::{gateway, mix, NymTopology};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
// TODO: is it somehow maybe possible to move it to `topology` and have if conditionally
|
||||
// available to other modules?
|
||||
/// Returns a hardcoded, valid instance of [`NymTopology`] that is to be used in
|
||||
/// tests requiring instance of topology.
|
||||
#[allow(dead_code)]
|
||||
fn topology_fixture() -> NymTopology {
|
||||
let mut mixes = BTreeMap::new();
|
||||
mixes.insert(
|
||||
1,
|
||||
vec![mix::LegacyNode {
|
||||
mix_id: 123,
|
||||
host: "10.20.30.40".parse().unwrap(),
|
||||
mix_host: "10.20.30.40:1789".parse().unwrap(),
|
||||
identity_key: identity::PublicKey::from_base58_string(
|
||||
"3ebjp1Fb9hdcS1AR6AZihgeJiMHkB5jjJUsvqNnfQwU7",
|
||||
)
|
||||
.unwrap(),
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(
|
||||
"B3GzG62aXAZNg14RoMCp3BhELNBrySLr2JqrwyfYFzRc",
|
||||
)
|
||||
.unwrap(),
|
||||
layer: LegacyMixLayer::One,
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
);
|
||||
|
||||
mixes.insert(
|
||||
2,
|
||||
vec![mix::LegacyNode {
|
||||
mix_id: 234,
|
||||
host: "11.21.31.41".parse().unwrap(),
|
||||
mix_host: "11.21.31.41:1789".parse().unwrap(),
|
||||
identity_key: identity::PublicKey::from_base58_string(
|
||||
"D6YaMzLSY7mANtSQRKXsmMZpqgqiVkeiagKM4V4oFPFr",
|
||||
)
|
||||
.unwrap(),
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(
|
||||
"5Z1VqYwM2xeKxd8H7fJpGWasNiDFijYBAee7MErkZ5QT",
|
||||
)
|
||||
.unwrap(),
|
||||
layer: LegacyMixLayer::Two,
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
);
|
||||
|
||||
mixes.insert(
|
||||
3,
|
||||
vec![mix::LegacyNode {
|
||||
mix_id: 456,
|
||||
host: "12.22.32.42".parse().unwrap(),
|
||||
mix_host: "12.22.32.42:1789".parse().unwrap(),
|
||||
identity_key: identity::PublicKey::from_base58_string(
|
||||
"GkWDysw4AjESv1KiAiVn7JzzCMJeksxNSXVfr1PpX8wD",
|
||||
)
|
||||
.unwrap(),
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(
|
||||
"9EyjhCggr2QEA2nakR88YHmXgpy92DWxoe2draDRkYof",
|
||||
)
|
||||
.unwrap(),
|
||||
layer: LegacyMixLayer::Three,
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
);
|
||||
|
||||
NymTopology::new(
|
||||
// currently coco_nodes don't really exist so this is still to be determined
|
||||
mixes,
|
||||
vec![gateway::LegacyNode {
|
||||
node_id: 789,
|
||||
host: "1.2.3.4".parse().unwrap(),
|
||||
mix_host: "1.2.3.4:1789".parse().unwrap(),
|
||||
clients_ws_port: 9000,
|
||||
clients_wss_port: None,
|
||||
identity_key: identity::PublicKey::from_base58_string(
|
||||
"FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML",
|
||||
)
|
||||
.unwrap(),
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(
|
||||
"EB42xvMFMD5rUCstE2CDazgQQJ22zLv8SPm1Luxni44c",
|
||||
)
|
||||
.unwrap(),
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,43 +42,8 @@ impl PendingSync {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockProcessorConfig {
|
||||
pub pruning_options: PruningOptions,
|
||||
pub store_precommits: bool,
|
||||
pub explicit_starting_block_height: Option<u32>,
|
||||
pub use_best_effort_start_height: bool,
|
||||
}
|
||||
|
||||
impl Default for BlockProcessorConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pruning_options: PruningOptions::nothing(),
|
||||
store_precommits: true,
|
||||
explicit_starting_block_height: None,
|
||||
use_best_effort_start_height: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockProcessorConfig {
|
||||
pub fn new(
|
||||
pruning_options: PruningOptions,
|
||||
store_precommits: bool,
|
||||
explicit_starting_block_height: Option<u32>,
|
||||
use_best_effort_start_height: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
pruning_options,
|
||||
store_precommits,
|
||||
explicit_starting_block_height,
|
||||
use_best_effort_start_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlockProcessor {
|
||||
config: BlockProcessorConfig,
|
||||
pruning_options: PruningOptions,
|
||||
cancel: CancellationToken,
|
||||
synced: Arc<Notify>,
|
||||
last_processed_height: u32,
|
||||
@@ -100,10 +65,9 @@ pub struct BlockProcessor {
|
||||
msg_modules: Vec<Box<dyn MsgModule + Send>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
impl BlockProcessor {
|
||||
pub async fn new(
|
||||
config: BlockProcessorConfig,
|
||||
pruning_options: PruningOptions,
|
||||
cancel: CancellationToken,
|
||||
synced: Arc<Notify>,
|
||||
incoming: UnboundedReceiver<BlockToProcess>,
|
||||
@@ -117,10 +81,8 @@ impl BlockProcessor {
|
||||
let last_pruned = storage.get_pruned_height().await?;
|
||||
let last_pruned_height = last_pruned.try_into().unwrap_or_default();
|
||||
|
||||
debug!(last_processed_height = %last_processed_height, pruned_height = %last_pruned_height, "setting up block processor...");
|
||||
|
||||
Ok(BlockProcessor {
|
||||
config,
|
||||
pruning_options,
|
||||
cancel,
|
||||
synced,
|
||||
last_processed_height,
|
||||
@@ -139,7 +101,7 @@ impl BlockProcessor {
|
||||
}
|
||||
|
||||
pub fn with_pruning(mut self, pruning_options: PruningOptions) -> Self {
|
||||
self.config.pruning_options = pruning_options;
|
||||
self.pruning_options = pruning_options;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -166,7 +128,7 @@ impl BlockProcessor {
|
||||
// we won't end up with a corrupted storage.
|
||||
let mut tx = self.storage.begin_processing_tx().await?;
|
||||
|
||||
persist_block(&full_info, &mut tx, self.config.store_precommits).await?;
|
||||
persist_block(&full_info, &mut tx).await?;
|
||||
|
||||
// let the modules do whatever they want
|
||||
// the ones wanting the full block:
|
||||
@@ -279,7 +241,7 @@ impl BlockProcessor {
|
||||
|
||||
#[instrument(skip(self))]
|
||||
async fn prune_storage(&mut self) -> Result<(), ScraperError> {
|
||||
let keep_recent = self.config.pruning_options.strategy_keep_recent();
|
||||
let keep_recent = self.pruning_options.strategy_keep_recent();
|
||||
let last_to_keep = self.last_processed_height - keep_recent;
|
||||
|
||||
info!(
|
||||
@@ -320,12 +282,12 @@ impl BlockProcessor {
|
||||
async fn maybe_prune_storage(&mut self) -> Result<(), ScraperError> {
|
||||
debug!("checking for storage pruning");
|
||||
|
||||
if self.config.pruning_options.strategy.is_nothing() {
|
||||
if self.pruning_options.strategy.is_nothing() {
|
||||
trace!("the current pruning strategy is 'nothing'");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let interval = self.config.pruning_options.strategy_interval();
|
||||
let interval = self.pruning_options.strategy_interval();
|
||||
if self.last_pruned_height + interval <= self.last_processed_height {
|
||||
self.prune_storage().await?;
|
||||
}
|
||||
@@ -401,69 +363,20 @@ impl BlockProcessor {
|
||||
// but we need it to help the compiler figure out the future is `Send`
|
||||
async fn startup_resync(&mut self) -> Result<(), ScraperError> {
|
||||
assert!(self.pending_sync.is_empty());
|
||||
info!("attempting to run startup resync...");
|
||||
|
||||
self.maybe_prune_storage().await?;
|
||||
|
||||
let latest_block = self.rpc_client.current_block_height().await? as u32;
|
||||
info!("obtained latest block height: {latest_block}");
|
||||
|
||||
if latest_block > self.last_processed_height && self.last_processed_height != 0 {
|
||||
info!("we have already processed some blocks in the past - attempting to resume...");
|
||||
// in case we were offline for a while,
|
||||
// make sure we don't request blocks we'd have to prune anyway
|
||||
let keep_recent = self.config.pruning_options.strategy_keep_recent();
|
||||
let keep_recent = self.pruning_options.strategy_keep_recent();
|
||||
let last_to_keep = latest_block - keep_recent;
|
||||
|
||||
if !self.config.pruning_options.strategy.is_nothing() {
|
||||
self.last_processed_height = max(self.last_processed_height, last_to_keep);
|
||||
}
|
||||
self.last_processed_height = max(self.last_processed_height, last_to_keep);
|
||||
|
||||
let request_range = self.last_processed_height + 1..latest_block + 1;
|
||||
info!(
|
||||
keep_recent = %keep_recent,
|
||||
last_to_keep = %last_to_keep,
|
||||
last_processed_height = %self.last_processed_height,
|
||||
"we need to request {request_range:?} to resync"
|
||||
);
|
||||
self.request_missing_blocks(request_range).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// this is the first time starting up
|
||||
if self.last_processed_height == 0 {
|
||||
info!("this is the first time starting up");
|
||||
let Some(starting_height) = self.config.explicit_starting_block_height else {
|
||||
info!("no starting block height set - will use the default behaviour");
|
||||
// nothing to do
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
info!("attempting to start the scraper from block {starting_height}");
|
||||
let earliest_available =
|
||||
self.rpc_client.earliest_available_block_height().await? as u32;
|
||||
info!("earliest available block height: {earliest_available}");
|
||||
|
||||
if earliest_available > starting_height && self.config.use_best_effort_start_height {
|
||||
error!("the earliest available block is higher than the desired starting height");
|
||||
return Err(ScraperError::BlocksUnavailable {
|
||||
height: starting_height,
|
||||
});
|
||||
}
|
||||
|
||||
let starting_height = if earliest_available > starting_height {
|
||||
// add few additional blocks to account for all the startup waiting
|
||||
// because the node might have pruned few blocks since
|
||||
earliest_available + 10
|
||||
} else {
|
||||
starting_height
|
||||
};
|
||||
|
||||
let request_range = starting_height..latest_block + 1;
|
||||
|
||||
info!("going to start the scraper from block {starting_height}");
|
||||
info!("we need to request {request_range:?} before properly starting up");
|
||||
|
||||
info!("we need to request {request_range:?} to resync");
|
||||
self.request_missing_blocks(request_range).await?;
|
||||
}
|
||||
|
||||
@@ -471,7 +384,7 @@ impl BlockProcessor {
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
info!("starting block processor processing loop");
|
||||
info!("starting processing loop");
|
||||
|
||||
// sure, we could be more efficient and reset it on every processed block,
|
||||
// but the overhead is so minimal that it doesn't matter
|
||||
|
||||
@@ -84,7 +84,13 @@ impl TryFrom<Event> for BlockToProcess {
|
||||
|
||||
// TODO: we're losing `result_begin_block` and `result_end_block` here but maybe that's fine?
|
||||
let maybe_block = match event.data {
|
||||
EventData::NewBlock { block, .. } => block,
|
||||
// we don't care about `NewBlock` until CometBFT 0.38, i.e. until we upgrade to wasmd 0.50
|
||||
EventData::NewBlock { .. } => {
|
||||
return Err(ScraperError::InvalidSubscriptionEvent {
|
||||
query,
|
||||
kind: "NewBlock".to_string(),
|
||||
})
|
||||
}
|
||||
EventData::LegacyNewBlock { block, .. } => block,
|
||||
EventData::Tx { .. } => {
|
||||
return Err(ScraperError::InvalidSubscriptionEvent {
|
||||
|
||||
@@ -19,9 +19,6 @@ pub enum ScraperError {
|
||||
#[error("the block scraper is already running")]
|
||||
ScraperAlreadyRunning,
|
||||
|
||||
#[error("block information for height {height} is not available on the provided rpc endpoint")]
|
||||
BlocksUnavailable { height: u32 },
|
||||
|
||||
#[error("failed to establish websocket connection to {url}: {source}")]
|
||||
WebSocketConnectionFailure {
|
||||
url: String,
|
||||
|
||||
@@ -15,7 +15,6 @@ pub(crate) mod scraper;
|
||||
pub mod storage;
|
||||
|
||||
pub use block_processor::pruning::{PruningOptions, PruningStrategy};
|
||||
pub use block_processor::types::ParsedTransactionResponse;
|
||||
pub use modules::{BlockModule, MsgModule, TxModule};
|
||||
pub use scraper::{Config, NyxdScraper, StartingBlockOpts};
|
||||
pub use scraper::{Config, NyxdScraper};
|
||||
pub use storage::models;
|
||||
|
||||
@@ -117,17 +117,6 @@ impl RpcClient {
|
||||
Ok(info.last_block_height.value())
|
||||
}
|
||||
|
||||
pub(crate) async fn earliest_available_block_height(&self) -> Result<u64, ScraperError> {
|
||||
debug!("getting earliest available block height");
|
||||
|
||||
let status = self
|
||||
.inner
|
||||
.status()
|
||||
.await
|
||||
.map_err(|source| ScraperError::AbciInfoQueryFailure { source })?;
|
||||
Ok(status.sync_info.earliest_block_height.value())
|
||||
}
|
||||
|
||||
async fn get_transaction_results(
|
||||
&self,
|
||||
raw: &[Vec<u8>],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user