Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 424e94a04e | |||
| d1fb926a2a | |||
| dea69acd49 | |||
| ada2d2247a | |||
| 0159d7c27a | |||
| 28997c7f97 | |||
| a6c586a33b | |||
| 7c85c1a271 | |||
| 92c8d1b73f | |||
| 554e9ca490 | |||
| 6e6675f7bf | |||
| a7f7ebfbae | |||
| 1aec8be85e | |||
| 4b474dd8ff | |||
| 9b627dd70f | |||
| 9a0b769425 | |||
| 8e14f5f884 | |||
| 1b64cb42b0 | |||
| 03c4895f2b | |||
| dcfb092758 | |||
| 9305ad5364 | |||
| ea5aef6c2f | |||
| 61a4433cd9 | |||
| 5c89d36140 | |||
| 5ab164d229 | |||
| 26538c5884 | |||
| adb248dbcc | |||
| fffec65cab | |||
| bb24004d46 | |||
| c487eff7ca | |||
| 5fa21c9aae | |||
| fd18aae0d6 | |||
| c202e2d598 | |||
| 62d23cff9f | |||
| e454d71b78 | |||
| a7874add88 | |||
| 0a47d5dcf8 | |||
| 3d84be22e2 | |||
| 6ccbb30491 | |||
| 91c205f83a | |||
| 4a704e992a | |||
| 6c88c7df42 | |||
| 2a748fc968 | |||
| 25766dc0ec | |||
| 07544d939e | |||
| 102cd1033c | |||
| 676e93a372 | |||
| 5a6770e5e2 | |||
| 529e8d49ee | |||
| 11d6ee2fdb | |||
| d704c428fc | |||
| bca070c1bd | |||
| a94c035c0a | |||
| 24480418f0 | |||
| 226c040a13 | |||
| a46245ffe3 | |||
| 7c1c13e139 | |||
| 836a93cd96 | |||
| b47a742dd0 | |||
| 6e14882246 | |||
| a7466a0e02 | |||
| 78f45012db | |||
| f6a2f62ea9 | |||
| 3efeededc5 | |||
| c482350ec6 | |||
| f7a7a8072f | |||
| acd068e5ab | |||
| caa17d933c |
@@ -79,7 +79,6 @@ jobs:
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-api
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-data-observatory
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
@@ -97,7 +96,6 @@ jobs:
|
||||
cp target/release/nym-socks5-client $OUTPUT_DIR
|
||||
cp target/release/nym-api $OUTPUT_DIR
|
||||
cp target/release/nym-network-requester $OUTPUT_DIR
|
||||
cp target/release/nym-data-observatory $OUTPUT_DIR
|
||||
cp target/release/nymvisor $OUTPUT_DIR
|
||||
cp target/release/nym-node $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.44.6
|
||||
uses: mikefarah/yq@v4.45.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
+150
@@ -4,6 +4,156 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.2-hu] (2025-01-28)
|
||||
|
||||
- chore :update version of chain watcher and validator rewarder ([#5394])
|
||||
- bugfix: correctly handle ingore epoch roles flag ([#5390])
|
||||
- bugfix: terminate mixnet socket listener on shutdown ([#5389])
|
||||
- feat: make client ignore dual mode nodes by default ([#5388])
|
||||
- Handle ecash network errors differently ([#5378])
|
||||
- Remove empty ephemeral keys ([#5376])
|
||||
- fixed sql migration for adding default message timestamp ([#5374])
|
||||
- Bind to [::] on nym-node for both IP versions ([#5361])
|
||||
- exposed NymApiClient method for obtaining node performance history ([#5360])
|
||||
- Client gateway selection ([#5358])
|
||||
- chore: refresh wasm sdk ([#5353])
|
||||
- chore: update indexed_db_futures ([#5347])
|
||||
- build(deps): bump mikefarah/yq from 4.44.6 to 4.45.1 ([#5342])
|
||||
- updated cosmrs and tendermint-rpc to their most recent versions ([#5339])
|
||||
- build(deps): bump ts-rs from 10.0.0 to 10.1.0 ([#5338])
|
||||
- build(deps): bump tempfile from 3.14.0 to 3.15.0 ([#5337])
|
||||
- build(deps): bump the patch-updates group with 8 updates ([#5336])
|
||||
- feature: introduce /load endpoint for self-reported quantised NymNode load ([#5326])
|
||||
- feature: `CancellationToken`-based shutdowns ([#5325])
|
||||
- Use expect in geodata test to give error message on failure ([#5314])
|
||||
- feature: periodically remove stale gateway messages ([#5312])
|
||||
- build(deps): bump the patch-updates group across 1 directory with 35 updates ([#5310])
|
||||
- Add dependabot assignes for the root cargo ecosystem ([#5297])
|
||||
- Move tun constants to network defaults ([#5286])
|
||||
- Include IPINFO_API_TOKEN in nightly CI ([#5285])
|
||||
- Nyx Chain Watcher ([#5274])
|
||||
- bugfix: remove unnecessary arguments for nym-api swagger endpoints ([#5272])
|
||||
- feature: nym topology revamp ([#5271])
|
||||
- Add windows to CI builds ([#5269])
|
||||
- http-api-client: deduplicate code ([#5267])
|
||||
- build(deps): bump http from 1.1.0 to 1.2.0 ([#5228])
|
||||
- NS API: add mixnet scraper ([#5200])
|
||||
- build(deps): bump criterion from 0.4.0 to 0.5.1 ([#4911])
|
||||
|
||||
[#5394]: https://github.com/nymtech/nym/pull/5394
|
||||
[#5390]: https://github.com/nymtech/nym/pull/5390
|
||||
[#5389]: https://github.com/nymtech/nym/pull/5389
|
||||
[#5388]: https://github.com/nymtech/nym/pull/5388
|
||||
[#5378]: https://github.com/nymtech/nym/pull/5378
|
||||
[#5376]: https://github.com/nymtech/nym/pull/5376
|
||||
[#5374]: https://github.com/nymtech/nym/pull/5374
|
||||
[#5361]: https://github.com/nymtech/nym/pull/5361
|
||||
[#5360]: https://github.com/nymtech/nym/pull/5360
|
||||
[#5358]: https://github.com/nymtech/nym/pull/5358
|
||||
[#5353]: https://github.com/nymtech/nym/pull/5353
|
||||
[#5347]: https://github.com/nymtech/nym/pull/5347
|
||||
[#5342]: https://github.com/nymtech/nym/pull/5342
|
||||
[#5339]: https://github.com/nymtech/nym/pull/5339
|
||||
[#5338]: https://github.com/nymtech/nym/pull/5338
|
||||
[#5337]: https://github.com/nymtech/nym/pull/5337
|
||||
[#5336]: https://github.com/nymtech/nym/pull/5336
|
||||
[#5326]: https://github.com/nymtech/nym/pull/5326
|
||||
[#5325]: https://github.com/nymtech/nym/pull/5325
|
||||
[#5314]: https://github.com/nymtech/nym/pull/5314
|
||||
[#5312]: https://github.com/nymtech/nym/pull/5312
|
||||
[#5310]: https://github.com/nymtech/nym/pull/5310
|
||||
[#5297]: https://github.com/nymtech/nym/pull/5297
|
||||
[#5286]: https://github.com/nymtech/nym/pull/5286
|
||||
[#5285]: https://github.com/nymtech/nym/pull/5285
|
||||
[#5274]: https://github.com/nymtech/nym/pull/5274
|
||||
[#5272]: https://github.com/nymtech/nym/pull/5272
|
||||
[#5271]: https://github.com/nymtech/nym/pull/5271
|
||||
[#5269]: https://github.com/nymtech/nym/pull/5269
|
||||
[#5267]: https://github.com/nymtech/nym/pull/5267
|
||||
[#5228]: https://github.com/nymtech/nym/pull/5228
|
||||
[#5200]: https://github.com/nymtech/nym/pull/5200
|
||||
[#4911]: https://github.com/nymtech/nym/pull/4911
|
||||
|
||||
## [2025.1-reeses] (2025-01-15)
|
||||
|
||||
- Feture/legacy alert ([#5346])
|
||||
- chore: readjusted --mode behaviour to fix the regression ([#5331])
|
||||
- chore: apply 1.84 linter suggestions ([#5330])
|
||||
- bugfix: make sure refresh data key matches bond info ([#5329])
|
||||
- reduce log severity for number of packets being delayed ([#5321])
|
||||
- feat: warn users if node is run in exit mode only ([#5320])
|
||||
- Bugfix/contract version assignment ([#5318])
|
||||
- fixed client session histogram buckets ([#5316])
|
||||
- amend 250gb limit ([#5313])
|
||||
- feature: expand nym-node prometheus metrics ([#5298])
|
||||
- Cherry picked #5286 ([#5287])
|
||||
- Add close to credential storage ([#5283])
|
||||
- feature: wireguard metrics ([#5278])
|
||||
- Add PATCH support to nym-http-api-client ([#5260])
|
||||
- chore: removed legacy socks5 listener ([#5259])
|
||||
- bugfix: make sure to apply gateway score filtering when choosing initial node ([#5256])
|
||||
- Update TS bindings ([#5255])
|
||||
- Add conversion unit tests for auth msg ([#5251])
|
||||
- Add control messages to GatewayTransciver ([#5247])
|
||||
- Remove unneeded async function annotation ([#5246])
|
||||
- bugfix: make sure to update timestamp of last batch verification to prevent double redemption ([#5239])
|
||||
- Add FromStr impl for UserAgent ([#5236])
|
||||
- Extend swagger docs ([#5235])
|
||||
- TicketType derive Hash and Eq ([#5233])
|
||||
- Add fd callback to client core ([#5230])
|
||||
- Extend raw ws fd for gateway client ([#5218])
|
||||
- Shipping raw metrics to PG ([#5216])
|
||||
- Change sqlite journal mode to WAL ([#5213])
|
||||
- Derive serialize for UserAgent ([#5210])
|
||||
- Restore Location fields ([#5208])
|
||||
- better date serialization ([#5207])
|
||||
- Fix overflow ([#5204])
|
||||
- feature: hopefully final steps of the smoosh™️ ([#5201])
|
||||
- Fix overflow ([#5184])
|
||||
- NS API - Gateway stats scraping ([#5180])
|
||||
- introduced initial internal commands for nym-cli: ecash key and request generation ([#5174])
|
||||
- Move NS client to separate package under NS API ([#5171])
|
||||
- build(deps): bump micromatch from 4.0.4 to 4.0.8 in /testnet-faucet ([#4813])
|
||||
|
||||
[#5346]: https://github.com/nymtech/nym/pull/5346
|
||||
[#5331]: https://github.com/nymtech/nym/pull/5331
|
||||
[#5330]: https://github.com/nymtech/nym/pull/5330
|
||||
[#5329]: https://github.com/nymtech/nym/pull/5329
|
||||
[#5321]: https://github.com/nymtech/nym/pull/5321
|
||||
[#5320]: https://github.com/nymtech/nym/pull/5320
|
||||
[#5318]: https://github.com/nymtech/nym/pull/5318
|
||||
[#5316]: https://github.com/nymtech/nym/pull/5316
|
||||
[#5313]: https://github.com/nymtech/nym/pull/5313
|
||||
[#5298]: https://github.com/nymtech/nym/pull/5298
|
||||
[#5287]: https://github.com/nymtech/nym/pull/5287
|
||||
[#5283]: https://github.com/nymtech/nym/pull/5283
|
||||
[#5278]: https://github.com/nymtech/nym/pull/5278
|
||||
[#5260]: https://github.com/nymtech/nym/pull/5260
|
||||
[#5259]: https://github.com/nymtech/nym/pull/5259
|
||||
[#5256]: https://github.com/nymtech/nym/pull/5256
|
||||
[#5255]: https://github.com/nymtech/nym/pull/5255
|
||||
[#5251]: https://github.com/nymtech/nym/pull/5251
|
||||
[#5247]: https://github.com/nymtech/nym/pull/5247
|
||||
[#5246]: https://github.com/nymtech/nym/pull/5246
|
||||
[#5239]: https://github.com/nymtech/nym/pull/5239
|
||||
[#5236]: https://github.com/nymtech/nym/pull/5236
|
||||
[#5235]: https://github.com/nymtech/nym/pull/5235
|
||||
[#5233]: https://github.com/nymtech/nym/pull/5233
|
||||
[#5230]: https://github.com/nymtech/nym/pull/5230
|
||||
[#5218]: https://github.com/nymtech/nym/pull/5218
|
||||
[#5216]: https://github.com/nymtech/nym/pull/5216
|
||||
[#5213]: https://github.com/nymtech/nym/pull/5213
|
||||
[#5210]: https://github.com/nymtech/nym/pull/5210
|
||||
[#5208]: https://github.com/nymtech/nym/pull/5208
|
||||
[#5207]: https://github.com/nymtech/nym/pull/5207
|
||||
[#5204]: https://github.com/nymtech/nym/pull/5204
|
||||
[#5201]: https://github.com/nymtech/nym/pull/5201
|
||||
[#5184]: https://github.com/nymtech/nym/pull/5184
|
||||
[#5180]: https://github.com/nymtech/nym/pull/5180
|
||||
[#5174]: https://github.com/nymtech/nym/pull/5174
|
||||
[#5171]: https://github.com/nymtech/nym/pull/5171
|
||||
[#4813]: https://github.com/nymtech/nym/pull/4813
|
||||
|
||||
## [2024.14-crunch-patched] (2024-12-17)
|
||||
|
||||
- Fixes an issue to allow previously registred clients to connect to latest nym-nodes
|
||||
|
||||
Generated
+593
-343
File diff suppressed because it is too large
Load Diff
+20
-24
@@ -194,8 +194,9 @@ aes-gcm = "0.10.1"
|
||||
aes-gcm-siv = "0.11.1"
|
||||
aead = "0.5.2"
|
||||
anyhow = "1.0.95"
|
||||
arc-swap = "1.7.1"
|
||||
argon2 = "0.5.0"
|
||||
async-trait = "0.1.84"
|
||||
async-trait = "0.1.85"
|
||||
axum-client-ip = "0.6.1"
|
||||
axum = "0.7.5"
|
||||
axum-extra = "0.9.4"
|
||||
@@ -216,7 +217,7 @@ chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.39"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.23"
|
||||
clap = "4.5.26"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.0"
|
||||
@@ -226,7 +227,7 @@ console-subscriber = "0.1.1"
|
||||
console_error_panic_hook = "0.1"
|
||||
const-str = "0.5.6"
|
||||
const_format = "0.2.34"
|
||||
criterion = "0.4"
|
||||
criterion = "0.5"
|
||||
csv = "1.3.1"
|
||||
ctr = "0.9.1"
|
||||
cupid = "0.6.1"
|
||||
@@ -245,7 +246,7 @@ envy = "0.4"
|
||||
eyre = "0.6.9"
|
||||
fastrand = "2.1.1"
|
||||
flate2 = "1.0.35"
|
||||
futures = "0.3.28"
|
||||
futures = "0.3.31"
|
||||
futures-util = "0.3"
|
||||
generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
@@ -289,7 +290,7 @@ parking_lot = "0.12.3"
|
||||
pem = "0.8"
|
||||
petgraph = "0.6.5"
|
||||
pin-project = "1.1"
|
||||
pin-project-lite = "0.2.15"
|
||||
pin-project-lite = "0.2.16"
|
||||
pretty_env_logger = "0.4.0"
|
||||
publicsuffix = "2.2.3"
|
||||
quote = "1"
|
||||
@@ -311,7 +312,7 @@ semver = "1.0.24"
|
||||
serde = "1.0.217"
|
||||
serde_bytes = "0.11.15"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.134"
|
||||
serde_json = "1.0.135"
|
||||
serde_json_path = "0.7.1"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
@@ -324,10 +325,10 @@ strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
subtle-encoding = "0.5"
|
||||
syn = "1"
|
||||
sysinfo = "0.30.13"
|
||||
sysinfo = "0.33.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.43"
|
||||
tempfile = "3.14"
|
||||
tempfile = "3.15"
|
||||
thiserror = "1.0.64"
|
||||
time = "0.3.37"
|
||||
tokio = "1.39"
|
||||
@@ -344,7 +345,7 @@ tracing-opentelemetry = "0.19.0"
|
||||
tracing-subscriber = "0.3.19"
|
||||
tracing-tree = "0.2.2"
|
||||
tracing-log = "0.2"
|
||||
ts-rs = "10.0.0"
|
||||
ts-rs = "10.1.0"
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
url = "2.5"
|
||||
utoipa = "5.2"
|
||||
@@ -353,7 +354,7 @@ utoipauto = "0.2"
|
||||
uuid = "*"
|
||||
vergen = { version = "=8.3.1", default-features = false }
|
||||
walkdir = "2"
|
||||
wasm-bindgen-test = "0.3.45"
|
||||
wasm-bindgen-test = "0.3.49"
|
||||
x25519-dalek = "2.0.0"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
@@ -386,29 +387,24 @@ cw-controllers = { version = "=1.1.0" }
|
||||
# cosmrs-related
|
||||
bip32 = { version = "0.5.2", default-features = false }
|
||||
|
||||
# temporarily using a fork again (yay.) because we need staking and slashing support (which are already on main but not released)
|
||||
# plus response message parsing (which is, as of the time of writing this message, waiting to get merged)
|
||||
#cosmrs = { path = "../cosmos-rust-fork/cosmos-rust/cosmrs" }
|
||||
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev = "4b1332e6d8258ac845cef71589c8d362a669675a" } # unfortuntely we need a fork by yours truly to get the staking support
|
||||
tendermint = "0.37.0" # same version as used by cosmrs
|
||||
tendermint-rpc = "0.37.0" # same version as used by cosmrs
|
||||
prost = { version = "0.12", default-features = false }
|
||||
|
||||
cosmrs = { version = "0.21.0" }
|
||||
tendermint = "0.40.0"
|
||||
tendermint-rpc = "0.40.0"
|
||||
prost = { version = "0.13", default-features = false }
|
||||
|
||||
# wasm-related dependencies
|
||||
gloo-utils = "0.2.0"
|
||||
gloo-net = "0.5.0"
|
||||
gloo-net = "0.6.0"
|
||||
|
||||
# use a separate branch due to feature unification failures
|
||||
# this is blocked until the upstream removes outdates `wasm_bindgen` feature usage
|
||||
# indexed_db_futures = "0.4.1"
|
||||
indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", branch = "update-uuid" }
|
||||
indexed_db_futures = "0.6.0"
|
||||
js-sys = "0.3.76"
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
tsify = "0.4.5"
|
||||
wasm-bindgen = "0.2.99"
|
||||
wasm-bindgen-futures = "0.4.49"
|
||||
wasmtimer = "0.2.0"
|
||||
web-sys = "0.3.72"
|
||||
wasmtimer = "0.4.1"
|
||||
web-sys = "0.3.76"
|
||||
|
||||
# Profile settings for individual crates
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
|
||||
* `nym-socks5-client` - a Socks5 proxy you can run on your machine and use with existing applications.
|
||||
* `nym-explorer` - a (projected) block explorer and (existing) mixnet viewer.
|
||||
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
|
||||
* `nym-cli` - a tool for interacting with the network from the CLI.
|
||||
* `nym-cli` - a tool for interacting with the network from the CLI.
|
||||
<!-- coming soon
|
||||
* `nym-network-monitor` - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
|
||||
-->
|
||||
@@ -42,10 +42,10 @@ client ───► Gateway ──┘ mix │ mix ┌─►mix ───►
|
||||
|
||||
References for developers:
|
||||
|
||||
* [Dev Docs](https://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)
|
||||
* [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)
|
||||
|
||||
### Developer chat
|
||||
|
||||
@@ -66,4 +66,4 @@ As a general approach, licensing is as follows this pattern:
|
||||
- libraries and components are Apache 2.0 or MIT
|
||||
- documentation is Apache 2.0 or CC0-1.0
|
||||
|
||||
Nym Node Operators and Validators Temrs and Conditions can be found [here](https://nymtech.net/terms-and-conditions/operators/v1.0.0).
|
||||
Nym Node Operators and Validators Temrs and Conditions can be found [here](https://nym.com/terms-and-conditions/operators/v1.0.0).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.45"
|
||||
version = "1.1.47"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.45"
|
||||
version = "1.1.47"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -28,7 +28,7 @@ pub type HmacSha256 = Hmac<Sha256>;
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
|
||||
@@ -517,7 +517,7 @@ impl Default for Acknowledgements {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
#[serde(default)]
|
||||
pub struct Topology {
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
@@ -553,12 +553,15 @@ pub struct Topology {
|
||||
|
||||
/// Specifies whether this client should attempt to retrieve all available network nodes
|
||||
/// as opposed to just active mixnodes/gateways.
|
||||
/// Useless without `ignore_epoch_roles = true`
|
||||
pub use_extended_topology: bool,
|
||||
|
||||
/// Specifies whether this client should ignore the current epoch role of the target egress node
|
||||
/// when constructing the final hop packets.
|
||||
pub ignore_egress_epoch_role: bool,
|
||||
|
||||
/// Specifies whether this client should ignore the current epoch role of the ingress node
|
||||
/// when attempting to establish new connection
|
||||
pub ignore_ingress_epoch_role: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
@@ -596,7 +599,9 @@ impl Default for Topology {
|
||||
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
|
||||
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
|
||||
use_extended_topology: false,
|
||||
ignore_egress_epoch_role: false,
|
||||
|
||||
ignore_egress_epoch_role: true,
|
||||
ignore_ingress_epoch_role: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,11 +115,12 @@ where
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::current_gateways(
|
||||
crate::init::helpers::gateways_for_init(
|
||||
&mut rng,
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -170,11 +170,12 @@ where
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::current_gateways(
|
||||
crate::init::helpers::gateways_for_init(
|
||||
&mut rng,
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -472,6 +472,7 @@ where
|
||||
.claim_initial_bandwidth()
|
||||
.await
|
||||
.map_err(gateway_failure)?;
|
||||
|
||||
gateway_client
|
||||
.start_listening_for_mixnet_messages()
|
||||
.map_err(gateway_failure)?;
|
||||
|
||||
@@ -65,7 +65,6 @@ pub trait MixnetClientStorage {
|
||||
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ephemeral {
|
||||
key_store: InMemEphemeralKeys,
|
||||
reply_store: reply_storage::Empty,
|
||||
@@ -121,7 +120,6 @@ impl MixnetClientStorage for Ephemeral {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(all(
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "fs-surb-storage",
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::client::key_manager::ClientKeys;
|
||||
use async_trait::async_trait;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -66,7 +65,6 @@ pub enum OnDiskKeysError {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct OnDiskKeys {
|
||||
paths: ClientKeysPaths,
|
||||
@@ -196,9 +194,8 @@ impl KeyStore for OnDiskKeys {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct InMemEphemeralKeys {
|
||||
keys: Arc<Mutex<ClientKeys>>,
|
||||
keys: Mutex<ClientKeys>,
|
||||
}
|
||||
|
||||
impl InMemEphemeralKeys {
|
||||
@@ -207,7 +204,7 @@ impl InMemEphemeralKeys {
|
||||
R: RngCore + CryptoRng,
|
||||
{
|
||||
InMemEphemeralKeys {
|
||||
keys: Arc::new(Mutex::new(ClientKeys::generate_new(rng))),
|
||||
keys: Mutex::new(ClientKeys::generate_new(rng)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::{spawn_future, ForgetMe};
|
||||
use log::*;
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use transceiver::ErasedGatewayError;
|
||||
|
||||
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
|
||||
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
|
||||
@@ -68,7 +70,10 @@ impl MixTrafficController {
|
||||
)
|
||||
}
|
||||
|
||||
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
|
||||
async fn on_messages(
|
||||
&mut self,
|
||||
mut mix_packets: Vec<MixPacket>,
|
||||
) -> Result<(), ErasedGatewayError> {
|
||||
debug_assert!(!mix_packets.is_empty());
|
||||
|
||||
let result = if mix_packets.len() == 1 {
|
||||
@@ -80,21 +85,14 @@ impl MixTrafficController {
|
||||
.await
|
||||
};
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("Failed to send sphinx packet(s) to the gateway: {err}");
|
||||
self.consecutive_gateway_failure_count += 1;
|
||||
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
|
||||
// todo: in the future this should initiate a 'graceful' shutdown or try
|
||||
// to reconnect?
|
||||
panic!("failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead. Can't do anything about it yet :(")
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
|
||||
self.consecutive_gateway_failure_count = 0;
|
||||
}
|
||||
if result.is_err() {
|
||||
self.consecutive_gateway_failure_count += 1;
|
||||
} else {
|
||||
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
|
||||
self.consecutive_gateway_failure_count = 0;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
|
||||
@@ -105,7 +103,18 @@ impl MixTrafficController {
|
||||
tokio::select! {
|
||||
mix_packets = self.mix_rx.recv() => match mix_packets {
|
||||
Some(mix_packets) => {
|
||||
self.on_messages(mix_packets).await;
|
||||
if let Err(err) = self.on_messages(mix_packets).await {
|
||||
error!("Failed to send sphinx packet(s) to the gateway: {err}");
|
||||
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
|
||||
// Disconnect from the gateway. If we should try to re-connect
|
||||
// is handled at a higher layer.
|
||||
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
|
||||
// Do we need to handle the embedded mixnet client case
|
||||
// separately?
|
||||
shutdown.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages));
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
log::trace!("MixTrafficController: Stopping since channel closed");
|
||||
|
||||
@@ -626,9 +626,14 @@ where
|
||||
messages: Vec<RealMessage>,
|
||||
transmission_lane: TransmissionLane,
|
||||
) {
|
||||
self.real_message_sender
|
||||
if let Err(err) = self
|
||||
.real_message_sender
|
||||
.send((messages, transmission_lane))
|
||||
.await
|
||||
.expect("real message receiver task (OutQueueControl) has died");
|
||||
{
|
||||
error!(
|
||||
"Failed to forward messages to the real message sender (OutQueueControl): {err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ use self::{
|
||||
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
|
||||
};
|
||||
use crate::client::real_messages_control::message_handler::MessageHandler;
|
||||
use crate::client::replies::reply_controller;
|
||||
use crate::client::replies::reply_controller::{
|
||||
ReplyController, ReplyControllerReceiver, ReplyControllerSender,
|
||||
};
|
||||
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||
use crate::config;
|
||||
use crate::{
|
||||
client::{
|
||||
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
|
||||
@@ -27,16 +29,13 @@ use nym_gateway_client::AcknowledgementReceiver;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::client::replies::reply_controller;
|
||||
use crate::config;
|
||||
pub(crate) use acknowledgement_control::{AckActionSender, Action};
|
||||
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
|
||||
pub(crate) mod acknowledgement_control;
|
||||
pub(crate) mod message_handler;
|
||||
pub(crate) mod real_traffic_stream;
|
||||
|
||||
@@ -545,7 +545,7 @@ where
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
_ = shutdown.recv() => {
|
||||
log::trace!("OutQueueControl: Received shutdown");
|
||||
break;
|
||||
}
|
||||
|
||||
+4
-1
@@ -70,7 +70,10 @@ impl SendingDelayController {
|
||||
lower_bound,
|
||||
multiplier_elevated_counter: 0,
|
||||
time_when_logged_about_elevated_multiplier: now
|
||||
- Duration::from_secs(INTERVAL_BETWEEN_WARNING_ABOUT_ELEVATED_MULTIPLIER_SECS),
|
||||
.checked_sub(Duration::from_secs(
|
||||
INTERVAL_BETWEEN_WARNING_ABOUT_ELEVATED_MULTIPLIER_SECS,
|
||||
))
|
||||
.unwrap_or(now),
|
||||
time_when_changed: now,
|
||||
time_when_backpressure_detected: now,
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
#![warn(clippy::todo)]
|
||||
#![warn(clippy::dbg_macro)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::StreamExt;
|
||||
use nym_client_core_config_types::StatsReporting;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_statistics_common::clients::{
|
||||
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
||||
};
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
client::inbound_messages::{InputMessage, InputMessageSender},
|
||||
@@ -94,10 +94,32 @@ impl StatisticsControl {
|
||||
async fn run_with_shutdown(&mut self, mut task_client: nym_task::TaskClient) {
|
||||
log::debug!("Started StatisticsControl with graceful shutdown support");
|
||||
|
||||
let mut stats_report_interval =
|
||||
tokio::time::interval(self.reporting_config.reporting_interval);
|
||||
let mut local_report_interval = tokio::time::interval(LOCAL_REPORT_INTERVAL);
|
||||
let mut snapshot_interval = tokio::time::interval(SNAPSHOT_INTERVAL);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let mut stats_report_interval = tokio_stream::wrappers::IntervalStream::new(
|
||||
tokio::time::interval(self.reporting_config.reporting_interval),
|
||||
);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let mut local_report_interval = tokio_stream::wrappers::IntervalStream::new(
|
||||
tokio::time::interval(LOCAL_REPORT_INTERVAL),
|
||||
);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let mut snapshot_interval =
|
||||
tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(SNAPSHOT_INTERVAL));
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let mut stats_report_interval = gloo_timers::future::IntervalStream::new(
|
||||
self.reporting_config.reporting_interval.as_millis() as u32,
|
||||
);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let mut local_report_interval =
|
||||
gloo_timers::future::IntervalStream::new(LOCAL_REPORT_INTERVAL.as_millis() as u32);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let mut snapshot_interval =
|
||||
gloo_timers::future::IntervalStream::new(SNAPSHOT_INTERVAL.as_millis() as u32);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -108,16 +130,20 @@ impl StatisticsControl {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = snapshot_interval.tick() => {
|
||||
_ = snapshot_interval.next() => {
|
||||
self.stats.snapshot();
|
||||
}
|
||||
_ = stats_report_interval.tick(), if self.reporting_config.enabled && self.reporting_config.provider_address.is_some() => {
|
||||
// SAFTEY : this branch executes only if reporting is not none, so unwrapp is fine
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.report_stats(self.reporting_config.provider_address.unwrap()).await;
|
||||
_ = stats_report_interval.next() => {
|
||||
let Some(recipient) = self.reporting_config.provider_address else {
|
||||
continue
|
||||
};
|
||||
|
||||
if self.reporting_config.enabled {
|
||||
self.report_stats(recipient).await;
|
||||
}
|
||||
}
|
||||
|
||||
_ = local_report_interval.tick() => {
|
||||
_ = local_report_interval.next() => {
|
||||
self.stats.local_report(&mut task_client);
|
||||
}
|
||||
_ = task_client.recv_with_delay() => {
|
||||
|
||||
@@ -96,6 +96,9 @@ pub enum ClientCoreError {
|
||||
#[error("timed out while trying to establish gateway connection")]
|
||||
GatewayConnectionTimeout,
|
||||
|
||||
#[error("failed to forward mix messages to gateway")]
|
||||
GatewayFailedToForwardMessages,
|
||||
|
||||
#[error("no ping measurements for the gateway ({identity}) performed")]
|
||||
NoGatewayMeasurements { identity: String },
|
||||
|
||||
|
||||
@@ -86,11 +86,12 @@ impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn current_gateways<R: Rng>(
|
||||
pub async fn gateways_for_init<R: Rng>(
|
||||
rng: &mut R,
|
||||
nym_apis: &[Url],
|
||||
user_agent: Option<UserAgent>,
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
@@ -108,8 +109,11 @@ pub async fn current_gateways<R: Rng>(
|
||||
|
||||
log::trace!("Gateways: {:#?}", gateways);
|
||||
|
||||
// filter out gateways below minimum performance and ones that could operate as a mixnode
|
||||
// (we don't want instability)
|
||||
let valid_gateways = gateways
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
|
||||
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
||||
.filter_map(|gateway| gateway.try_into().ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -17,7 +17,7 @@ use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
@@ -221,6 +221,34 @@ pub enum GatewaySetup {
|
||||
},
|
||||
}
|
||||
|
||||
impl Debug for GatewaySetup {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GatewaySetup::MustLoad { gateway_id } => f
|
||||
.debug_struct("GatewaySetup::MustLoad")
|
||||
.field("gateway_id", gateway_id)
|
||||
.finish(),
|
||||
GatewaySetup::New {
|
||||
specification,
|
||||
available_gateways,
|
||||
} => f
|
||||
.debug_struct("GatewaySetup::New")
|
||||
.field("specification", specification)
|
||||
.field("available_gateways", available_gateways)
|
||||
.field("gateways", specification)
|
||||
.finish(),
|
||||
GatewaySetup::ReuseConnection {
|
||||
gateway_details, ..
|
||||
} => f
|
||||
.debug_struct("GatewaySetup::ReuseConnection")
|
||||
.field("authenticated_ephemeral_client", &"***")
|
||||
.field("gateway_details", gateway_details)
|
||||
.field("client_keys", &"***")
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewaySetup {
|
||||
pub fn try_reuse_connection(init_res: InitialisationResult) -> Result<Self, ClientCoreError> {
|
||||
if let Some(authenticated_ephemeral_client) = init_res.authenticated_ephemeral_client {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -19,8 +19,9 @@ use nym_api_requests::ecash::{
|
||||
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
|
||||
};
|
||||
use nym_api_requests::models::{
|
||||
ApiHealthResponse, GatewayBondAnnotated, GatewayCoreStatusResponse, MixnodeCoreStatusResponse,
|
||||
MixnodeStatusResponse, NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
ApiHealthResponse, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||
HistoricalPerformanceResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
||||
@@ -264,6 +265,31 @@ impl<C, S> Client<C, S> {
|
||||
Ok(self.nym_api.get_gateways_detailed_unfiltered().await?)
|
||||
}
|
||||
|
||||
pub async fn get_full_node_performance_history(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Result<Vec<HistoricalPerformanceResponse>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut history = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_node_performance_history(node_id, Some(page), None)
|
||||
.await?;
|
||||
|
||||
history.append(&mut res.history.data);
|
||||
if history.len() < res.history.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(history)
|
||||
}
|
||||
|
||||
// TODO: combine with NymApiClient...
|
||||
pub async fn get_all_cached_described_nodes(
|
||||
&self,
|
||||
|
||||
@@ -65,6 +65,12 @@ pub enum EcashApiError {
|
||||
#[from]
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
|
||||
#[error("nym api error")]
|
||||
NymApi {
|
||||
#[from]
|
||||
source: crate::ValidatorClientError,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<ContractVKShare> for EcashApiClient {
|
||||
|
||||
@@ -13,7 +13,7 @@ use nym_api_requests::ecash::models::{
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||
NodeRefreshBody, NymNodeDescription, RewardedSetResponse,
|
||||
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
@@ -163,6 +163,35 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_node_performance_history(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PerformanceHistoryResponse, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if let Some(page) = page {
|
||||
params.push(("page", page.to_string()))
|
||||
}
|
||||
|
||||
if let Some(per_page) = per_page {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_PERFORMANCE_HISTORY,
|
||||
&*node_id.to_string(),
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_nodes_described(
|
||||
&self,
|
||||
@@ -179,8 +208,15 @@ pub trait NymApiClientExt: ApiClient {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(&[routes::API_VERSION, "nym-nodes", "described"], ¶ms)
|
||||
.await
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_DESCRIBED,
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
@@ -199,8 +235,15 @@ pub trait NymApiClientExt: ApiClient {
|
||||
params.push(("per_page", per_page.to_string()))
|
||||
}
|
||||
|
||||
self.get_json(&[routes::API_VERSION, "nym-nodes", "bonded"], ¶ms)
|
||||
.await
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_BONDED,
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
@@ -210,7 +253,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
"nym-nodes",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"mixnodes",
|
||||
"skimmed",
|
||||
],
|
||||
@@ -226,7 +269,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
"nym-nodes",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"gateways",
|
||||
"skimmed",
|
||||
],
|
||||
@@ -238,7 +281,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, "nym-nodes", "rewarded-set"],
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_REWARDED_SET,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
@@ -271,7 +318,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
"nym-nodes",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
"entry-gateways",
|
||||
"all",
|
||||
@@ -308,7 +355,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
"nym-nodes",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
"mixnodes",
|
||||
"active",
|
||||
@@ -345,7 +392,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
"nym-nodes",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
"mixnodes",
|
||||
"all",
|
||||
@@ -377,7 +424,12 @@ pub trait NymApiClientExt: ApiClient {
|
||||
}
|
||||
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, "unstable", "nym-nodes", "skimmed"],
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
"skimmed",
|
||||
],
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
@@ -686,8 +738,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"nym-nodes",
|
||||
"performance",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_PERFORMANCE,
|
||||
&node_id.to_string(),
|
||||
],
|
||||
NO_PARAMS,
|
||||
@@ -702,8 +754,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"nym-nodes",
|
||||
"annotation",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_ANNOTATION,
|
||||
&node_id.to_string(),
|
||||
],
|
||||
NO_PARAMS,
|
||||
@@ -927,7 +979,11 @@ pub trait NymApiClientExt: ApiClient {
|
||||
request: &NodeRefreshBody,
|
||||
) -> Result<(), NymAPIError> {
|
||||
self.post_json(
|
||||
&[routes::API_VERSION, "nym-nodes", "refresh-described"],
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::NYM_NODES_REFRESH_DESCRIBED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request,
|
||||
)
|
||||
|
||||
@@ -34,6 +34,19 @@ pub mod ecash {
|
||||
pub const EPOCH_ID_PARAM: &str = "epoch_id";
|
||||
}
|
||||
|
||||
pub const NYM_NODES_ROUTES: &str = "nym-nodes";
|
||||
|
||||
pub use nym_nodes::*;
|
||||
pub mod nym_nodes {
|
||||
pub const NYM_NODES_PERFORMANCE_HISTORY: &str = "performance-history";
|
||||
pub const NYM_NODES_PERFORMANCE: &str = "performance";
|
||||
pub const NYM_NODES_ANNOTATION: &str = "annotation";
|
||||
pub const NYM_NODES_DESCRIBED: &str = "described";
|
||||
pub const NYM_NODES_BONDED: &str = "bonded";
|
||||
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
|
||||
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
|
||||
}
|
||||
|
||||
pub const STATUS_ROUTES: &str = "status";
|
||||
pub const API_STATUS_ROUTES: &str = "api-status";
|
||||
pub const HEALTH: &str = "health";
|
||||
|
||||
+31
@@ -153,13 +153,20 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
let req = QueryAllBalancesRequest {
|
||||
address: address.to_string(),
|
||||
pagination,
|
||||
resolve_denom: false,
|
||||
};
|
||||
|
||||
let mut res = self
|
||||
.make_abci_query::<_, QueryAllBalancesResponse>(path.clone(), req)
|
||||
.await?;
|
||||
|
||||
let early_break = res.balances.is_empty();
|
||||
raw_balances.append(&mut res.balances);
|
||||
|
||||
if early_break {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
@@ -187,7 +194,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
.make_abci_query::<_, QueryTotalSupplyResponse>(path.clone(), req)
|
||||
.await?;
|
||||
|
||||
let early_break = res.supply.is_empty();
|
||||
supply.append(&mut res.supply);
|
||||
|
||||
if early_break {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
@@ -328,7 +341,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
.make_abci_query::<_, QueryCodesResponse>(path.clone(), req)
|
||||
.await?;
|
||||
|
||||
let early_break = res.code_infos.is_empty();
|
||||
raw_codes.append(&mut res.code_infos);
|
||||
|
||||
if early_break {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
@@ -373,7 +392,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
.make_abci_query::<_, QueryContractsByCodeResponse>(path.clone(), req)
|
||||
.await?;
|
||||
|
||||
let early_break = res.contracts.is_empty();
|
||||
raw_contracts.append(&mut res.contracts);
|
||||
|
||||
if early_break {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
@@ -429,7 +454,13 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
.make_abci_query::<_, QueryContractHistoryResponse>(path.clone(), req)
|
||||
.await?;
|
||||
|
||||
let early_break = res.entries.is_empty();
|
||||
raw_entries.append(&mut res.entries);
|
||||
|
||||
if early_break {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(next_key) = next_page_key(res.pagination) {
|
||||
pagination = Some(create_pagination(next_key))
|
||||
} else {
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
use crate::rpc::TendermintRpcClient;
|
||||
use async_trait::async_trait;
|
||||
use base64::Engine;
|
||||
use cosmrs::tendermint;
|
||||
use cosmrs::tendermint::{block::Height, evidence::Evidence, Hash};
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::{header, RequestBuilder};
|
||||
use tendermint_rpc::dialect::{v0_34, v0_37, v0_38, LatestDialect};
|
||||
use tendermint_rpc::{
|
||||
client::CompatMode,
|
||||
dialect::{self, Dialect},
|
||||
@@ -21,8 +23,21 @@ macro_rules! perform_with_compat {
|
||||
($self:expr, $request:expr) => {{
|
||||
let request = $request;
|
||||
match $self.compat {
|
||||
CompatMode::V0_37 => $self.perform_v0_37(request).await,
|
||||
CompatMode::V0_34 => $self.perform_v0_34(request).await,
|
||||
CompatMode::V0_38 => {
|
||||
$self
|
||||
.perform_request_with_dialect(request, dialect::v0_38::Dialect)
|
||||
.await
|
||||
}
|
||||
CompatMode::V0_37 => {
|
||||
$self
|
||||
.perform_request_with_dialect(request, dialect::v0_37::Dialect)
|
||||
.await
|
||||
}
|
||||
CompatMode::V0_34 => {
|
||||
$self
|
||||
.perform_request_with_dialect(request, dialect::v0_34::Dialect)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
@@ -70,7 +85,11 @@ impl ReqwestRpcClient {
|
||||
.headers(headers)
|
||||
}
|
||||
|
||||
async fn perform_request<R, S>(&self, request: R) -> Result<R::Output, Error>
|
||||
async fn perform_request_with_dialect<R, S>(
|
||||
&self,
|
||||
request: R,
|
||||
_dialect: S,
|
||||
) -> Result<R::Output, Error>
|
||||
where
|
||||
R: SimpleRequest<S>,
|
||||
S: Dialect,
|
||||
@@ -81,26 +100,25 @@ impl ReqwestRpcClient {
|
||||
.send()
|
||||
.await
|
||||
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
|
||||
let response_status = response.status();
|
||||
let bytes = response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(TendermintRpcErrorMap::into_rpc_err)?;
|
||||
|
||||
// Successful JSON-RPC requests are expected to return a 200 OK HTTP status.
|
||||
// Otherwise, this means that the HTTP request failed as a whole,
|
||||
// as opposed to the JSON-RPC request returning an error,
|
||||
// and we cannot expect the response body to be a valid JSON-RPC response.
|
||||
if response_status != reqwest::StatusCode::OK {
|
||||
// hehe, that's so nasty but we have to somehow convert between different versions of the same lib
|
||||
return Err(Error::http_request_failed(
|
||||
response_status.as_u16().try_into().unwrap(),
|
||||
));
|
||||
}
|
||||
|
||||
R::Response::from_string(bytes).map(Into::into)
|
||||
}
|
||||
|
||||
async fn perform_v0_34<R>(&self, request: R) -> Result<R::Output, Error>
|
||||
where
|
||||
R: SimpleRequest<dialect::v0_34::Dialect>,
|
||||
{
|
||||
self.perform_request(request).await
|
||||
}
|
||||
|
||||
async fn perform_v0_37<R>(&self, request: R) -> Result<R::Output, Error>
|
||||
where
|
||||
R: SimpleRequest<dialect::v0_37::Dialect>,
|
||||
{
|
||||
self.perform_request(request).await
|
||||
}
|
||||
}
|
||||
|
||||
trait TendermintRpcErrorMap {
|
||||
@@ -120,18 +138,50 @@ impl TendermintRpcClient for ReqwestRpcClient {
|
||||
where
|
||||
R: SimpleRequest,
|
||||
{
|
||||
self.perform_request(request).await
|
||||
self.perform_request_with_dialect(request, LatestDialect)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn block_results<H>(&self, height: H) -> Result<block_results::Response, Error>
|
||||
async fn block<H>(&self, height: H) -> Result<endpoint::block::Response, Error>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
perform_with_compat!(self, block_results::Request::new(height.into()))
|
||||
perform_with_compat!(self, endpoint::block::Request::new(height.into()))
|
||||
}
|
||||
|
||||
async fn latest_block_results(&self) -> Result<block_results::Response, Error> {
|
||||
perform_with_compat!(self, block_results::Request::default())
|
||||
async fn block_by_hash(
|
||||
&self,
|
||||
hash: tendermint::Hash,
|
||||
) -> Result<endpoint::block_by_hash::Response, Error> {
|
||||
perform_with_compat!(self, endpoint::block_by_hash::Request::new(hash))
|
||||
}
|
||||
|
||||
async fn latest_block(&self) -> Result<endpoint::block::Response, Error> {
|
||||
perform_with_compat!(self, endpoint::block::Request::default())
|
||||
}
|
||||
|
||||
async fn block_results<H>(&self, height: H) -> Result<endpoint::block_results::Response, Error>
|
||||
where
|
||||
H: Into<Height> + Send,
|
||||
{
|
||||
perform_with_compat!(self, endpoint::block_results::Request::new(height.into()))
|
||||
}
|
||||
|
||||
async fn latest_block_results(&self) -> Result<endpoint::block_results::Response, Error> {
|
||||
perform_with_compat!(self, endpoint::block_results::Request::default())
|
||||
}
|
||||
|
||||
async fn block_search(
|
||||
&self,
|
||||
query: Query,
|
||||
page: u32,
|
||||
per_page: u8,
|
||||
order: Order,
|
||||
) -> Result<endpoint::block_search::Response, Error> {
|
||||
perform_with_compat!(
|
||||
self,
|
||||
endpoint::block_search::Request::new(query, page, per_page, order)
|
||||
)
|
||||
}
|
||||
|
||||
async fn header<H>(&self, height: H) -> Result<endpoint::header::Response, Error>
|
||||
@@ -140,11 +190,26 @@ impl TendermintRpcClient for ReqwestRpcClient {
|
||||
{
|
||||
let height = height.into();
|
||||
match self.compat {
|
||||
CompatMode::V0_37 => self.perform(endpoint::header::Request::new(height)).await,
|
||||
CompatMode::V0_38 => {
|
||||
self.perform_request_with_dialect(
|
||||
endpoint::header::Request::new(height),
|
||||
v0_38::Dialect,
|
||||
)
|
||||
.await
|
||||
}
|
||||
CompatMode::V0_37 => {
|
||||
self.perform_request_with_dialect(
|
||||
endpoint::header::Request::new(height),
|
||||
v0_37::Dialect,
|
||||
)
|
||||
.await
|
||||
}
|
||||
CompatMode::V0_34 => {
|
||||
// Back-fill with a request to /block endpoint and
|
||||
// taking just the header from the response.
|
||||
let resp = self.perform_v0_34(block::Request::new(height)).await?;
|
||||
let resp = self
|
||||
.perform_request_with_dialect(block::Request::new(height), v0_34::Dialect)
|
||||
.await?;
|
||||
Ok(resp.into())
|
||||
}
|
||||
}
|
||||
@@ -152,12 +217,25 @@ impl TendermintRpcClient for ReqwestRpcClient {
|
||||
|
||||
async fn header_by_hash(&self, hash: Hash) -> Result<header_by_hash::Response, Error> {
|
||||
match self.compat {
|
||||
CompatMode::V0_37 => self.perform(header_by_hash::Request::new(hash)).await,
|
||||
CompatMode::V0_38 => {
|
||||
self.perform_request_with_dialect(
|
||||
header_by_hash::Request::new(hash),
|
||||
v0_38::Dialect,
|
||||
)
|
||||
.await
|
||||
}
|
||||
CompatMode::V0_37 => {
|
||||
self.perform_request_with_dialect(
|
||||
header_by_hash::Request::new(hash),
|
||||
v0_37::Dialect,
|
||||
)
|
||||
.await
|
||||
}
|
||||
CompatMode::V0_34 => {
|
||||
// Back-fill with a request to /block_by_hash endpoint and
|
||||
// taking just the header from the response.
|
||||
let resp = self
|
||||
.perform_v0_34(block_by_hash::Request::new(hash))
|
||||
.perform_request_with_dialect(block_by_hash::Request::new(hash), v0_34::Dialect)
|
||||
.await?;
|
||||
Ok(resp.into())
|
||||
}
|
||||
@@ -167,8 +245,18 @@ impl TendermintRpcClient for ReqwestRpcClient {
|
||||
/// `/broadcast_evidence`: broadcast an evidence.
|
||||
async fn broadcast_evidence(&self, e: Evidence) -> Result<evidence::Response, Error> {
|
||||
match self.compat {
|
||||
CompatMode::V0_37 => self.perform(evidence::Request::new(e)).await,
|
||||
CompatMode::V0_34 => self.perform_v0_34(evidence::Request::new(e)).await,
|
||||
CompatMode::V0_38 => {
|
||||
self.perform_request_with_dialect(evidence::Request::new(e), v0_38::Dialect)
|
||||
.await
|
||||
}
|
||||
CompatMode::V0_37 => {
|
||||
self.perform_request_with_dialect(evidence::Request::new(e), v0_37::Dialect)
|
||||
.await
|
||||
}
|
||||
CompatMode::V0_34 => {
|
||||
self.perform_request_with_dialect(evidence::Request::new(e), v0_34::Dialect)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -863,11 +863,4 @@ pub enum QueryMsg {
|
||||
pub struct MigrateMsg {
|
||||
pub unsafe_skip_state_updates: Option<bool>,
|
||||
pub vesting_contract_address: Option<String>,
|
||||
pub current_nym_node_semver: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub version_score_weights: OutdatedVersionWeights,
|
||||
|
||||
#[serde(default)]
|
||||
pub version_score_params: VersionScoreFormulaParams,
|
||||
}
|
||||
|
||||
@@ -113,6 +113,10 @@ impl Role {
|
||||
pub fn is_standby(&self) -> bool {
|
||||
matches!(self, Role::Standby)
|
||||
}
|
||||
|
||||
pub fn is_mixnode(&self) -> bool {
|
||||
matches!(self, Role::Layer1 | Role::Layer2 | Role::Layer3)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Role {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -13,6 +13,7 @@ use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
|
||||
use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody};
|
||||
use nym_credentials_interface::Bandwidth;
|
||||
use nym_credentials_interface::{ClientTicket, TicketType};
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
|
||||
@@ -352,7 +353,9 @@ impl CredentialHandler {
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to send ticket {ticket_id} for verification to ecash signer '{client}': {err}. if we don't reach quorum, we'll retry later");
|
||||
Ok(false)
|
||||
Err(EcashTicketError::ApiFailure(EcashApiError::NymApi {
|
||||
source: err,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ license.workspace = true
|
||||
[dependencies]
|
||||
bincode = { workspace = true }
|
||||
defguard_wireguard_rs = { workspace = true }
|
||||
log = { workspace = true }
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
ALTER TABLE message_store
|
||||
RENAME TO message_store_old;
|
||||
|
||||
-- add new column with message timestamp.
|
||||
-- note: we can't simply alter existing table to add it since the default value is non-constant
|
||||
CREATE TABLE message_store
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
client_address_bs58 TEXT NOT NULL,
|
||||
content BLOB NOT NULL,
|
||||
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO message_store(id, client_address_bs58, content)
|
||||
SELECT id, client_address_bs58, content
|
||||
FROM message_store_old;
|
||||
DROP TABLE message_store_old;
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::models::StoredMessage;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct InboxManager {
|
||||
pub 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
|
||||
@@ -71,44 +73,22 @@ impl InboxManager {
|
||||
// get 1 additional message to check whether there will be more to grab
|
||||
// next time
|
||||
let limit = self.retrieval_limit + 1;
|
||||
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?
|
||||
};
|
||||
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?;
|
||||
|
||||
if res.len() > self.retrieval_limit as usize {
|
||||
res.truncate(self.retrieval_limit as usize);
|
||||
@@ -146,4 +126,13 @@ 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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use bandwidth::BandwidthManager;
|
||||
use clients::{ClientManager, ClientType};
|
||||
use inboxes::InboxManager;
|
||||
use models::{
|
||||
Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage,
|
||||
VerifiedTicket, WireguardPeer,
|
||||
@@ -31,6 +30,7 @@ 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(crate) fn inbox_manager(&self) -> &InboxManager {
|
||||
pub fn inbox_manager(&self) -> &InboxManager {
|
||||
&self.inbox_manager
|
||||
}
|
||||
|
||||
|
||||
@@ -48,11 +48,13 @@ 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)]
|
||||
|
||||
@@ -27,3 +27,5 @@ nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-metrics = { path = "../nym-metrics" }
|
||||
nym-task = { path = "../task" }
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||
workspace = true
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
|
||||
use super::ClientStatsEvents;
|
||||
use core::fmt;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::{collections::VecDeque, time::Duration};
|
||||
|
||||
use nym_metrics::{inc, inc_by};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -18,6 +15,51 @@ use si_scale::helpers::bibytes2;
|
||||
// Also, set it larger than the packet report interval so that we don't miss notable singular events
|
||||
const RECORDING_WINDOW_MS: u64 = 2300;
|
||||
|
||||
#[derive(PartialOrd, PartialEq, Clone, Copy, Debug)]
|
||||
struct Instant {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
inner: std::time::Instant,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
inner: wasmtimer::std::Instant,
|
||||
}
|
||||
|
||||
impl Instant {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn inner(&self) -> &std::time::Instant {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn inner(&self) -> &wasmtimer::std::Instant {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn now() -> Self {
|
||||
Instant {
|
||||
inner: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn now() -> Self {
|
||||
Instant {
|
||||
inner: wasmtimer::std::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_sub(&self, duration: Duration) -> Option<Instant> {
|
||||
self.inner()
|
||||
.checked_sub(duration)
|
||||
.map(|inner| Instant { inner })
|
||||
}
|
||||
|
||||
fn duration_since(&self, earlier: &Instant) -> Duration {
|
||||
self.inner.duration_since(*earlier.inner())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct PacketStatistics {
|
||||
// Sent
|
||||
@@ -424,7 +466,11 @@ impl PacketStatisticsControl {
|
||||
self.history.push_back((Instant::now(), self.stats.clone()));
|
||||
|
||||
// Filter out old ones
|
||||
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
|
||||
let now = Instant::now();
|
||||
let recording_window = Instant::now()
|
||||
.checked_sub(Duration::from_millis(RECORDING_WINDOW_MS))
|
||||
.unwrap_or(now);
|
||||
|
||||
while self
|
||||
.history
|
||||
.front()
|
||||
@@ -442,7 +488,7 @@ impl PacketStatisticsControl {
|
||||
|
||||
// Do basic averaging over the entire history, which just uses the first and last
|
||||
if let Some((start, start_stats)) = self.history.front() {
|
||||
let duration_secs = Instant::now().duration_since(*start).as_secs_f64();
|
||||
let duration_secs = Instant::now().duration_since(start).as_secs_f64();
|
||||
let delta = self.stats.clone() - start_stats.clone();
|
||||
let rates = PacketRates::from(delta) / duration_secs;
|
||||
Some(rates)
|
||||
@@ -458,7 +504,10 @@ impl PacketStatisticsControl {
|
||||
}
|
||||
|
||||
// Filter out old ones
|
||||
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
|
||||
let now = Instant::now();
|
||||
let recording_window = now
|
||||
.checked_sub(Duration::from_millis(RECORDING_WINDOW_MS))
|
||||
.unwrap_or(now);
|
||||
while self
|
||||
.rates
|
||||
.front()
|
||||
|
||||
@@ -67,7 +67,7 @@ impl Default for ClientStatsReport {
|
||||
pub struct OsInformation {
|
||||
pub(crate) os_type: String,
|
||||
pub(crate) os_version: Option<String>,
|
||||
pub(crate) os_arch: Option<String>,
|
||||
pub(crate) os_arch: String,
|
||||
}
|
||||
|
||||
impl OsInformation {
|
||||
|
||||
@@ -8,10 +8,13 @@ license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cfg-if = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
log = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "sync"] }
|
||||
tokio-util = { workspace = true, features = ["rt"] }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
workspace = true
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{TaskClient, TaskManager};
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
use tokio::task::JoinSet;
|
||||
use tokio::time::sleep;
|
||||
use tokio_util::sync::{CancellationToken, DropGuard};
|
||||
use tokio_util::task::TaskTracker;
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
#[cfg(unix)]
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
|
||||
pub const DEFAULT_MAX_SHUTDOWN_DURATION: Duration = Duration::from_secs(5);
|
||||
|
||||
pub fn token_name(name: &Option<String>) -> String {
|
||||
name.clone().unwrap_or_else(|| "unknown".to_string())
|
||||
}
|
||||
|
||||
// a wrapper around tokio's CancellationToken that adds optional `name` information to more easily
|
||||
// track down sources of shutdown
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ShutdownToken {
|
||||
name: Option<String>,
|
||||
inner: CancellationToken,
|
||||
}
|
||||
|
||||
impl Clone for ShutdownToken {
|
||||
fn clone(&self) -> Self {
|
||||
// make sure to not accidentally overflow the stack if we keep cloning the handle
|
||||
let name = if let Some(name) = &self.name {
|
||||
if name != Self::OVERFLOW_NAME && name.len() < Self::MAX_NAME_LENGTH {
|
||||
Some(format!("{name}-child"))
|
||||
} else {
|
||||
Some(Self::OVERFLOW_NAME.to_string())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
ShutdownToken {
|
||||
name,
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ShutdownToken {
|
||||
type Target = CancellationToken;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl ShutdownToken {
|
||||
const MAX_NAME_LENGTH: usize = 128;
|
||||
const OVERFLOW_NAME: &'static str = "reached maximum ShutdownToken children name depth";
|
||||
|
||||
pub fn new(name: impl Into<String>) -> Self {
|
||||
ShutdownToken {
|
||||
name: Some(name.into()),
|
||||
inner: CancellationToken::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a ShutdownToken which will get cancelled whenever the current token gets cancelled.
|
||||
// Unlike a cloned/forked ShutdownToken, cancelling a child token does not cancel the parent token.
|
||||
#[must_use]
|
||||
pub fn child_token<S: Into<String>>(&self, child_suffix: S) -> Self {
|
||||
let suffix = child_suffix.into();
|
||||
let child_name = if let Some(base) = &self.name {
|
||||
format!("{base}-{suffix}")
|
||||
} else {
|
||||
format!("unknown-{suffix}")
|
||||
};
|
||||
|
||||
ShutdownToken {
|
||||
name: Some(child_name),
|
||||
inner: self.inner.child_token(),
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a clone of the ShutdownToken which will get cancelled whenever the current token gets cancelled, and vice versa.
|
||||
#[must_use]
|
||||
pub fn clone_with_suffix<S: Into<String>>(&self, child_suffix: S) -> Self {
|
||||
let mut child = self.clone();
|
||||
let suffix = child_suffix.into();
|
||||
let child_name = if let Some(base) = &self.name {
|
||||
format!("{base}-{suffix}")
|
||||
} else {
|
||||
format!("unknown-{suffix}")
|
||||
};
|
||||
|
||||
child.name = Some(child_name);
|
||||
child
|
||||
}
|
||||
|
||||
// exposed method with the old name for easier migration
|
||||
// it will eventually be removed so please try to use `.clone_with_suffix` instead
|
||||
#[must_use]
|
||||
pub fn fork<S: Into<String>>(&self, child_suffix: S) -> Self {
|
||||
self.clone_with_suffix(child_suffix)
|
||||
}
|
||||
|
||||
// exposed method with the old name for easier migration
|
||||
// it will eventually be removed so please try to use `.clone().named(name)` instead
|
||||
#[must_use]
|
||||
pub fn fork_named<S: Into<String>>(&self, name: S) -> Self {
|
||||
self.clone().named(name)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn named<S: Into<String>>(mut self, name: S) -> Self {
|
||||
self.name = Some(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn add_suffix<S: Into<String>>(self, suffix: S) -> Self {
|
||||
let suffix = suffix.into();
|
||||
let name = if let Some(base) = &self.name {
|
||||
format!("{base}-{suffix}")
|
||||
} else {
|
||||
format!("unknown-{suffix}")
|
||||
};
|
||||
self.named(name)
|
||||
}
|
||||
|
||||
// Returned guard will cancel this token (and all its children) on drop unless disarmed.
|
||||
pub fn drop_guard(self) -> ShutdownDropGuard {
|
||||
ShutdownDropGuard {
|
||||
name: self.name,
|
||||
inner: self.inner.drop_guard(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
token_name(&self.name)
|
||||
}
|
||||
|
||||
pub async fn run_until_cancelled<F>(&self, fut: F) -> Option<F::Output>
|
||||
where
|
||||
F: Future,
|
||||
{
|
||||
let res = self.inner.run_until_cancelled(fut).await;
|
||||
trace!("'{}' got cancelled", self.name());
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShutdownDropGuard {
|
||||
name: Option<String>,
|
||||
inner: DropGuard,
|
||||
}
|
||||
|
||||
impl Deref for ShutdownDropGuard {
|
||||
type Target = DropGuard;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl ShutdownDropGuard {
|
||||
pub fn disarm(self) -> ShutdownToken {
|
||||
ShutdownToken {
|
||||
name: self.name,
|
||||
inner: self.inner.disarm(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
token_name(&self.name)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShutdownManager {
|
||||
pub root_token: ShutdownToken,
|
||||
|
||||
legacy_task_manager: Option<TaskManager>,
|
||||
|
||||
shutdown_signals: JoinSet<()>,
|
||||
|
||||
// the reason I'm not using a `JoinSet` is because it forces us to use futures with the same `::Output` type
|
||||
tracker: TaskTracker,
|
||||
|
||||
max_shutdown_duration: Duration,
|
||||
}
|
||||
|
||||
impl Deref for ShutdownManager {
|
||||
type Target = TaskTracker;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.tracker
|
||||
}
|
||||
}
|
||||
|
||||
impl ShutdownManager {
|
||||
pub fn new(root_token_name: impl Into<String>) -> Self {
|
||||
let manager = ShutdownManager {
|
||||
root_token: ShutdownToken::new(root_token_name),
|
||||
legacy_task_manager: None,
|
||||
shutdown_signals: Default::default(),
|
||||
tracker: Default::default(),
|
||||
max_shutdown_duration: Default::default(),
|
||||
};
|
||||
|
||||
// we need to add an explicit watcher for the cancellation token being cancelled
|
||||
// so that we could cancel all legacy tasks
|
||||
let cancel_watcher = manager.root_token.clone();
|
||||
manager.with_shutdown(async move { cancel_watcher.cancelled().await })
|
||||
}
|
||||
|
||||
pub fn with_legacy_task_manager(mut self) -> Self {
|
||||
let mut legacy_manager =
|
||||
TaskManager::default().named(format!("{}-legacy", self.root_token.name()));
|
||||
let mut legacy_error_rx = legacy_manager.task_return_error_rx();
|
||||
let mut legacy_drop_rx = legacy_manager.task_drop_rx();
|
||||
|
||||
self.legacy_task_manager = Some(legacy_manager);
|
||||
|
||||
// add a task that listens for legacy task clients being dropped to trigger cancellation
|
||||
self.with_shutdown(async move {
|
||||
tokio::select! {
|
||||
_ = legacy_error_rx.recv() => (),
|
||||
_ = legacy_drop_rx.recv() => (),
|
||||
}
|
||||
|
||||
info!("received legacy shutdown signal");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn with_default_shutdown_signals(self) -> std::io::Result<Self> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
self.with_interrupt_signal()
|
||||
.with_terminate_signal()?
|
||||
.with_quit_signal()
|
||||
} else {
|
||||
Ok(self.with_interrupt_signal())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_shutdown<F>(mut self, shutdown: F) -> Self
|
||||
where
|
||||
F: Future<Output = ()>,
|
||||
F: Send + 'static,
|
||||
{
|
||||
let shutdown_token = self.root_token.clone();
|
||||
self.shutdown_signals.spawn(async move {
|
||||
shutdown.await;
|
||||
|
||||
info!("sending cancellation after receiving shutdown signal");
|
||||
shutdown_token.cancel();
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn with_shutdown_signal(self, signal_kind: SignalKind) -> std::io::Result<Self> {
|
||||
let mut sig = signal(signal_kind)?;
|
||||
Ok(self.with_shutdown(async move {
|
||||
sig.recv().await;
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn with_interrupt_signal(self) -> Self {
|
||||
self.with_shutdown(async move {
|
||||
let _ = tokio::signal::ctrl_c().await;
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn with_terminate_signal(self) -> std::io::Result<Self> {
|
||||
self.with_shutdown_signal(SignalKind::terminate())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn with_quit_signal(self) -> std::io::Result<Self> {
|
||||
self.with_shutdown_signal(SignalKind::quit())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_shutdown_duration(mut self, duration: Duration) -> Self {
|
||||
self.max_shutdown_duration = duration;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn child_token<S: Into<String>>(&self, child_suffix: S) -> ShutdownToken {
|
||||
self.root_token.child_token(child_suffix)
|
||||
}
|
||||
|
||||
pub fn clone_token<S: Into<String>>(&self, child_suffix: S) -> ShutdownToken {
|
||||
self.root_token.clone_with_suffix(child_suffix)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn subscribe_legacy<S: Into<String>>(&self, child_suffix: S) -> TaskClient {
|
||||
// alternatively we could have set self.legacy_task_manager = Some(TaskManager::default());
|
||||
// on demand if it wasn't unavailable, but then we'd have to use mutable reference
|
||||
#[allow(clippy::expect_used)]
|
||||
self.legacy_task_manager
|
||||
.as_ref()
|
||||
.expect("did not enable legacy shutdown support")
|
||||
.subscribe_named(child_suffix)
|
||||
}
|
||||
|
||||
async fn finish_shutdown(mut self) {
|
||||
let mut wait_futures = FuturesUnordered::<Pin<Box<dyn Future<Output = ()>>>>::new();
|
||||
|
||||
// force shutdown via ctrl-c
|
||||
wait_futures.push(Box::pin(async move {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let interrupt_future = tokio::signal::ctrl_c();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let interrupt_future = futures::future::pending::<()>();
|
||||
|
||||
let _ = interrupt_future.await;
|
||||
info!("received interrupt - forcing shutdown");
|
||||
}));
|
||||
|
||||
// timeout
|
||||
wait_futures.push(Box::pin(async move {
|
||||
sleep(self.max_shutdown_duration).await;
|
||||
info!("timeout reached, forcing shutdown");
|
||||
}));
|
||||
|
||||
// graceful
|
||||
wait_futures.push(Box::pin(async move {
|
||||
self.tracker.wait().await;
|
||||
debug!("migrated tasks successfully shutdown");
|
||||
if let Some(legacy) = self.legacy_task_manager.as_mut() {
|
||||
legacy.wait_for_graceful_shutdown().await;
|
||||
debug!("legacy tasks successfully shutdown");
|
||||
}
|
||||
|
||||
info!("all registered tasks successfully shutdown")
|
||||
}));
|
||||
|
||||
wait_futures.next().await;
|
||||
}
|
||||
|
||||
pub async fn wait_for_shutdown_signal(mut self) {
|
||||
self.shutdown_signals.join_next().await;
|
||||
|
||||
if let Some(legacy_manager) = self.legacy_task_manager.as_mut() {
|
||||
info!("attempting to shutdown legacy tasks");
|
||||
let _ = legacy_manager.signal_shutdown();
|
||||
}
|
||||
|
||||
info!("waiting for tasks to finish... (press ctrl-c to force)");
|
||||
self.finish_shutdown().await;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod cancellation;
|
||||
pub mod connections;
|
||||
pub mod event;
|
||||
pub mod manager;
|
||||
@@ -8,9 +9,11 @@ pub mod manager;
|
||||
pub mod signal;
|
||||
pub mod spawn;
|
||||
|
||||
pub use cancellation::{ShutdownDropGuard, ShutdownManager, ShutdownToken};
|
||||
pub use event::{StatusReceiver, StatusSender, TaskStatus, TaskStatusEvent};
|
||||
pub use manager::{TaskClient, TaskHandle, TaskManager};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use signal::wait_for_signal_and_error;
|
||||
|
||||
pub use spawn::{spawn, spawn_with_report_error};
|
||||
pub use tokio_util::task::TaskTracker;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use signal::{wait_for_signal, wait_for_signal_and_error};
|
||||
|
||||
@@ -185,6 +185,19 @@ impl TaskManager {
|
||||
}
|
||||
}
|
||||
|
||||
// used for compatibility with the ShutdownManager
|
||||
pub(crate) fn task_return_error_rx(&mut self) -> ErrorReceiver {
|
||||
self.task_return_error_rx
|
||||
.take()
|
||||
.expect("unable to get error channel: attempt to wait twice?")
|
||||
}
|
||||
|
||||
pub(crate) fn task_drop_rx(&mut self) -> ErrorReceiver {
|
||||
self.task_drop_rx
|
||||
.take()
|
||||
.expect("unable to get task drop channel: attempt to wait twice?")
|
||||
}
|
||||
|
||||
pub async fn wait_for_error(&mut self) -> Option<SentError> {
|
||||
let mut error_rx = self
|
||||
.task_return_error_rx
|
||||
@@ -208,6 +221,13 @@ impl TaskManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn wait_for_graceful_shutdown(&mut self) {
|
||||
if let Some(notify_rx) = self.notify_rx.take() {
|
||||
drop(notify_rx);
|
||||
}
|
||||
self.notify_tx.closed().await
|
||||
}
|
||||
|
||||
pub async fn wait_for_shutdown(&mut self) {
|
||||
log::debug!("Waiting for shutdown");
|
||||
if let Some(notify_rx) = self.notify_rx.take() {
|
||||
|
||||
@@ -401,20 +401,17 @@ impl NymTopology {
|
||||
});
|
||||
};
|
||||
|
||||
// a 'valid' egress is one assigned to either entry role (i.e. entry for another client)
|
||||
// or exit role (as a service provider)
|
||||
// a 'valid' egress is one that is currently **not** acting as a mixnode
|
||||
if !ignore_epoch_roles {
|
||||
let Some(role) = self.rewarded_set.role(node.node_id) else {
|
||||
return Err(NymTopologyError::InvalidEgressRole {
|
||||
node_identity: Box::new(node_identity),
|
||||
});
|
||||
};
|
||||
if !matches!(role, Role::EntryGateway | Role::ExitGateway) {
|
||||
return Err(NymTopologyError::InvalidEgressRole {
|
||||
node_identity: Box::new(node_identity),
|
||||
});
|
||||
if let Some(role) = self.rewarded_set.role(node.node_id) {
|
||||
if role.is_mixnode() {
|
||||
return Err(NymTopologyError::InvalidEgressRole {
|
||||
node_identity: Box::new(node_identity),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::measurements::packet::{EchoPacket, ReplyPacket};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::StreamExt;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::{io, process};
|
||||
@@ -19,19 +19,19 @@ use tracing::{debug, error, info, trace, warn};
|
||||
pub struct PacketListener {
|
||||
address: SocketAddr,
|
||||
connection_handler: Arc<ConnectionHandler>,
|
||||
shutdown: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl PacketListener {
|
||||
pub fn new(
|
||||
address: SocketAddr,
|
||||
identity: Arc<identity::KeyPair>,
|
||||
shutdown: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
PacketListener {
|
||||
address,
|
||||
connection_handler: Arc::new(ConnectionHandler { identity }),
|
||||
shutdown,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,26 +51,22 @@ impl PacketListener {
|
||||
|
||||
info!("Started listening for echo packets on {}", self.address);
|
||||
|
||||
let mut shutdown_listener = self.shutdown.clone();
|
||||
|
||||
while !shutdown_listener.is_shutdown() {
|
||||
while !self.shutdown_token.is_cancelled() {
|
||||
// cloning the arc as each accepted socket is handled in separate task
|
||||
let connection_handler = Arc::clone(&self.connection_handler);
|
||||
let mut handler_shutdown_listener = self.shutdown.clone();
|
||||
handler_shutdown_listener.disarm();
|
||||
|
||||
tokio::select! {
|
||||
socket = listener.accept() => {
|
||||
match socket {
|
||||
Ok((socket, remote_addr)) => {
|
||||
debug!("New verloc connection from {}", remote_addr);
|
||||
|
||||
tokio::spawn(connection_handler.handle_connection(socket, remote_addr, handler_shutdown_listener));
|
||||
debug!("New verloc connection from {remote_addr}");
|
||||
let cancel = self.shutdown_token.child_token(format!("handler_{remote_addr}"));
|
||||
tokio::spawn(async move { cancel.run_until_cancelled(connection_handler.handle_connection(socket, remote_addr)).await });
|
||||
}
|
||||
Err(err) => warn!("Failed to accept incoming connection - {err}"),
|
||||
}
|
||||
},
|
||||
_ = shutdown_listener.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("PacketListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
@@ -88,50 +84,29 @@ impl ConnectionHandler {
|
||||
packet.construct_reply(self.identity.private_key())
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_connection(
|
||||
self: Arc<Self>,
|
||||
conn: TcpStream,
|
||||
remote: SocketAddr,
|
||||
mut shutdown_listener: TaskClient,
|
||||
) {
|
||||
debug!("Starting connection handler for {:?}", remote);
|
||||
pub(crate) async fn handle_connection(self: Arc<Self>, conn: TcpStream, remote: SocketAddr) {
|
||||
debug!("Starting connection handler for {remote}");
|
||||
|
||||
let mut framed_conn = Framed::new(conn, EchoPacketCodec);
|
||||
while !shutdown_listener.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown_listener.recv() => {
|
||||
trace!("ConnectionHandler: Shutdown received");
|
||||
while let Some(echo_packet) = framed_conn.next().await {
|
||||
let reply_packet = match echo_packet {
|
||||
Ok(echo_packet) => self.handle_echo_packet(echo_packet),
|
||||
Err(err) => {
|
||||
debug!(
|
||||
"The socket connection got corrupted with error: {err}. Closing the socket"
|
||||
);
|
||||
return;
|
||||
}
|
||||
maybe_echo_packet = framed_conn.next() => {
|
||||
// handle echo packet
|
||||
let reply_packet = match maybe_echo_packet {
|
||||
Some(Ok(echo_packet)) => self.handle_echo_packet(echo_packet),
|
||||
Some(Err(err)) => {
|
||||
debug!(
|
||||
"The socket connection got corrupted with error: {err}. Closing the socket",
|
||||
);
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
debug!("The socket connection got terminated by the remote!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// write back the reply (note the lack of framing)
|
||||
if let Err(err) = framed_conn
|
||||
.get_mut()
|
||||
.write_all(reply_packet.to_bytes().as_ref())
|
||||
.await
|
||||
{
|
||||
debug!(
|
||||
"Failed to write reply packet back to the sender - {}. Closing the socket on our end",
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
// write back the reply (note the lack of framing)
|
||||
if let Err(err) = framed_conn
|
||||
.get_mut()
|
||||
.write_all(reply_packet.to_bytes().as_ref())
|
||||
.await
|
||||
{
|
||||
debug!("Failed to write reply packet back to the sender: {err}. Closing the socket on our end");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::models::VerlocNodeResult;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_validator_client::models::NymNodeDescription;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::prelude::SliceRandom;
|
||||
@@ -23,7 +23,7 @@ pub struct VerlocMeasurer {
|
||||
config: Config,
|
||||
packet_sender: Arc<PacketSender>,
|
||||
packet_listener: Arc<PacketListener>,
|
||||
shutdown_listener: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
state: SharedVerlocStats,
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ impl VerlocMeasurer {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
identity: Arc<identity::KeyPair>,
|
||||
shutdown_listener: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
VerlocMeasurer {
|
||||
packet_sender: Arc::new(PacketSender::new(
|
||||
@@ -40,14 +40,14 @@ impl VerlocMeasurer {
|
||||
config.packet_timeout,
|
||||
config.connection_timeout,
|
||||
config.delay_between_packets,
|
||||
shutdown_listener.clone().named("VerlocPacketSender"),
|
||||
shutdown_token.clone_with_suffix("packet_sender"),
|
||||
)),
|
||||
packet_listener: Arc::new(PacketListener::new(
|
||||
config.listening_address,
|
||||
Arc::clone(&identity),
|
||||
shutdown_listener.clone().named("VerlocPacketListener"),
|
||||
shutdown_token.clone_with_suffix("packet_listener"),
|
||||
)),
|
||||
shutdown_listener,
|
||||
shutdown_token,
|
||||
config,
|
||||
state: SharedVerlocStats::default(),
|
||||
}
|
||||
@@ -69,9 +69,6 @@ impl VerlocMeasurer {
|
||||
return MeasurementOutcome::Done;
|
||||
}
|
||||
|
||||
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
|
||||
shutdown_listener.disarm();
|
||||
|
||||
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
|
||||
let mut chunk_results = Vec::with_capacity(chunk.len());
|
||||
|
||||
@@ -95,7 +92,7 @@ impl VerlocMeasurer {
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
// exhaust the results
|
||||
while !shutdown_listener.is_shutdown() {
|
||||
while !self.shutdown_token.is_cancelled() {
|
||||
tokio::select! {
|
||||
measurement_result = measurement_chunk.next() => {
|
||||
let Some(result) = measurement_result else {
|
||||
@@ -120,7 +117,7 @@ impl VerlocMeasurer {
|
||||
};
|
||||
chunk_results.push(VerlocNodeResult::new(identity, measurement_result));
|
||||
},
|
||||
_ = shutdown_listener.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("Shutdown received while measuring");
|
||||
return MeasurementOutcome::Shutdown;
|
||||
}
|
||||
@@ -155,7 +152,7 @@ impl VerlocMeasurer {
|
||||
pub async fn run(&mut self) {
|
||||
self.start_listening();
|
||||
|
||||
while !self.shutdown_listener.is_shutdown() {
|
||||
while !self.shutdown_token.is_cancelled() {
|
||||
info!("Starting verloc measurements");
|
||||
// TODO: should we also measure gateways?
|
||||
|
||||
@@ -209,7 +206,7 @@ impl VerlocMeasurer {
|
||||
|
||||
tokio::select! {
|
||||
_ = sleep(self.config.testing_interval) => {},
|
||||
_ = self.shutdown_listener.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("Shutdown received while sleeping");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::error::VerlocError;
|
||||
use crate::measurements::packet::{EchoPacket, ReplyPacket};
|
||||
use crate::models::VerlocMeasurement;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
@@ -45,7 +45,7 @@ pub struct PacketSender {
|
||||
packet_timeout: Duration,
|
||||
connection_timeout: Duration,
|
||||
delay_between_packets: Duration,
|
||||
shutdown_listener: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl PacketSender {
|
||||
@@ -55,7 +55,7 @@ impl PacketSender {
|
||||
packet_timeout: Duration,
|
||||
connection_timeout: Duration,
|
||||
delay_between_packets: Duration,
|
||||
shutdown_listener: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
PacketSender {
|
||||
identity,
|
||||
@@ -63,7 +63,7 @@ impl PacketSender {
|
||||
packet_timeout,
|
||||
connection_timeout,
|
||||
delay_between_packets,
|
||||
shutdown_listener,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +83,6 @@ impl PacketSender {
|
||||
self: Arc<Self>,
|
||||
tested_node: TestedNode,
|
||||
) -> Result<VerlocMeasurement, VerlocError> {
|
||||
let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string());
|
||||
shutdown_listener.disarm();
|
||||
|
||||
let mut conn = match tokio::time::timeout(
|
||||
self.connection_timeout,
|
||||
TcpStream::connect(tested_node.address),
|
||||
@@ -148,7 +145,7 @@ impl PacketSender {
|
||||
Ok(Ok(_)) => {}
|
||||
}
|
||||
},
|
||||
_ = shutdown_listener.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("PacketSender: Received shutdown while sending");
|
||||
return Err(VerlocError::ShutdownReceived);
|
||||
},
|
||||
@@ -190,7 +187,7 @@ impl PacketSender {
|
||||
}
|
||||
}
|
||||
},
|
||||
_ = shutdown_listener.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("PacketSender: Received shutdown while waiting for reply");
|
||||
return Err(VerlocError::ShutdownReceived);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
target_arch = "wasm32"
|
||||
@@ -390,12 +390,15 @@ pub struct TopologyWasm {
|
||||
|
||||
/// Specifies whether this client should attempt to retrieve all available network nodes
|
||||
/// as opposed to just active mixnodes/gateways.
|
||||
/// Useless without `ignore_epoch_roles = true`
|
||||
pub use_extended_topology: bool,
|
||||
|
||||
/// Specifies whether this client should ignore the current epoch role of the target egress node
|
||||
/// when constructing the final hop packets.
|
||||
pub ignore_egress_epoch_role: bool,
|
||||
|
||||
/// Specifies whether this client should ignore the current epoch role of the ingress node
|
||||
/// when attempting to establish new connection
|
||||
pub ignore_ingress_epoch_role: bool,
|
||||
}
|
||||
|
||||
impl Default for TopologyWasm {
|
||||
@@ -420,6 +423,7 @@ impl From<TopologyWasm> for ConfigTopology {
|
||||
minimum_gateway_performance: topology.minimum_gateway_performance,
|
||||
use_extended_topology: topology.use_extended_topology,
|
||||
ignore_egress_epoch_role: topology.ignore_egress_epoch_role,
|
||||
ignore_ingress_epoch_role: topology.ignore_ingress_epoch_role,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -437,6 +441,7 @@ impl From<ConfigTopology> for TopologyWasm {
|
||||
minimum_gateway_performance: topology.minimum_gateway_performance,
|
||||
use_extended_topology: topology.use_extended_topology,
|
||||
ignore_egress_epoch_role: topology.ignore_egress_epoch_role,
|
||||
ignore_ingress_epoch_role: topology.ignore_ingress_epoch_role,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +274,6 @@ pub struct TopologyWasmOverride {
|
||||
|
||||
/// Specifies whether this client should attempt to retrieve all available network nodes
|
||||
/// as opposed to just active mixnodes/gateways.
|
||||
/// Useless without `ignore_epoch_roles = true`
|
||||
#[tsify(optional)]
|
||||
pub use_extended_topology: Option<bool>,
|
||||
|
||||
@@ -282,6 +281,11 @@ pub struct TopologyWasmOverride {
|
||||
/// when constructing the final hop packets.
|
||||
#[tsify(optional)]
|
||||
pub ignore_egress_epoch_role: Option<bool>,
|
||||
|
||||
/// Specifies whether this client should ignore the current epoch role of the ingress node
|
||||
/// when attempting to establish new connection
|
||||
#[tsify(optional)]
|
||||
pub ignore_ingress_epoch_role: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<TopologyWasmOverride> for TopologyWasm {
|
||||
@@ -311,6 +315,9 @@ impl From<TopologyWasmOverride> for TopologyWasm {
|
||||
ignore_egress_epoch_role: value
|
||||
.ignore_egress_epoch_role
|
||||
.unwrap_or(def.ignore_egress_epoch_role),
|
||||
ignore_ingress_epoch_role: value
|
||||
.ignore_ingress_epoch_role
|
||||
.unwrap_or(def.ignore_ingress_epoch_role),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ use crate::error::WasmCoreError;
|
||||
use crate::storage::wasm_client_traits::WasmClientStorage;
|
||||
use crate::storage::ClientStorage;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::client::base_client::storage::helpers::set_active_gateway;
|
||||
use nym_client_core::client::base_client::storage::GatewaysDetailsStore;
|
||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||
use nym_client_core::config;
|
||||
use nym_client_core::init::helpers::current_gateways;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_client_core::init::helpers::gateways_for_init;
|
||||
use nym_client_core::init::types::GatewaySelectionSpecification;
|
||||
use nym_client_core::init::{
|
||||
self,
|
||||
self, setup_gateway,
|
||||
types::{GatewaySetup, InitialisationResult},
|
||||
};
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
@@ -18,7 +21,7 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
|
||||
use nym_topology::{NymTopology, RoutingNode};
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use nym_validator_client::{NymApiClient, UserAgent};
|
||||
use rand::thread_rng;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
@@ -26,6 +29,7 @@ use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::error::PromisableResult;
|
||||
|
||||
pub use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
use wasm_utils::console_log;
|
||||
|
||||
// don't get too excited about the name, under the hood it's just a big fat placeholder
|
||||
// with no disk_persistence
|
||||
@@ -128,12 +132,37 @@ pub async fn setup_gateway_from_api(
|
||||
chosen_gateway: Option<IdentityKey>,
|
||||
nym_apis: &[Url],
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<InitialisationResult, WasmCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
let gateways = current_gateways(&mut rng, nym_apis, None, minimum_performance).await?;
|
||||
let gateways = gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
None,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
)
|
||||
.await?;
|
||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
||||
}
|
||||
|
||||
pub async fn current_gateways_wasm(
|
||||
nym_apis: &[Url],
|
||||
user_agent: Option<UserAgent>,
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
user_agent,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn setup_from_topology(
|
||||
explicit_gateway: Option<IdentityKey>,
|
||||
force_tls: bool,
|
||||
@@ -143,3 +172,83 @@ pub async fn setup_from_topology(
|
||||
let gateways = topology.entry_capable_nodes().cloned().collect::<Vec<_>>();
|
||||
setup_gateway_wasm(client_store, force_tls, explicit_gateway, gateways).await
|
||||
}
|
||||
|
||||
pub async fn generate_new_client_keys(store: &ClientStorage) -> Result<(), WasmCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
init::generate_new_client_keys(&mut rng, store).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn add_gateway(
|
||||
preferred_gateway: Option<IdentityKey>,
|
||||
latency_based_selection: Option<bool>,
|
||||
force_tls: bool,
|
||||
nym_apis: &[Url],
|
||||
user_agent: UserAgent,
|
||||
min_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
storage: &ClientStorage,
|
||||
) -> Result<(), WasmCoreError> {
|
||||
let selection_spec = GatewaySelectionSpecification::new(
|
||||
preferred_gateway.clone(),
|
||||
latency_based_selection,
|
||||
force_tls,
|
||||
);
|
||||
|
||||
let preferred_gateway = preferred_gateway
|
||||
.as_ref()
|
||||
.map(|g| g.parse())
|
||||
.transpose()
|
||||
.map_err(|source| WasmCoreError::InvalidGatewayIdentity { source })?;
|
||||
|
||||
let registered_gateways = storage.all_gateways_identities().await.map_err(|source| {
|
||||
ClientCoreError::GatewaysDetailsStoreError {
|
||||
source: Box::new(source),
|
||||
}
|
||||
})?;
|
||||
|
||||
// if user provided gateway id (and we can't overwrite data), make sure we're not trying to register
|
||||
// with a known gateway
|
||||
if let Some(user_chosen) = preferred_gateway {
|
||||
if registered_gateways.contains(&user_chosen) {
|
||||
return Err(ClientCoreError::AlreadyRegistered {
|
||||
gateway_id: user_chosen.to_base58_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||
// one but with keys kept, or reusing the gateway configuration.
|
||||
let available_gateways = current_gateways_wasm(
|
||||
nym_apis,
|
||||
Some(user_agent),
|
||||
min_performance,
|
||||
ignore_epoch_roles,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// since we're registering with a brand new gateway,
|
||||
// make sure the list of available gateways doesn't overlap the list of known gateways
|
||||
let available_gateways = available_gateways
|
||||
.into_iter()
|
||||
.filter(|g| !registered_gateways.contains(&g.identity()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if available_gateways.is_empty() {
|
||||
return Err(ClientCoreError::NoNewGatewaysAvailable.into());
|
||||
}
|
||||
|
||||
let gateway_setup = GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways,
|
||||
};
|
||||
|
||||
let init_details = setup_gateway(gateway_setup, storage, storage).await?;
|
||||
let gateway = init_details.gateway_id().to_base58_string();
|
||||
set_active_gateway(storage, &gateway).await?;
|
||||
|
||||
console_log!("finished registration with gateway {gateway}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -128,8 +128,12 @@ impl GatewaysDetailsStore for ClientStorage {
|
||||
}
|
||||
|
||||
async fn all_gateways(&self) -> Result<Vec<GatewayRegistration>, Self::StorageError> {
|
||||
todo!()
|
||||
// let identities = self.all
|
||||
let identities = self.registered_gateways().await?;
|
||||
let mut registered = Vec::with_capacity(identities.len());
|
||||
for gateway_id in identities {
|
||||
registered.push(self.load_gateway_details(&gateway_id).await?);
|
||||
}
|
||||
Ok(registered)
|
||||
}
|
||||
|
||||
async fn has_gateway_details(&self, gateway_id: &str) -> Result<bool, Self::StorageError> {
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
use crate::error::WasmCoreError;
|
||||
use crate::storage::wasm_client_traits::{v1, v2, WasmClientStorage};
|
||||
use async_trait::async_trait;
|
||||
use js_sys::{Array, Promise};
|
||||
use js_sys::Promise;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_storage::traits::BaseWasmStorage;
|
||||
use wasm_storage::{IdbVersionChangeEvent, WasmStorage};
|
||||
use wasm_storage::{
|
||||
Build, Database, RawDbResult, TryFromJs, TryToJs, VersionChangeEvent, WasmStorage,
|
||||
};
|
||||
use wasm_utils::error::{simple_js_error, PromisableResult};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
@@ -44,26 +46,29 @@ impl ClientStorage {
|
||||
// special care must be taken on JS side to ensure it's correctly used there.
|
||||
let passphrase = Zeroizing::new(passphrase);
|
||||
|
||||
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
let migrate_fn = Some(|evt: VersionChangeEvent, db: Database| -> RawDbResult<()> {
|
||||
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
||||
// works with an unsigned integer.
|
||||
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
||||
let old_version = evt.old_version() as u32;
|
||||
let db = evt.db();
|
||||
|
||||
if old_version < 1 {
|
||||
// migrating to version 2
|
||||
|
||||
db.create_object_store(v1::KEYS_STORE)?;
|
||||
db.create_object_store(v1::CORE_STORE)?;
|
||||
db.create_object_store(v1::KEYS_STORE).build()?;
|
||||
db.create_object_store(v1::CORE_STORE).build()?;
|
||||
|
||||
db.create_object_store(v2::GATEWAY_REGISTRATIONS_ACTIVE_GATEWAY_STORE)?;
|
||||
db.create_object_store(v2::GATEWAY_REGISTRATIONS_REGISTERED_GATEWAYS_STORE)?;
|
||||
db.create_object_store(v2::GATEWAY_REGISTRATIONS_ACTIVE_GATEWAY_STORE)
|
||||
.build()?;
|
||||
db.create_object_store(v2::GATEWAY_REGISTRATIONS_REGISTERED_GATEWAYS_STORE)
|
||||
.build()?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// version 1 -> unimplemented migration
|
||||
if old_version < 2 {
|
||||
return Err(simple_js_error("this client is incompatible with existing storage. please initialise it again."));
|
||||
return Err(simple_js_error("this client is incompatible with existing storage. please initialise it again.").into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -110,7 +115,7 @@ impl BaseWasmStorage for ClientStorage {
|
||||
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
K: JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
Ok(self.inner.read_value(store, key).await?)
|
||||
}
|
||||
@@ -123,33 +128,33 @@ impl BaseWasmStorage for ClientStorage {
|
||||
) -> Result<(), Self::StorageError>
|
||||
where
|
||||
T: Serialize,
|
||||
K: JsCast,
|
||||
K: TryToJs + TryFromJs,
|
||||
{
|
||||
Ok(self.inner.store_value(store, key, value).await?)
|
||||
}
|
||||
|
||||
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
||||
where
|
||||
K: JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
Ok(self.inner.remove_value(store, key).await?)
|
||||
}
|
||||
|
||||
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
||||
where
|
||||
K: JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
Ok(self.inner.has_value(store, key).await?)
|
||||
}
|
||||
|
||||
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
||||
where
|
||||
K: JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
Ok(self.inner.key_count(store, key).await?)
|
||||
}
|
||||
|
||||
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
|
||||
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError> {
|
||||
Ok(self.inner.get_all_keys(store).await?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,15 @@ pub trait WasmClientStorage: BaseWasmStorage {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn has_identity_key(&self) -> Result<bool, <Self as WasmClientStorage>::StorageError> {
|
||||
self.has_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_identity_keypair(
|
||||
&self,
|
||||
keypair: &identity::KeyPair,
|
||||
@@ -277,8 +286,8 @@ pub trait WasmClientStorage: BaseWasmStorage {
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map(|arr| {
|
||||
arr.to_vec()
|
||||
.into_iter()
|
||||
arr.iter()
|
||||
.cloned()
|
||||
.filter_map(|key| key.as_string())
|
||||
.collect()
|
||||
})
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
target_arch = "wasm32"
|
||||
@@ -9,7 +9,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
getrandom = { workspace = true, features = ["js"] }
|
||||
js-sys = { workspace = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use indexed_db_futures::web_sys::DomException;
|
||||
use serde_wasm_bindgen::Error;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
@@ -12,15 +11,11 @@ pub enum StorageError {
|
||||
#[error("{0}")]
|
||||
Json(String),
|
||||
|
||||
#[error("DomException {name} ({code}): {message}")]
|
||||
DomException {
|
||||
/// DomException code
|
||||
code: u16,
|
||||
/// Specific name of the DomException
|
||||
name: String,
|
||||
/// Message given to the DomException
|
||||
message: String,
|
||||
},
|
||||
#[error("storage failure: {message}")]
|
||||
InternalStorageFailure { message: String },
|
||||
|
||||
#[error("failed to open the db file: {message}")]
|
||||
DbOpenFailure { message: String },
|
||||
|
||||
#[error("FATAL ERROR: storage key is somehow present {count} times in the table!")]
|
||||
DuplicateKey { count: u32 },
|
||||
@@ -46,12 +41,18 @@ impl From<StorageError> for JsValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomException> for StorageError {
|
||||
fn from(value: DomException) -> StorageError {
|
||||
StorageError::DomException {
|
||||
name: value.name(),
|
||||
message: value.message(),
|
||||
code: value.code(),
|
||||
impl From<indexed_db_futures::error::Error> for StorageError {
|
||||
fn from(value: indexed_db_futures::error::Error) -> Self {
|
||||
StorageError::InternalStorageFailure {
|
||||
message: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<indexed_db_futures::error::OpenDbError> for StorageError {
|
||||
fn from(value: indexed_db_futures::error::OpenDbError) -> Self {
|
||||
StorageError::DbOpenFailure {
|
||||
message: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2023-2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::cipher_export::StoredExportedStoreCipher;
|
||||
use crate::error::StorageError;
|
||||
use indexed_db_futures::transaction::TransactionMode;
|
||||
use nym_store_cipher::{
|
||||
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, KeySizeUser, Params, StoreCipher, Unsigned,
|
||||
Version,
|
||||
@@ -13,7 +14,10 @@ use std::future::IntoFuture;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::console_log;
|
||||
|
||||
pub use indexed_db_futures::database::{Database, VersionChangeEvent};
|
||||
pub use indexed_db_futures::prelude::*;
|
||||
pub use indexed_db_futures::primitive::{TryFromJs, TryToJs};
|
||||
pub use indexed_db_futures::Result as RawDbResult;
|
||||
|
||||
mod cipher_export;
|
||||
pub mod error;
|
||||
@@ -54,31 +58,29 @@ impl WasmStorage {
|
||||
passphrase: Option<&[u8]>,
|
||||
) -> Result<Self, StorageError>
|
||||
where
|
||||
F: Fn(&IdbVersionChangeEvent) -> Result<(), JsValue> + 'static,
|
||||
F: Fn(VersionChangeEvent, Database) -> RawDbResult<()> + 'static,
|
||||
{
|
||||
let mut db_req: OpenDbRequest = IdbDatabase::open_u32(db_name, version)?;
|
||||
|
||||
// we must always ensure the cipher table is present
|
||||
db_req.set_on_upgrade_needed(Some(
|
||||
move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
let db = Database::open(db_name)
|
||||
.with_version(version)
|
||||
.with_on_upgrade_needed(move |event, db| {
|
||||
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
||||
// works with an unsigned integer.
|
||||
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
||||
let old_version = evt.old_version() as u32;
|
||||
let old_version = event.old_version() as u32;
|
||||
|
||||
if old_version < 1 {
|
||||
evt.db().create_object_store(CIPHER_INFO_STORE)?;
|
||||
db.create_object_store(CIPHER_INFO_STORE).build()?;
|
||||
}
|
||||
|
||||
if let Some(migrate) = migrate_fn.as_ref() {
|
||||
migrate(evt)
|
||||
migrate(event, db)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
));
|
||||
})
|
||||
.await?;
|
||||
|
||||
let db: IdbDatabase = db_req.into_future().await?;
|
||||
let inner = IdbWrapper(db);
|
||||
let store_cipher = inner.setup_store_cipher(passphrase).await?;
|
||||
|
||||
@@ -94,13 +96,12 @@ impl WasmStorage {
|
||||
}
|
||||
|
||||
pub async fn remove(db_name: &str) -> Result<(), StorageError> {
|
||||
IdbDatabase::delete_by_name(db_name)?.into_future().await?;
|
||||
Database::delete_by_name(db_name)?.into_future().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn exists(db_name: &str) -> Result<bool, StorageError> {
|
||||
let db_req: OpenDbRequest = IdbDatabase::open(db_name)?;
|
||||
let db: IdbDatabase = db_req.into_future().await?;
|
||||
let db = Database::open(db_name).await?;
|
||||
|
||||
// if the db was already created before, at the very least cipher info store should exist,
|
||||
// thus the iterator should return at least one value
|
||||
@@ -139,7 +140,7 @@ impl WasmStorage {
|
||||
pub async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, StorageError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
self.inner
|
||||
.read_value_raw(store, key)
|
||||
@@ -156,7 +157,7 @@ impl WasmStorage {
|
||||
) -> Result<(), StorageError>
|
||||
where
|
||||
T: Serialize,
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs + TryFromJs,
|
||||
{
|
||||
self.inner
|
||||
.store_value_raw(store, key, &self.serialize_value(&value)?)
|
||||
@@ -165,14 +166,14 @@ impl WasmStorage {
|
||||
|
||||
pub async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
self.inner.remove_value_raw(store, key).await
|
||||
}
|
||||
|
||||
pub async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
match self.key_count(store, key).await? {
|
||||
0 => Ok(false),
|
||||
@@ -183,82 +184,98 @@ impl WasmStorage {
|
||||
|
||||
pub async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
self.inner.get_key_count(store, key).await
|
||||
}
|
||||
|
||||
pub async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, StorageError> {
|
||||
pub async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, StorageError> {
|
||||
self.inner.get_all_keys(store).await
|
||||
}
|
||||
}
|
||||
|
||||
struct IdbWrapper(IdbDatabase);
|
||||
struct IdbWrapper(Database);
|
||||
|
||||
impl IdbWrapper {
|
||||
async fn read_value_raw<K>(&self, store: &str, key: K) -> Result<Option<JsValue>, StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
self.0
|
||||
.transaction_on_one_with_mode(store, IdbTransactionMode::Readonly)?
|
||||
.transaction(store)
|
||||
.with_mode(TransactionMode::Readonly)
|
||||
.build()?
|
||||
.object_store(store)?
|
||||
.get(&key)?
|
||||
.get(&key)
|
||||
.primitive()?
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_value_raw<K>(
|
||||
async fn store_value_raw<K, T>(
|
||||
&self,
|
||||
store: &str,
|
||||
key: K,
|
||||
value: &JsValue,
|
||||
value: &T,
|
||||
) -> Result<(), StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs + TryFromJs,
|
||||
T: TryToJs,
|
||||
{
|
||||
self.0
|
||||
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
|
||||
.object_store(store)?
|
||||
.put_key_val_owned(key, value)?
|
||||
.into_future()
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
let tx = self
|
||||
.0
|
||||
.transaction(store)
|
||||
.with_mode(TransactionMode::Readwrite)
|
||||
.build()?;
|
||||
|
||||
let store = tx.object_store(store)?;
|
||||
store.put(value).with_key(key).primitive()?.await?;
|
||||
|
||||
tx.commit().await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn remove_value_raw<K>(&self, store: &str, key: K) -> Result<(), StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
self.0
|
||||
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
|
||||
.object_store(store)?
|
||||
.delete_owned(key)?
|
||||
.into_future()
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
let tx = self
|
||||
.0
|
||||
.transaction(store)
|
||||
.with_mode(TransactionMode::Readwrite)
|
||||
.build()?;
|
||||
|
||||
let store = tx.object_store(store)?;
|
||||
store.delete(key).primitive()?.await?;
|
||||
|
||||
tx.commit().await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_key_count<K>(&self, store: &str, key: K) -> Result<u32, StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
self.0
|
||||
.transaction_on_one_with_mode(store, IdbTransactionMode::Readwrite)?
|
||||
.transaction(store)
|
||||
.with_mode(TransactionMode::Readonly)
|
||||
.build()?
|
||||
.object_store(store)?
|
||||
.count_with_key_owned(key)?
|
||||
.into_future()
|
||||
.count()
|
||||
.with_query(key)
|
||||
.primitive()?
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, StorageError> {
|
||||
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, StorageError> {
|
||||
self.0
|
||||
.transaction_on_one_with_mode(store, IdbTransactionMode::Readonly)?
|
||||
.transaction(store)
|
||||
.with_mode(TransactionMode::Readonly)
|
||||
.build()?
|
||||
.object_store(store)?
|
||||
.get_all_keys()?
|
||||
.into_future()
|
||||
.await
|
||||
.get_all_keys()
|
||||
.primitive()?
|
||||
.await?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
|
||||
use crate::WasmStorage;
|
||||
use async_trait::async_trait;
|
||||
use js_sys::Array;
|
||||
use indexed_db_futures::primitive::{TryFromJs, TryToJs};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::error::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait BaseWasmStorage {
|
||||
@@ -17,7 +18,7 @@ pub trait BaseWasmStorage {
|
||||
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
K: wasm_bindgen::JsCast;
|
||||
K: TryToJs;
|
||||
|
||||
async fn store_value<T, K>(
|
||||
&self,
|
||||
@@ -27,21 +28,21 @@ pub trait BaseWasmStorage {
|
||||
) -> Result<(), Self::StorageError>
|
||||
where
|
||||
T: Serialize,
|
||||
K: wasm_bindgen::JsCast;
|
||||
K: TryToJs + TryFromJs;
|
||||
|
||||
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast;
|
||||
K: TryToJs;
|
||||
|
||||
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast;
|
||||
K: TryToJs;
|
||||
|
||||
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast;
|
||||
K: TryToJs;
|
||||
|
||||
async fn get_all_keys(&self, store: &str) -> Result<js_sys::Array, Self::StorageError>;
|
||||
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError>;
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
@@ -55,7 +56,7 @@ impl BaseWasmStorage for WasmStorage {
|
||||
async fn read_value<T, K>(&self, store: &str, key: K) -> Result<Option<T>, Self::StorageError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
self.read_value(store, key).await
|
||||
}
|
||||
@@ -68,33 +69,33 @@ impl BaseWasmStorage for WasmStorage {
|
||||
) -> Result<(), Self::StorageError>
|
||||
where
|
||||
T: Serialize,
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs + TryFromJs,
|
||||
{
|
||||
self.store_value(store, key, value).await
|
||||
}
|
||||
|
||||
async fn remove_value<K>(&self, store: &str, key: K) -> Result<(), Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
self.remove_value(store, key).await
|
||||
}
|
||||
|
||||
async fn has_value<K>(&self, store: &str, key: K) -> Result<bool, Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
self.has_value(store, key).await
|
||||
}
|
||||
|
||||
async fn key_count<K>(&self, store: &str, key: K) -> Result<u32, Self::StorageError>
|
||||
where
|
||||
K: wasm_bindgen::JsCast,
|
||||
K: TryToJs,
|
||||
{
|
||||
self.key_count(store, key).await
|
||||
}
|
||||
|
||||
async fn get_all_keys(&self, store: &str) -> Result<Array, Self::StorageError> {
|
||||
async fn get_all_keys(&self, store: &str) -> Result<Vec<JsValue>, Self::StorageError> {
|
||||
self.get_all_keys(store).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
target_arch = "wasm32"
|
||||
@@ -267,18 +267,27 @@ impl PeerController {
|
||||
}))
|
||||
}
|
||||
|
||||
fn update_metrics(&self, new_host: &Host) {
|
||||
async fn update_metrics(&self, new_host: &Host) {
|
||||
let now = SystemTime::now();
|
||||
const ACTIVITY_THRESHOLD: Duration = Duration::from_secs(60);
|
||||
|
||||
let old_host = self.host_information.read().await;
|
||||
|
||||
let total_peers = new_host.peers.len();
|
||||
let mut active_peers = 0;
|
||||
let mut total_rx = 0;
|
||||
let mut total_tx = 0;
|
||||
let mut new_rx = 0;
|
||||
let mut new_tx = 0;
|
||||
|
||||
for peer in new_host.peers.values() {
|
||||
total_rx += peer.rx_bytes;
|
||||
total_tx += peer.tx_bytes;
|
||||
for (peer_key, peer) in new_host.peers.iter() {
|
||||
// only consider pre-existing peers,
|
||||
// so that the value would always be increasing
|
||||
if let Some(prior) = old_host.peers.get(peer_key) {
|
||||
let delta_rx = peer.rx_bytes.saturating_sub(prior.rx_bytes);
|
||||
let delta_tx = prior.tx_bytes.saturating_sub(prior.tx_bytes);
|
||||
|
||||
new_rx += delta_rx;
|
||||
new_tx += delta_tx;
|
||||
}
|
||||
|
||||
// if a peer hasn't performed a handshake in last minute,
|
||||
// I think it's reasonable to assume it's no longer active
|
||||
@@ -296,10 +305,10 @@ impl PeerController {
|
||||
self.metrics.wireguard.update(
|
||||
// if the conversion fails it means we're running not running on a 64bit system
|
||||
// and that's a reason enough for this failure.
|
||||
total_rx.try_into().expect(
|
||||
new_rx.try_into().expect(
|
||||
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
|
||||
),
|
||||
total_tx.try_into().expect(
|
||||
new_tx.try_into().expect(
|
||||
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
|
||||
),
|
||||
total_peers,
|
||||
@@ -316,7 +325,7 @@ impl PeerController {
|
||||
log::error!("Can't read wireguard kernel data");
|
||||
continue;
|
||||
};
|
||||
self.update_metrics(&host);
|
||||
self.update_metrics(&host).await;
|
||||
|
||||
*self.host_information.write().await = host;
|
||||
}
|
||||
|
||||
Generated
+11
-11
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
@@ -1699,18 +1699,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.23"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.214"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -1735,9 +1735,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.214"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1965,9 +1965,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.36"
|
||||
version = "0.3.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -1988,9 +1988,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.18"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
|
||||
@@ -464,10 +464,7 @@ pub fn instantiate_contracts(
|
||||
mixnet_contract_address.clone(),
|
||||
&nym_mixnet_contract_common::MigrateMsg {
|
||||
vesting_contract_address: Some(vesting_contract_address.to_string()),
|
||||
current_nym_node_semver: "1.1.10".to_string(),
|
||||
version_score_weights: Default::default(),
|
||||
unsafe_skip_state_updates: Some(true),
|
||||
version_score_params: Default::default(),
|
||||
},
|
||||
mixnet_code_id,
|
||||
)
|
||||
|
||||
@@ -3470,43 +3470,13 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "MigrateMsg",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_nym_node_semver"
|
||||
],
|
||||
"properties": {
|
||||
"current_nym_node_semver": {
|
||||
"type": "string"
|
||||
},
|
||||
"unsafe_skip_state_updates": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"version_score_params": {
|
||||
"default": {
|
||||
"penalty": "0.995",
|
||||
"penalty_scaling": "1.65"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/VersionScoreFormulaParams"
|
||||
}
|
||||
]
|
||||
},
|
||||
"version_score_weights": {
|
||||
"default": {
|
||||
"major": 100,
|
||||
"minor": 10,
|
||||
"patch": 1,
|
||||
"prerelease": 1
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/OutdatedVersionWeights"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vesting_contract_address": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -3514,63 +3484,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"OutdatedVersionWeights": {
|
||||
"description": "Defines weights for calculating numbers of versions behind the current release.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"major",
|
||||
"minor",
|
||||
"patch",
|
||||
"prerelease"
|
||||
],
|
||||
"properties": {
|
||||
"major": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"minor": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"patch": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"prerelease": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
"penalty_scaling"
|
||||
],
|
||||
"properties": {
|
||||
"penalty": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"penalty_scaling": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sudo": null,
|
||||
"responses": {
|
||||
|
||||
@@ -2,43 +2,13 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "MigrateMsg",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_nym_node_semver"
|
||||
],
|
||||
"properties": {
|
||||
"current_nym_node_semver": {
|
||||
"type": "string"
|
||||
},
|
||||
"unsafe_skip_state_updates": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"version_score_params": {
|
||||
"default": {
|
||||
"penalty": "0.995",
|
||||
"penalty_scaling": "1.65"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/VersionScoreFormulaParams"
|
||||
}
|
||||
]
|
||||
},
|
||||
"version_score_weights": {
|
||||
"default": {
|
||||
"major": 100,
|
||||
"minor": 10,
|
||||
"patch": 1,
|
||||
"prerelease": 1
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/OutdatedVersionWeights"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vesting_contract_address": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -46,61 +16,5 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"Decimal": {
|
||||
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
|
||||
"type": "string"
|
||||
},
|
||||
"OutdatedVersionWeights": {
|
||||
"description": "Defines weights for calculating numbers of versions behind the current release.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"major",
|
||||
"minor",
|
||||
"patch",
|
||||
"prerelease"
|
||||
],
|
||||
"properties": {
|
||||
"major": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"minor": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"patch": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"prerelease": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
"penalty_scaling"
|
||||
],
|
||||
"properties": {
|
||||
"penalty": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
},
|
||||
"penalty_scaling": {
|
||||
"$ref": "#/definitions/Decimal"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
||||
@@ -602,18 +602,18 @@ pub fn query(
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(
|
||||
mut deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
msg: MigrateMsg,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
set_build_information!(deps.storage)?;
|
||||
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
|
||||
let skip_state_updates = msg.unsafe_skip_state_updates.unwrap_or(false);
|
||||
|
||||
if !skip_state_updates {
|
||||
crate::queued_migrations::add_config_score_params(deps.branch(), env, &msg)?;
|
||||
}
|
||||
// let skip_state_updates = msg.unsafe_skip_state_updates.unwrap_or(false);
|
||||
//
|
||||
// if !skip_state_updates {
|
||||
//
|
||||
// }
|
||||
|
||||
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
|
||||
// and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh
|
||||
|
||||
@@ -34,9 +34,13 @@ impl NymNodeVersionHistory<'_> {
|
||||
}
|
||||
|
||||
fn next_id(&self, storage: &mut dyn Storage) -> Result<u32, MixnetContractError> {
|
||||
let next = self.id_counter.may_load(storage)?.unwrap_or_default();
|
||||
self.id_counter.save(storage, &next)?;
|
||||
Ok(next)
|
||||
let id = self
|
||||
.id_counter
|
||||
.may_load(storage)?
|
||||
.map(|current| current + 1)
|
||||
.unwrap_or_default();
|
||||
self.id_counter.save(storage, &id)?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn current_version(
|
||||
@@ -56,10 +60,10 @@ impl NymNodeVersionHistory<'_> {
|
||||
pub fn insert_new(
|
||||
&self,
|
||||
storage: &mut dyn Storage,
|
||||
entry: HistoricalNymNodeVersion,
|
||||
entry: &HistoricalNymNodeVersion,
|
||||
) -> Result<u32, MixnetContractError> {
|
||||
let next_id = self.next_id(storage)?;
|
||||
self.version_history.save(storage, next_id, &entry)?;
|
||||
self.version_history.save(storage, next_id, entry)?;
|
||||
Ok(next_id)
|
||||
}
|
||||
|
||||
@@ -79,7 +83,7 @@ impl NymNodeVersionHistory<'_> {
|
||||
// treat this as genesis
|
||||
let genesis =
|
||||
HistoricalNymNodeVersion::genesis(raw_semver.to_string(), env.block.height);
|
||||
return self.insert_new(storage, genesis);
|
||||
return self.insert_new(storage, &genesis);
|
||||
};
|
||||
|
||||
let current_semver = current.version_information.semver_unchecked();
|
||||
@@ -99,7 +103,7 @@ impl NymNodeVersionHistory<'_> {
|
||||
introduced_at_height: env.block.height,
|
||||
difference_since_genesis: diff,
|
||||
};
|
||||
self.insert_new(storage, entry)
|
||||
self.insert_new(storage, &entry)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,3 +174,218 @@ pub(crate) fn initialise_storage(
|
||||
ADMIN.set(deps, Some(initial_admin))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod nym_node_version_history {
|
||||
use super::*;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env};
|
||||
|
||||
#[test]
|
||||
fn getting_current() -> anyhow::Result<()> {
|
||||
// empty storage
|
||||
let deps = mock_dependencies();
|
||||
let storage = NymNodeVersionHistory::new();
|
||||
assert!(storage.current_version(&deps.storage)?.is_none());
|
||||
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let zeroth = storage.current_version(test.storage())?.unwrap();
|
||||
let manual_zeroth = storage.version_history.load(test.storage(), 0)?;
|
||||
assert_eq!(zeroth.version_information, manual_zeroth);
|
||||
|
||||
// manually update the counter to make sure data is still read correctly
|
||||
let dummy = HistoricalNymNodeVersion {
|
||||
semver: "1.2.3".to_string(),
|
||||
introduced_at_height: 1234,
|
||||
difference_since_genesis: Default::default(),
|
||||
};
|
||||
storage.id_counter.save(test.storage_mut(), &123)?;
|
||||
storage
|
||||
.version_history
|
||||
.save(test.storage_mut(), 123, &dummy)?;
|
||||
|
||||
let updated = storage.current_version(test.storage())?.unwrap();
|
||||
assert_eq!(updated.version_information, dummy);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inserting_new_entry() -> anyhow::Result<()> {
|
||||
let mut test = TestSetup::new();
|
||||
let storage = NymNodeVersionHistory::new();
|
||||
|
||||
let first = HistoricalNymNodeVersion {
|
||||
semver: "1.1.1".to_string(),
|
||||
introduced_at_height: 12,
|
||||
difference_since_genesis: Default::default(),
|
||||
};
|
||||
let second = HistoricalNymNodeVersion {
|
||||
semver: "1.1.2".to_string(),
|
||||
introduced_at_height: 123,
|
||||
difference_since_genesis: Default::default(),
|
||||
};
|
||||
let third = HistoricalNymNodeVersion {
|
||||
semver: "1.1.3".to_string(),
|
||||
introduced_at_height: 1234,
|
||||
difference_since_genesis: Default::default(),
|
||||
};
|
||||
|
||||
assert_eq!(storage.id_counter.load(test.storage())?, 0);
|
||||
|
||||
// id is correctly incremented for each case and no entry is overwritten
|
||||
storage.insert_new(test.storage_mut(), &first)?;
|
||||
assert_eq!(storage.id_counter.load(test.storage())?, 1);
|
||||
|
||||
storage.insert_new(test.storage_mut(), &second)?;
|
||||
assert_eq!(storage.id_counter.load(test.storage())?, 2);
|
||||
|
||||
storage.insert_new(test.storage_mut(), &third)?;
|
||||
assert_eq!(storage.id_counter.load(test.storage())?, 3);
|
||||
|
||||
assert_eq!(storage.version_history.load(test.storage(), 1)?, first);
|
||||
assert_eq!(storage.version_history.load(test.storage(), 2)?, second);
|
||||
assert_eq!(storage.version_history.load(test.storage(), 3)?, third);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inserting_initial_semver() -> anyhow::Result<()> {
|
||||
// empty storage
|
||||
let mut deps = mock_dependencies();
|
||||
let env = mock_env();
|
||||
let storage = NymNodeVersionHistory::new();
|
||||
|
||||
assert!(storage
|
||||
.id_counter
|
||||
.may_load(deps.as_mut().storage)?
|
||||
.is_none());
|
||||
|
||||
storage.try_insert_new(deps.as_mut().storage, &env, "1.1.1")?;
|
||||
assert_eq!(storage.id_counter.load(deps.as_mut().storage)?, 0);
|
||||
|
||||
assert_eq!(
|
||||
storage
|
||||
.version_history
|
||||
.load(deps.as_ref().storage, 0)?
|
||||
.semver,
|
||||
"1.1.1"
|
||||
);
|
||||
assert_eq!(
|
||||
storage
|
||||
.current_version(deps.as_ref().storage)?
|
||||
.unwrap()
|
||||
.version_information
|
||||
.semver,
|
||||
"1.1.1"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inserting_second_semver() -> anyhow::Result<()> {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
let storage = NymNodeVersionHistory::new();
|
||||
|
||||
// lower version
|
||||
assert!(storage
|
||||
.try_insert_new(test.storage_mut(), &env, "1.1.9")
|
||||
.is_err());
|
||||
assert!(storage
|
||||
.try_insert_new(test.storage_mut(), &env, "1.0.1")
|
||||
.is_err());
|
||||
|
||||
// malformed
|
||||
assert!(storage
|
||||
.try_insert_new(test.storage_mut(), &env, "1.0")
|
||||
.is_err());
|
||||
assert!(storage
|
||||
.try_insert_new(test.storage_mut(), &env, "1.0bad")
|
||||
.is_err());
|
||||
assert!(storage
|
||||
.try_insert_new(test.storage_mut(), &env, "foomp")
|
||||
.is_err());
|
||||
|
||||
// patch
|
||||
let mut test = TestSetup::new();
|
||||
storage.try_insert_new(test.storage_mut(), &env, "1.1.11")?;
|
||||
let current = storage
|
||||
.current_version(test.storage_mut())?
|
||||
.unwrap()
|
||||
.version_information;
|
||||
assert_eq!(current.semver, "1.1.11");
|
||||
assert_eq!(current.difference_since_genesis.major, 0);
|
||||
assert_eq!(current.difference_since_genesis.minor, 0);
|
||||
assert_eq!(current.difference_since_genesis.patch, 1);
|
||||
assert_eq!(current.difference_since_genesis.prerelease, 0);
|
||||
|
||||
// minor
|
||||
let mut test = TestSetup::new();
|
||||
storage.try_insert_new(test.storage_mut(), &env, "1.2.0")?;
|
||||
let current = storage
|
||||
.current_version(test.storage_mut())?
|
||||
.unwrap()
|
||||
.version_information;
|
||||
assert_eq!(current.semver, "1.2.0");
|
||||
assert_eq!(current.difference_since_genesis.major, 0);
|
||||
assert_eq!(current.difference_since_genesis.minor, 1);
|
||||
assert_eq!(current.difference_since_genesis.patch, 0);
|
||||
assert_eq!(current.difference_since_genesis.prerelease, 0);
|
||||
|
||||
// minor alt.
|
||||
let mut test = TestSetup::new();
|
||||
storage.try_insert_new(test.storage_mut(), &env, "1.2.3")?;
|
||||
let current = storage
|
||||
.current_version(test.storage_mut())?
|
||||
.unwrap()
|
||||
.version_information;
|
||||
assert_eq!(current.semver, "1.2.3");
|
||||
assert_eq!(current.difference_since_genesis.major, 0);
|
||||
assert_eq!(current.difference_since_genesis.minor, 1);
|
||||
assert_eq!(current.difference_since_genesis.patch, 0);
|
||||
assert_eq!(current.difference_since_genesis.prerelease, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inserting_subsequent_semver() -> anyhow::Result<()> {
|
||||
let mut test = TestSetup::new();
|
||||
let env = test.env();
|
||||
let storage = NymNodeVersionHistory::new();
|
||||
|
||||
storage.try_insert_new(test.storage_mut(), &env, "1.2.0")?;
|
||||
storage.try_insert_new(test.storage_mut(), &env, "1.2.1")?;
|
||||
storage.try_insert_new(test.storage_mut(), &env, "1.2.3")?;
|
||||
let current = storage
|
||||
.current_version(test.storage_mut())?
|
||||
.unwrap()
|
||||
.version_information;
|
||||
assert_eq!(current.semver, "1.2.3");
|
||||
assert_eq!(current.difference_since_genesis.major, 0);
|
||||
assert_eq!(current.difference_since_genesis.minor, 1);
|
||||
assert_eq!(current.difference_since_genesis.patch, 3);
|
||||
assert_eq!(current.difference_since_genesis.prerelease, 0);
|
||||
|
||||
storage.try_insert_new(test.storage_mut(), &env, "1.3.0")?;
|
||||
let current = storage
|
||||
.current_version(test.storage_mut())?
|
||||
.unwrap()
|
||||
.version_information;
|
||||
assert_eq!(current.semver, "1.3.0");
|
||||
assert_eq!(current.difference_since_genesis.major, 0);
|
||||
assert_eq!(current.difference_since_genesis.minor, 2);
|
||||
assert_eq!(current.difference_since_genesis.patch, 3);
|
||||
assert_eq!(current.difference_since_genesis.prerelease, 0);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,4 +238,446 @@ pub mod tests {
|
||||
// let res = try_update_contract_settings(deps.as_mut(), info, new_params);
|
||||
// assert_eq!(Err(MixnetContractError::ZeroActiveSet), res);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod updating_current_nym_node_semver {
|
||||
use super::*;
|
||||
use crate::mixnet_contract_settings::queries::query_current_nym_node_version;
|
||||
use crate::support::tests::test_helpers::TestSetup;
|
||||
|
||||
#[test]
|
||||
fn is_restricted_to_the_admin() -> anyhow::Result<()> {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let not_admin = mock_info("not-admin", &[]);
|
||||
let admin = mock_info(test.admin().as_ref(), &[]);
|
||||
|
||||
let env = test.env();
|
||||
let res = try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
not_admin,
|
||||
"1.2.1".to_string(),
|
||||
);
|
||||
assert!(res.is_err());
|
||||
|
||||
let env = test.env();
|
||||
let res = try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env,
|
||||
admin,
|
||||
"1.2.1".to_string(),
|
||||
);
|
||||
assert!(res.is_ok());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updates_current_semver_value() -> anyhow::Result<()> {
|
||||
let mut test = TestSetup::new();
|
||||
|
||||
let res = query_current_nym_node_version(test.deps())?;
|
||||
|
||||
let initial = res.version.unwrap().version_information.semver;
|
||||
// sanity check to make sure our contract init hasn't changed
|
||||
assert_eq!(initial, "1.1.10");
|
||||
|
||||
let update = "1.2.0".to_string();
|
||||
|
||||
let env = test.env();
|
||||
let sender = test.admin_sender();
|
||||
try_update_current_nym_node_semver(test.deps_mut(), env, sender, update.clone())?;
|
||||
|
||||
let updated = query_current_nym_node_version(test.deps())?;
|
||||
let version = updated.version.unwrap().version_information.semver;
|
||||
assert_eq!(version, update);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod semver_chain_updates {
|
||||
use super::*;
|
||||
use crate::mixnet_contract_settings::queries::query_nym_node_version_history_paged;
|
||||
use mixnet_contract_common::{
|
||||
HistoricalNymNodeVersion, HistoricalNymNodeVersionEntry, TotalVersionDifference,
|
||||
};
|
||||
|
||||
fn test_setup_with_initial_checks() -> anyhow::Result<TestSetup> {
|
||||
let test = TestSetup::new();
|
||||
|
||||
let res = query_current_nym_node_version(test.deps())?;
|
||||
let initial = res.version.unwrap().version_information.semver;
|
||||
|
||||
// sanity check to make sure our contract init hasn't changed
|
||||
assert_eq!(initial, "1.1.10");
|
||||
|
||||
let history = query_nym_node_version_history_paged(test.deps(), None, None)?;
|
||||
assert_eq!(history.history.len(), 1);
|
||||
|
||||
Ok(test)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_patch() -> anyhow::Result<()> {
|
||||
let mut test = test_setup_with_initial_checks()?;
|
||||
let initial = query_current_nym_node_version(test.deps())?
|
||||
.version
|
||||
.unwrap();
|
||||
|
||||
let env = test.env();
|
||||
let sender = test.admin_sender();
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender,
|
||||
"1.1.11".to_string(),
|
||||
)?;
|
||||
|
||||
let history =
|
||||
query_nym_node_version_history_paged(test.deps(), None, None)?.history;
|
||||
assert_eq!(history.len(), 2);
|
||||
assert_eq!(history[0], initial);
|
||||
assert_eq!(
|
||||
history[1],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 1,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.1.11".to_string(),
|
||||
introduced_at_height: env.block.height,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 0,
|
||||
patch: 1,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_minor() -> anyhow::Result<()> {
|
||||
let mut test = test_setup_with_initial_checks()?;
|
||||
let initial = query_current_nym_node_version(test.deps())?
|
||||
.version
|
||||
.unwrap();
|
||||
|
||||
let env = test.env();
|
||||
let sender = test.admin_sender();
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender,
|
||||
"1.2.0".to_string(),
|
||||
)?;
|
||||
|
||||
let history =
|
||||
query_nym_node_version_history_paged(test.deps(), None, None)?.history;
|
||||
assert_eq!(history.len(), 2);
|
||||
assert_eq!(history[0], initial);
|
||||
assert_eq!(
|
||||
history[1],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 1,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.2.0".to_string(),
|
||||
introduced_at_height: env.block.height,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 1,
|
||||
patch: 0,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_patches() -> anyhow::Result<()> {
|
||||
let mut test = test_setup_with_initial_checks()?;
|
||||
let initial = query_current_nym_node_version(test.deps())?
|
||||
.version
|
||||
.unwrap();
|
||||
|
||||
let mut env = test.env();
|
||||
let sender = test.admin_sender();
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender.clone(),
|
||||
"1.1.11".to_string(),
|
||||
)?;
|
||||
env.block.height += 1;
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender.clone(),
|
||||
"1.1.12".to_string(),
|
||||
)?;
|
||||
env.block.height += 1;
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender,
|
||||
"1.1.13".to_string(),
|
||||
)?;
|
||||
|
||||
let history =
|
||||
query_nym_node_version_history_paged(test.deps(), None, None)?.history;
|
||||
assert_eq!(history.len(), 4);
|
||||
assert_eq!(history[0], initial);
|
||||
assert_eq!(
|
||||
history[1],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 1,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.1.11".to_string(),
|
||||
introduced_at_height: env.block.height - 2,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 0,
|
||||
patch: 1,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
history[2],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 2,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.1.12".to_string(),
|
||||
introduced_at_height: env.block.height - 1,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 0,
|
||||
patch: 2,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
history[3],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 3,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.1.13".to_string(),
|
||||
introduced_at_height: env.block.height,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 0,
|
||||
patch: 3,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_minors() -> anyhow::Result<()> {
|
||||
let mut test = test_setup_with_initial_checks()?;
|
||||
let initial = query_current_nym_node_version(test.deps())?
|
||||
.version
|
||||
.unwrap();
|
||||
|
||||
let mut env = test.env();
|
||||
let sender = test.admin_sender();
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender.clone(),
|
||||
"1.2.0".to_string(),
|
||||
)?;
|
||||
env.block.height += 1;
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender.clone(),
|
||||
"1.3.0".to_string(),
|
||||
)?;
|
||||
env.block.height += 1;
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender,
|
||||
"1.4.0".to_string(),
|
||||
)?;
|
||||
|
||||
let history =
|
||||
query_nym_node_version_history_paged(test.deps(), None, None)?.history;
|
||||
assert_eq!(history.len(), 4);
|
||||
assert_eq!(history[0], initial);
|
||||
assert_eq!(
|
||||
history[1],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 1,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.2.0".to_string(),
|
||||
introduced_at_height: env.block.height - 2,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 1,
|
||||
patch: 0,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
history[2],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 2,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.3.0".to_string(),
|
||||
introduced_at_height: env.block.height - 1,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 2,
|
||||
patch: 0,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
history[3],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 3,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.4.0".to_string(),
|
||||
introduced_at_height: env.block.height,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 3,
|
||||
patch: 0,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_multiple_updates() -> anyhow::Result<()> {
|
||||
let mut test = test_setup_with_initial_checks()?;
|
||||
let initial = query_current_nym_node_version(test.deps())?
|
||||
.version
|
||||
.unwrap();
|
||||
|
||||
let mut env = test.env();
|
||||
let sender = test.admin_sender();
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender.clone(),
|
||||
"1.2.0".to_string(),
|
||||
)?;
|
||||
env.block.height += 1;
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender.clone(),
|
||||
"1.2.1".to_string(),
|
||||
)?;
|
||||
env.block.height += 1;
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender.clone(),
|
||||
"1.2.3".to_string(),
|
||||
)?;
|
||||
env.block.height += 1;
|
||||
try_update_current_nym_node_semver(
|
||||
test.deps_mut(),
|
||||
env.clone(),
|
||||
sender,
|
||||
"1.3.0".to_string(),
|
||||
)?;
|
||||
|
||||
let history =
|
||||
query_nym_node_version_history_paged(test.deps(), None, None)?.history;
|
||||
assert_eq!(history.len(), 5);
|
||||
assert_eq!(history[0], initial);
|
||||
assert_eq!(
|
||||
history[1],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 1,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.2.0".to_string(),
|
||||
introduced_at_height: env.block.height - 3,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 1,
|
||||
patch: 0,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
history[2],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 2,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.2.1".to_string(),
|
||||
introduced_at_height: env.block.height - 2,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 1,
|
||||
patch: 1,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
history[3],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 3,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.2.3".to_string(),
|
||||
introduced_at_height: env.block.height - 1,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 1,
|
||||
patch: 3,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
history[4],
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: 4,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: "1.3.0".to_string(),
|
||||
introduced_at_height: env.block.height,
|
||||
difference_since_genesis: TotalVersionDifference {
|
||||
major: 0,
|
||||
minor: 2,
|
||||
patch: 3,
|
||||
prerelease: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +1,2 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod config_score_params {
|
||||
use crate::constants::CONTRACT_STATE_KEY;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnet_contract_settings::storage::NymNodeVersionHistory;
|
||||
use cosmwasm_std::{Addr, Coin, DepsMut, Env};
|
||||
use cw_storage_plus::Item;
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{
|
||||
ConfigScoreParams, ContractState, ContractStateParams, DelegationsParams, MigrateMsg,
|
||||
OperatingCostRange, OperatorsParams, ProfitMarginRange,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub(crate) fn add_config_score_params(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
msg: &MigrateMsg,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
if semver::Version::from_str(&msg.current_nym_node_semver).is_err() {
|
||||
return Err(MixnetContractError::InvalidNymNodeSemver {
|
||||
provided: msg.current_nym_node_semver.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct OldContractState {
|
||||
pub owner: Option<Addr>,
|
||||
pub rewarding_validator_address: Addr,
|
||||
pub vesting_contract_address: Addr,
|
||||
pub rewarding_denom: String,
|
||||
pub params: OldContractStateParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct OldContractStateParams {
|
||||
pub minimum_delegation: Option<Coin>,
|
||||
pub minimum_pledge: Coin,
|
||||
#[serde(default)]
|
||||
pub profit_margin: ProfitMarginRange,
|
||||
#[serde(default)]
|
||||
pub interval_operating_cost: OperatingCostRange,
|
||||
}
|
||||
|
||||
const OLD_CONTRACT_STATE: Item<'_, OldContractState> = Item::new(CONTRACT_STATE_KEY);
|
||||
let old_state = OLD_CONTRACT_STATE.load(deps.storage)?;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let new_state = ContractState {
|
||||
owner: old_state.owner,
|
||||
rewarding_validator_address: old_state.rewarding_validator_address,
|
||||
vesting_contract_address: old_state.vesting_contract_address,
|
||||
rewarding_denom: old_state.rewarding_denom,
|
||||
params: ContractStateParams {
|
||||
delegations_params: DelegationsParams {
|
||||
minimum_delegation: old_state.params.minimum_delegation,
|
||||
},
|
||||
operators_params: OperatorsParams {
|
||||
minimum_pledge: old_state.params.minimum_pledge,
|
||||
profit_margin: old_state.params.profit_margin,
|
||||
interval_operating_cost: old_state.params.interval_operating_cost,
|
||||
},
|
||||
config_score_params: ConfigScoreParams {
|
||||
version_weights: msg.version_score_weights,
|
||||
version_score_formula_params: msg.version_score_params,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &new_state)?;
|
||||
|
||||
// initialise the version chain
|
||||
NymNodeVersionHistory::new().try_insert_new(
|
||||
deps.storage,
|
||||
&env,
|
||||
&msg.current_nym_node_semver,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use config_score_params::add_config_score_params;
|
||||
|
||||
@@ -18,7 +18,7 @@ pub mod test_helpers {
|
||||
};
|
||||
use crate::interval::{pending_events, storage as interval_storage};
|
||||
use crate::mixnet_contract_settings::storage::{
|
||||
self as mixnet_params_storage, minimum_node_pledge,
|
||||
self as mixnet_params_storage, minimum_node_pledge, ADMIN,
|
||||
};
|
||||
use crate::mixnet_contract_settings::storage::{rewarding_denom, rewarding_validator_address};
|
||||
use crate::mixnodes::helpers::get_mixnode_details_by_id;
|
||||
@@ -318,6 +318,10 @@ pub mod test_helpers {
|
||||
compare_decimals(mix_info.delegates, subtotal, Some(epsilon))
|
||||
}
|
||||
|
||||
pub fn admin(&self) -> Addr {
|
||||
ADMIN.get(self.deps()).unwrap().unwrap()
|
||||
}
|
||||
|
||||
pub fn random_address(&mut self) -> String {
|
||||
format!("n1foomp{}", self.rng.next_u64())
|
||||
}
|
||||
@@ -330,6 +334,14 @@ pub mod test_helpers {
|
||||
self.deps.as_mut()
|
||||
}
|
||||
|
||||
pub fn storage(&self) -> &dyn Storage {
|
||||
self.deps().storage
|
||||
}
|
||||
|
||||
pub fn storage_mut(&mut self) -> &mut dyn Storage {
|
||||
self.deps_mut().storage
|
||||
}
|
||||
|
||||
pub fn env(&self) -> Env {
|
||||
self.env.clone()
|
||||
}
|
||||
@@ -470,6 +482,10 @@ pub mod test_helpers {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn admin_sender(&self) -> MessageInfo {
|
||||
mock_info(self.admin().as_ref(), &[])
|
||||
}
|
||||
|
||||
pub fn owner(&self) -> MessageInfo {
|
||||
self.owner.clone()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This is v2 of the nym docs, condensed from various mdbooks projects that we had previously.
|
||||
|
||||
These docs are hosted at [nymtech.net/docs](www.nymtech.net/docs).
|
||||
These docs are hosted at [nym.com/docs](https://nym.com/docs).
|
||||
|
||||
## Doc projects
|
||||
`docs/pages/` contains several subdirs, each hosting a subsection of the docs:
|
||||
@@ -11,6 +11,7 @@ These docs are hosted at [nymtech.net/docs](www.nymtech.net/docs).
|
||||
* `operators` contains node setup and maintenance guides.
|
||||
|
||||
## Local development
|
||||
|
||||
### Dependencies
|
||||
Our `prebuild` script relies on the following:
|
||||
- `python`
|
||||
@@ -54,7 +55,7 @@ This is a monorepo and components that make up Nym as a system are licensed indi
|
||||
|
||||
As a general approach, licensing is as follows this pattern:
|
||||
|
||||
* <p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/"><a property="dct:title" rel="cc:attributionURL" href="https://nymtech.net/docs">Nym Documentation</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://nymtech.net">Nym Technologies</a> is licensed under <a href="http://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC BY-NC-SA 4.0<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1"></a></p>
|
||||
* <p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/"><a property="dct:title" rel="cc:attributionURL" href="https://nym.com/docs">Nym Documentation</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://nym.com">Nym Technologies</a> is licensed under <a href="http://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC BY-NC-SA 4.0<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1"><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg?ref=chooser-v1"></a></p>
|
||||
|
||||
* Nym applications and binaries are [GPL-3.0-only](https://www.gnu.org/licenses/)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import Stack from '@mui/material/Stack';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
|
||||
|
||||
const defaultUrl = 'https://nymtech.net/favicon.svg';
|
||||
const defaultUrl = 'https://nym.com/favicon.svg';
|
||||
const args = { mode: 'unsafe-ignore-cors' };
|
||||
|
||||
const mixFetchOptions: SetupMixFetchOps = {
|
||||
|
||||
@@ -6,10 +6,14 @@ import { useTheme } from "@mui/material/styles";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
import networkDocs from "../public/images/landing/network-docs.png";
|
||||
import developerDocs from "../public/images/landing/developer-docs.png";
|
||||
import sdkDocs from "../public/images/landing/sdk-docs.png";
|
||||
import operatorGuide from "../public/images/landing/operator-guide.png";
|
||||
// import networkDocs from "../public/images/landing/network-docs.png";
|
||||
// import developerDocs from "../public/images/landing/developer-docs.png";
|
||||
// import sdkDocs from "../public/images/landing/sdk-docs.png";
|
||||
// import operatorGuide from "../public/images/landing/operator-guide.png";
|
||||
import networkDocs from "../public/images/landing/Vector1.png";
|
||||
import developerDocs from "../public/images/landing/Vector2.png";
|
||||
import sdkDocs from "../public/images/landing/Vector3.png";
|
||||
import operatorGuide from "../public/images/landing/Vector4.png";
|
||||
export const LandingPage = () => {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.up("md"));
|
||||
@@ -129,7 +133,7 @@ export const LandingPage = () => {
|
||||
{square.description}
|
||||
</Typography>
|
||||
|
||||
<Typography sx={{ color: "#ff6600", fontWeight: 600 }}>
|
||||
<Typography sx={{ color: "#14E76F", fontWeight: 600 }}>
|
||||
Open
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
@@ -9,7 +9,7 @@ import Stack from "@mui/material/Stack";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat";
|
||||
|
||||
const defaultUrl = "https://nymtech.net/favicon.svg";
|
||||
const defaultUrl = "https://nym.com/favicon.svg";
|
||||
const args = { mode: "unsafe-ignore-cors" };
|
||||
|
||||
const mixFetchOptions: SetupMixFetchOps = {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
### Terms & Conditions
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
From `nym-node` version `1.1.3` onward is required to accept [**Operators Terms & Conditions**](https://nymtech.net/terms-and-conditions/operators/v1.0.0) in order to be part of the active set. Make sure to read them before you add the flag.
|
||||
</Callout>
|
||||
|
||||
There has been a long ongoing discussion whether and how to apply Terms and Conditions for Nym network operators, with an aim to stay aligned with the philosophy of Free Software and provide legal defense for both node operators and Nym developers. To understand better the reasoning behind this decision, you can listen to the first [Nym Operator Town Hall](https://www.youtube.com/live/7hwb8bAZIuc?si=3mQ2ed7AyUA1SsCp&t=915) introducing the T&Cs or to [Operator AMA with CEO Harry Halpin](https://www.youtube.com/watch?v=yIN-zYQw0I0) from June 4th, 2024, explaining pros and cons of T&Cs implementation.
|
||||
@@ -1 +1 @@
|
||||
Monday, January 6th 2025, 11:03:09 UTC
|
||||
Thursday, January 16th 2025, 09:57:52 UTC
|
||||
|
||||
@@ -2,5 +2,5 @@ import { Callout } from 'nextra/components'
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
Our documentation often refer to syntax annotated in `<>` brackets. We use this expression for variables that are unique to each user (like path, local moniker, versions etcetra).
|
||||
Any syntax in `<>` brackets needs to be substituted with your correct name or version, without the `<>` brackets. If you are unsure, please check our table of essential [parameters and variables](https://nymtech.net/docs/operators/variables.html).
|
||||
Any syntax in `<>` brackets needs to be substituted with your correct name or version, without the `<>` brackets. If you are unsure, please check our table of essential [parameters and variables](https://nym.com/docs/operators/variables).
|
||||
</Callout>
|
||||
|
||||
@@ -100,7 +100,7 @@ const config = {
|
||||
},
|
||||
{
|
||||
source: "/docs/nodes/overview.html ",
|
||||
destination: "/docs/network/architecture/mixnet/nodes",
|
||||
destination: "/docs/network/architecture/mixnet#nym-nodes",
|
||||
permanent: true,
|
||||
basePath: false,
|
||||
},
|
||||
@@ -132,21 +132,19 @@ const config = {
|
||||
},
|
||||
{
|
||||
source: "/docs/nyx/smart-contracts.html",
|
||||
destination: "/docs/network/architecture/nyx/smart-contracts",
|
||||
destination: "/docs/network/architecture/nyx#smart-contracts",
|
||||
permanent: true,
|
||||
basePath: false,
|
||||
},
|
||||
{
|
||||
source: "/docs/nyx/mixnet-contract.html",
|
||||
destination:
|
||||
"/docs/network/architecture/nyx/smart-contracts/mixnet-contract",
|
||||
destination: "/docs/network/architecture/nyx#mixnet-contract",
|
||||
permanent: true,
|
||||
basePath: false,
|
||||
},
|
||||
{
|
||||
source: "/docs/nyx/vesting-contract.html",
|
||||
destination:
|
||||
"/docs/network/architecture/nyx/smart-contracts/vesting-contract",
|
||||
destination: "/docs/network/architecture/nyx#vesting-contract",
|
||||
permanent: true,
|
||||
basePath: false,
|
||||
},
|
||||
@@ -631,7 +629,7 @@ const config = {
|
||||
},
|
||||
{
|
||||
source: "/docs/network/architecture/nyx/smart-contracts/ecash",
|
||||
destination: "/docs/network/architecture/nyx/smart-contracts/zknym",
|
||||
destination: "/docs/network/architecture/nyx#zk-nym-contract",
|
||||
permanent: true,
|
||||
basePath: false,
|
||||
},
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# NymVPN alpha
|
||||
|
||||
<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/897010658?h=1f55870fe6&badge=0&autopause=0&player_id=0&app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="NYMVPN alpha demo 37C3"></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
|
||||
|
||||
```admonish tip title="web3summit testing"
|
||||
**If you testing NymVPN CLI on Web3 Summit Berlin, visit our event page with all info tailored for the event: [nym-vpn-cli.sandbox.nymtech.net](https://nym-vpn-cli.sandbox.nymtech.net/).**
|
||||
```
|
||||
|
||||
**NymVPN alpha** is a client that uses [Nym Mixnet](https://nymtech.net) to anonymise all of a user's internet traffic through either a 5-hop mixnet (for a full network privacy) or the faster 2-hop decentralised VPN (with some extra features).
|
||||
|
||||
|
||||
**You are invited to take part in the alpha testing** of this new application. Register for private testing round at [nymvpn.com](https://nymvpn.com/en), that will grant you access to the [download page](https://nymvpn.com/download). Visit [NymVPN Support & FAQ](https://nymvpn.com/en/support) or join the [NymVPN matrix channel](https://matrix.to/#/#NymVPN:nymtech.chat) if you have any questions, comments or blockers.
|
||||
|
||||
Checkout the [release page](https://github.com/nymtech/nym-vpn-client/releases) for available binaries.
|
||||
|
||||
*NOTE: NymVPN alpha is experimental software for testing purposes only.*
|
||||
|
||||
|
||||
## NymVPN Overview
|
||||
|
||||
To understand what's under the hood of NymVPN and the mixnet, we recommend interested developers to begin with [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html) pages.
|
||||
|
||||
The default setup of NymVPN is to run in 5-hop mode (mixnet):
|
||||
|
||||
```
|
||||
┌─►mix──┐ mix mix
|
||||
│ │
|
||||
Entry │ │ Exit
|
||||
client ───► Gateway ──┘ mix │ mix ┌─►mix ───► Gateway ───► internet
|
||||
│ │
|
||||
│ │
|
||||
mix └─►mix──┘ mix
|
||||
```
|
||||
|
||||
Users can switch to 2-hop only mode, which is a faster but less private option. In this mode traffic is only sent between the two Gateways, and is not passed between Mix Nodes. It uses Mixnet Sphinx packets with shorter, fixed routes, which improve latency, but doesn't offer the same level of protection as the 5 hop mode.
|
||||
<!-- TO BE IMPLEMENTED:
|
||||
Users can switch to 2-hop only mode, which is a faster but less private option. In this mode traffic is only sent between the two Gateways, and is not passed between Mix Nodes. The client than use two wireguard tunnels with the entry and exit gateway, the Exit Gateway one being tunnelled itself through the entry gateway tunnel. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device.
|
||||
-->
|
||||
@@ -1,142 +0,0 @@
|
||||
# NymVPN alpha CLI Guide
|
||||
|
||||
```admonish info
|
||||
NymVPN is an experimental software and it's for testing purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the workshop) so we use their results to improve the client, and submit the form [*NymVPN User research*]({{nym_vpn_form_url}}) with the testing results.
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
1. Open Github [releases page](https://github.com/nymtech/nym-vpn-client) and download the CLI latest binary for your system
|
||||
|
||||
2. Verify sha hash of your downloaded binary with the one listed on the [releases page](https://github.com/nymtech/nym-vpn-client). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "0e4abb461e86b2c168577e0294112a3bacd3a24bf8565b49783bfebd9b530e23 nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_ubuntu-22.04_amd64.tar.gz" | shasum -a 256 -c
|
||||
```
|
||||
|
||||
3. Extract files:
|
||||
```sh
|
||||
tar -xvf <BINARY>.tar.gz
|
||||
# for example
|
||||
# tar -xvf nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_ubuntu-22.04_x86_64.tar.gz
|
||||
```
|
||||
|
||||
4. Make executable:
|
||||
```sh
|
||||
# make sure you are in the right sub-directory
|
||||
chmod u+x ./nym-vpn-cli
|
||||
```
|
||||
|
||||
## Run NymVPN
|
||||
|
||||
**For NymVPN to work, all other VPNs must be switched off!** At this alpha stage of NymVPN, the network connection (wifi) must be reconnected after or in between the testing rounds.
|
||||
|
||||
Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary.
|
||||
|
||||
1. Go to [nymvpn.com/en/alpha](https://nymvpn.com/en/alpha) to get the entire command with all the needed arguments' values and your wireguard private key for testing purposes
|
||||
2. Run it as root with `sudo` - the command will look like this with specified arguments:
|
||||
```sh
|
||||
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WIREGUARD_IP>
|
||||
```
|
||||
3. To choose different Gateways, visit [explorer.nymtech.net/network-components/gateways](https://explorer.nymtech.net/network-components/gateways) and copy-paste an identity key of your choice
|
||||
4. See all possibilities in [command explanation](#cli-commands-and-options) section below
|
||||
|
||||
In case of errors, see [troubleshooting section](troubleshooting.md).
|
||||
|
||||
### CLI Commands and Options
|
||||
|
||||
The basic syntax of `nym-vpn-cli` is:
|
||||
```sh
|
||||
sudo ./nym-vpn-cli <--exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY>>
|
||||
```
|
||||
* To choose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://explorer.nymtech.net/network-components/gateways)
|
||||
* To see all possibilities run with `--help` flag:
|
||||
```sh
|
||||
./nym-vpn-cli --help
|
||||
```
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```sh
|
||||
Usage: nym-vpn-cli [OPTIONS] <--exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY>>
|
||||
|
||||
Options:
|
||||
-c, --config-env-file <CONFIG_ENV_FILE>
|
||||
Path pointing to an env file describing the network
|
||||
--mixnet-client-path <MIXNET_CLIENT_PATH>
|
||||
Path to the data directory of a previously initialised mixnet client, where the keys reside
|
||||
--entry-gateway-id <ENTRY_GATEWAY_ID>
|
||||
Mixnet public ID of the entry gateway
|
||||
--entry-gateway-country <ENTRY_GATEWAY_COUNTRY>
|
||||
Auto-select entry gateway by country ISO
|
||||
--entry-gateway-low-latency
|
||||
Auto-select entry gateway by latency
|
||||
--exit-router-address <EXIT_ROUTER_ADDRESS>
|
||||
Mixnet recipient address
|
||||
--exit-gateway-id <EXIT_GATEWAY_ID>
|
||||
|
||||
--exit-gateway-country <EXIT_GATEWAY_COUNTRY>
|
||||
Mixnet recipient address
|
||||
--enable-wireguard
|
||||
Enable the wireguard traffic between the client and the entry gateway
|
||||
--private-key <PRIVATE_KEY>
|
||||
Associated private key
|
||||
--wg-ip <WG_IP>
|
||||
The IP address of the wireguard interface used for the first hop to the entry gateway
|
||||
--nym-ipv4 <NYM_IPV4>
|
||||
The IPv4 address of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--nym-ipv6 <NYM_IPV6>
|
||||
The IPv6 address of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--nym-mtu <NYM_MTU>
|
||||
The MTU of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--disable-routing
|
||||
Disable routing all traffic through the nym TUN device. When the flag is set, the nym TUN device will be created, but to route traffic through it you will need to do it manually, e.g. ping -Itun0
|
||||
--enable-two-hop
|
||||
Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to exit gateway
|
||||
--enable-poisson-rate
|
||||
Enable Poisson process rate limiting of outbound traffic
|
||||
--disable-background-cover-traffic
|
||||
Disable constant rate background loop cover traffic
|
||||
-h, --help
|
||||
Print help
|
||||
-V, --version
|
||||
Print version
|
||||
```
|
||||
~~~
|
||||
|
||||
Here is a list of the options and their descriptions. Some are essential, some are more technical and not needed to be adjusted by users.
|
||||
|
||||
**Fundamental commands and arguments**
|
||||
|
||||
- `-c` is a path to the [Sandbox config](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) file saved as `sandbox.env`
|
||||
- `--entry-gateway-id`: paste one of the values labeled with a key `"identityKey"` (without `" "`) from [here](https://nymvpn.com/en/alpha/api/gateways)
|
||||
- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`) from here [here](https://nymvpn.com/en/alpha/api/gateways)
|
||||
- `--enable-wireguard`: Enable the wireguard traffic between the client and the entry gateway. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device
|
||||
- `--wg-ip`: The address of the wireguard interface, you can get it [here](https://nymvpn.com/en/alpha)
|
||||
- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/alpha)
|
||||
- `--enable-two-hop` is a faster setup where the traffic is routed from the client to Entry Gateway and directly to Exit Gateway (default is 5-hops)
|
||||
|
||||
**Advanced options**
|
||||
|
||||
- `--enable-poisson`: Enables process rate limiting of outbound traffic (disabled by default). It means that NymVPN client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
|
||||
- `--ip` is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway.
|
||||
- `--mtu`: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway.
|
||||
- `--disable-routing`: Disable routing all traffic through the VPN TUN device.
|
||||
|
||||
## Testnet environment
|
||||
|
||||
If you want to run NymVPN CLI in Nym Sandbox environment, there are a few adjustments to be done:
|
||||
|
||||
1. Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries by running:
|
||||
```sh
|
||||
curl -o sandbox.env -L https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env
|
||||
```
|
||||
|
||||
1. Check available Gateways at [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
|
||||
|
||||
2. Run with a flag `-c`
|
||||
```sh
|
||||
sudo ./nym-vpn-cli -c <PATH_TO>/sandbox.env <--exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY>>
|
||||
```
|
||||
@@ -1,123 +0,0 @@
|
||||
# NymVPN alpha CLI: Guide for MacOS
|
||||
|
||||
```admonish info
|
||||
NymVPN is an experimental software and it's for testing purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the workshop) so we use their results to improve the client, and submit the form [*NymVPN User research*]({{nym_vpn_form_url}}) with the testing results.
|
||||
```
|
||||
|
||||
## Preparation
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
1. Open Github [releases page](https://github.com/nymtech/nym-vpn-client) and download the binary for MacOS
|
||||
2. Verify sha hash of your downloaded binary with the one listed on the [releases page](https://github.com/nymtech/nym-vpn-client). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "96623ccc69bc4cc0e4e3e18528b6dae6be69f645d0a592d926a3158ce2d0c269 nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_macos_x86_64.zip" | shasum -a 256 -c
|
||||
```
|
||||
3. Extract files:
|
||||
```sh
|
||||
tar -xvf <BINARY>
|
||||
# for example
|
||||
# tar -xvf nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_macos_aarch64.tar.gz
|
||||
```
|
||||
4. Make executable by running:
|
||||
```sh
|
||||
# possibly you may have to cd into a sub-directory
|
||||
chmod u+x ./nym-vpn-cli
|
||||
```
|
||||
5. Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries by running:
|
||||
```sh
|
||||
curl -L "https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env" -o sandbox.env
|
||||
```
|
||||
|
||||
## Run NymVPN
|
||||
|
||||
**For NymVPN to work, all other VPNs must be switched off!** At this alpha stage of NymVPN, the network connection (wifi) must be reconnected after or in between the testing rounds.
|
||||
|
||||
Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary.
|
||||
|
||||
1. Go to [nymvpn.com/en/alpha](https://nymvpn.com/en/alpha) to get the entire command with all the needed arguments' values and your wireguard private key for testing purposes
|
||||
2. Run it as root with `sudo` - the command will look like this with specified arguments:
|
||||
```sh
|
||||
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WIREGUARD_IP>
|
||||
```
|
||||
3. To choose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways) and pick one
|
||||
4. See all possibilities in [command explanation](#cli-commands-and-options) section below
|
||||
|
||||
In case of errors, see [troubleshooting section](troubleshooting.md).
|
||||
|
||||
### CLI Commands and Options
|
||||
|
||||
The basic syntax of `nym-vpn-cli` is:
|
||||
```sh
|
||||
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WG_IP>
|
||||
```
|
||||
* To choose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
|
||||
* To see all possibilities run with `--help` flag:
|
||||
```sh
|
||||
./nym-vpn-cli --help
|
||||
```
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```sh
|
||||
Usage: nym-vpn-cli [OPTIONS]
|
||||
|
||||
Options:
|
||||
-c, --config-env-file <CONFIG_ENV_FILE>
|
||||
Path pointing to an env file describing the network
|
||||
--mixnet-client-path <MIXNET_CLIENT_PATH>
|
||||
Path to the data directory of a previously initialised mixnet client, where the keys reside
|
||||
--entry-gateway-id <ENTRY_GATEWAY_ID>
|
||||
Mixnet public ID of the entry gateway
|
||||
--entry-gateway-country <ENTRY_GATEWAY_COUNTRY>
|
||||
Auto-select entry gateway by country ISO
|
||||
--exit-router-address <EXIT_ROUTER_ADDRESS>
|
||||
Mixnet recipient address
|
||||
--exit-gateway-id <EXIT_GATEWAY_ID>
|
||||
|
||||
--exit-router-country <EXIT_ROUTER_COUNTRY>
|
||||
Mixnet recipient address
|
||||
--enable-wireguard
|
||||
Enable the wireguard traffic between the client and the entry gateway
|
||||
--private-key <PRIVATE_KEY>
|
||||
Associated private key
|
||||
--wg-ip <WG_IP>
|
||||
The IP address of the wireguard interface used for the first hop to the entry gateway
|
||||
--nym-ip <NYM_IP>
|
||||
The IP address of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--nym-mtu <NYM_MTU>
|
||||
The MTU of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--disable-routing
|
||||
Disable routing all traffic through the nym TUN device. When the flag is set, the nym TUN device will be created, but to route traffic through it you will need to do it manually, e.g. ping -Itun0
|
||||
--enable-two-hop
|
||||
Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to exit gateway
|
||||
--enable-poisson-rate
|
||||
Enable Poisson process rate limiting of outbound traffic
|
||||
--disable-background-cover-traffic
|
||||
Disable constant rate background loop cover traffic
|
||||
-h, --help
|
||||
Print help
|
||||
-V, --version
|
||||
Print version
|
||||
```
|
||||
~~~
|
||||
|
||||
Here is a list of the options and their descriptions. Some are essential, some are more technical and not needed to be adjusted by users.
|
||||
|
||||
**Fundamental commands and arguments**
|
||||
|
||||
- `-c` is a path to the [Sandbox config](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) file saved as `sandbox.env`
|
||||
- `--entry-gateway-id`: paste one of the values labeled with a key `"identityKey"` (without `" "`) from [here](https://nymvpn.com/en/alpha/api/gateways)
|
||||
- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`) from here [here](https://nymvpn.com/en/alpha/api/gateways)
|
||||
- `--enable-wireguard`: Enable the wireguard traffic between the client and the entry gateway. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device
|
||||
- `--wg-ip`: The address of the wireguard interface, you can get it [here](https://nymvpn.com/en/alpha)
|
||||
- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/alpha)
|
||||
- `--enable-two-hop` is a faster setup where the traffic is routed from the client to Entry Gateway and directly to Exit Gateway (default is 5-hops)
|
||||
|
||||
**Advanced options**
|
||||
|
||||
- `--enable-poisson`: Enables process rate limiting of outbound traffic (disabled by default). It means that NymVPN client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
|
||||
- `--ip` is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway.
|
||||
- `--mtu`: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway.
|
||||
- `--disable-routing`: Disable routing all traffic through the VPN TUN device.
|
||||
@@ -1,240 +0,0 @@
|
||||
# NymVPN CLI Guide
|
||||
|
||||
```admonish tip title="web3summit testing"
|
||||
**If you testing NymVPN CLI on Web3 Summit Berlin, visit our event page with all info tailored for the event: [nym-vpn-cli.sandbox.nymtech.net](https://nym-vpn-cli.sandbox.nymtech.net/).**
|
||||
```
|
||||
|
||||
```admonish info
|
||||
To download NymVPN desktop version, visit [nymvpn.com/en/download](https://nymvpn.com/en/download).
|
||||
|
||||
NymVPN is an experimental software and it's for testing purposes only. Anyone can submit a registration to the private alpha round on [nymvpn.com](https://nymvpn.com/en).
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
The core binaries consist of:
|
||||
|
||||
- **`nym-vpn-cli`**: Basic commandline client for running the vpn. This runs in the foreground.
|
||||
|
||||
- **`nym-vpnd`**: Daemon implementation of the vpn client that can run in the background and interacted with using `nym-vpnc`.
|
||||
|
||||
- **`nym-vpnc`**: The commandline client used to interact with `nym-vpnd`.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
1. Open Github [releases page](https://github.com/nymtech/nym-vpn-client) and download the CLI latest binary for your system (labelled as `nym-vpn-core`)
|
||||
|
||||
2. Verify sha hash of your downloaded binary with the one listed on the [releases page](https://github.com/nymtech/nym-vpn-client). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "0e4abb461e86b2c168577e0294112a3bacd3a24bf8565b49783bfebd9b530e23 nym-vpn-cli_<!-- cmdrun ../../../scripts/cmdrun/nym_vpn_cli_version.sh -->_ubuntu-22.04_amd64.tar.gz" | shasum -a 256 -c
|
||||
```
|
||||
|
||||
3. Extract files:
|
||||
```sh
|
||||
tar -xvf <BINARY>.tar.gz
|
||||
# for example
|
||||
# tar -xvf nym-vpn-cli_<!-- cmdrun ../../../scripts/cmdrun/nym_vpn_cli_version.sh -->_ubuntu-22.04_x86_64.tar.gz
|
||||
```
|
||||
|
||||
### Building From Source
|
||||
|
||||
NymVPN CLI can be built from source. This process is recommended for more advanced users as the installation may require different dependencies based on the operating system used.
|
||||
|
||||
Start by installing [Go](https://go.dev/doc/install) and [Rust](https://rustup.rs/) languages on your system and then follow these steps:
|
||||
|
||||
1. Clone NymVPN repository:
|
||||
```sh
|
||||
git clone https://github.com/nymtech/nym-vpn-client.git
|
||||
```
|
||||
|
||||
2. Move to `nym-vpn-client` directory and compile `wireguard`:
|
||||
|
||||
```sh
|
||||
cd nym-vpn-client
|
||||
|
||||
make build-wireguard
|
||||
```
|
||||
|
||||
3. Compile NymVPN CLI
|
||||
```sh
|
||||
make build-nym-vpn-core
|
||||
```
|
||||
|
||||
Now your NymVPN CLI is installed. Navigate to `nym-vpn-core/target/release` and use the commands the section below to run the client.
|
||||
|
||||
## Running
|
||||
|
||||
If you are running Debian/Ubuntu/PopOS or any other distributio supporting debian packages and systemd, see the [relevant section below](#debian-package-for-debianubuntupopos).
|
||||
|
||||
### Daemon
|
||||
|
||||
Start the daemon with
|
||||
|
||||
```sh
|
||||
sudo -E ./nym-vpnd
|
||||
```
|
||||
|
||||
Then run
|
||||
|
||||
```sh
|
||||
./nym-vpnc status
|
||||
./nym-vpnc connect
|
||||
./nym-vpnc disconnect
|
||||
```
|
||||
|
||||
### CLI
|
||||
|
||||
An alternative to the daemon is to run the `nym-vpn-cli` commandline client that runs in the foreground.
|
||||
```sh
|
||||
./nym-vpn-cli run
|
||||
```
|
||||
|
||||
## Credentials
|
||||
|
||||
NymVPN uses [zkNym bandwidth credentials](https://nymtech.net/docs/bandwidth-credentials.html). Those can be imported as a file or base58 encoded string.
|
||||
|
||||
|
||||
```sh
|
||||
sudo -E ./nym-vpn-cli import-credential --credential-path </PATH/TO/freepass.nym>
|
||||
sudo -E ./nym-vpn-cli import-credential --credential-data "<STRING>"
|
||||
```
|
||||
|
||||
## Debian package for Debian/Ubuntu/PopOS
|
||||
|
||||
For linux platforms using deb packages and systemd, there are also debian packages.
|
||||
|
||||
```sh
|
||||
sudo apt install ./nym-vpnd_<!-- cmdrun ../../../scripts/cmdrun/nym_vpn_cli_version.sh -->-1_amd64.deb ./nym-vpnc_<!-- cmdrun ../../../scripts/cmdrun/nym_vpn_cli_version.sh -->-1_amd64.deb
|
||||
|
||||
# In case of error please substitute the correct version
|
||||
```
|
||||
|
||||
Installing the `nym-vpnd` deb package starts a `nym-vpnd.service`. Check that the daemon is running with
|
||||
```sh
|
||||
systemctl status nym-vpnd.service
|
||||
```
|
||||
and check its logs with
|
||||
```sh
|
||||
sudo journalctl -u nym-vpnd.service -f
|
||||
```
|
||||
To stop the background service
|
||||
```sh
|
||||
systemctl stop nym-vpnd.service
|
||||
```
|
||||
It will start again on startup, so disable with
|
||||
```sh
|
||||
systemctl disable nym-vpnd.service
|
||||
```
|
||||
|
||||
Interact with it with `nym-vpnc`
|
||||
```sh
|
||||
nym-vpnc status
|
||||
nym-vpnc connect
|
||||
nym-vpnc disconnect
|
||||
```
|
||||
|
||||
## Commands & Options
|
||||
|
||||
```admonish note
|
||||
Nym Exit Gateway functionality was implemented just recently and not all the Gateways are upgraded and ready to handle the VPN connections. If you want to make sure you are connecting to a Gateway with an embedded Network Requester, IP Packet Router and applied Nym exit policy, visit [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net/) and search Gateways with all the functionalities enabled.
|
||||
```
|
||||
|
||||
The basic syntax of `nym-vpn-cli` is:
|
||||
```sh
|
||||
# choose only one conditional --argument listed in {brackets}
|
||||
sudo ./nym-vpn-cli { --exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY> }
|
||||
```
|
||||
|
||||
To see all the possibilities run with `--help` flag:
|
||||
```sh
|
||||
./nym-vpn-cli --help
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="nym-vpn-cli --help"
|
||||
```sh
|
||||
Usage: nym-vpn-cli [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
run Run the client
|
||||
import-credential Import credential
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-c, --config-env-file <CONFIG_ENV_FILE> Path pointing to an env file describing the network
|
||||
--data-path <DATA_PATH> Path to the data directory of the mixnet client
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
~~~
|
||||
|
||||
You can also run any command with `--help` flag to see a list of all options associated witht that command, the most important may be `run` command, like in this example.
|
||||
|
||||
~~~admonish example collapsible=true title="nym-vpn-cli run --help"
|
||||
```sh
|
||||
Run the client
|
||||
|
||||
Usage: nym-vpn-cli run [OPTIONS]
|
||||
|
||||
Options:
|
||||
--entry-gateway-id <ENTRY_GATEWAY_ID>
|
||||
Mixnet public ID of the entry gateway
|
||||
--entry-gateway-country <ENTRY_GATEWAY_COUNTRY>
|
||||
Auto-select entry gateway by country ISO
|
||||
--entry-gateway-low-latency
|
||||
Auto-select entry gateway by latency
|
||||
--exit-router-address <EXIT_ROUTER_ADDRESS>
|
||||
Mixnet recipient address
|
||||
--exit-gateway-id <EXIT_GATEWAY_ID>
|
||||
Mixnet public ID of the exit gateway
|
||||
--exit-gateway-country <EXIT_GATEWAY_COUNTRY>
|
||||
Auto-select exit gateway by country ISO
|
||||
--wireguard-mode
|
||||
Enable the wireguard mode
|
||||
--nym-ipv4 <NYM_IPV4>
|
||||
The IPv4 address of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--nym-ipv6 <NYM_IPV6>
|
||||
The IPv6 address of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--nym-mtu <NYM_MTU>
|
||||
The MTU of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--dns <DNS>
|
||||
The DNS server to use
|
||||
--disable-routing
|
||||
Disable routing all traffic through the nym TUN device. When the flag is set, the nym TUN device will be created, but to route traffic through it you will need to do it manually, e.g. ping -Itun0
|
||||
--enable-two-hop
|
||||
Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to exit gateway
|
||||
--enable-poisson-rate
|
||||
Enable Poisson process rate limiting of outbound traffic
|
||||
--disable-background-cover-traffic
|
||||
Disable constant rate background loop cover traffic
|
||||
--enable-credentials-mode
|
||||
Enable credentials mode
|
||||
--min-mixnode-performance <MIN_MIXNODE_PERFORMANCE>
|
||||
Set the minimum performance level for mixnodes
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
## Testnet environment
|
||||
|
||||
If you want to run NymVPN CLI in Nym Sandbox environment, there are a few adjustments to be done:
|
||||
|
||||
1. Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries:
|
||||
```sh
|
||||
curl -o sandbox.env -L https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env
|
||||
```
|
||||
|
||||
2. Check available Gateways at [Sandbox API](https://sandbox-nym-api1.nymtech.net/api/v1/gateways) or [Sandbox Swagger page](https://sandbox-nym-api1.nymtech.net/api/swagger/index.html)
|
||||
|
||||
3. Run with a flag `-c`
|
||||
```sh
|
||||
sudo ./nym-vpn-cli -c <PATH_TO>/sandbox.env <--exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY>>
|
||||
```
|
||||
@@ -1,71 +0,0 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
**NymVPN [*Support & FAQ page*](https://nymvpn.com/en/support)** contains all essential FAQs regarding the client. This page (below) is a source of additional information often seeked by users, operators and developers testing NymVPN.
|
||||
|
||||
If you interested to read more about Nym platform, you can have a look at [Nym general FAQ](https://nymtech.net/developers/faq/general-faq.html) and read through Nym's technical [documentation](https://nymtech.net/docs), [Developer Portal](https://nymtech.net/developers) and [Operators Guide](https://nymtech.net/operators).
|
||||
|
||||
## NymVPN
|
||||
|
||||
If this your first time hearing about NymVPN, make sure you visit [NymVPN webpage](https://nymvpn.com/en), the official NymVPN [support & FAQ page](https://nymvpn.com/en/support) and the proceed to the introduction and guide on how to [install, run and test](intro.md#nymvpn-guides) the client.
|
||||
|
||||
Below are some extra FAQs which came out during the previous alpha testing rounds.
|
||||
|
||||
### What's the difference between 2-hops and 5-hops
|
||||
|
||||
The default is 5-hops (including Entry and Exit Gateways), which means that the traffic goes from the local client to Entry Gateway -> through 3 layers of Mix Nodes -> to Exit Gateway -> internet. this option uses all the Nym Mixnet features for maximum privacy.
|
||||
|
||||
```
|
||||
┌─►mix──┐ mix mix
|
||||
│ │
|
||||
Entry │ │ Exit
|
||||
client ───► Gateway ──┘ mix │ mix ┌─►mix ───► Gateway ───► internet
|
||||
│ │
|
||||
│ │
|
||||
mix └─►mix──┘ mix
|
||||
```
|
||||
|
||||
The 2-hop option is going from the local client -> Entry Gateway -> directly to Exit Gateway -> internet. This option is good for operations demanding faster connection. Keep in mind that this setup by-passes the 3 layers of Mix Nodes. The anonymising features done by your local client like breaking data into same-size packets with inserting additional "dummy" ones to break the time and volume patterns is done in both options.
|
||||
|
||||
```
|
||||
Entry Exit
|
||||
client ───► Gateway ────► Gateway ───► internet
|
||||
```
|
||||
|
||||
We highly recommend to read more about [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html).
|
||||
|
||||
### Why do I see different sizes of packets in my terminal log?
|
||||
|
||||
One of features of Nym Mixnet's clients is to break data into the same size packets called Sphinx, which is currently ~2kb. When running NymVPN, the data log shows payload sizes, which are the raw sizes of the IP packets, not Sphinx. The payload sizes will be capped by the configured MTU, which is set around 1500 bytes.
|
||||
|
||||
### What is 'poisson filter' about?
|
||||
|
||||
By default `--enable-poisson` is disabled and packets are sent from the local client to the Entry Gateway as quickly as possible. With the poisson process enabled the Nym client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
|
||||
|
||||
Enabling the poisson filter is one of the key mechanisms to de-correlate input and output traffic to the Mixnet. The performance impact however is dramatic:
|
||||
1 packer per 20ms is 50 packets / sec so ballpark 100kb/s.
|
||||
For mobile clients that means constantly sending data eating up data allowance.
|
||||
|
||||
|
||||
## Nym Mixnet Architecture and Rewards
|
||||
|
||||
We have a list of questions related to Nym Nodes and the incentives behind running them under [FAQ pages](https://nymtech.net/operators/faq/mixnodes-faq.html) in our [Operators Guide](https://nymtech.net/operators). For better knowledge about Nym architecture we recommend to read [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html) in our [technical documentation](https://nymtech.net/docs).
|
||||
|
||||
## Project Smoosh
|
||||
|
||||
Project Smoosh is a code name for a process in which different components of Nym Mixnet architecture get *smooshed* into one binary. Check out [Smoosh FAQ](https://nymtech.net/operators/faq/smoosh-faq.html) in Operators Guide to read more.
|
||||
|
||||
## Exit Gateway
|
||||
|
||||
Part of the the transition under code name [Project Smoosh](#project-smoosh) is a creation of [Nym Exit Gateway](https://nymtech.net/operators/legal/exit-gateway.html) functionality. The operators running Gateways would have to “open” their nodes to a wider range of online services, in a similar fashion to Tor exit relays. The main change will be to expand the original short [allowed.list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) to a more permissive setup. An [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) will constrain the hosts that the users of the Nym VPN and Mixnet can connect to. This will be done in an effort to protect the operators, as Gateways will act both as SOCKS5 Network Requesters, and exit nodes for IP traffic from Nym VPN and Mixnet clients.
|
||||
|
||||
* Read more how the exit policy gets implemented [here](https://nymtech.net/operators/faq/smoosh-faq.html#how-will-the-exit-policy-be-implemented)
|
||||
* Check out [Nym Operators Legal Forum](https://nymtech.net/operators/legal/exit-gateway.html)
|
||||
* Do reach out to us with any experiences you may have running Tor Exit relays or legal findings and suggestions for Nym Exit Gateway operators
|
||||
|
||||
## Nym Integrations and SDKs
|
||||
|
||||
If you are a dev who is interested to integrate Nym, have a look on our SDK tutorials:
|
||||
|
||||
* [Rust SDKs](https://nymtech.net/developers/tutorials/cosmos-service/intro.html)
|
||||
* [TypeScript SDKs](https://sdk.nymtech.net/)
|
||||
* [Integration FAQ](https://nymtech.net/developers/faq/integrations-faq.html)
|
||||
@@ -1,85 +0,0 @@
|
||||
# NymVPN alpha - Desktop: Guide for GNU/Linux
|
||||
|
||||
<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/908221306?h=404b2bbdc8" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
|
||||
|
||||
```admonish info
|
||||
NymVPN is an experimental software and it's for testing purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the workshop) so we use their results to improve the client, and submit the form [*NymVPN User research*]({{nym_vpn_form_url}}) with the testing results.
|
||||
```
|
||||
|
||||
## Preparation
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Open Github [releases page](https://github.com/nymtech/nym-vpn-client) and download the binary for Debian based Linux
|
||||
|
||||
2. (Optional: if you don't want to check shasum, skip this point) Verify sha hash of your downloaded binary with the one listed on the [releases page](https://github.com/nymtech/nym-vpn-client). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "a5f91f20d587975e30b6a75d3a9e195234cf1269eac278139a5b9c39b039e807 nym-vpn-desktop_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_ubuntu-22.04_x86_64.tar.gz" | shasum -a 256 -c
|
||||
```
|
||||
|
||||
3. Extract files:
|
||||
```sh
|
||||
tar -xvf <BINARY>.tar.gz
|
||||
# for example
|
||||
# tar -xvf nym-vpn-desktop_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_ubuntu-22.04_x86_64.tar.gz
|
||||
```
|
||||
|
||||
4. If you prefer to run `.AppImage` make executable by running:
|
||||
```sh
|
||||
# make sure you cd into the right sub-directory after extraction
|
||||
chmod u+x ./nym-vpn_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_amd64.AppImage
|
||||
```
|
||||
|
||||
5. If you prefer to use the `.deb` version for installation (works on Debian based Linux only), open terminal in the same directory and run:
|
||||
```sh
|
||||
# make sure you cd into the right sub-directory after extraction
|
||||
sudo dpkg -i ./nym-vpn_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_amd64.deb
|
||||
# or
|
||||
sudo apt-get install -f ./nym-vpn_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_amd64.deb
|
||||
```
|
||||
|
||||
<!--
|
||||
NymVPN alpha version runs over Nym testnet (called sandbox), a little extra configuration is needed for the application to work.
|
||||
|
||||
### Configuration
|
||||
|
||||
To test NymVPN alpha we must create two configuration files: an environment config file `sandbox.env` and `config.toml` file pointing the application to run over the testnet environment.
|
||||
|
||||
6. Create a NymVPN config directory called `nym-vpn` in your `~/.config`, either manually or by a command:
|
||||
```sh
|
||||
mkdir $HOME/.config/nym-vpn/
|
||||
```
|
||||
7. Create the network testnet config: copy-paste [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) and save as `sandbox.env` in the directory `~/.config/nym-vpn/` you just created. Aternatively do it by runnin a command
|
||||
```sh
|
||||
curl -o $HOME/.config/nym-vpn/sandbox.env -L https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env
|
||||
```
|
||||
|
||||
8. Create NymVPN main config file: copy-paste the line below and save it as `config.toml` in the same directory `~/.config/nym-vpn/`:
|
||||
```toml
|
||||
# change <USER> to your username
|
||||
env_config_file = "/home/<USER>/.config/nym-vpn/sandbox.env"
|
||||
```
|
||||
-->
|
||||
|
||||
## Run NymVPN
|
||||
|
||||
**For NymVPN to work, all other VPNs must be switched off!** At this alpha stage of NymVPN, the network connection (wifi) must be reconnected after or in between the testing rounds.
|
||||
|
||||
In case you used `.deb` package and installed the client, you may be able to have a NymVPN application icon in your app menu. However this may not work as the application needs root permission.
|
||||
|
||||
Open terminal and run:
|
||||
|
||||
```sh
|
||||
# .AppImage must be run from the same directory as the binary
|
||||
sudo -E ./nym-vpn_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_amd64.AppImage
|
||||
|
||||
# .deb installation shall be executable from anywhere as
|
||||
sudo -E nym-vpn
|
||||
```
|
||||
|
||||
In case of errors, see [troubleshooting section](troubleshooting.md).
|
||||
@@ -1,65 +0,0 @@
|
||||
# NymVPN alpha - Desktop: Guide for Mac OS
|
||||
|
||||
```admonish info
|
||||
NymVPN is an experimental software and it's for testing purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the workshop) so we use their results to improve the client, and submit the form [*NymVPN User research*]({{nym_vpn_form_url}}) with the testing results.
|
||||
```
|
||||
|
||||
## Preparation
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Open Github [releases page](https://github.com/nymtech/nym-vpn-client) and download the binary for your version of MacOS
|
||||
|
||||
2. Recommended (skip this point if you don't want to verify): Verify sha hash of your downloaded binary with the one listed on the [releases page](https://github.com/nymtech/nym-vpn-client). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "da4c0bf8e8b52658312d341fa3581954cfcb6efd516d9a448c76d042a454b5df nym-vpn-desktop_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_macos_x86_64.zip" | shasum -a 256 -c
|
||||
```
|
||||
|
||||
3. Extract the downloaded file manually or by a command:
|
||||
```sh
|
||||
tar -xvf <BINARY>.tar.gz
|
||||
# for example
|
||||
# tar -xvf nym-vpn-desktop_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_macos_aarch64.tar.gz
|
||||
```
|
||||
4. Mount the `.dmg` image you extracted by double clicking on it and move it (drag it) to your `/Application` folder
|
||||
|
||||
<!--
|
||||
NymVPN alpha version runs over Nym testnet (called sandbox), a little extra configuration is needed for the application to work.
|
||||
|
||||
### Configuration
|
||||
|
||||
To test NymVPN alpha we must create two configuration files: an environment config file `sandbox.env` and `config.toml` file pointing the application to run over the testnet environment.
|
||||
|
||||
5. Create testnet configuration file: Open a text editor, copy-paste [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) and save it as `sandbox.env` in `/Applications/nym-vpn.app/Contents/MacOS/`. Alternatively use this command:
|
||||
```sh
|
||||
curl -L "https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env" -o "/Applications/nym-vpn.app/Contents/MacOS/sandbox.env"
|
||||
```
|
||||
|
||||
6. Create application configuration file: Open a text editor, copy-paste the line below and save as `config.toml` in the same directory `/Applications/nym-vpn.app/Contents/MacOS/`
|
||||
```toml
|
||||
env_config_file = "sandbox.env"
|
||||
```
|
||||
Alternatively do it by using this command:
|
||||
```sh
|
||||
echo "env_config_file = sandbox.env" > /Applications/nym-vpn.app/Contents/MacOS/config.toml
|
||||
```
|
||||
-->
|
||||
|
||||
## Run NymVPN
|
||||
|
||||
**For NymVPN to work, all other VPNs must be switched off!** At this alpha stage of NymVPN, the network connection (wifi) must be reconnected after or in between the testing rounds.
|
||||
|
||||
Run:
|
||||
```sh
|
||||
sudo /Applications/nym-vpn.app/Contents/MacOS/nym-vpn
|
||||
|
||||
# If it didn't start try to run with -E flag
|
||||
sudo -E /Applications/nym-vpn.app/Contents/MacOS/nym-vpn
|
||||
```
|
||||
|
||||
In case of errors check out the [troubleshooting](troubleshooting.md#running-gui-failed-due-to-toml-parse-error) section.
|
||||
@@ -1,50 +0,0 @@
|
||||
# NymVPN - Desktop (GUI)
|
||||
|
||||
```admonish info
|
||||
Our alpha testing round is done with participants at live workshop events and the application in this stage may not work for everyone.
|
||||
|
||||
**If you commit to test NymVPN alpha, please start with the [user research form]({{nym_vpn_form_url}}) where all the steps will be provided**. If you disagree with any of the conditions listed, please leave this page.
|
||||
```
|
||||
|
||||
This is a desktop (GUI) version of NymVPN client. A demo of how the application will look like for majority of day-to-day users.
|
||||
|
||||
Follow the simple [automated script](#automated-script-for-gui-installation) below to install and run NymVPN GUI. If the script didn't work for your distribution or you prefer to do a manual setup follow the steps in the guide for [Linux](gui-linux.md) or [MacOS](gui-mac.md) .
|
||||
|
||||
Visit NymVPN alpha latest [release page](https://github.com/nymtech/nym-vpn-client) to check sha sums or download the binaries directly.
|
||||
|
||||
## Linux AppImage Automated Installation Method
|
||||
|
||||
The latest releases contain `appimage.sh` script. This method makes the installation simple for Linux users who want to run NymVPN from AppImmage. Executing the command below will download the binary to `~/.local/bin` and verify the checksum:
|
||||
```sh
|
||||
curl -fsSL https://github.com/nymtech/nym-vpn-client/releases/download/nym-vpn-desktop-v<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->/appimage.sh | bash
|
||||
```
|
||||
|
||||
Run with the command:
|
||||
```sh
|
||||
sudo -E ~/.local/bin/nym-vpn.appimage
|
||||
```
|
||||
|
||||
## Automated Script for GUI Installation (Linux and Mac)
|
||||
|
||||
We wrote a [script](https://gist.github.com/tommyv1987/7d210d4daa8f7abc61f9a696d0321f19) which does download of dependencies and the application, sha256 verification, extraction, installation and configuration for Linux and MacOS users automatically. Turn off all VPNs and follow the steps below.
|
||||
|
||||
1. Open a terminal window in a directory where you want the script to be downloaded and run
|
||||
```sh
|
||||
curl -o nym-vpn-desktop-install-run.sh -L https://gist.githubusercontent.com/tommyv1987/7d210d4daa8f7abc61f9a696d0321f19/raw/939ac8d0afed69f43739b9cf2e5728454ea2c437/nym-vpn-client-install-run.sh && chmod u+x nym-vpn-desktop-install-run.sh && sudo -E ./nym-vpn-desktop-install-run.sh
|
||||
```
|
||||
|
||||
2. Follow the prompts in the program
|
||||
|
||||
To start the application again, reconnect your wifi and run
|
||||
```sh
|
||||
# Linux .AppImage
|
||||
sudo -E ~/nym-vpn-latest/nym-vpn-desktop_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_ubuntu-22.04_x86_64/nym-vpn_<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->_amd64.AppImage
|
||||
|
||||
# Linux .deb
|
||||
sudo -E nym-vpn
|
||||
|
||||
# MacOS
|
||||
sudo -E $HOME/nym-vpn-latest/nym-vpn
|
||||
```
|
||||
|
||||
In case of errors check out the [troubleshooting](troubleshooting.md#running-gui-failed-due-to-toml-parse-error) section.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user