Compare commits

..

8 Commits

Author SHA1 Message Date
Fran Arbanas a0daabab03 fix version 2025-01-16 10:10:16 +01:00
Fran Arbanas b0a5b60945 update version 2025-01-16 10:06:34 +01:00
Fran Arbanas 5f06414a12 bump version 2024-12-20 14:34:34 +01:00
Fran Arbanas 656838811a fix permissions 2024-12-20 14:34:10 +01:00
Fran Arbanas 7b8458630a bump version 2024-12-20 14:22:07 +01:00
Fran Arbanas cf2ab08b4d fix dockerfile 2024-12-20 14:20:43 +01:00
Fran Arbanas 2466112829 test version 2024-12-20 13:19:18 +01:00
Fran Arbanas e5306908e4 feat: add entrypoint script 2024-12-20 13:18:52 +01:00
499 changed files with 5767 additions and 22624 deletions
-1
View File
@@ -1 +0,0 @@
nym-validator-rewarder/.sqlx/** diff=nodiff
-2
View File
@@ -31,5 +31,3 @@ updates:
update-types:
- "patch"
open-pull-requests-limit: 10
assignees:
- "octol"
@@ -79,6 +79,7 @@ jobs:
target/release/nym-socks5-client
target/release/nym-api
target/release/nym-network-requester
target/release/nym-data-observatory
target/release/nym-cli
target/release/nymvisor
target/release/nym-node
@@ -96,6 +97,7 @@ jobs:
cp target/release/nym-socks5-client $OUTPUT_DIR
cp target/release/nym-api $OUTPUT_DIR
cp target/release/nym-network-requester $OUTPUT_DIR
cp target/release/nym-data-observatory $OUTPUT_DIR
cp target/release/nymvisor $OUTPUT_DIR
cp target/release/nym-node $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
+8 -10
View File
@@ -8,18 +8,16 @@ on:
- 'explorer-api/**'
- 'gateway/**'
- 'integrations/**'
- 'nym-api/**'
- 'nym-credential-proxy/**'
- 'nym-network-monitor/**'
- 'nym-node/**'
- 'nym-node-status-api/**'
- 'nym-outfox/**'
- 'nym-validator-rewarder/**'
- 'nyx-chain-watcher/**'
- 'sdk/ffi/**'
- 'mixnode/**'
- 'sdk/rust/**'
- 'sdk/lib/**'
- 'service-providers/**'
- 'nym-browser-extension/storage/**'
- 'nym-network-monitor/**'
- 'nym-api/**'
- 'nym-node/**'
- 'nym-outfox/**'
- 'nym-data-observatory/**'
- 'nym-validator-rewarder/**'
- 'tools/**'
- 'wasm/**'
- 'Cargo.toml'
-1
View File
@@ -1,7 +1,6 @@
name: ci-sdk-wasm
on:
workflow_dispatch:
pull_request:
paths:
- 'wasm/**'
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.44.6
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.44.6
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.44.6
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
@@ -31,7 +31,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.44.6
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.44.6
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.44.6
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.44.6
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.44.6
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -2
View File
@@ -54,8 +54,7 @@ nym-network-monitor/__pycache__
nym-network-monitor/*.key
nym-network-monitor/.envrc
nym-network-monitor/.envrc
nym-api/redocly/formatted-openapi.json
*.sqlite
.build
.build
-85
View File
@@ -4,91 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.1-reeses] (2025-01-15)
- Feture/legacy alert ([#5346])
- chore: readjusted --mode behaviour to fix the regression ([#5331])
- chore: apply 1.84 linter suggestions ([#5330])
- bugfix: make sure refresh data key matches bond info ([#5329])
- reduce log severity for number of packets being delayed ([#5321])
- feat: warn users if node is run in exit mode only ([#5320])
- Bugfix/contract version assignment ([#5318])
- fixed client session histogram buckets ([#5316])
- amend 250gb limit ([#5313])
- feature: expand nym-node prometheus metrics ([#5298])
- Cherry picked #5286 ([#5287])
- Add close to credential storage ([#5283])
- feature: wireguard metrics ([#5278])
- Add PATCH support to nym-http-api-client ([#5260])
- chore: removed legacy socks5 listener ([#5259])
- bugfix: make sure to apply gateway score filtering when choosing initial node ([#5256])
- Update TS bindings ([#5255])
- Add conversion unit tests for auth msg ([#5251])
- Add control messages to GatewayTransciver ([#5247])
- Remove unneeded async function annotation ([#5246])
- bugfix: make sure to update timestamp of last batch verification to prevent double redemption ([#5239])
- Add FromStr impl for UserAgent ([#5236])
- Extend swagger docs ([#5235])
- TicketType derive Hash and Eq ([#5233])
- Add fd callback to client core ([#5230])
- Extend raw ws fd for gateway client ([#5218])
- Shipping raw metrics to PG ([#5216])
- Change sqlite journal mode to WAL ([#5213])
- Derive serialize for UserAgent ([#5210])
- Restore Location fields ([#5208])
- better date serialization ([#5207])
- Fix overflow ([#5204])
- feature: hopefully final steps of the smoosh™️ ([#5201])
- Fix overflow ([#5184])
- NS API - Gateway stats scraping ([#5180])
- introduced initial internal commands for nym-cli: ecash key and request generation ([#5174])
- Move NS client to separate package under NS API ([#5171])
- build(deps): bump micromatch from 4.0.4 to 4.0.8 in /testnet-faucet ([#4813])
[#5346]: https://github.com/nymtech/nym/pull/5346
[#5331]: https://github.com/nymtech/nym/pull/5331
[#5330]: https://github.com/nymtech/nym/pull/5330
[#5329]: https://github.com/nymtech/nym/pull/5329
[#5321]: https://github.com/nymtech/nym/pull/5321
[#5320]: https://github.com/nymtech/nym/pull/5320
[#5318]: https://github.com/nymtech/nym/pull/5318
[#5316]: https://github.com/nymtech/nym/pull/5316
[#5313]: https://github.com/nymtech/nym/pull/5313
[#5298]: https://github.com/nymtech/nym/pull/5298
[#5287]: https://github.com/nymtech/nym/pull/5287
[#5283]: https://github.com/nymtech/nym/pull/5283
[#5278]: https://github.com/nymtech/nym/pull/5278
[#5260]: https://github.com/nymtech/nym/pull/5260
[#5259]: https://github.com/nymtech/nym/pull/5259
[#5256]: https://github.com/nymtech/nym/pull/5256
[#5255]: https://github.com/nymtech/nym/pull/5255
[#5251]: https://github.com/nymtech/nym/pull/5251
[#5247]: https://github.com/nymtech/nym/pull/5247
[#5246]: https://github.com/nymtech/nym/pull/5246
[#5239]: https://github.com/nymtech/nym/pull/5239
[#5236]: https://github.com/nymtech/nym/pull/5236
[#5235]: https://github.com/nymtech/nym/pull/5235
[#5233]: https://github.com/nymtech/nym/pull/5233
[#5230]: https://github.com/nymtech/nym/pull/5230
[#5218]: https://github.com/nymtech/nym/pull/5218
[#5216]: https://github.com/nymtech/nym/pull/5216
[#5213]: https://github.com/nymtech/nym/pull/5213
[#5210]: https://github.com/nymtech/nym/pull/5210
[#5208]: https://github.com/nymtech/nym/pull/5208
[#5207]: https://github.com/nymtech/nym/pull/5207
[#5204]: https://github.com/nymtech/nym/pull/5204
[#5201]: https://github.com/nymtech/nym/pull/5201
[#5184]: https://github.com/nymtech/nym/pull/5184
[#5180]: https://github.com/nymtech/nym/pull/5180
[#5174]: https://github.com/nymtech/nym/pull/5174
[#5171]: https://github.com/nymtech/nym/pull/5171
[#4813]: https://github.com/nymtech/nym/pull/4813
## [2024.14-crunch-patched] (2024-12-17)
- Fixes an issue to allow previously registred clients to connect to latest nym-nodes
- Fixes compatibility issues between nym-nodes and older clients
## [2024.14-crunch] (2024-12-11)
- Merge/release/2024.14-crunch ([#5242])
Generated
+643 -1323
View File
File diff suppressed because it is too large Load Diff
+73 -69
View File
@@ -53,8 +53,8 @@ members = [
"common/execute",
"common/exit-policy",
"common/gateway-requests",
"common/gateway-stats-storage",
"common/gateway-storage",
"common/gateway-stats-storage",
"common/http-api-client",
"common/http-api-common",
"common/inclusion-probability",
@@ -93,7 +93,6 @@ members = [
"common/topology",
"common/tun",
"common/types",
"common/verloc",
"common/wasm/client-core",
"common/wasm/storage",
"common/wasm/utils",
@@ -105,22 +104,6 @@ members = [
"explorer-api/explorer-client",
"gateway",
"integrations/bity",
"nym-api",
"nym-api/nym-api-requests",
"nym-browser-extension/storage",
"nym-credential-proxy/nym-credential-proxy",
"nym-credential-proxy/nym-credential-proxy-requests",
"nym-credential-proxy/vpn-api-lib-wasm",
"nym-network-monitor",
"nym-node",
"nym-node-status-api/nym-node-status-agent",
"nym-node-status-api/nym-node-status-api",
"nym-node-status-api/nym-node-status-client",
"nym-node/nym-node-metrics",
"nym-node/nym-node-requests",
"nym-outfox",
"nym-validator-rewarder",
"nyx-chain-watcher",
"sdk/ffi/cpp",
"sdk/ffi/go",
"sdk/ffi/shared",
@@ -129,16 +112,26 @@ members = [
"service-providers/common",
"service-providers/ip-packet-router",
"service-providers/network-requester",
"nym-api",
"nym-api/nym-api-requests",
"nym-browser-extension/storage",
"nym-credential-proxy/nym-credential-proxy",
"nym-credential-proxy/nym-credential-proxy-requests",
"nym-credential-proxy/vpn-api-lib-wasm",
"nym-network-monitor",
"nyx-chain-watcher",
"nym-node",
"nym-node/nym-node-requests",
"nym-node/nym-node-metrics",
"nym-node-status-api/nym-node-status-agent",
"nym-node-status-api/nym-node-status-api",
"nym-node-status-api/nym-node-status-client",
"nym-outfox",
"nym-validator-rewarder",
"tools/echo-server",
"tools/echo-server",
"tools/internal/contract-state-importer/importer-cli",
"tools/internal/contract-state-importer/importer-contract",
"tools/internal/mixnet-connectivity-check",
# "tools/internal/sdk-version-bump",
"tools/internal/ssl-inject",
# "tools/internal/sdk-version-bump",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/nym-cli",
"tools/nym-id-cli",
@@ -150,6 +143,13 @@ members = [
"wasm/mix-fetch",
"wasm/node-tester",
"wasm/zknym-lib",
"tools/echo-server",
"tools/internal/contract-state-importer/importer-cli",
"tools/internal/contract-state-importer/importer-contract",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"common/verloc",
"tools/internal/mixnet-connectivity-check",
]
default-members = [
@@ -173,6 +173,7 @@ exclude = [
"explorer",
"contracts",
"nym-wallet",
"nym-vpn/ui/src-tauri",
"cpu-cycles",
]
@@ -192,10 +193,9 @@ aes = "0.8.1"
aes-gcm = "0.10.1"
aes-gcm-siv = "0.11.1"
aead = "0.5.2"
anyhow = "1.0.95"
arc-swap = "1.7.1"
anyhow = "1.0.90"
argon2 = "0.5.0"
async-trait = "0.1.85"
async-trait = "0.1.83"
axum-client-ip = "0.6.1"
axum = "0.7.5"
axum-extra = "0.9.4"
@@ -204,7 +204,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 +214,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.26"
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"
criterion = "0.5"
csv = "1.3.1"
const_format = "0.2.33"
criterion = "0.4"
csv = "1.3.0"
ctr = "0.9.1"
cupid = "0.6.1"
curve25519-dalek = "4.1"
@@ -244,8 +244,8 @@ etherparse = "0.13.0"
envy = "0.4"
eyre = "0.6.9"
fastrand = "2.1.1"
flate2 = "1.0.35"
futures = "0.3.31"
flate2 = "1.0.34"
futures = "0.3.28"
futures-util = "0.3"
generic-array = "0.14.7"
getrandom = "0.2.10"
@@ -254,7 +254,6 @@ handlebars = "3.5.5"
headers = "0.4.0"
hex = "0.4.3"
hex-literal = "0.3.3"
hickory-resolver = "0.24.2"
hkdf = "0.12.3"
hmac = "0.12.1"
http = "1"
@@ -265,7 +264,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"
@@ -290,7 +289,7 @@ parking_lot = "0.12.3"
pem = "0.8"
petgraph = "0.6.5"
pin-project = "1.1"
pin-project-lite = "0.2.16"
pin-project-lite = "0.2.14"
pretty_env_logger = "0.4.0"
publicsuffix = "2.2.3"
quote = "1"
@@ -308,11 +307,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.135"
serde_json = "1.0.132"
serde_json_path = "0.7.1"
serde_repr = "0.1"
serde_with = "3.9.0"
@@ -325,36 +324,36 @@ strum = "0.26"
strum_macros = "0.26"
subtle-encoding = "0.5"
syn = "1"
sysinfo = "0.33.0"
sysinfo = "0.30.13"
tap = "1.0.1"
tar = "0.4.43"
tempfile = "3.15"
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.1.0"
ts-rs = "10.0.0"
tungstenite = { version = "0.20.1", default-features = false }
url = "2.5"
utoipa = "5.2"
utoipa-swagger-ui = "8.0"
utoipauto = "0.2"
utoipa = "4.2"
utoipa-swagger-ui = "7.1"
utoipauto = "0.1"
uuid = "*"
vergen = { version = "=8.3.1", default-features = false }
walkdir = "2"
wasm-bindgen-test = "0.3.49"
wasm-bindgen-test = "0.3.43"
x25519-dalek = "2.0.0"
zeroize = "1.6.0"
@@ -387,24 +386,29 @@ cw-controllers = { version = "=1.1.0" }
# cosmrs-related
bip32 = { version = "0.5.2", default-features = false }
cosmrs = { version = "0.21.0" }
tendermint = "0.40.0"
tendermint-rpc = "0.40.0"
prost = { version = "0.13", default-features = false }
# temporarily using a fork again (yay.) because we need staking and slashing support (which are already on main but not released)
# plus response message parsing (which is, as of the time of writing this message, waiting to get merged)
#cosmrs = { path = "../cosmos-rust-fork/cosmos-rust/cosmrs" }
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev = "4b1332e6d8258ac845cef71589c8d362a669675a" } # unfortuntely we need a fork by yours truly to get the staking support
tendermint = "0.37.0" # same version as used by cosmrs
tendermint-rpc = "0.37.0" # same version as used by cosmrs
prost = { version = "0.12", default-features = false }
# wasm-related dependencies
gloo-utils = "0.2.0"
gloo-net = "0.6.0"
gloo-net = "0.5.0"
indexed_db_futures = "0.6.0"
js-sys = "0.3.76"
# use a separate branch due to feature unification failures
# this is blocked until the upstream removes outdates `wasm_bindgen` feature usage
# indexed_db_futures = "0.4.1"
indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", branch = "update-uuid" }
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"
wasmtimer = "0.4.1"
web-sys = "0.3.76"
wasm-bindgen-futures = "0.4.45"
wasmtimer = "0.2.0"
web-sys = "0.3.72"
# Profile settings for individual crates
-23
View File
@@ -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.
+7 -7
View File
@@ -13,8 +13,8 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* `nym-client` - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
* `nym-socks5-client` - a Socks5 proxy you can run on your machine and use with existing applications.
* `nym-explorer` - a (projected) block explorer and (existing) mixnet viewer.
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.app)) framework.
* `nym-cli` - a tool for interacting with the network from the CLI.
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
* `nym-cli` - a tool for interacting with the network from the CLI.
<!-- coming soon
* `nym-network-monitor` - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
-->
@@ -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 Terms and Conditions can be found [here](https://nym.com/operators-validators-terms).
Nym Node Operators and Validators Temrs and Conditions can be found [here](https://nymtech.net/terms-and-conditions/operators/v1.0.0).
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.46"
version = "1.1.45"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.46"
version = "1.1.45"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -28,7 +28,7 @@ pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IpPair {
@@ -553,6 +553,7 @@ pub struct Topology {
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
/// Useless without `ignore_epoch_roles = true`
pub use_extended_topology: bool,
/// Specifies whether this client should ignore the current epoch role of the target egress node
@@ -15,7 +15,6 @@ pub mod error;
mod manager;
mod models;
#[derive(Clone)]
pub struct OnDiskGatewaysDetails {
manager: StorageManager,
}
@@ -20,12 +20,12 @@ pub enum InMemStorageError {
MalformedGateway(#[from] BadGateway),
}
#[derive(Clone, Debug, Default)]
#[derive(Debug, Default)]
pub struct InMemGatewaysDetails {
inner: Arc<RwLock<InMemStorageInner>>,
}
#[derive(Clone, Debug, Default)]
#[derive(Debug, Default)]
struct InMemStorageInner {
active_gateway: Option<String>,
gateways: HashMap<String, GatewayRegistration>,
@@ -115,7 +115,7 @@ where
hardcoded_topology.entry_capable_nodes().cloned().collect()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::gateways_for_init(
crate::init::helpers::current_gateways(
&mut rng,
&core.client.nym_api_urls,
user_agent,
@@ -170,7 +170,7 @@ where
hardcoded_topology.entry_capable_nodes().cloned().collect()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::gateways_for_init(
crate::init::helpers::current_gateways(
&mut rng,
&core.client.nym_api_urls,
user_agent,
@@ -472,7 +472,6 @@ where
.claim_initial_bandwidth()
.await
.map_err(gateway_failure)?;
gateway_client
.start_listening_for_mixnet_messages()
.map_err(gateway_failure)?;
@@ -63,7 +63,7 @@ pub trait MixnetClientStorage {
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
}
#[derive(Clone, Default)]
#[derive(Default)]
pub struct Ephemeral {
key_store: InMemEphemeralKeys,
reply_store: reply_storage::Empty,
@@ -114,7 +114,6 @@ impl MixnetClientStorage for Ephemeral {
}
}
#[derive(Clone)]
#[cfg(all(
not(target_arch = "wasm32"),
feature = "fs-surb-storage",
@@ -4,7 +4,6 @@
use crate::client::key_manager::ClientKeys;
use async_trait::async_trait;
use std::error::Error;
use std::sync::Arc;
use tokio::sync::Mutex;
#[cfg(not(target_arch = "wasm32"))]
@@ -65,7 +64,6 @@ pub enum OnDiskKeysError {
},
}
#[derive(Clone)]
#[cfg(not(target_arch = "wasm32"))]
pub struct OnDiskKeys {
paths: ClientKeysPaths,
@@ -195,9 +193,9 @@ impl KeyStore for OnDiskKeys {
}
}
#[derive(Clone, Default)]
#[derive(Default)]
pub struct InMemEphemeralKeys {
keys: Arc<Mutex<Option<ClientKeys>>>,
keys: Mutex<Option<ClientKeys>>,
}
#[derive(Debug, thiserror::Error)]
@@ -9,12 +9,10 @@ use self::{
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
};
use crate::client::real_messages_control::message_handler::MessageHandler;
use crate::client::replies::reply_controller;
use crate::client::replies::reply_controller::{
ReplyController, ReplyControllerReceiver, ReplyControllerSender,
};
use crate::client::replies::reply_storage::CombinedReplyStorage;
use crate::config;
use crate::{
client::{
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
@@ -29,13 +27,16 @@ use nym_gateway_client::AcknowledgementReceiver;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType;
use nym_statistics_common::clients::ClientStatsSender;
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
use crate::client::replies::reply_controller;
use crate::config;
pub(crate) use acknowledgement_control::{AckActionSender, Action};
use nym_statistics_common::clients::ClientStatsSender;
pub(crate) mod acknowledgement_control;
pub(crate) mod message_handler;
pub(crate) mod real_traffic_stream;
@@ -70,10 +70,7 @@ impl SendingDelayController {
lower_bound,
multiplier_elevated_counter: 0,
time_when_logged_about_elevated_multiplier: now
.checked_sub(Duration::from_secs(
INTERVAL_BETWEEN_WARNING_ABOUT_ELEVATED_MULTIPLIER_SECS,
))
.unwrap_or(now),
- Duration::from_secs(INTERVAL_BETWEEN_WARNING_ABOUT_ELEVATED_MULTIPLIER_SECS),
time_when_changed: now,
time_when_backpressure_detected: now,
}
@@ -16,14 +16,14 @@
#![warn(clippy::todo)]
#![warn(clippy::dbg_macro)]
use futures::StreamExt;
use std::time::Duration;
use nym_client_core_config_types::StatsReporting;
use nym_sphinx::addressing::Recipient;
use nym_statistics_common::clients::{
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
};
use nym_task::connections::TransmissionLane;
use std::time::Duration;
use crate::{
client::inbound_messages::{InputMessage, InputMessageSender},
@@ -94,32 +94,10 @@ impl StatisticsControl {
async fn run_with_shutdown(&mut self, mut task_client: nym_task::TaskClient) {
log::debug!("Started StatisticsControl with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
let mut stats_report_interval = tokio_stream::wrappers::IntervalStream::new(
tokio::time::interval(self.reporting_config.reporting_interval),
);
#[cfg(not(target_arch = "wasm32"))]
let mut local_report_interval = tokio_stream::wrappers::IntervalStream::new(
tokio::time::interval(LOCAL_REPORT_INTERVAL),
);
#[cfg(not(target_arch = "wasm32"))]
let mut snapshot_interval =
tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(SNAPSHOT_INTERVAL));
#[cfg(target_arch = "wasm32")]
let mut stats_report_interval = gloo_timers::future::IntervalStream::new(
self.reporting_config.reporting_interval.as_millis() as u32,
);
#[cfg(target_arch = "wasm32")]
let mut local_report_interval =
gloo_timers::future::IntervalStream::new(LOCAL_REPORT_INTERVAL.as_millis() as u32);
#[cfg(target_arch = "wasm32")]
let mut snapshot_interval =
gloo_timers::future::IntervalStream::new(SNAPSHOT_INTERVAL.as_millis() as u32);
let mut stats_report_interval =
tokio::time::interval(self.reporting_config.reporting_interval);
let mut local_report_interval = tokio::time::interval(LOCAL_REPORT_INTERVAL);
let mut snapshot_interval = tokio::time::interval(SNAPSHOT_INTERVAL);
loop {
tokio::select! {
@@ -130,20 +108,16 @@ impl StatisticsControl {
break;
}
},
_ = snapshot_interval.next() => {
_ = snapshot_interval.tick() => {
self.stats.snapshot();
}
_ = stats_report_interval.next() => {
let Some(recipient) = self.reporting_config.provider_address else {
continue
};
if self.reporting_config.enabled {
self.report_stats(recipient).await;
}
_ = stats_report_interval.tick(), if self.reporting_config.enabled && self.reporting_config.provider_address.is_some() => {
// SAFTEY : this branch executes only if reporting is not none, so unwrapp is fine
#[allow(clippy::unwrap_used)]
self.report_stats(self.reporting_config.provider_address.unwrap()).await;
}
_ = local_report_interval.next() => {
_ = local_report_interval.tick() => {
self.stats.local_report(&mut task_client);
}
_ = task_client.recv_with_delay() => {
+1 -4
View File
@@ -86,7 +86,7 @@ impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
}
}
pub async fn gateways_for_init<R: Rng>(
pub async fn current_gateways<R: Rng>(
rng: &mut R,
nym_apis: &[Url],
user_agent: Option<UserAgent>,
@@ -108,11 +108,8 @@ pub async fn gateways_for_init<R: Rng>(
log::trace!("Gateways: {:#?}", gateways);
// filter out gateways below minimum performance and ones that could operate as a mixnode
// (we don't want instability)
let valid_gateways = gateways
.iter()
.filter(|g| !g.supported_roles.mixnode)
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
.filter_map(|gateway| gateway.try_into().ok())
.collect::<Vec<_>>();
+1 -29
View File
@@ -17,7 +17,7 @@ use nym_topology::node::RoutingNode;
use nym_validator_client::client::IdentityKey;
use nym_validator_client::nyxd::AccountId;
use serde::Serialize;
use std::fmt::{Debug, Display};
use std::fmt::Display;
use std::sync::Arc;
use time::OffsetDateTime;
use url::Url;
@@ -221,34 +221,6 @@ pub enum GatewaySetup {
},
}
impl Debug for GatewaySetup {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GatewaySetup::MustLoad { gateway_id } => f
.debug_struct("GatewaySetup::MustLoad")
.field("gateway_id", gateway_id)
.finish(),
GatewaySetup::New {
specification,
available_gateways,
} => f
.debug_struct("GatewaySetup::New")
.field("specification", specification)
.field("available_gateways", available_gateways)
.field("gateways", specification)
.finish(),
GatewaySetup::ReuseConnection {
gateway_details, ..
} => f
.debug_struct("GatewaySetup::ReuseConnection")
.field("authenticated_ephemeral_client", &"***")
.field("gateway_details", gateway_details)
.field("client_keys", &"***")
.finish(),
}
}
}
impl GatewaySetup {
pub fn try_reuse_connection(init_res: InitialisationResult) -> Result<Self, ClientCoreError> {
if let Some(authenticated_ephemeral_client) = init_res.authenticated_ephemeral_client {
@@ -22,7 +22,7 @@ mod error;
mod manager;
mod models;
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct Backend {
temporary_old_path: Option<PathBuf>,
database_path: PathBuf,
@@ -19,7 +19,7 @@ pub mod fs_backend;
#[error("no information provided")]
pub struct UndefinedError;
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct Empty {
// we need to keep 'basic' metadata here to "load" the CombinedReplyStorage
pub min_surb_threshold: usize,
+1 -3
View File
@@ -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" }
+86 -141
View File
@@ -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,
&current_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]
@@ -19,9 +19,8 @@ use nym_api_requests::ecash::{
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
};
use nym_api_requests::models::{
ApiHealthResponse, GatewayBondAnnotated, GatewayCoreStatusResponse,
HistoricalPerformanceResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
ApiHealthResponse, GatewayBondAnnotated, GatewayCoreStatusResponse, MixnodeCoreStatusResponse,
MixnodeStatusResponse, NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
};
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::SkimmedNode;
@@ -265,31 +264,6 @@ impl<C, S> Client<C, S> {
Ok(self.nym_api.get_gateways_detailed_unfiltered().await?)
}
pub async fn get_full_node_performance_history(
&self,
node_id: NodeId,
) -> Result<Vec<HistoricalPerformanceResponse>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut history = Vec::new();
loop {
let mut res = self
.nym_api
.get_node_performance_history(node_id, Some(page), None)
.await?;
history.append(&mut res.history.data);
if history.len() < res.history.pagination.total {
page += 1
} else {
break;
}
}
Ok(history)
}
// TODO: combine with NymApiClient...
pub async fn get_all_cached_described_nodes(
&self,
@@ -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, PerformanceHistoryResponse, RewardedSetResponse,
NodeRefreshBody, NymNodeDescription, RewardedSetResponse,
};
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
use nym_api_requests::pagination::PaginatedResponse;
@@ -163,35 +163,6 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[tracing::instrument(level = "debug", skip_all)]
async fn get_node_performance_history(
&self,
node_id: NodeId,
page: Option<u32>,
per_page: Option<u32>,
) -> Result<PerformanceHistoryResponse, NymAPIError> {
let mut params = Vec::new();
if let Some(page) = page {
params.push(("page", page.to_string()))
}
if let Some(per_page) = per_page {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_PERFORMANCE_HISTORY,
&*node_id.to_string(),
],
&params,
)
.await
}
#[tracing::instrument(level = "debug", skip_all)]
async fn get_nodes_described(
&self,
@@ -208,15 +179,8 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_DESCRIBED,
],
&params,
)
.await
self.get_json(&[routes::API_VERSION, "nym-nodes", "described"], &params)
.await
}
#[tracing::instrument(level = "debug", skip_all)]
@@ -235,15 +199,8 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_BONDED,
],
&params,
)
.await
self.get_json(&[routes::API_VERSION, "nym-nodes", "bonded"], &params)
.await
}
#[deprecated]
@@ -253,7 +210,7 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
"unstable",
routes::NYM_NODES_ROUTES,
"nym-nodes",
"mixnodes",
"skimmed",
],
@@ -269,7 +226,7 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
"unstable",
routes::NYM_NODES_ROUTES,
"nym-nodes",
"gateways",
"skimmed",
],
@@ -281,11 +238,7 @@ pub trait NymApiClientExt: ApiClient {
#[instrument(level = "debug", skip(self))]
async fn get_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_REWARDED_SET,
],
&[routes::API_VERSION, "nym-nodes", "rewarded-set"],
NO_PARAMS,
)
.await
@@ -318,7 +271,7 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
"unstable",
routes::NYM_NODES_ROUTES,
"nym-nodes",
"skimmed",
"entry-gateways",
"all",
@@ -355,7 +308,7 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
"unstable",
routes::NYM_NODES_ROUTES,
"nym-nodes",
"skimmed",
"mixnodes",
"active",
@@ -392,7 +345,7 @@ pub trait NymApiClientExt: ApiClient {
&[
routes::API_VERSION,
"unstable",
routes::NYM_NODES_ROUTES,
"nym-nodes",
"skimmed",
"mixnodes",
"all",
@@ -424,12 +377,7 @@ pub trait NymApiClientExt: ApiClient {
}
self.get_json(
&[
routes::API_VERSION,
"unstable",
routes::NYM_NODES_ROUTES,
"skimmed",
],
&[routes::API_VERSION, "unstable", "nym-nodes", "skimmed"],
&params,
)
.await
@@ -738,8 +686,8 @@ pub trait NymApiClientExt: ApiClient {
self.get_json(
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_PERFORMANCE,
"nym-nodes",
"performance",
&node_id.to_string(),
],
NO_PARAMS,
@@ -754,8 +702,8 @@ pub trait NymApiClientExt: ApiClient {
self.get_json(
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_ANNOTATION,
"nym-nodes",
"annotation",
&node_id.to_string(),
],
NO_PARAMS,
@@ -979,11 +927,7 @@ pub trait NymApiClientExt: ApiClient {
request: &NodeRefreshBody,
) -> Result<(), NymAPIError> {
self.post_json(
&[
routes::API_VERSION,
routes::NYM_NODES_ROUTES,
routes::NYM_NODES_REFRESH_DESCRIBED,
],
&[routes::API_VERSION, "nym-nodes", "refresh-described"],
NO_PARAMS,
request,
)
@@ -34,19 +34,6 @@ pub mod ecash {
pub const EPOCH_ID_PARAM: &str = "epoch_id";
}
pub const NYM_NODES_ROUTES: &str = "nym-nodes";
pub use nym_nodes::*;
pub mod nym_nodes {
pub const NYM_NODES_PERFORMANCE_HISTORY: &str = "performance-history";
pub const NYM_NODES_PERFORMANCE: &str = "performance";
pub const NYM_NODES_ANNOTATION: &str = "annotation";
pub const NYM_NODES_DESCRIBED: &str = "described";
pub const NYM_NODES_BONDED: &str = "bonded";
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
}
pub const STATUS_ROUTES: &str = "status";
pub const API_STATUS_ROUTES: &str = "api-status";
pub const HEALTH: &str = "health";
@@ -153,20 +153,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
let req = QueryAllBalancesRequest {
address: address.to_string(),
pagination,
resolve_denom: false,
};
let mut res = self
.make_abci_query::<_, QueryAllBalancesResponse>(path.clone(), req)
.await?;
let early_break = res.balances.is_empty();
raw_balances.append(&mut res.balances);
if early_break {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
@@ -194,13 +187,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
.make_abci_query::<_, QueryTotalSupplyResponse>(path.clone(), req)
.await?;
let early_break = res.supply.is_empty();
supply.append(&mut res.supply);
if early_break {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
@@ -341,13 +328,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
.make_abci_query::<_, QueryCodesResponse>(path.clone(), req)
.await?;
let early_break = res.code_infos.is_empty();
raw_codes.append(&mut res.code_infos);
if early_break {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
@@ -392,13 +373,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
.make_abci_query::<_, QueryContractsByCodeResponse>(path.clone(), req)
.await?;
let early_break = res.contracts.is_empty();
raw_contracts.append(&mut res.contracts);
if early_break {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
@@ -454,13 +429,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
.make_abci_query::<_, QueryContractHistoryResponse>(path.clone(), req)
.await?;
let early_break = res.entries.is_empty();
raw_entries.append(&mut res.entries);
if early_break {
break;
}
if let Some(next_key) = next_page_key(res.pagination) {
pagination = Some(create_pagination(next_key))
} else {
@@ -4,11 +4,9 @@
use crate::rpc::TendermintRpcClient;
use async_trait::async_trait;
use base64::Engine;
use cosmrs::tendermint;
use cosmrs::tendermint::{block::Height, evidence::Evidence, Hash};
use reqwest::header::HeaderMap;
use reqwest::{header, RequestBuilder};
use tendermint_rpc::dialect::{v0_34, v0_37, v0_38, LatestDialect};
use tendermint_rpc::{
client::CompatMode,
dialect::{self, Dialect},
@@ -23,21 +21,8 @@ macro_rules! perform_with_compat {
($self:expr, $request:expr) => {{
let request = $request;
match $self.compat {
CompatMode::V0_38 => {
$self
.perform_request_with_dialect(request, dialect::v0_38::Dialect)
.await
}
CompatMode::V0_37 => {
$self
.perform_request_with_dialect(request, dialect::v0_37::Dialect)
.await
}
CompatMode::V0_34 => {
$self
.perform_request_with_dialect(request, dialect::v0_34::Dialect)
.await
}
CompatMode::V0_37 => $self.perform_v0_37(request).await,
CompatMode::V0_34 => $self.perform_v0_34(request).await,
}
}};
}
@@ -85,11 +70,7 @@ impl ReqwestRpcClient {
.headers(headers)
}
async fn perform_request_with_dialect<R, S>(
&self,
request: R,
_dialect: S,
) -> Result<R::Output, Error>
async fn perform_request<R, S>(&self, request: R) -> Result<R::Output, Error>
where
R: SimpleRequest<S>,
S: Dialect,
@@ -100,25 +81,26 @@ impl ReqwestRpcClient {
.send()
.await
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
let response_status = response.status();
let bytes = response
.bytes()
.await
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
// Successful JSON-RPC requests are expected to return a 200 OK HTTP status.
// Otherwise, this means that the HTTP request failed as a whole,
// as opposed to the JSON-RPC request returning an error,
// and we cannot expect the response body to be a valid JSON-RPC response.
if response_status != reqwest::StatusCode::OK {
// hehe, that's so nasty but we have to somehow convert between different versions of the same lib
return Err(Error::http_request_failed(
response_status.as_u16().try_into().unwrap(),
));
}
R::Response::from_string(bytes).map(Into::into)
}
async fn perform_v0_34<R>(&self, request: R) -> Result<R::Output, Error>
where
R: SimpleRequest<dialect::v0_34::Dialect>,
{
self.perform_request(request).await
}
async fn perform_v0_37<R>(&self, request: R) -> Result<R::Output, Error>
where
R: SimpleRequest<dialect::v0_37::Dialect>,
{
self.perform_request(request).await
}
}
trait TendermintRpcErrorMap {
@@ -138,50 +120,18 @@ impl TendermintRpcClient for ReqwestRpcClient {
where
R: SimpleRequest,
{
self.perform_request_with_dialect(request, LatestDialect)
.await
self.perform_request(request).await
}
async fn block<H>(&self, height: H) -> Result<endpoint::block::Response, Error>
async fn block_results<H>(&self, height: H) -> Result<block_results::Response, Error>
where
H: Into<Height> + Send,
{
perform_with_compat!(self, endpoint::block::Request::new(height.into()))
perform_with_compat!(self, block_results::Request::new(height.into()))
}
async fn block_by_hash(
&self,
hash: tendermint::Hash,
) -> Result<endpoint::block_by_hash::Response, Error> {
perform_with_compat!(self, endpoint::block_by_hash::Request::new(hash))
}
async fn latest_block(&self) -> Result<endpoint::block::Response, Error> {
perform_with_compat!(self, endpoint::block::Request::default())
}
async fn block_results<H>(&self, height: H) -> Result<endpoint::block_results::Response, Error>
where
H: Into<Height> + Send,
{
perform_with_compat!(self, endpoint::block_results::Request::new(height.into()))
}
async fn latest_block_results(&self) -> Result<endpoint::block_results::Response, Error> {
perform_with_compat!(self, endpoint::block_results::Request::default())
}
async fn block_search(
&self,
query: Query,
page: u32,
per_page: u8,
order: Order,
) -> Result<endpoint::block_search::Response, Error> {
perform_with_compat!(
self,
endpoint::block_search::Request::new(query, page, per_page, order)
)
async fn latest_block_results(&self) -> Result<block_results::Response, Error> {
perform_with_compat!(self, block_results::Request::default())
}
async fn header<H>(&self, height: H) -> Result<endpoint::header::Response, Error>
@@ -190,26 +140,11 @@ impl TendermintRpcClient for ReqwestRpcClient {
{
let height = height.into();
match self.compat {
CompatMode::V0_38 => {
self.perform_request_with_dialect(
endpoint::header::Request::new(height),
v0_38::Dialect,
)
.await
}
CompatMode::V0_37 => {
self.perform_request_with_dialect(
endpoint::header::Request::new(height),
v0_37::Dialect,
)
.await
}
CompatMode::V0_37 => self.perform(endpoint::header::Request::new(height)).await,
CompatMode::V0_34 => {
// Back-fill with a request to /block endpoint and
// taking just the header from the response.
let resp = self
.perform_request_with_dialect(block::Request::new(height), v0_34::Dialect)
.await?;
let resp = self.perform_v0_34(block::Request::new(height)).await?;
Ok(resp.into())
}
}
@@ -217,25 +152,12 @@ impl TendermintRpcClient for ReqwestRpcClient {
async fn header_by_hash(&self, hash: Hash) -> Result<header_by_hash::Response, Error> {
match self.compat {
CompatMode::V0_38 => {
self.perform_request_with_dialect(
header_by_hash::Request::new(hash),
v0_38::Dialect,
)
.await
}
CompatMode::V0_37 => {
self.perform_request_with_dialect(
header_by_hash::Request::new(hash),
v0_37::Dialect,
)
.await
}
CompatMode::V0_37 => self.perform(header_by_hash::Request::new(hash)).await,
CompatMode::V0_34 => {
// Back-fill with a request to /block_by_hash endpoint and
// taking just the header from the response.
let resp = self
.perform_request_with_dialect(block_by_hash::Request::new(hash), v0_34::Dialect)
.perform_v0_34(block_by_hash::Request::new(hash))
.await?;
Ok(resp.into())
}
@@ -245,18 +167,8 @@ impl TendermintRpcClient for ReqwestRpcClient {
/// `/broadcast_evidence`: broadcast an evidence.
async fn broadcast_evidence(&self, e: Evidence) -> Result<evidence::Response, Error> {
match self.compat {
CompatMode::V0_38 => {
self.perform_request_with_dialect(evidence::Request::new(e), v0_38::Dialect)
.await
}
CompatMode::V0_37 => {
self.perform_request_with_dialect(evidence::Request::new(e), v0_37::Dialect)
.await
}
CompatMode::V0_34 => {
self.perform_request_with_dialect(evidence::Request::new(e), v0_34::Dialect)
.await
}
CompatMode::V0_37 => self.perform(evidence::Request::new(e)).await,
CompatMode::V0_34 => self.perform_v0_34(evidence::Request::new(e)).await,
}
}
@@ -13,7 +13,6 @@ cosmwasm-std = { workspace = true }
cosmwasm-schema = { workspace = true }
cw-storage-plus = { workspace = true }
schemars = { workspace = true }
utoipa = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
@@ -24,5 +23,4 @@ serde_json = { workspace = true }
vergen = { workspace = true, features = ["build", "git", "gitcl", "rustc", "cargo"] }
[features]
naive_float = []
utoipa = ["dep:utoipa"]
naive_float = []
@@ -221,7 +221,6 @@ fn default_unknown() -> String {
// TODO: there's no reason this couldn't be used for proper binaries, but in that case
// perhaps the struct should get renamed and moved to a "more" common crate
#[cw_serde]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ContractBuildInformation {
/// Provides the name of the binary, i.e. the content of `CARGO_PKG_NAME` environmental variable.
#[serde(default = "default_unknown")]
@@ -42,11 +42,9 @@ pub struct Gateway {
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct GatewayBond {
/// Original amount pledged by the operator of this node.
#[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
pub pledge_amount: Coin,
/// Address of the owner of this gateway.
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub owner: Addr,
/// Block height at which this gateway has been bonded.
@@ -57,7 +55,6 @@ pub struct GatewayBond {
/// Entity who bonded this gateway on behalf of the owner.
/// If exists, it's most likely the address of the vesting contract.
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub proxy: Option<Addr>,
}
@@ -82,25 +82,20 @@ impl MixNodeDetails {
// currently this struct is shared between mixnodes and nymnodes
#[cw_serde]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct NodeRewarding {
/// Information provided by the operator that influence the cost function.
pub cost_params: NodeCostParams,
/// Total pledge and compounded reward earned by the node operator.
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub operator: Decimal,
/// Total delegation and compounded reward earned by all node delegators.
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub delegates: Decimal,
/// Cumulative reward earned by the "unit delegation" since the block 0.
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub total_unit_reward: Decimal,
/// Value of the theoretical "unit delegation" that has delegated to this node at block 0.
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub unit_delegation: Decimal,
/// Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt
@@ -497,17 +492,14 @@ impl NodeRewarding {
::cosmwasm_schema::schemars::JsonSchema,
)]
#[schemars(crate = "::cosmwasm_schema::schemars")]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct MixNodeBond {
/// Unique id assigned to the bonded mixnode.
pub mix_id: NodeId,
/// Address of the owner of this mixnode.
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub owner: Addr,
/// Original amount pledged by the operator of this node.
#[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
pub original_pledge: Coin,
// REMOVED (but might be needed due to legacy things, idk yet)
@@ -518,7 +510,6 @@ pub struct MixNodeBond {
/// Entity who bonded this mixnode on behalf of the owner.
/// If exists, it's most likely the address of the vesting contract.
#[cfg_attr(feature = "utoipa", schema(value_type = Option<String>))]
pub proxy: Option<Addr>,
/// Block height at which this mixnode has been bonded.
@@ -554,7 +545,6 @@ impl MixNodeBond {
feature = "generate-ts",
ts(export, export_to = "ts-packages/types/src/types/rust/Mixnode.ts")
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct MixNode {
/// Network address of this mixnode, for example 1.1.1.1 or foo.mixnode.com
pub host: String,
@@ -581,14 +571,11 @@ pub struct MixNode {
/// The cost parameters, or the cost function, defined for the particular mixnode that influences
/// how the rewards should be split between the node operator and its delegators.
#[cw_serde]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct NodeCostParams {
/// The profit margin of the associated node, i.e. the desired percent of the reward to be distributed to the operator.
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub profit_margin_percent: Percent,
/// Operating cost of the associated node per the entire interval.
#[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
pub interval_operating_cost: Coin,
}
@@ -693,9 +680,7 @@ pub struct PendingMixNodeChanges {
}
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct LegacyPendingMixNodeChanges {
#[cfg_attr(feature = "utoipa", schema(value_type = Option<u32>))]
pub pledge_change: Option<EpochEventId>,
}
@@ -863,4 +863,11 @@ pub enum QueryMsg {
pub struct MigrateMsg {
pub unsafe_skip_state_updates: Option<bool>,
pub vesting_contract_address: Option<String>,
pub current_nym_node_semver: String,
#[serde(default)]
pub version_score_weights: OutdatedVersionWeights,
#[serde(default)]
pub version_score_params: VersionScoreFormulaParams,
}
@@ -113,10 +113,6 @@ impl Role {
pub fn is_standby(&self) -> bool {
matches!(self, Role::Standby)
}
pub fn is_mixnode(&self) -> bool {
matches!(self, Role::Layer1 | Role::Layer2 | Role::Layer3)
}
}
impl Display for Role {
@@ -235,7 +231,6 @@ pub struct RoleMetadata {
/// Full details associated with given node.
#[cw_serde]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct NymNodeDetails {
/// Basic bond information of this node, such as owner address, original pledge, etc.
pub bond_information: NymNodeBond,
@@ -293,19 +288,14 @@ impl NymNodeDetails {
}
#[cw_serde]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct NymNodeBond {
/// Unique id assigned to the bonded node.
#[cfg_attr(feature = "utoipa", schema(value_type = u32))]
pub node_id: NodeId,
/// Address of the owner of this nym-node.
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub owner: Addr,
/// Original amount pledged by the operator of this node.
#[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
pub original_pledge: Coin,
/// Block height at which this nym-node has been bonded.
@@ -358,7 +348,6 @@ impl NymNodeBond {
feature = "generate-ts",
ts(export, export_to = "ts-packages/types/src/types/rust/NymNode.ts")
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct NymNode {
/// Network address of this nym-node, for example 1.1.1.1 or foo.mixnode.com
/// that is used to discover other capabilities of this node.
@@ -369,7 +358,6 @@ pub struct NymNode {
pub custom_http_port: Option<u16>,
/// Base58-encoded ed25519 EdDSA public key.
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub identity_key: IdentityKey,
// TODO: I don't think we want to include sphinx keys here,
// given we want to rotate them and keeping that in sync with contract will be a PITA
@@ -447,11 +435,8 @@ pub struct NodeConfigUpdate {
export_to = "ts-packages/types/src/types/rust/PendingNodeChanges.ts"
)
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct PendingNodeChanges {
#[cfg_attr(feature = "utoipa", schema(value_type = Option<u32>))]
pub pledge_change: Option<EpochEventId>,
#[cfg_attr(feature = "utoipa", schema(value_type = Option<u32>))]
pub cost_params_change: Option<IntervalEventId>,
}
@@ -21,37 +21,31 @@ pub type WorkFactor = Decimal;
)]
#[cw_serde]
#[derive(Copy)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct IntervalRewardParams {
/// Current value of the rewarding pool.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub reward_pool: Decimal,
/// Current value of the staking supply.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub staking_supply: Decimal,
/// Defines the percentage of stake needed to reach saturation for all of the nodes in the rewarded set.
/// Also known as `beta`.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub staking_supply_scale_factor: Percent,
// computed values
/// Current value of the computed reward budget per epoch, per node.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub epoch_reward_budget: Decimal,
/// Current value of the stake saturation point.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub stake_saturation_point: Decimal,
// constants(-ish)
@@ -60,7 +54,6 @@ pub struct IntervalRewardParams {
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub sybil_resistance: Percent,
// default: 10
@@ -68,7 +61,6 @@ pub struct IntervalRewardParams {
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub active_set_work_factor: Decimal,
// default: 2%
@@ -78,7 +70,6 @@ pub struct IntervalRewardParams {
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub interval_pool_emission: Percent,
}
@@ -99,7 +90,6 @@ impl IntervalRewardParams {
)]
#[cw_serde]
#[derive(Copy)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RewardingParams {
/// Parameters that should remain unchanged throughout an interval.
pub interval: IntervalRewardParams,
@@ -264,7 +254,6 @@ impl RewardingParams {
)]
#[cw_serde]
#[derive(Copy)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RewardedSetParams {
/// The expected number of nodes assigned entry gateway role (i.e. [`Role::EntryGateway`])
pub entry_gateways: u32,
@@ -17,12 +17,10 @@ pub mod simulator;
)]
#[cw_serde]
#[derive(Copy, Default)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct RewardEstimate {
/// The amount of **decimal** coins that are going to get distributed to the node,
/// i.e. the operator and all its delegators.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub total_node_reward: Decimal,
// note that operator reward includes the operating_cost,
@@ -30,17 +28,14 @@ pub struct RewardEstimate {
// in that case the operator reward would still be `1nym` as opposed to 0
/// The share of the reward that is going to get distributed to the node operator.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub operator: Decimal,
/// The share of the reward that is going to get distributed among the node delegators.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub delegates: Decimal,
/// The operating cost of this node. Note: it's already included in the operator reward.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
#[cfg_attr(feature = "utoipa", schema(value_type = String))]
pub operating_cost: Decimal,
}
@@ -175,14 +175,6 @@ where
}
}
#[cfg(feature = "utoipa")]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "utoipa", schema(title = "Coin"))]
pub struct CoinSchema {
pub denom: String,
pub amount: String,
}
/// The current state of the mixnet contract.
#[cw_serde]
pub struct ContractState {
+1 -1
View File
@@ -19,7 +19,7 @@ use std::error::Error;
// `SELECT total_tickets, used_tickets FROM ecash_ticketbook WHERE expiration_date >= ?`, today_date
// then for each calculate the diff total_tickets - used_tickets and multiply the result by the size of the ticket
#[async_trait]
pub trait Storage: Clone + Send + Sync {
pub trait Storage: Send + Sync {
type StorageError: Error;
async fn close(&self);
-1
View File
@@ -16,7 +16,6 @@ serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
strum = { workspace = true, features = ["derive"] }
time = { workspace = true, features = ["serde"] }
utoipa = { workspace = true }
rand = { workspace = true }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
@@ -86,6 +86,7 @@ impl Display for AddressPolicyAction {
/// ```
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "openapi", aliases(ExitPolicy))]
pub struct AddressPolicy {
/// A list of rules to apply to find out whether an address is
/// contained by this policy.
@@ -726,10 +727,10 @@ mod test {
let policy = AddressPolicy::parse_from_torrc(
r#"
ExitPolicy reject 1.2.3.4/32:*
ExitPolicy reject 1.2.3.5:*
ExitPolicy reject 1.2.3.5:*
ExitPolicy reject 1.2.3.6/16:*
ExitPolicy reject 1.2.3.6/16:123-456
ExitPolicy accept *:53
ExitPolicy reject 1.2.3.6/16:123-456
ExitPolicy accept *:53
ExitPolicy accept6 *6:119
ExitPolicy accept *4:120
ExitPolicy reject6 [FC00::]/7:*
+1
View File
@@ -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;
+39 -28
View File
@@ -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);
@@ -126,13 +146,4 @@ impl InboxManager {
.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(())
}
}
+2 -2
View File
@@ -3,6 +3,7 @@
use bandwidth::BandwidthManager;
use clients::{ClientManager, ClientType};
use inboxes::InboxManager;
use models::{
Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage,
VerifiedTicket, WireguardPeer,
@@ -30,7 +31,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)]
@@ -53,7 +53,7 @@ impl GatewayStorage {
&self.shared_key_manager
}
pub fn inbox_manager(&self) -> &InboxManager {
pub(crate) fn inbox_manager(&self) -> &InboxManager {
&self.inbox_manager
}
-2
View File
@@ -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)]
-7
View File
@@ -15,7 +15,6 @@ async-trait = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
http.workspace = true
url = { workspace = true }
once_cell = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
@@ -23,13 +22,7 @@ tracing = { workspace = true }
nym-bin-common = { path = "../bin-common" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "webpki-roots"] }
# for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
workspace = true
features = ["tokio"]
[dev-dependencies]
tokio = { workspace = true, features=["rt", "macros"] }
-177
View File
@@ -1,177 +0,0 @@
//! DNS resolver configuration for internal lookups.
//!
//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints
//! supporting DoH and DoT.
//!
//! This resolver implements a fallback mechanism where, should the DNS-over-TLS resolution fail, a
//! followup resolution will be done using the hosts configured default (e.g. `/etc/resolve.conf` on
//! linux).
//!
//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the
//! `hickory-resolver` crate
#![deny(missing_docs)]
use crate::ClientBuilder;
use std::{net::SocketAddr, sync::Arc};
use hickory_resolver::lookup_ip::LookupIp;
use hickory_resolver::{
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
error::ResolveError,
lookup_ip::LookupIpIntoIter,
TokioAsyncResolver,
};
use once_cell::sync::OnceCell;
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use tracing::warn;
impl ClientBuilder {
/// Override the DNS resolver implementation used by the underlying http client.
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver);
self
}
}
struct SocketAddrs {
iter: LookupIpIntoIter,
}
#[derive(Debug, thiserror::Error)]
#[error("hickory-dns resolver error: {hickory_error}")]
pub struct HickoryDnsError {
#[from]
hickory_error: ResolveError,
}
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
#[derive(Debug, Default, Clone)]
pub struct HickoryDnsResolver {
/// Since we might not have been called in the context of a
/// Tokio Runtime in initialization, so we must delay the actual
/// construction of the resolver.
state: Arc<OnceCell<TokioAsyncResolver>>,
fallback: Arc<OnceCell<TokioAsyncResolver>>,
}
impl Resolve for HickoryDnsResolver {
fn resolve(&self, name: Name) -> Resolving {
let resolver = self.state.clone();
let fallback = self.fallback.clone();
Box::pin(async move {
let resolver = resolver.get_or_try_init(new_resolver)?;
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name.as_str()).await {
Ok(res) => res,
Err(e) => {
// on failure use the fall back system configured DNS resolver
warn!("primary DNS failed w/ error {e}: using system fallback");
let resolver = fallback.get_or_try_init(new_resolver_system)?;
resolver.lookup_ip(name.as_str()).await?
}
};
let addrs: Addrs = Box::new(SocketAddrs {
iter: lookup.into_iter(),
});
Ok(addrs)
})
}
}
impl Iterator for SocketAddrs {
type Item = SocketAddr;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
}
}
impl HickoryDnsResolver {
/// Attempt to resolve a domain name to a set of ['IpAddr']s
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
let resolver = self.state.get_or_try_init(new_resolver)?;
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name).await {
Ok(res) => res,
Err(e) => {
// on failure use the fall back system configured DNS resolver
warn!("primary DNS failed w/ error {e}: using system fallback");
let resolver = self.fallback.get_or_try_init(new_resolver_system)?;
resolver.lookup_ip(name).await?
}
};
Ok(lookup)
}
}
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
fn new_resolver() -> Result<TokioAsyncResolver, HickoryDnsError> {
let mut name_servers = NameServerConfigGroup::google_tls();
name_servers.merge(NameServerConfigGroup::google_https());
// name_servers.merge(NameServerConfigGroup::google_h3());
name_servers.merge(NameServerConfigGroup::quad9_tls());
name_servers.merge(NameServerConfigGroup::quad9_https());
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
name_servers.merge(NameServerConfigGroup::cloudflare_https());
let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
let mut opts = ResolverOpts::default();
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
// Would like to enable this when 0.25 stabilizes
// opts.server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
Ok(TokioAsyncResolver::tokio(config, opts))
}
/// Create a new resolver with the default configuration, which reads from the system DNS config
/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6
/// addresses to work with "happy eyeballs" algorithm.
fn new_resolver_system() -> Result<TokioAsyncResolver, HickoryDnsError> {
let (config, mut opts) = hickory_resolver::system_conf::read_system_conf()?;
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
Ok(TokioAsyncResolver::tokio(config, opts))
}
#[cfg(test)]
mod test {
use super::*;
#[tokio::test]
async fn reqwest_hickory_doh() {
let resolver = HickoryDnsResolver::default();
let client = reqwest::ClientBuilder::new()
.dns_resolver(resolver.into())
.build()
.unwrap();
let resp = client
.get("http://ifconfig.me:80")
.send()
.await
.unwrap()
.bytes()
.await
.unwrap();
assert!(!resp.is_empty());
}
#[tokio::test]
async fn dns_lookup() -> Result<(), HickoryDnsError> {
let resolver = HickoryDnsResolver::default();
let domain = "ifconfig.me";
let addrs = resolver.resolve_str(domain).await?;
assert!(addrs.into_iter().next().is_some());
Ok(())
}
}
+4 -17
View File
@@ -6,23 +6,17 @@ use reqwest::header::HeaderValue;
use reqwest::{RequestBuilder, Response, StatusCode};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::time::Duration;
use thiserror::Error;
use tracing::{instrument, warn};
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Arc;
use std::{fmt::Display, time::Duration};
pub use reqwest::IntoUrl;
mod user_agent;
pub use user_agent::UserAgent;
#[cfg(not(target_arch = "wasm32"))]
mod dns;
#[cfg(not(target_arch = "wasm32"))]
pub use dns::HickoryDnsResolver;
mod user_agent;
// The timeout is relatively high as we are often making requests over the mixnet, where latency is
// high and chatty protocols take a while to complete.
@@ -92,18 +86,11 @@ impl ClientBuilder {
// TODO: or should we maybe default to https?
Self::new(alt)
} else {
#[cfg(target_arch = "wasm32")]
let reqwest_client_builder = reqwest::ClientBuilder::new();
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder =
reqwest::ClientBuilder::new().dns_resolver(Arc::new(HickoryDnsResolver::default()));
Ok(ClientBuilder {
url: url.into_url()?,
timeout: None,
custom_user_agent: false,
reqwest_client_builder,
reqwest_client_builder: reqwest::ClientBuilder::new(),
})
}
}
+1
View File
@@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
pub mod middleware;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub enum FormattedResponse<T> {
Json(Json<T>),
Yaml(Yaml<T>),
@@ -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> {
+1 -1
View File
@@ -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
View File
@@ -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 {
&REGISTRY
}
#[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");
}
}
+1 -1
View File
@@ -63,4 +63,4 @@ par_signing = ["rayon"]
# but given it's not done very frequently, it shouldn't be too much of a problem
# furthermore, we can't and shouldn't dedicate the entire nym-api CPU just for verification,
# but this feature might potentially be desirable for clients.
par_verify = ["rayon"]
par_verify = ["rayon"]
@@ -4,10 +4,10 @@
use crate::ecash_group_parameters;
use crate::error::Result;
use crate::helpers::{g1_tuple_to_bytes, recover_g1_tuple};
use bls12_381::{G1Projective, Scalar};
use serde::{Deserialize, Serialize};
use subtle::Choice;
pub use bls12_381::{G1Projective, G2Projective, Scalar};
pub type SignerIndex = u64;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
+1 -1
View File
@@ -36,7 +36,7 @@ pub mod common_types;
pub mod constants;
pub mod error;
mod helpers;
pub mod proofs;
mod proofs;
pub mod scheme;
pub mod tests;
mod traits;
-2
View File
@@ -27,5 +27,3 @@ nym-credentials-interface = { path = "../credentials-interface" }
nym-metrics = { path = "../nym-metrics" }
nym-task = { path = "../task" }
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
workspace = true
@@ -3,7 +3,10 @@
use super::ClientStatsEvents;
use core::fmt;
use std::{collections::VecDeque, time::Duration};
use std::{
collections::VecDeque,
time::{Duration, Instant},
};
use nym_metrics::{inc, inc_by};
use serde::{Deserialize, Serialize};
@@ -15,51 +18,6 @@ use si_scale::helpers::bibytes2;
// Also, set it larger than the packet report interval so that we don't miss notable singular events
const RECORDING_WINDOW_MS: u64 = 2300;
#[derive(PartialOrd, PartialEq, Clone, Copy, Debug)]
struct Instant {
#[cfg(not(target_arch = "wasm32"))]
inner: std::time::Instant,
#[cfg(target_arch = "wasm32")]
inner: wasmtimer::std::Instant,
}
impl Instant {
#[cfg(not(target_arch = "wasm32"))]
fn inner(&self) -> &std::time::Instant {
&self.inner
}
#[cfg(target_arch = "wasm32")]
fn inner(&self) -> &wasmtimer::std::Instant {
&self.inner
}
#[cfg(not(target_arch = "wasm32"))]
fn now() -> Self {
Instant {
inner: std::time::Instant::now(),
}
}
#[cfg(target_arch = "wasm32")]
fn now() -> Self {
Instant {
inner: wasmtimer::std::Instant::now(),
}
}
fn checked_sub(&self, duration: Duration) -> Option<Instant> {
self.inner()
.checked_sub(duration)
.map(|inner| Instant { inner })
}
fn duration_since(&self, earlier: &Instant) -> Duration {
self.inner.duration_since(*earlier.inner())
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub(crate) struct PacketStatistics {
// Sent
@@ -466,11 +424,7 @@ impl PacketStatisticsControl {
self.history.push_back((Instant::now(), self.stats.clone()));
// Filter out old ones
let now = Instant::now();
let recording_window = Instant::now()
.checked_sub(Duration::from_millis(RECORDING_WINDOW_MS))
.unwrap_or(now);
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
while self
.history
.front()
@@ -488,7 +442,7 @@ impl PacketStatisticsControl {
// Do basic averaging over the entire history, which just uses the first and last
if let Some((start, start_stats)) = self.history.front() {
let duration_secs = Instant::now().duration_since(start).as_secs_f64();
let duration_secs = Instant::now().duration_since(*start).as_secs_f64();
let delta = self.stats.clone() - start_stats.clone();
let rates = PacketRates::from(delta) / duration_secs;
Some(rates)
@@ -504,10 +458,7 @@ impl PacketStatisticsControl {
}
// Filter out old ones
let now = Instant::now();
let recording_window = now
.checked_sub(Duration::from_millis(RECORDING_WINDOW_MS))
.unwrap_or(now);
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
while self
.rates
.front()
+1 -1
View File
@@ -67,7 +67,7 @@ impl Default for ClientStatsReport {
pub struct OsInformation {
pub(crate) os_type: String,
pub(crate) os_version: Option<String>,
pub(crate) os_arch: String,
pub(crate) os_arch: Option<String>,
}
impl OsInformation {
-3
View File
@@ -8,13 +8,10 @@ license.workspace = true
repository.workspace = true
[dependencies]
cfg-if = { workspace = true }
futures = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["macros", "sync"] }
tokio-util = { workspace = true, features = ["rt"] }
tracing = { workspace = true }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
workspace = true
-366
View File
@@ -1,366 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{TaskClient, TaskManager};
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use std::future::Future;
use std::ops::Deref;
use std::pin::Pin;
use std::time::Duration;
use tokio::task::JoinSet;
use tokio::time::sleep;
use tokio_util::sync::{CancellationToken, DropGuard};
use tokio_util::task::TaskTracker;
use tracing::{debug, info, trace};
#[cfg(unix)]
use tokio::signal::unix::{signal, SignalKind};
pub const DEFAULT_MAX_SHUTDOWN_DURATION: Duration = Duration::from_secs(5);
pub fn token_name(name: &Option<String>) -> String {
name.clone().unwrap_or_else(|| "unknown".to_string())
}
// a wrapper around tokio's CancellationToken that adds optional `name` information to more easily
// track down sources of shutdown
#[derive(Debug, Default)]
pub struct ShutdownToken {
name: Option<String>,
inner: CancellationToken,
}
impl Clone for ShutdownToken {
fn clone(&self) -> Self {
// make sure to not accidentally overflow the stack if we keep cloning the handle
let name = if let Some(name) = &self.name {
if name != Self::OVERFLOW_NAME && name.len() < Self::MAX_NAME_LENGTH {
Some(format!("{name}-child"))
} else {
Some(Self::OVERFLOW_NAME.to_string())
}
} else {
None
};
ShutdownToken {
name,
inner: self.inner.clone(),
}
}
}
impl Deref for ShutdownToken {
type Target = CancellationToken;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ShutdownToken {
const MAX_NAME_LENGTH: usize = 128;
const OVERFLOW_NAME: &'static str = "reached maximum ShutdownToken children name depth";
pub fn new(name: impl Into<String>) -> Self {
ShutdownToken {
name: Some(name.into()),
inner: CancellationToken::new(),
}
}
// Creates a ShutdownToken which will get cancelled whenever the current token gets cancelled.
// Unlike a cloned/forked ShutdownToken, cancelling a child token does not cancel the parent token.
#[must_use]
pub fn child_token<S: Into<String>>(&self, child_suffix: S) -> Self {
let suffix = child_suffix.into();
let child_name = if let Some(base) = &self.name {
format!("{base}-{suffix}")
} else {
format!("unknown-{suffix}")
};
ShutdownToken {
name: Some(child_name),
inner: self.inner.child_token(),
}
}
// Creates a clone of the ShutdownToken which will get cancelled whenever the current token gets cancelled, and vice versa.
#[must_use]
pub fn clone_with_suffix<S: Into<String>>(&self, child_suffix: S) -> Self {
let mut child = self.clone();
let suffix = child_suffix.into();
let child_name = if let Some(base) = &self.name {
format!("{base}-{suffix}")
} else {
format!("unknown-{suffix}")
};
child.name = Some(child_name);
child
}
// exposed method with the old name for easier migration
// it will eventually be removed so please try to use `.clone_with_suffix` instead
#[must_use]
pub fn fork<S: Into<String>>(&self, child_suffix: S) -> Self {
self.clone_with_suffix(child_suffix)
}
// exposed method with the old name for easier migration
// it will eventually be removed so please try to use `.clone().named(name)` instead
#[must_use]
pub fn fork_named<S: Into<String>>(&self, name: S) -> Self {
self.clone().named(name)
}
#[must_use]
pub fn named<S: Into<String>>(mut self, name: S) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn add_suffix<S: Into<String>>(self, suffix: S) -> Self {
let suffix = suffix.into();
let name = if let Some(base) = &self.name {
format!("{base}-{suffix}")
} else {
format!("unknown-{suffix}")
};
self.named(name)
}
// Returned guard will cancel this token (and all its children) on drop unless disarmed.
pub fn drop_guard(self) -> ShutdownDropGuard {
ShutdownDropGuard {
name: self.name,
inner: self.inner.drop_guard(),
}
}
pub fn name(&self) -> String {
token_name(&self.name)
}
pub async fn run_until_cancelled<F>(&self, fut: F) -> Option<F::Output>
where
F: Future,
{
let res = self.inner.run_until_cancelled(fut).await;
trace!("'{}' got cancelled", self.name());
res
}
}
pub struct ShutdownDropGuard {
name: Option<String>,
inner: DropGuard,
}
impl Deref for ShutdownDropGuard {
type Target = DropGuard;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ShutdownDropGuard {
pub fn disarm(self) -> ShutdownToken {
ShutdownToken {
name: self.name,
inner: self.inner.disarm(),
}
}
pub fn name(&self) -> String {
token_name(&self.name)
}
}
pub struct ShutdownManager {
pub root_token: ShutdownToken,
legacy_task_manager: Option<TaskManager>,
shutdown_signals: JoinSet<()>,
// the reason I'm not using a `JoinSet` is because it forces us to use futures with the same `::Output` type
tracker: TaskTracker,
max_shutdown_duration: Duration,
}
impl Deref for ShutdownManager {
type Target = TaskTracker;
fn deref(&self) -> &Self::Target {
&self.tracker
}
}
impl ShutdownManager {
pub fn new(root_token_name: impl Into<String>) -> Self {
let manager = ShutdownManager {
root_token: ShutdownToken::new(root_token_name),
legacy_task_manager: None,
shutdown_signals: Default::default(),
tracker: Default::default(),
max_shutdown_duration: Default::default(),
};
// we need to add an explicit watcher for the cancellation token being cancelled
// so that we could cancel all legacy tasks
let cancel_watcher = manager.root_token.clone();
manager.with_shutdown(async move { cancel_watcher.cancelled().await })
}
pub fn with_legacy_task_manager(mut self) -> Self {
let mut legacy_manager =
TaskManager::default().named(format!("{}-legacy", self.root_token.name()));
let mut legacy_error_rx = legacy_manager.task_return_error_rx();
let mut legacy_drop_rx = legacy_manager.task_drop_rx();
self.legacy_task_manager = Some(legacy_manager);
// add a task that listens for legacy task clients being dropped to trigger cancellation
self.with_shutdown(async move {
tokio::select! {
_ = legacy_error_rx.recv() => (),
_ = legacy_drop_rx.recv() => (),
}
info!("received legacy shutdown signal");
})
}
#[cfg(not(target_arch = "wasm32"))]
pub fn with_default_shutdown_signals(self) -> std::io::Result<Self> {
cfg_if::cfg_if! {
if #[cfg(unix)] {
self.with_interrupt_signal()
.with_terminate_signal()?
.with_quit_signal()
} else {
Ok(self.with_interrupt_signal())
}
}
}
#[must_use]
pub fn with_shutdown<F>(mut self, shutdown: F) -> Self
where
F: Future<Output = ()>,
F: Send + 'static,
{
let shutdown_token = self.root_token.clone();
self.shutdown_signals.spawn(async move {
shutdown.await;
info!("sending cancellation after receiving shutdown signal");
shutdown_token.cancel();
});
self
}
#[cfg(unix)]
pub fn with_shutdown_signal(self, signal_kind: SignalKind) -> std::io::Result<Self> {
let mut sig = signal(signal_kind)?;
Ok(self.with_shutdown(async move {
sig.recv().await;
}))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn with_interrupt_signal(self) -> Self {
self.with_shutdown(async move {
let _ = tokio::signal::ctrl_c().await;
})
}
#[cfg(unix)]
pub fn with_terminate_signal(self) -> std::io::Result<Self> {
self.with_shutdown_signal(SignalKind::terminate())
}
#[cfg(unix)]
pub fn with_quit_signal(self) -> std::io::Result<Self> {
self.with_shutdown_signal(SignalKind::quit())
}
#[must_use]
pub fn with_shutdown_duration(mut self, duration: Duration) -> Self {
self.max_shutdown_duration = duration;
self
}
pub fn child_token<S: Into<String>>(&self, child_suffix: S) -> ShutdownToken {
self.root_token.child_token(child_suffix)
}
pub fn clone_token<S: Into<String>>(&self, child_suffix: S) -> ShutdownToken {
self.root_token.clone_with_suffix(child_suffix)
}
#[must_use]
pub fn subscribe_legacy<S: Into<String>>(&self, child_suffix: S) -> TaskClient {
// alternatively we could have set self.legacy_task_manager = Some(TaskManager::default());
// on demand if it wasn't unavailable, but then we'd have to use mutable reference
#[allow(clippy::expect_used)]
self.legacy_task_manager
.as_ref()
.expect("did not enable legacy shutdown support")
.subscribe_named(child_suffix)
}
async fn finish_shutdown(mut self) {
let mut wait_futures = FuturesUnordered::<Pin<Box<dyn Future<Output = ()>>>>::new();
// force shutdown via ctrl-c
wait_futures.push(Box::pin(async move {
#[cfg(not(target_arch = "wasm32"))]
let interrupt_future = tokio::signal::ctrl_c();
#[cfg(target_arch = "wasm32")]
let interrupt_future = futures::future::pending::<()>();
let _ = interrupt_future.await;
info!("received interrupt - forcing shutdown");
}));
// timeout
wait_futures.push(Box::pin(async move {
sleep(self.max_shutdown_duration).await;
info!("timeout reached, forcing shutdown");
}));
// graceful
wait_futures.push(Box::pin(async move {
self.tracker.wait().await;
debug!("migrated tasks successfully shutdown");
if let Some(legacy) = self.legacy_task_manager.as_mut() {
legacy.wait_for_graceful_shutdown().await;
debug!("legacy tasks successfully shutdown");
}
info!("all registered tasks successfully shutdown")
}));
wait_futures.next().await;
}
pub async fn wait_for_shutdown_signal(mut self) {
self.shutdown_signals.join_next().await;
if let Some(legacy_manager) = self.legacy_task_manager.as_mut() {
info!("attempting to shutdown legacy tasks");
let _ = legacy_manager.signal_shutdown();
}
info!("waiting for tasks to finish... (press ctrl-c to force)");
self.finish_shutdown().await;
}
}
+3 -6
View File
@@ -1,7 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod cancellation;
pub mod connections;
pub mod event;
pub mod manager;
@@ -9,11 +8,9 @@ pub mod manager;
pub mod signal;
pub mod spawn;
pub use cancellation::{ShutdownDropGuard, ShutdownManager, ShutdownToken};
pub use event::{StatusReceiver, StatusSender, TaskStatus, TaskStatusEvent};
pub use manager::{TaskClient, TaskHandle, TaskManager};
pub use spawn::{spawn, spawn_with_report_error};
pub use tokio_util::task::TaskTracker;
#[cfg(not(target_arch = "wasm32"))]
pub use signal::{wait_for_signal, wait_for_signal_and_error};
pub use signal::wait_for_signal_and_error;
pub use spawn::{spawn, spawn_with_report_error};
-20
View File
@@ -185,19 +185,6 @@ impl TaskManager {
}
}
// used for compatibility with the ShutdownManager
pub(crate) fn task_return_error_rx(&mut self) -> ErrorReceiver {
self.task_return_error_rx
.take()
.expect("unable to get error channel: attempt to wait twice?")
}
pub(crate) fn task_drop_rx(&mut self) -> ErrorReceiver {
self.task_drop_rx
.take()
.expect("unable to get task drop channel: attempt to wait twice?")
}
pub async fn wait_for_error(&mut self) -> Option<SentError> {
let mut error_rx = self
.task_return_error_rx
@@ -221,13 +208,6 @@ impl TaskManager {
}
}
pub(crate) async fn wait_for_graceful_shutdown(&mut self) {
if let Some(notify_rx) = self.notify_rx.take() {
drop(notify_rx);
}
self.notify_tx.closed().await
}
pub async fn wait_for_shutdown(&mut self) {
log::debug!("Waiting for shutdown");
if let Some(notify_rx) = self.notify_rx.take() {
+1 -2
View File
@@ -14,7 +14,6 @@ readme.workspace = true
sha2 = { workspace = true }
rs_merkle = { workspace = true }
schemars = { workspace = true }
utoipa = { workspace = true }
serde = { workspace = true, features = ["derive"] }
time = { workspace = true }
@@ -24,4 +23,4 @@ nym-serde-helpers = { path = "../serde-helpers", features = ["date", "base64", "
[dev-dependencies]
rand_chacha = { workspace = true }
rand = { workspace = true }
serde_json = { workspace = true }
serde_json = { workspace = true }
+6 -9
View File
@@ -14,18 +14,15 @@ use serde::{Deserialize, Serialize};
use sha2::Digest;
use std::fmt::{Debug, Formatter};
use time::Date;
use utoipa::ToSchema;
// no point in importing the entire contract commons just for this one type
pub type DepositId = u32;
pub type DKGEpochId = u64;
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, JsonSchema, ToSchema)]
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct IssuedTicketbook {
#[schema(value_type = u32)]
pub deposit_id: DepositId,
#[schema(value_type = u32)]
pub epoch_id: DKGEpochId,
// 96 bytes serialised 'BlindedSignature'
@@ -40,11 +37,9 @@ pub struct IssuedTicketbook {
#[schemars(with = "String")]
#[serde(with = "nym_serde_helpers::date")]
#[schema(value_type = String)]
pub expiration_date: Date,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub ticketbook_type: TicketType,
}
@@ -85,7 +80,7 @@ pub struct InsertedMerkleLeaf {
pub leaf: MerkleLeaf,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialOrd, PartialEq, Eq, ToSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialOrd, PartialEq, Eq)]
pub struct MerkleLeaf {
#[schemars(with = "String")]
#[serde(with = "nym_serde_helpers::hex")]
@@ -167,14 +162,16 @@ impl IssuedTicketbooksMerkleTree {
}
}
#[derive(Serialize, Deserialize, JsonSchema, ToSchema)]
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct IssuedTicketbooksFullMerkleProof {
#[schemars(with = "String")]
#[serde(with = "inner_proof_base64_serde")]
#[schema(value_type = String)]
inner_proof: MerkleProof<Sha256>,
included_leaves: Vec<MerkleLeaf>,
total_leaves: usize,
#[schemars(with = "String")]
#[serde(with = "nym_serde_helpers::hex")]
root: Vec<u8>,
+11 -8
View File
@@ -401,17 +401,20 @@ impl NymTopology {
});
};
// a 'valid' egress is one that is currently **not** acting as a mixnode
// a 'valid' egress is one assigned to either entry role (i.e. entry for another client)
// or exit role (as a service provider)
if !ignore_epoch_roles {
if let Some(role) = self.rewarded_set.role(node.node_id) {
if role.is_mixnode() {
return Err(NymTopologyError::InvalidEgressRole {
node_identity: Box::new(node_identity),
});
}
let Some(role) = self.rewarded_set.role(node.node_id) else {
return Err(NymTopologyError::InvalidEgressRole {
node_identity: Box::new(node_identity),
});
};
if !matches!(role, Role::EntryGateway | Role::ExitGateway) {
return Err(NymTopologyError::InvalidEgressRole {
node_identity: Box::new(node_identity),
});
}
}
Ok(node)
}
-1
View File
@@ -22,7 +22,6 @@ strum = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
ts-rs = { workspace = true }
url = { workspace = true }
utoipa = { workspace = true }
x25519-dalek = { workspace = true, features = ["static_secrets"] }
cosmwasm-std = { workspace = true }
+2 -4
View File
@@ -3,7 +3,6 @@ use nym_mixnet_contract_common::NodeId;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{collections::HashSet, sync::LazyLock, time::SystemTime};
use utoipa::ToSchema;
static NETWORK_MONITORS: LazyLock<HashSet<String>> = LazyLock::new(|| {
let mut nm = HashSet::new();
@@ -11,9 +10,8 @@ static NETWORK_MONITORS: LazyLock<HashSet<String>> = LazyLock::new(|| {
nm
});
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, ToSchema)]
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
pub struct NodeResult {
#[schema(value_type = u32)]
pub node_id: NodeId,
pub identity: String,
pub reliability: u8,
@@ -36,7 +34,7 @@ pub enum MonitorResults {
Gateway(Vec<NodeResult>),
}
#[derive(Serialize, Deserialize, JsonSchema, ToSchema)]
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct MonitorMessage {
results: Vec<NodeResult>,
signature: String,
+53 -28
View File
@@ -6,7 +6,7 @@ use crate::measurements::packet::{EchoPacket, ReplyPacket};
use bytes::{BufMut, BytesMut};
use futures::StreamExt;
use nym_crypto::asymmetric::identity;
use nym_task::ShutdownToken;
use nym_task::TaskClient;
use std::net::SocketAddr;
use std::sync::Arc;
use std::{io, process};
@@ -19,19 +19,19 @@ use tracing::{debug, error, info, trace, warn};
pub struct PacketListener {
address: SocketAddr,
connection_handler: Arc<ConnectionHandler>,
shutdown_token: ShutdownToken,
shutdown: TaskClient,
}
impl PacketListener {
pub fn new(
address: SocketAddr,
identity: Arc<identity::KeyPair>,
shutdown_token: ShutdownToken,
shutdown: TaskClient,
) -> Self {
PacketListener {
address,
connection_handler: Arc::new(ConnectionHandler { identity }),
shutdown_token,
shutdown,
}
}
}
@@ -51,22 +51,26 @@ impl PacketListener {
info!("Started listening for echo packets on {}", self.address);
while !self.shutdown_token.is_cancelled() {
let mut shutdown_listener = self.shutdown.clone();
while !shutdown_listener.is_shutdown() {
// cloning the arc as each accepted socket is handled in separate task
let connection_handler = Arc::clone(&self.connection_handler);
let mut handler_shutdown_listener = self.shutdown.clone();
handler_shutdown_listener.disarm();
tokio::select! {
socket = listener.accept() => {
match socket {
Ok((socket, remote_addr)) => {
debug!("New verloc connection from {remote_addr}");
let cancel = self.shutdown_token.child_token(format!("handler_{remote_addr}"));
tokio::spawn(async move { cancel.run_until_cancelled(connection_handler.handle_connection(socket, remote_addr)).await });
debug!("New verloc connection from {}", remote_addr);
tokio::spawn(connection_handler.handle_connection(socket, remote_addr, handler_shutdown_listener));
}
Err(err) => warn!("Failed to accept incoming connection - {err}"),
}
},
_ = self.shutdown_token.cancelled() => {
_ = shutdown_listener.recv() => {
trace!("PacketListener: Received shutdown");
}
}
@@ -84,29 +88,50 @@ impl ConnectionHandler {
packet.construct_reply(self.identity.private_key())
}
pub(crate) async fn handle_connection(self: Arc<Self>, conn: TcpStream, remote: SocketAddr) {
debug!("Starting connection handler for {remote}");
pub(crate) async fn handle_connection(
self: Arc<Self>,
conn: TcpStream,
remote: SocketAddr,
mut shutdown_listener: TaskClient,
) {
debug!("Starting connection handler for {:?}", remote);
let mut framed_conn = Framed::new(conn, EchoPacketCodec);
while let Some(echo_packet) = framed_conn.next().await {
let reply_packet = match echo_packet {
Ok(echo_packet) => self.handle_echo_packet(echo_packet),
Err(err) => {
debug!(
"The socket connection got corrupted with error: {err}. Closing the socket"
);
return;
while !shutdown_listener.is_shutdown() {
tokio::select! {
biased;
_ = shutdown_listener.recv() => {
trace!("ConnectionHandler: Shutdown received");
}
};
maybe_echo_packet = framed_conn.next() => {
// handle echo packet
let reply_packet = match maybe_echo_packet {
Some(Ok(echo_packet)) => self.handle_echo_packet(echo_packet),
Some(Err(err)) => {
debug!(
"The socket connection got corrupted with error: {err}. Closing the socket",
);
return;
}
None => {
debug!("The socket connection got terminated by the remote!");
return;
}
};
// write back the reply (note the lack of framing)
if let Err(err) = framed_conn
.get_mut()
.write_all(reply_packet.to_bytes().as_ref())
.await
{
debug!("Failed to write reply packet back to the sender: {err}. Closing the socket on our end");
return;
// write back the reply (note the lack of framing)
if let Err(err) = framed_conn
.get_mut()
.write_all(reply_packet.to_bytes().as_ref())
.await
{
debug!(
"Failed to write reply packet back to the sender - {}. Closing the socket on our end",
err
);
return;
}
},
}
}
}
+13 -10
View File
@@ -8,7 +8,7 @@ use crate::models::VerlocNodeResult;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use nym_crypto::asymmetric::identity;
use nym_task::ShutdownToken;
use nym_task::TaskClient;
use nym_validator_client::models::NymNodeDescription;
use nym_validator_client::NymApiClient;
use rand::prelude::SliceRandom;
@@ -23,7 +23,7 @@ pub struct VerlocMeasurer {
config: Config,
packet_sender: Arc<PacketSender>,
packet_listener: Arc<PacketListener>,
shutdown_token: ShutdownToken,
shutdown_listener: TaskClient,
state: SharedVerlocStats,
}
@@ -31,7 +31,7 @@ impl VerlocMeasurer {
pub fn new(
config: Config,
identity: Arc<identity::KeyPair>,
shutdown_token: ShutdownToken,
shutdown_listener: TaskClient,
) -> Self {
VerlocMeasurer {
packet_sender: Arc::new(PacketSender::new(
@@ -40,14 +40,14 @@ impl VerlocMeasurer {
config.packet_timeout,
config.connection_timeout,
config.delay_between_packets,
shutdown_token.clone_with_suffix("packet_sender"),
shutdown_listener.clone().named("VerlocPacketSender"),
)),
packet_listener: Arc::new(PacketListener::new(
config.listening_address,
Arc::clone(&identity),
shutdown_token.clone_with_suffix("packet_listener"),
shutdown_listener.clone().named("VerlocPacketListener"),
)),
shutdown_token,
shutdown_listener,
config,
state: SharedVerlocStats::default(),
}
@@ -69,6 +69,9 @@ impl VerlocMeasurer {
return MeasurementOutcome::Done;
}
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
shutdown_listener.disarm();
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
let mut chunk_results = Vec::with_capacity(chunk.len());
@@ -92,7 +95,7 @@ impl VerlocMeasurer {
.collect::<FuturesUnordered<_>>();
// exhaust the results
while !self.shutdown_token.is_cancelled() {
while !shutdown_listener.is_shutdown() {
tokio::select! {
measurement_result = measurement_chunk.next() => {
let Some(result) = measurement_result else {
@@ -117,7 +120,7 @@ impl VerlocMeasurer {
};
chunk_results.push(VerlocNodeResult::new(identity, measurement_result));
},
_ = self.shutdown_token.cancelled() => {
_ = shutdown_listener.recv() => {
trace!("Shutdown received while measuring");
return MeasurementOutcome::Shutdown;
}
@@ -152,7 +155,7 @@ impl VerlocMeasurer {
pub async fn run(&mut self) {
self.start_listening();
while !self.shutdown_token.is_cancelled() {
while !self.shutdown_listener.is_shutdown() {
info!("Starting verloc measurements");
// TODO: should we also measure gateways?
@@ -206,7 +209,7 @@ impl VerlocMeasurer {
tokio::select! {
_ = sleep(self.config.testing_interval) => {},
_ = self.shutdown_token.cancelled() => {
_ = self.shutdown_listener.recv() => {
trace!("Shutdown received while sleeping");
}
}
+9 -6
View File
@@ -5,7 +5,7 @@ use crate::error::VerlocError;
use crate::measurements::packet::{EchoPacket, ReplyPacket};
use crate::models::VerlocMeasurement;
use nym_crypto::asymmetric::ed25519;
use nym_task::ShutdownToken;
use nym_task::TaskClient;
use rand::{thread_rng, Rng};
use std::net::SocketAddr;
use std::sync::Arc;
@@ -45,7 +45,7 @@ pub struct PacketSender {
packet_timeout: Duration,
connection_timeout: Duration,
delay_between_packets: Duration,
shutdown_token: ShutdownToken,
shutdown_listener: TaskClient,
}
impl PacketSender {
@@ -55,7 +55,7 @@ impl PacketSender {
packet_timeout: Duration,
connection_timeout: Duration,
delay_between_packets: Duration,
shutdown_token: ShutdownToken,
shutdown_listener: TaskClient,
) -> Self {
PacketSender {
identity,
@@ -63,7 +63,7 @@ impl PacketSender {
packet_timeout,
connection_timeout,
delay_between_packets,
shutdown_token,
shutdown_listener,
}
}
@@ -83,6 +83,9 @@ impl PacketSender {
self: Arc<Self>,
tested_node: TestedNode,
) -> Result<VerlocMeasurement, VerlocError> {
let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string());
shutdown_listener.disarm();
let mut conn = match tokio::time::timeout(
self.connection_timeout,
TcpStream::connect(tested_node.address),
@@ -145,7 +148,7 @@ impl PacketSender {
Ok(Ok(_)) => {}
}
},
_ = self.shutdown_token.cancelled() => {
_ = shutdown_listener.recv() => {
trace!("PacketSender: Received shutdown while sending");
return Err(VerlocError::ShutdownReceived);
},
@@ -187,7 +190,7 @@ impl PacketSender {
}
}
},
_ = self.shutdown_token.cancelled() => {
_ = shutdown_listener.recv() => {
trace!("PacketSender: Received shutdown while waiting for reply");
return Err(VerlocError::ShutdownReceived);
}
@@ -1,3 +0,0 @@
[build]
target = "wasm32-unknown-unknown"
target_arch = "wasm32"
@@ -390,6 +390,7 @@ pub struct TopologyWasm {
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
/// Useless without `ignore_epoch_roles = true`
pub use_extended_topology: bool,
/// Specifies whether this client should ignore the current epoch role of the target egress node
@@ -274,6 +274,7 @@ pub struct TopologyWasmOverride {
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
/// Useless without `ignore_epoch_roles = true`
#[tsify(optional)]
pub use_extended_topology: Option<bool>,
+4 -90
View File
@@ -5,15 +5,12 @@ use crate::error::WasmCoreError;
use crate::storage::wasm_client_traits::WasmClientStorage;
use crate::storage::ClientStorage;
use js_sys::Promise;
use nym_client_core::client::base_client::storage::helpers::set_active_gateway;
use nym_client_core::client::base_client::storage::GatewaysDetailsStore;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
use nym_client_core::error::ClientCoreError;
use nym_client_core::init::helpers::gateways_for_init;
use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::types::GatewaySelectionSpecification;
use nym_client_core::init::{
self, setup_gateway,
self,
types::{GatewaySetup, InitialisationResult},
};
use nym_sphinx::addressing::clients::Recipient;
@@ -21,7 +18,7 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
use nym_topology::{NymTopology, RoutingNode};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::{NymApiClient, UserAgent};
use nym_validator_client::NymApiClient;
use rand::thread_rng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
@@ -29,7 +26,6 @@ use wasm_bindgen_futures::future_to_promise;
use wasm_utils::error::PromisableResult;
pub use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use wasm_utils::console_log;
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no disk_persistence
@@ -134,19 +130,10 @@ pub async fn setup_gateway_from_api(
minimum_performance: u8,
) -> Result<InitialisationResult, WasmCoreError> {
let mut rng = thread_rng();
let gateways = gateways_for_init(&mut rng, nym_apis, None, minimum_performance).await?;
let gateways = current_gateways(&mut rng, nym_apis, None, minimum_performance).await?;
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
}
pub async fn current_gateways_wasm(
nym_apis: &[Url],
user_agent: Option<UserAgent>,
minimum_performance: u8,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
let mut rng = thread_rng();
gateways_for_init(&mut rng, nym_apis, user_agent, minimum_performance).await
}
pub async fn setup_from_topology(
explicit_gateway: Option<IdentityKey>,
force_tls: bool,
@@ -156,76 +143,3 @@ pub async fn setup_from_topology(
let gateways = topology.entry_capable_nodes().cloned().collect::<Vec<_>>();
setup_gateway_wasm(client_store, force_tls, explicit_gateway, gateways).await
}
pub async fn generate_new_client_keys(store: &ClientStorage) -> Result<(), WasmCoreError> {
let mut rng = thread_rng();
init::generate_new_client_keys(&mut rng, store).await?;
Ok(())
}
pub async fn add_gateway(
preferred_gateway: Option<IdentityKey>,
latency_based_selection: Option<bool>,
force_tls: bool,
nym_apis: &[Url],
user_agent: UserAgent,
min_performance: u8,
storage: &ClientStorage,
) -> Result<(), WasmCoreError> {
let selection_spec = GatewaySelectionSpecification::new(
preferred_gateway.clone(),
latency_based_selection,
force_tls,
);
let preferred_gateway = preferred_gateway
.as_ref()
.map(|g| g.parse())
.transpose()
.map_err(|source| WasmCoreError::InvalidGatewayIdentity { source })?;
let registered_gateways = storage.all_gateways_identities().await.map_err(|source| {
ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
}
})?;
// if user provided gateway id (and we can't overwrite data), make sure we're not trying to register
// with a known gateway
if let Some(user_chosen) = preferred_gateway {
if registered_gateways.contains(&user_chosen) {
return Err(ClientCoreError::AlreadyRegistered {
gateway_id: user_chosen.to_base58_string(),
}
.into());
}
}
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let available_gateways =
current_gateways_wasm(nym_apis, Some(user_agent), min_performance).await?;
// since we're registering with a brand new gateway,
// make sure the list of available gateways doesn't overlap the list of known gateways
let available_gateways = available_gateways
.into_iter()
.filter(|g| !registered_gateways.contains(&g.identity()))
.collect::<Vec<_>>();
if available_gateways.is_empty() {
return Err(ClientCoreError::NoNewGatewaysAvailable.into());
}
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
};
let init_details = setup_gateway(gateway_setup, storage, storage).await?;
let gateway = init_details.gateway_id().to_base58_string();
set_active_gateway(storage, &gateway).await?;
console_log!("finished registration with gateway {gateway}");
Ok(())
}
@@ -128,12 +128,8 @@ impl GatewaysDetailsStore for ClientStorage {
}
async fn all_gateways(&self) -> Result<Vec<GatewayRegistration>, Self::StorageError> {
let identities = self.registered_gateways().await?;
let mut registered = Vec::with_capacity(identities.len());
for gateway_id in identities {
registered.push(self.load_gateway_details(&gateway_id).await?);
}
Ok(registered)
todo!()
// let identities = self.all
}
async fn has_gateway_details(&self, gateway_id: &str) -> Result<bool, Self::StorageError> {
+15 -20
View File
@@ -4,15 +4,13 @@
use crate::error::WasmCoreError;
use crate::storage::wasm_client_traits::{v1, v2, WasmClientStorage};
use async_trait::async_trait;
use js_sys::Promise;
use js_sys::{Array, Promise};
use serde::de::DeserializeOwned;
use serde::Serialize;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_storage::traits::BaseWasmStorage;
use wasm_storage::{
Build, Database, RawDbResult, TryFromJs, TryToJs, VersionChangeEvent, WasmStorage,
};
use wasm_storage::{IdbVersionChangeEvent, WasmStorage};
use wasm_utils::error::{simple_js_error, PromisableResult};
use zeroize::Zeroizing;
@@ -46,29 +44,26 @@ impl ClientStorage {
// special care must be taken on JS side to ensure it's correctly used there.
let passphrase = Zeroizing::new(passphrase);
let migrate_fn = Some(|evt: VersionChangeEvent, db: Database| -> RawDbResult<()> {
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
// works with an unsigned integer.
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
let old_version = evt.old_version() as u32;
let db = evt.db();
if old_version < 1 {
// migrating to version 2
db.create_object_store(v1::KEYS_STORE).build()?;
db.create_object_store(v1::CORE_STORE).build()?;
db.create_object_store(v1::KEYS_STORE)?;
db.create_object_store(v1::CORE_STORE)?;
db.create_object_store(v2::GATEWAY_REGISTRATIONS_ACTIVE_GATEWAY_STORE)
.build()?;
db.create_object_store(v2::GATEWAY_REGISTRATIONS_REGISTERED_GATEWAYS_STORE)
.build()?;
return Ok(());
db.create_object_store(v2::GATEWAY_REGISTRATIONS_ACTIVE_GATEWAY_STORE)?;
db.create_object_store(v2::GATEWAY_REGISTRATIONS_REGISTERED_GATEWAYS_STORE)?;
}
// version 1 -> unimplemented migration
if old_version < 2 {
return Err(simple_js_error("this client is incompatible with existing storage. please initialise it again.").into());
return Err(simple_js_error("this client is incompatible with existing storage. please initialise it again."));
}
Ok(())
@@ -115,7 +110,7 @@ impl BaseWasmStorage for ClientStorage {
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
where
T: DeserializeOwned,
K: TryToJs,
K: JsCast,
{
Ok(self.inner.read_value(store, key).await?)
}
@@ -128,33 +123,33 @@ impl BaseWasmStorage for ClientStorage {
) -> Result<(), Self::StorageError>
where
T: Serialize,
K: TryToJs + TryFromJs,
K: JsCast,
{
Ok(self.inner.store_value(store, key, value).await?)
}
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
where
K: TryToJs,
K: JsCast,
{
Ok(self.inner.remove_value(store, key).await?)
}
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
where
K: TryToJs,
K: JsCast,
{
Ok(self.inner.has_value(store, key).await?)
}
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
where
K: TryToJs,
K: JsCast,
{
Ok(self.inner.key_count(store, key).await?)
}
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError> {
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
Ok(self.inner.get_all_keys(store).await?)
}
}
@@ -119,15 +119,6 @@ pub trait WasmClientStorage: BaseWasmStorage {
.map_err(Into::into)
}
async fn has_identity_key(&self) -> Result<bool, <Self as WasmClientStorage>::StorageError> {
self.has_value(
v1::KEYS_STORE,
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
)
.await
.map_err(Into::into)
}
async fn store_identity_keypair(
&self,
keypair: &identity::KeyPair,
@@ -286,8 +277,8 @@ pub trait WasmClientStorage: BaseWasmStorage {
.await
.map_err(Into::into)
.map(|arr| {
arr.iter()
.cloned()
arr.to_vec()
.into_iter()
.filter_map(|key| key.as_string())
.collect()
})
-3
View File
@@ -1,3 +0,0 @@
[build]
target = "wasm32-unknown-unknown"
target_arch = "wasm32"
+1 -1
View File
@@ -9,7 +9,7 @@ license.workspace = true
[dependencies]
async-trait = { workspace = true }
getrandom = { workspace = true, features = ["js"] }
futures = { workspace = true }
js-sys = { workspace = true }
wasm-bindgen = { workspace = true }
serde = { workspace = true, features = ["derive"] }
+16 -17
View File
@@ -1,6 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use indexed_db_futures::web_sys::DomException;
use serde_wasm_bindgen::Error;
use thiserror::Error;
use wasm_bindgen::JsValue;
@@ -11,11 +12,15 @@ pub enum StorageError {
#[error("{0}")]
Json(String),
#[error("storage failure: {message}")]
InternalStorageFailure { message: String },
#[error("failed to open the db file: {message}")]
DbOpenFailure { message: String },
#[error("DomException {name} ({code}): {message}")]
DomException {
/// DomException code
code: u16,
/// Specific name of the DomException
name: String,
/// Message given to the DomException
message: String,
},
#[error("FATAL ERROR: storage key is somehow present {count} times in the table!")]
DuplicateKey { count: u32 },
@@ -41,18 +46,12 @@ impl From<StorageError> for JsValue {
}
}
impl From<indexed_db_futures::error::Error> for StorageError {
fn from(value: indexed_db_futures::error::Error) -> Self {
StorageError::InternalStorageFailure {
message: value.to_string(),
}
}
}
impl From<indexed_db_futures::error::OpenDbError> for StorageError {
fn from(value: indexed_db_futures::error::OpenDbError) -> Self {
StorageError::DbOpenFailure {
message: value.to_string(),
impl From<DomException> for StorageError {
fn from(value: DomException) -> StorageError {
StorageError::DomException {
name: value.name(),
message: value.message(),
code: value.code(),
}
}
}
+57 -74
View File
@@ -1,9 +1,8 @@
// Copyright 2023-2025 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::cipher_export::StoredExportedStoreCipher;
use crate::error::StorageError;
use indexed_db_futures::transaction::TransactionMode;
use nym_store_cipher::{
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned,
Version,
@@ -14,10 +13,7 @@ use std::future::IntoFuture;
use wasm_bindgen::JsValue;
use wasm_utils::console_log;
pub use indexed_db_futures::database::{Database, VersionChangeEvent};
pub use indexed_db_futures::prelude::*;
pub use indexed_db_futures::primitive::{TryFromJs, TryToJs};
pub use indexed_db_futures::Result as RawDbResult;
mod cipher_export;
pub mod error;
@@ -58,29 +54,31 @@ impl WasmStorage {
passphrase: Option<&[u8]>,
) -> Result<Self, StorageError>
where
F: Fn(VersionChangeEvent, Database) -> RawDbResult<()> + 'static,
F: Fn(&IdbVersionChangeEvent) -> Result<(), JsValue> + 'static,
{
let mut db_req: OpenDbRequest = IdbDatabase::open_u32(db_name, version)?;
// we must always ensure the cipher table is present
let db = Database::open(db_name)
.with_version(version)
.with_on_upgrade_needed(move |event, db| {
db_req.set_on_upgrade_needed(Some(
move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
// works with an unsigned integer.
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
let old_version = event.old_version() as u32;
let old_version = evt.old_version() as u32;
if old_version < 1 {
db.create_object_store(CIPHER_INFO_STORE).build()?;
evt.db().create_object_store(CIPHER_INFO_STORE)?;
}
if let Some(migrate) = migrate_fn.as_ref() {
migrate(event, db)
migrate(evt)
} else {
Ok(())
}
})
.await?;
},
));
let db: IdbDatabase = db_req.into_future().await?;
let inner = IdbWrapper(db);
let store_cipher = inner.setup_store_cipher(passphrase).await?;
@@ -96,12 +94,13 @@ impl WasmStorage {
}
pub async fn remove(db_name: &str) -> Result<(), StorageError> {
Database::delete_by_name(db_name)?.into_future().await?;
IdbDatabase::delete_by_name(db_name)?.into_future().await?;
Ok(())
}
pub async fn exists(db_name: &str) -> Result<bool, StorageError> {
let db = Database::open(db_name).await?;
let db_req: OpenDbRequest = IdbDatabase::open(db_name)?;
let db: IdbDatabase = db_req.into_future().await?;
// if the db was already created before, at the very least cipher info store should exist,
// thus the iterator should return at least one value
@@ -140,7 +139,7 @@ impl WasmStorage {
pub async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, StorageError>
where
T: DeserializeOwned,
K: TryToJs,
K: wasm_bindgen::JsCast,
{
self.inner
.read_value_raw(store, key)
@@ -157,7 +156,7 @@ impl WasmStorage {
) -> Result<(), StorageError>
where
T: Serialize,
K: TryToJs + TryFromJs,
K: wasm_bindgen::JsCast,
{
self.inner
.store_value_raw(store, key, &self.serialize_value(&value)?)
@@ -166,14 +165,14 @@ impl WasmStorage {
pub async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), StorageError>
where
K: TryToJs,
K: wasm_bindgen::JsCast,
{
self.inner.remove_value_raw(store, key).await
}
pub async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, StorageError>
where
K: TryToJs,
K: wasm_bindgen::JsCast,
{
match self.key_count(store, key).await? {
0 => Ok(false),
@@ -184,98 +183,82 @@ impl WasmStorage {
pub async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
where
K: TryToJs,
K: wasm_bindgen::JsCast,
{
self.inner.get_key_count(store, key).await
}
pub async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, StorageError> {
pub async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, StorageError> {
self.inner.get_all_keys(store).await
}
}
struct IdbWrapper(Database);
struct IdbWrapper(IdbDatabase);
impl IdbWrapper {
async fn read_value_raw<K>(&self, store: &str, key: K) -> Result<Option<JsValue>, StorageError>
where
K: TryToJs,
K: wasm_bindgen::JsCast,
{
self.0
.transaction(store)
.with_mode(TransactionMode::Readonly)
.build()?
.transaction_on_one_with_mode(store, IdbTransactionMode::Readonly)?
.object_store(store)?
.get(&key)
.primitive()?
.get(&key)?
.await
.map_err(Into::into)
}
async fn store_value_raw<K, T>(
async fn store_value_raw<K>(
&self,
store: &str,
key: K,
value: &T,
value: &JsValue,
) -> Result<(), StorageError>
where
K: TryToJs + TryFromJs,
T: TryToJs,
K: wasm_bindgen::JsCast,
{
let tx = self
.0
.transaction(store)
.with_mode(TransactionMode::Readwrite)
.build()?;
let store = tx.object_store(store)?;
store.put(value).with_key(key).primitive()?.await?;
tx.commit().await.map_err(Into::into)
self.0
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
.object_store(store)?
.put_key_val_owned(key, value)?
.into_future()
.await
.map_err(Into::into)
}
async fn remove_value_raw<K>(&self, store: &str, key: K) -> Result<(), StorageError>
where
K: TryToJs,
{
let tx = self
.0
.transaction(store)
.with_mode(TransactionMode::Readwrite)
.build()?;
let store = tx.object_store(store)?;
store.delete(key).primitive()?.await?;
tx.commit().await.map_err(Into::into)
}
async fn get_key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
where
K: TryToJs,
K: wasm_bindgen::JsCast,
{
self.0
.transaction(store)
.with_mode(TransactionMode::Readonly)
.build()?
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
.object_store(store)?
.count()
.with_query(key)
.primitive()?
.delete_owned(key)?
.into_future()
.await
.map_err(Into::into)
}
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, StorageError> {
async fn get_key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
where
K: wasm_bindgen::JsCast,
{
self.0
.transaction(store)
.with_mode(TransactionMode::Readonly)
.build()?
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
.object_store(store)?
.get_all_keys()
.primitive()?
.await?
.collect::<Result<Vec<_>, _>>()
.count_with_key_owned(key)?
.into_future()
.await
.map_err(Into::into)
}
async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, StorageError> {
self.0
.transaction_on_one_with_mode(store, IdbTransactionMode::Readonly)?
.object_store(store)?
.get_all_keys()?
.into_future()
.await
.map_err(Into::into)
}
+13 -14
View File
@@ -3,11 +3,10 @@
use crate::WasmStorage;
use async_trait::async_trait;
use indexed_db_futures::primitive::{TryFromJs, TryToJs};
use js_sys::Array;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::error::Error;
use wasm_bindgen::JsValue;
#[async_trait(?Send)]
pub trait BaseWasmStorage {
@@ -18,7 +17,7 @@ pub trait BaseWasmStorage {
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
where
T: DeserializeOwned,
K: TryToJs;
K: wasm_bindgen::JsCast;
async fn store_value<T, K>(
&self,
@@ -28,21 +27,21 @@ pub trait BaseWasmStorage {
) -> Result<(), Self::StorageError>
where
T: Serialize,
K: TryToJs + TryFromJs;
K: wasm_bindgen::JsCast;
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
where
K: TryToJs;
K: wasm_bindgen::JsCast;
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
where
K: TryToJs;
K: wasm_bindgen::JsCast;
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
where
K: TryToJs;
K: wasm_bindgen::JsCast;
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError>;
async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, Self::StorageError>;
}
#[async_trait(?Send)]
@@ -56,7 +55,7 @@ impl BaseWasmStorage for WasmStorage {
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
where
T: DeserializeOwned,
K: TryToJs,
K: wasm_bindgen::JsCast,
{
self.read_value(store, key).await
}
@@ -69,33 +68,33 @@ impl BaseWasmStorage for WasmStorage {
) -> Result<(), Self::StorageError>
where
T: Serialize,
K: TryToJs + TryFromJs,
K: wasm_bindgen::JsCast,
{
self.store_value(store, key, value).await
}
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
where
K: TryToJs,
K: wasm_bindgen::JsCast,
{
self.remove_value(store, key).await
}
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
where
K: TryToJs,
K: wasm_bindgen::JsCast,
{
self.has_value(store, key).await
}
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
where
K: TryToJs,
K: wasm_bindgen::JsCast,
{
self.key_count(store, key).await
}
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError> {
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
self.get_all_keys(store).await
}
}
-3
View File
@@ -1,3 +0,0 @@
[build]
target = "wasm32-unknown-unknown"
target_arch = "wasm32"
+9 -18
View File
@@ -267,27 +267,18 @@ impl PeerController {
}))
}
async fn update_metrics(&self, new_host: &Host) {
fn update_metrics(&self, new_host: &Host) {
let now = SystemTime::now();
const ACTIVITY_THRESHOLD: Duration = Duration::from_secs(60);
let old_host = self.host_information.read().await;
let total_peers = new_host.peers.len();
let mut active_peers = 0;
let mut new_rx = 0;
let mut new_tx = 0;
let mut total_rx = 0;
let mut total_tx = 0;
for (peer_key, peer) in new_host.peers.iter() {
// only consider pre-existing peers,
// so that the value would always be increasing
if let Some(prior) = old_host.peers.get(peer_key) {
let delta_rx = peer.rx_bytes.saturating_sub(prior.rx_bytes);
let delta_tx = prior.tx_bytes.saturating_sub(prior.tx_bytes);
new_rx += delta_rx;
new_tx += delta_tx;
}
for peer in new_host.peers.values() {
total_rx += peer.rx_bytes;
total_tx += peer.tx_bytes;
// if a peer hasn't performed a handshake in last minute,
// I think it's reasonable to assume it's no longer active
@@ -305,10 +296,10 @@ impl PeerController {
self.metrics.wireguard.update(
// if the conversion fails it means we're running not running on a 64bit system
// and that's a reason enough for this failure.
new_rx.try_into().expect(
total_rx.try_into().expect(
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
),
new_tx.try_into().expect(
total_tx.try_into().expect(
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
),
total_peers,
@@ -325,7 +316,7 @@ impl PeerController {
log::error!("Can't read wireguard kernel data");
continue;
};
self.update_metrics(&host).await;
self.update_metrics(&host);
*self.host_information.write().await = host;
}

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