Compare commits

...

38 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacşu 9fdf5273e3 Force update to be run 2025-01-16 17:24:52 +00:00
Bogdan-Ștefan Neacşu 8b1490cb99 Bind to [::] on nym-node for both IP versions 2025-01-16 16:16:38 +00:00
benedetta davico 5ab164d229 Update Cargo.toml 2025-01-16 12:51:53 +01:00
Jędrzej Stuczyński 26538c5884 bugfix: only consider pre-existing peers for wg bytes metric (#5357) 2025-01-16 11:50:26 +00:00
benedettadavico c202e2d598 adding changelog for reeses 2025-01-15 10:27:39 +01:00
huximaxi a7874add88 Merge pull request #5346 from nymtech/feture/legacy_alert
Feture/legacy alert
2025-01-14 15:00:49 +01:00
RadekSabacky 3d84be22e2 + add releaseAlert component 2025-01-14 13:41:30 +01:00
RadekSabacky 25766dc0ec + add alert message into nav components 2025-01-14 13:22:31 +01:00
Jędrzej Stuczyński 676e93a372 bugfix: make sure refresh data key matches bond info (#5329) 2025-01-10 14:52:52 +00:00
Jędrzej Stuczyński 5a6770e5e2 chore: readjusted --mode behaviour to fix the regression (#5331) 2025-01-10 13:17:03 +00:00
Jędrzej Stuczyński 529e8d49ee chore: apply 1.84 linter suggestions (#5330)
* chore: apply 1.84 linter suggestions

* updated wasm dependencies to fix the macro issue

* second batch of clippy fixes
2025-01-10 13:00:18 +00:00
benedettadavico a94c035c0a correct the nym-node bumped version 2025-01-09 12:36:05 +01:00
Jędrzej Stuczyński 24480418f0 Bugfix/contract version assignment (#5318)
* fixed contract version being overwritten

* introduced migration to fix existing [mainnet] state

* updated contract schema

* updated testnet manager migrate msg code
2025-01-09 10:00:37 +00:00
Jędrzej Stuczyński a46245ffe3 feat: warn users if node is run in exit mode only (#5320)
* added 'full-gateway' nymnode mode to enable both entry and exit at the same time

* warning for running node in exit mode only
2025-01-09 09:02:52 +00:00
Jędrzej Stuczyński 7c1c13e139 reduce log severity for number of packets being delayed (#5321) 2025-01-09 09:02:37 +00:00
Jędrzej Stuczyński 836a93cd96 fixed client session histogram buckets (#5316) 2025-01-08 10:26:40 +00:00
benedettadavico b47a742dd0 update nym-node binary version 2025-01-08 10:37:48 +01:00
benedetta davico 6e14882246 Merge pull request #5315 from nymtech/release/2024.14-crunch-patched
Merge crunch patched to reeses
2025-01-08 10:35:54 +01:00
Tommy Verrall a7466a0e02 Merge pull request #5313 from nymtech/bugfix/append-gb-cap
amend 250gb limit
2025-01-08 09:50:04 +01:00
Tommy Verrall 78f45012db amend 250gb limit 2025-01-08 09:44:14 +01:00
benedettadavico f6a2f62ea9 bump versions of binaries 2025-01-08 09:28:48 +01:00
Jędrzej Stuczyński 3efeededc5 feature: expand nym-node prometheus metrics (#5298)
* fixed bearer auth for prometheus route

* basic prometheus metrics

* added rates on global values

* improved structure on the prometheus metrics

* added additional metrics for ingress websockets and egress mixnet connections

* some channel business metrics

* fixed metrics registration and added additional variants

* added counter for number of disk persisted packets

* counter for pending egress packets

* counter for pending egress forward packets

* clippy
2025-01-07 13:34:18 +00:00
Jędrzej Stuczyński c482350ec6 feature: wireguard metrics (#5278)
* experimental log

* introduce wireguard metrics updates

* add wireguard traffic rates to console logger

* missing import

* changed order of displayed values

* expose bytes information via rest endpoint

* clippy
2025-01-07 13:32:07 +00:00
Bogdan-Ștefan Neacşu f7a7a8072f Move tun constants to network defaults (#5286) (#5287) 2024-12-18 16:23:18 +02:00
Jon Häggblad acd068e5ab Add close to credential storage (#5283)
* Add close method to credential storage

* wip
2024-12-18 12:37:16 +01:00
benedetta davico 8d5a41a790 Merge pull request #5277 from nymtech/feature/modify_changelog
Modify CHANGELOG
2024-12-18 11:07:49 +01:00
Bogdan-Ștefan Neacşu caa17d933c Add windows to CI builds (#5269)
* Add windows to CI builds

* Fix win build for node status api

* Fix win build for sdk

* Fix win build for cred proxy
2024-12-17 22:26:38 +01:00
Bogdan-Ștefan Neacşu 039b05cf7e Modify CHANGELOG 2024-12-17 18:59:49 +02:00
benedetta davico 37b10b59aa update changelog for nym-node v1.2.1 2024-12-17 17:54:18 +01:00
benedetta davico a9ede22bbd update nym-node version 2024-12-17 17:41:12 +01:00
Bogdan-Ștefan Neacşu b656003306 Expect that previously regitrated clients don't have v6 addr 2024-12-17 16:59:01 +02:00
dynco-nym b4f51baf94 Change sqlite journal mode to WAL (#5213)
* Change sqlite journal mode to WAL

* Synchronous mode & auto vacuum

* Bump probe git ref to 1.1.0
2024-12-16 16:40:02 +01:00
Drazen Urch a3f3d83c1b Shipping raw metrics to PG (#5216)
* Shipping raw metrics to PG

* Put cancel token back in its place

* fmt
2024-12-16 16:19:37 +01:00
Drazen Urch 84d7004cb2 Add control messages to GatewayTransciver (#5247)
* Add control messages to GatewayTransciver

* Add forget me flag to clients

* CI gate IPIINFO test

* Handle ForgetMe for client and stats db

* fmt
2024-12-16 15:18:04 +01:00
import this be063a36eb syntax hotfix (#5266) 2024-12-16 13:17:38 +00:00
windy-ux 0a712b9fce Fix/web 615 seo setup (#5265)
* + add header into Packet Mixing docs

* + add head changes for testing

* / updated version of metatags in theme.config

* + add env file

* / theme.config to use NEXT_PUBLIC_SITE_URL from env file

* @ Fix broken link in theme.config

* - remove favicon code

* + add desription for intro pages

* + add default book's desriptions

* Revert "+ add desription for intro pages"

This reverts commit 98c78242d4.
2024-12-16 13:17:25 +00:00
Bogdan-Ștefan Neacşu 88d6fb4e22 Add fd callback to client core (#5230)
* Add fd callback to client core

* Include in sdk

* Fix clippy many args

* Method in builder

* Replace Box with Arc
2024-12-16 13:57:34 +02:00
Jon Häggblad 04c2045d94 Add PATCH support to nym-http-api-client (#5260) 2024-12-16 12:28:44 +01:00
151 changed files with 6008 additions and 976 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ arc-ubuntu-20.04, custom-runner-mac-m1 ] os: [ arc-ubuntu-20.04, custom-windows-11, custom-runner-mac-m1 ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
+3 -1
View File
@@ -51,4 +51,6 @@ ppa-private-key.b64
ppa-private-key.asc ppa-private-key.asc
nym-network-monitor/topology.json nym-network-monitor/topology.json
nym-network-monitor/__pycache__ nym-network-monitor/__pycache__
nym-network-monitor/*.key nym-network-monitor/*.key
nym-network-monitor/.envrc
nym-network-monitor/.envrc
+85
View File
@@ -4,6 +4,91 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased] ## [Unreleased]
## [2025.1-reeses] (2025-01-15)
- Feture/legacy alert ([#5346])
- chore: readjusted --mode behaviour to fix the regression ([#5331])
- chore: apply 1.84 linter suggestions ([#5330])
- bugfix: make sure refresh data key matches bond info ([#5329])
- reduce log severity for number of packets being delayed ([#5321])
- feat: warn users if node is run in exit mode only ([#5320])
- Bugfix/contract version assignment ([#5318])
- fixed client session histogram buckets ([#5316])
- amend 250gb limit ([#5313])
- feature: expand nym-node prometheus metrics ([#5298])
- Cherry picked #5286 ([#5287])
- Add close to credential storage ([#5283])
- feature: wireguard metrics ([#5278])
- Add PATCH support to nym-http-api-client ([#5260])
- chore: removed legacy socks5 listener ([#5259])
- bugfix: make sure to apply gateway score filtering when choosing initial node ([#5256])
- Update TS bindings ([#5255])
- Add conversion unit tests for auth msg ([#5251])
- Add control messages to GatewayTransciver ([#5247])
- Remove unneeded async function annotation ([#5246])
- bugfix: make sure to update timestamp of last batch verification to prevent double redemption ([#5239])
- Add FromStr impl for UserAgent ([#5236])
- Extend swagger docs ([#5235])
- TicketType derive Hash and Eq ([#5233])
- Add fd callback to client core ([#5230])
- Extend raw ws fd for gateway client ([#5218])
- Shipping raw metrics to PG ([#5216])
- Change sqlite journal mode to WAL ([#5213])
- Derive serialize for UserAgent ([#5210])
- Restore Location fields ([#5208])
- better date serialization ([#5207])
- Fix overflow ([#5204])
- feature: hopefully final steps of the smoosh™️ ([#5201])
- Fix overflow ([#5184])
- NS API - Gateway stats scraping ([#5180])
- introduced initial internal commands for nym-cli: ecash key and request generation ([#5174])
- Move NS client to separate package under NS API ([#5171])
- build(deps): bump micromatch from 4.0.4 to 4.0.8 in /testnet-faucet ([#4813])
[#5346]: https://github.com/nymtech/nym/pull/5346
[#5331]: https://github.com/nymtech/nym/pull/5331
[#5330]: https://github.com/nymtech/nym/pull/5330
[#5329]: https://github.com/nymtech/nym/pull/5329
[#5321]: https://github.com/nymtech/nym/pull/5321
[#5320]: https://github.com/nymtech/nym/pull/5320
[#5318]: https://github.com/nymtech/nym/pull/5318
[#5316]: https://github.com/nymtech/nym/pull/5316
[#5313]: https://github.com/nymtech/nym/pull/5313
[#5298]: https://github.com/nymtech/nym/pull/5298
[#5287]: https://github.com/nymtech/nym/pull/5287
[#5283]: https://github.com/nymtech/nym/pull/5283
[#5278]: https://github.com/nymtech/nym/pull/5278
[#5260]: https://github.com/nymtech/nym/pull/5260
[#5259]: https://github.com/nymtech/nym/pull/5259
[#5256]: https://github.com/nymtech/nym/pull/5256
[#5255]: https://github.com/nymtech/nym/pull/5255
[#5251]: https://github.com/nymtech/nym/pull/5251
[#5247]: https://github.com/nymtech/nym/pull/5247
[#5246]: https://github.com/nymtech/nym/pull/5246
[#5239]: https://github.com/nymtech/nym/pull/5239
[#5236]: https://github.com/nymtech/nym/pull/5236
[#5235]: https://github.com/nymtech/nym/pull/5235
[#5233]: https://github.com/nymtech/nym/pull/5233
[#5230]: https://github.com/nymtech/nym/pull/5230
[#5218]: https://github.com/nymtech/nym/pull/5218
[#5216]: https://github.com/nymtech/nym/pull/5216
[#5213]: https://github.com/nymtech/nym/pull/5213
[#5210]: https://github.com/nymtech/nym/pull/5210
[#5208]: https://github.com/nymtech/nym/pull/5208
[#5207]: https://github.com/nymtech/nym/pull/5207
[#5204]: https://github.com/nymtech/nym/pull/5204
[#5201]: https://github.com/nymtech/nym/pull/5201
[#5184]: https://github.com/nymtech/nym/pull/5184
[#5180]: https://github.com/nymtech/nym/pull/5180
[#5174]: https://github.com/nymtech/nym/pull/5174
[#5171]: https://github.com/nymtech/nym/pull/5171
[#4813]: https://github.com/nymtech/nym/pull/4813
## [2024.14-crunch-patched] (2024-12-17)
- Fixes an issue to allow previously registred clients to connect to latest nym-nodes
- Fixes compatibility issues between nym-nodes and older clients
## [2024.14-crunch] (2024-12-11) ## [2024.14-crunch] (2024-12-11)
- Merge/release/2024.14-crunch ([#5242]) - Merge/release/2024.14-crunch ([#5242])
Generated
+122 -35
View File
@@ -2416,7 +2416,7 @@ dependencies = [
[[package]] [[package]]
name = "explorer-api" name = "explorer-api"
version = "1.1.43" version = "1.1.44"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap 4.5.20", "clap 4.5.20",
@@ -2476,10 +2476,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "fancy_constructor" name = "fallible-iterator"
version = "1.2.2" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71f317e4af73b2f8f608fac190c52eac4b1879d2145df1db2fe48881ca69435" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fancy_constructor"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07b19d0e43eae2bfbafe4931b5e79c73fb1a849ca15cd41a761a7b8587f9a1a2"
dependencies = [ dependencies = [
"macroific", "macroific",
"proc-macro2", "proc-macro2",
@@ -2840,15 +2846,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "gloo-net" name = "gloo-net"
version = "0.5.0" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173" checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"gloo-utils 0.2.0", "gloo-utils 0.2.0",
"http 0.2.12", "http 1.1.0",
"js-sys", "js-sys",
"pin-project", "pin-project",
"serde", "serde",
@@ -3426,7 +3432,8 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]] [[package]]
name = "indexed_db_futures" name = "indexed_db_futures"
version = "0.4.2" version = "0.4.2"
source = "git+https://github.com/TiemenSch/rust-indexed-db?branch=update-uuid#9745d015707008b0c410115d787014a6d1af2efb" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0704b71f13f81b5933d791abf2de26b33c40935143985220299a357721166706"
dependencies = [ dependencies = [
"accessory", "accessory",
"cfg-if", "cfg-if",
@@ -3664,10 +3671,11 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.72" version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
dependencies = [ dependencies = [
"once_cell",
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -4387,7 +4395,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "nym-api" name = "nym-api"
version = "1.1.47" version = "1.1.48"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -4636,7 +4644,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-cli" name = "nym-cli"
version = "1.1.45" version = "1.1.46"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.22.1", "base64 0.22.1",
@@ -4719,7 +4727,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-client" name = "nym-client"
version = "1.1.45" version = "1.1.46"
dependencies = [ dependencies = [
"bs58", "bs58",
"clap 4.5.20", "clap 4.5.20",
@@ -5673,18 +5681,20 @@ version = "0.1.0"
dependencies = [ dependencies = [
"dashmap", "dashmap",
"lazy_static", "lazy_static",
"log",
"prometheus", "prometheus",
"tracing",
] ]
[[package]] [[package]]
name = "nym-mixnet-client" name = "nym-mixnet-client"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dashmap",
"futures", "futures",
"nym-sphinx", "nym-sphinx",
"nym-task", "nym-task",
"tokio", "tokio",
"tokio-stream",
"tokio-util", "tokio-util",
"tracing", "tracing",
] ]
@@ -5782,6 +5792,7 @@ dependencies = [
"nym-bin-common", "nym-bin-common",
"nym-client-core", "nym-client-core",
"nym-crypto", "nym-crypto",
"nym-gateway-requests",
"nym-network-defaults", "nym-network-defaults",
"nym-sdk", "nym-sdk",
"nym-sphinx", "nym-sphinx",
@@ -5795,6 +5806,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
"tokio-postgres",
"tokio-util", "tokio-util",
"utoipa", "utoipa",
"utoipa-swagger-ui", "utoipa-swagger-ui",
@@ -5802,7 +5814,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-requester" name = "nym-network-requester"
version = "1.1.46" version = "1.1.47"
dependencies = [ dependencies = [
"addr", "addr",
"anyhow", "anyhow",
@@ -5853,7 +5865,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-node" name = "nym-node"
version = "1.2.0" version = "1.3.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -6225,7 +6237,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.45" version = "1.1.46"
dependencies = [ dependencies = [
"bs58", "bs58",
"clap 4.5.20", "clap 4.5.20",
@@ -6799,6 +6811,7 @@ dependencies = [
"nym-crypto", "nym-crypto",
"nym-gateway-storage", "nym-gateway-storage",
"nym-network-defaults", "nym-network-defaults",
"nym-node-metrics",
"nym-task", "nym-task",
"nym-wireguard-types", "nym-wireguard-types",
"thiserror", "thiserror",
@@ -6826,7 +6839,7 @@ dependencies = [
[[package]] [[package]]
name = "nymvisor" name = "nymvisor"
version = "0.1.10" version = "0.1.11"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -7250,6 +7263,24 @@ dependencies = [
"indexmap 2.2.6", "indexmap 2.2.6",
] ]
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher 0.3.11",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.6" version = "1.1.6"
@@ -7388,6 +7419,35 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
[[package]]
name = "postgres-protocol"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acda0ebdebc28befa84bee35e651e4c5f09073d668c7aed4cf7e23c3cda84b23"
dependencies = [
"base64 0.22.1",
"byteorder",
"bytes",
"fallible-iterator",
"hmac",
"md-5",
"memchr",
"rand",
"sha2 0.10.8",
"stringprep",
]
[[package]]
name = "postgres-types"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f"
dependencies = [
"bytes",
"fallible-iterator",
"postgres-protocol",
]
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@@ -9722,6 +9782,32 @@ dependencies = [
"syn 2.0.90", "syn 2.0.90",
] ]
[[package]]
name = "tokio-postgres"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b5d3742945bc7d7f210693b0c58ae542c6fd47b17adbbda0885f3dcb34a6bdb"
dependencies = [
"async-trait",
"byteorder",
"bytes",
"fallible-iterator",
"futures-channel",
"futures-util",
"log",
"parking_lot",
"percent-encoding",
"phf",
"pin-project-lite",
"postgres-protocol",
"postgres-types",
"rand",
"socket2",
"tokio",
"tokio-util",
"whoami",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.24.1" version = "0.24.1"
@@ -10678,9 +10764,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.95" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@@ -10689,13 +10775,12 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.95" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
"once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.90",
@@ -10704,21 +10789,22 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.45" version = "0.4.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
"once_cell",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
] ]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.95" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -10726,9 +10812,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.95" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -10739,9 +10825,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.95" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]] [[package]]
name = "wasm-bindgen-test" name = "wasm-bindgen-test"
@@ -10849,9 +10935,9 @@ dependencies = [
[[package]] [[package]]
name = "wasmtimer" name = "wasmtimer"
version = "0.2.0" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23"
dependencies = [ dependencies = [
"futures", "futures",
"js-sys", "js-sys",
@@ -10863,9 +10949,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.72" version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -10912,6 +10998,7 @@ checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d"
dependencies = [ dependencies = [
"redox_syscall 0.5.1", "redox_syscall 0.5.1",
"wasite", "wasite",
"web-sys",
] ]
[[package]] [[package]]
+8 -10
View File
@@ -394,19 +394,17 @@ prost = { version = "0.12", default-features = false }
# wasm-related dependencies # wasm-related dependencies
gloo-utils = "0.2.0" gloo-utils = "0.2.0"
gloo-net = "0.5.0" gloo-net = "0.6.0"
# use a separate branch due to feature unification failures # TODO: migrate to 0.6+
# this is blocked until the upstream removes outdates `wasm_bindgen` feature usage indexed_db_futures = "0.4.2"
# indexed_db_futures = "0.4.1" js-sys = "0.3.76"
indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", branch = "update-uuid" }
js-sys = "0.3.70"
serde-wasm-bindgen = "0.6.5" serde-wasm-bindgen = "0.6.5"
tsify = "0.4.5" tsify = "0.4.5"
wasm-bindgen = "0.2.95" wasm-bindgen = "0.2.99"
wasm-bindgen-futures = "0.4.45" wasm-bindgen-futures = "0.4.49"
wasmtimer = "0.2.0" wasmtimer = "0.4.1"
web-sys = "0.3.72" web-sys = "0.3.76"
# Profile settings for individual crates # Profile settings for individual crates
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-client" name = "nym-client"
version = "1.1.45" version = "1.1.46"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client" description = "Implementation of the Nym Client"
edition = "2021" edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.1.45" version = "1.1.46"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021" edition = "2021"
@@ -28,7 +28,7 @@ pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64; pub type Nonce = u64;
pub type Taken = Option<SystemTime>; 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)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IpPair { pub struct IpPair {
@@ -8,7 +8,10 @@ use crate::{
}, },
}; };
use log::{debug, error}; use log::{debug, error};
use sqlx::ConnectOptions; use sqlx::{
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
ConnectOptions,
};
use std::path::Path; use std::path::Path;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -30,6 +33,9 @@ impl StorageManager {
} }
let opts = sqlx::sqlite::SqliteConnectOptions::new() let opts = sqlx::sqlite::SqliteConnectOptions::new()
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.filename(database_path) .filename(database_path)
.create_if_missing(true) .create_if_missing(true)
.disable_statement_logging(); .disable_statement_logging();
@@ -110,7 +116,7 @@ impl StorageManager {
) -> Result<(), sqlx::Error> { ) -> Result<(), sqlx::Error> {
sqlx::query!( sqlx::query!(
r#" r#"
INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type) INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type)
VALUES (?, ?, ?) VALUES (?, ?, ?)
"#, "#,
registered_gateway.gateway_id_bs58, registered_gateway.gateway_id_bs58,
@@ -224,7 +230,7 @@ impl StorageManager {
) -> Result<(), sqlx::Error> { ) -> Result<(), sqlx::Error> {
sqlx::query!( sqlx::query!(
r#" r#"
INSERT INTO custom_gateway_details(gateway_id_bs58, data) INSERT INTO custom_gateway_details(gateway_id_bs58, data)
VALUES (?, ?) VALUES (?, ?)
"#, "#,
custom.gateway_id_bs58, custom.gateway_id_bs58,
@@ -32,7 +32,7 @@ use crate::init::{
setup_gateway, setup_gateway,
types::{GatewaySetup, InitialisationResult}, types::{GatewaySetup, InitialisationResult},
}; };
use crate::{config, spawn_future}; use crate::{config, spawn_future, ForgetMe};
use futures::channel::mpsc; use futures::channel::mpsc;
use log::*; use log::*;
use nym_bandwidth_controller::BandwidthController; use nym_bandwidth_controller::BandwidthController;
@@ -188,6 +188,11 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
user_agent: Option<UserAgent>, user_agent: Option<UserAgent>,
setup_method: GatewaySetup, setup_method: GatewaySetup,
#[cfg(unix)]
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
forget_me: ForgetMe,
} }
impl<'a, C, S> BaseClientBuilder<'a, C, S> impl<'a, C, S> BaseClientBuilder<'a, C, S>
@@ -210,9 +215,18 @@ where
shutdown: None, shutdown: None,
user_agent: None, user_agent: None,
setup_method: GatewaySetup::MustLoad { gateway_id: None }, setup_method: GatewaySetup::MustLoad { gateway_id: None },
#[cfg(unix)]
connection_fd_callback: None,
forget_me: Default::default(),
} }
} }
#[must_use]
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
self.forget_me = forget_me.clone();
self
}
#[must_use] #[must_use]
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self { pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
self.setup_method = setup; self.setup_method = setup;
@@ -261,6 +275,15 @@ where
Ok(self) Ok(self)
} }
#[cfg(unix)]
pub fn with_connection_fd_callback(
mut self,
callback: Arc<dyn Fn(RawFd) + Send + Sync>,
) -> Self {
self.connection_fd_callback = Some(callback);
self
}
// note: do **NOT** make this method public as its only valid usage is from within `start_base` // note: do **NOT** make this method public as its only valid usage is from within `start_base`
// because it relies on the crypto keys being already loaded // because it relies on the crypto keys being already loaded
fn mix_address(details: &InitialisationResult) -> Recipient { fn mix_address(details: &InitialisationResult) -> Recipient {
@@ -352,6 +375,7 @@ where
controller.start_with_shutdown(shutdown) controller.start_with_shutdown(shutdown)
} }
#[allow(clippy::too_many_arguments)]
async fn start_gateway_client( async fn start_gateway_client(
config: &Config, config: &Config,
initialisation_result: InitialisationResult, initialisation_result: InitialisationResult,
@@ -359,6 +383,7 @@ where
details_store: &S::GatewaysDetailsStore, details_store: &S::GatewaysDetailsStore,
packet_router: PacketRouter, packet_router: PacketRouter,
stats_reporter: ClientStatsSender, stats_reporter: ClientStatsSender,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
shutdown: TaskClient, shutdown: TaskClient,
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError> ) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
where where
@@ -401,6 +426,8 @@ where
packet_router, packet_router,
bandwidth_controller, bandwidth_controller,
stats_reporter, stats_reporter,
#[cfg(unix)]
connection_fd_callback,
shutdown, shutdown,
) )
}; };
@@ -462,6 +489,7 @@ where
details_store: &S::GatewaysDetailsStore, details_store: &S::GatewaysDetailsStore,
packet_router: PacketRouter, packet_router: PacketRouter,
stats_reporter: ClientStatsSender, stats_reporter: ClientStatsSender,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
mut shutdown: TaskClient, mut shutdown: TaskClient,
) -> Result<Box<dyn GatewayTransceiver + Send>, ClientCoreError> ) -> Result<Box<dyn GatewayTransceiver + Send>, ClientCoreError>
where where
@@ -493,6 +521,8 @@ where
details_store, details_store,
packet_router, packet_router,
stats_reporter, stats_reporter,
#[cfg(unix)]
connection_fd_callback,
shutdown, shutdown,
) )
.await?; .await?;
@@ -615,9 +645,11 @@ where
fn start_mix_traffic_controller( fn start_mix_traffic_controller(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>, gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
shutdown: TaskClient, shutdown: TaskClient,
forget_me: ForgetMe,
) -> BatchMixMessageSender { ) -> BatchMixMessageSender {
info!("Starting mix traffic controller..."); info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_transceiver); let (mix_traffic_controller, mix_tx) =
MixTrafficController::new(gateway_transceiver, forget_me);
mix_traffic_controller.start_with_shutdown(shutdown); mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx mix_tx
} }
@@ -772,6 +804,8 @@ where
&details_store, &details_store,
gateway_packet_router, gateway_packet_router,
stats_reporter.clone(), stats_reporter.clone(),
#[cfg(unix)]
self.connection_fd_callback,
shutdown.fork("gateway_transceiver"), shutdown.fork("gateway_transceiver"),
) )
.await?; .await?;
@@ -797,9 +831,11 @@ where
// that are to be sent to the mixnet. They are used by cover traffic stream and real // that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream. // traffic stream.
// The MixTrafficController then sends the actual traffic // The MixTrafficController then sends the actual traffic
let message_sender = Self::start_mix_traffic_controller( let message_sender = Self::start_mix_traffic_controller(
gateway_transceiver, gateway_transceiver,
shutdown.fork("mix_traffic_controller"), shutdown.fork("mix_traffic_controller"),
self.forget_me,
); );
// Channels that the websocket listener can use to signal downstream to the real traffic // Channels that the websocket listener can use to signal downstream to the real traffic
@@ -2,8 +2,9 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::mix_traffic::transceiver::GatewayTransceiver; use crate::client::mix_traffic::transceiver::GatewayTransceiver;
use crate::spawn_future; use crate::{spawn_future, ForgetMe};
use log::*; use log::*;
use nym_gateway_requests::ClientRequest;
use nym_sphinx::forwarding::packet::MixPacket; use nym_sphinx::forwarding::packet::MixPacket;
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>; pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
@@ -26,10 +27,14 @@ pub struct MixTrafficController {
// TODO: this is temporary work-around. // TODO: this is temporary work-around.
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway. // in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
consecutive_gateway_failure_count: usize, consecutive_gateway_failure_count: usize,
forget_me: ForgetMe,
} }
impl MixTrafficController { impl MixTrafficController {
pub fn new<T>(gateway_transceiver: T) -> (MixTrafficController, BatchMixMessageSender) pub fn new<T>(
gateway_transceiver: T,
forget_me: ForgetMe,
) -> (MixTrafficController, BatchMixMessageSender)
where where
T: GatewayTransceiver + Send + 'static, T: GatewayTransceiver + Send + 'static,
{ {
@@ -40,6 +45,7 @@ impl MixTrafficController {
gateway_transceiver: Box::new(gateway_transceiver), gateway_transceiver: Box::new(gateway_transceiver),
mix_rx: message_receiver, mix_rx: message_receiver,
consecutive_gateway_failure_count: 0, consecutive_gateway_failure_count: 0,
forget_me,
}, },
message_sender, message_sender,
) )
@@ -47,6 +53,7 @@ impl MixTrafficController {
pub fn new_dynamic( pub fn new_dynamic(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>, gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
forget_me: ForgetMe,
) -> (MixTrafficController, BatchMixMessageSender) { ) -> (MixTrafficController, BatchMixMessageSender) {
let (message_sender, message_receiver) = let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE); tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
@@ -55,6 +62,7 @@ impl MixTrafficController {
gateway_transceiver, gateway_transceiver,
mix_rx: message_receiver, mix_rx: message_receiver,
consecutive_gateway_failure_count: 0, consecutive_gateway_failure_count: 0,
forget_me,
}, },
message_sender, message_sender,
) )
@@ -111,7 +119,27 @@ impl MixTrafficController {
} }
} }
shutdown.recv_timeout().await; shutdown.recv_timeout().await;
if self.forget_me.any() {
log::info!("Sending forget me request to the gateway");
match self
.gateway_transceiver
.send_client_request(ClientRequest::ForgetMe {
client: self.forget_me.client(),
stats: self.forget_me.stats(),
})
.await
{
Ok(_) => {
log::info!("Successfully sent forget me request to the gateway");
}
Err(err) => {
log::error!("Failed to send forget me request to the gateway: {err}");
}
}
}
log::debug!("MixTrafficController: Exiting"); log::debug!("MixTrafficController: Exiting");
}) });
} }
} }
@@ -5,8 +5,10 @@ use async_trait::async_trait;
use log::{debug, error}; use log::{debug, error};
use nym_credential_storage::storage::Storage as CredentialStorage; use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::identity; use nym_crypto::asymmetric::identity;
use nym_gateway_client::error::GatewayClientError;
use nym_gateway_client::GatewayClient; use nym_gateway_client::GatewayClient;
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter}; pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
use nym_gateway_requests::ClientRequest;
use nym_sphinx::forwarding::packet::MixPacket; use nym_sphinx::forwarding::packet::MixPacket;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use std::fmt::Debug; use std::fmt::Debug;
@@ -26,9 +28,14 @@ fn erase_err<E: std::error::Error + Send + Sync + 'static>(err: E) -> ErasedGate
} }
/// This combines combines the functionalities of being able to send and receive mix packets. /// This combines combines the functionalities of being able to send and receive mix packets.
#[async_trait]
pub trait GatewayTransceiver: GatewaySender + GatewayReceiver { pub trait GatewayTransceiver: GatewaySender + GatewayReceiver {
fn gateway_identity(&self) -> identity::PublicKey; fn gateway_identity(&self) -> identity::PublicKey;
fn ws_fd(&self) -> Option<RawFd>; fn ws_fd(&self) -> Option<RawFd>;
async fn send_client_request(
&mut self,
message: ClientRequest,
) -> Result<(), GatewayClientError>;
} }
/// This trait defines the functionality of sending `MixPacket` into the mixnet, /// This trait defines the functionality of sending `MixPacket` into the mixnet,
@@ -65,6 +72,7 @@ pub trait GatewayReceiver {
} }
// to allow for dynamic dispatch // to allow for dynamic dispatch
#[async_trait]
impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> { impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
#[inline] #[inline]
fn gateway_identity(&self) -> identity::PublicKey { fn gateway_identity(&self) -> identity::PublicKey {
@@ -73,6 +81,13 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
fn ws_fd(&self) -> Option<RawFd> { fn ws_fd(&self) -> Option<RawFd> {
(**self).ws_fd() (**self).ws_fd()
} }
async fn send_client_request(
&mut self,
message: ClientRequest,
) -> Result<(), GatewayClientError> {
(**self).send_client_request(message).await
}
} }
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -91,7 +106,6 @@ impl<G: GatewaySender + ?Sized + Send> GatewaySender for Box<G> {
(**self).batch_send_mix_packets(packets).await (**self).batch_send_mix_packets(packets).await
} }
} }
impl<G: GatewayReceiver + ?Sized> GatewayReceiver for Box<G> { impl<G: GatewayReceiver + ?Sized> GatewayReceiver for Box<G> {
#[inline] #[inline]
fn set_packet_router(&mut self, packet_router: PacketRouter) -> Result<(), ErasedGatewayError> { fn set_packet_router(&mut self, packet_router: PacketRouter) -> Result<(), ErasedGatewayError> {
@@ -111,6 +125,7 @@ impl<C, St> RemoteGateway<C, St> {
} }
} }
#[async_trait]
impl<C, St> GatewayTransceiver for RemoteGateway<C, St> impl<C, St> GatewayTransceiver for RemoteGateway<C, St>
where where
C: DkgQueryClient + Send + Sync, C: DkgQueryClient + Send + Sync,
@@ -123,6 +138,20 @@ where
fn ws_fd(&self) -> Option<RawFd> { fn ws_fd(&self) -> Option<RawFd> {
self.gateway_client.ws_fd() self.gateway_client.ws_fd()
} }
async fn send_client_request(
&mut self,
message: ClientRequest,
) -> Result<(), GatewayClientError> {
if let Some(shared_key) = self.gateway_client.shared_key() {
self.gateway_client
.send_websocket_message(message.encrypt(&*shared_key)?)
.await?;
Ok(())
} else {
Err(GatewayClientError::ConnectionInInvalidState)
}
}
} }
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -195,6 +224,7 @@ impl LocalGateway {
mod nonwasm_sealed { mod nonwasm_sealed {
use super::*; use super::*;
#[async_trait]
impl GatewayTransceiver for LocalGateway { impl GatewayTransceiver for LocalGateway {
fn gateway_identity(&self) -> identity::PublicKey { fn gateway_identity(&self) -> identity::PublicKey {
self.local_identity self.local_identity
@@ -202,6 +232,13 @@ mod nonwasm_sealed {
fn ws_fd(&self) -> Option<RawFd> { fn ws_fd(&self) -> Option<RawFd> {
None None
} }
async fn send_client_request(
&mut self,
_message: ClientRequest,
) -> Result<(), GatewayClientError> {
Ok(())
}
} }
#[async_trait] #[async_trait]
@@ -269,6 +306,7 @@ impl GatewaySender for MockGateway {
} }
} }
#[async_trait]
impl GatewayTransceiver for MockGateway { impl GatewayTransceiver for MockGateway {
fn gateway_identity(&self) -> identity::PublicKey { fn gateway_identity(&self) -> identity::PublicKey {
self.dummy_identity self.dummy_identity
@@ -276,4 +314,11 @@ impl GatewayTransceiver for MockGateway {
fn ws_fd(&self) -> Option<RawFd> { fn ws_fd(&self) -> Option<RawFd> {
None None
} }
async fn send_client_request(
&mut self,
_message: ClientRequest,
) -> Result<(), GatewayClientError> {
Ok(())
}
} }
+1 -1
View File
@@ -190,7 +190,7 @@ where
Ok(GatewayWithLatency::new(gateway, avg)) Ok(GatewayWithLatency::new(gateway, avg))
} }
pub async fn choose_gateway_by_latency<'a, R: Rng, G: ConnectableGateway + Clone>( pub async fn choose_gateway_by_latency<R: Rng, G: ConnectableGateway + Clone>(
rng: &mut R, rng: &mut R,
gateways: &[G], gateways: &[G],
must_use_tls: bool, must_use_tls: bool,
+45
View File
@@ -34,3 +34,48 @@ where
{ {
tokio::spawn(future); tokio::spawn(future);
} }
#[derive(Clone, Default, Debug)]
pub struct ForgetMe {
client: bool,
stats: bool,
}
impl ForgetMe {
pub fn new_all() -> Self {
Self {
client: true,
stats: true,
}
}
pub fn new_client() -> Self {
Self {
client: true,
stats: false,
}
}
pub fn new_stats() -> Self {
Self {
client: false,
stats: true,
}
}
pub fn new(client: bool, stats: bool) -> Self {
Self { client, stats }
}
pub fn any(&self) -> bool {
self.client || self.stats
}
pub fn client(&self) -> bool {
self.client
}
pub fn stats(&self) -> bool {
self.stats
}
}
@@ -9,7 +9,10 @@ use crate::backend::fs_backend::{
}, },
}; };
use log::{error, info}; use log::{error, info};
use sqlx::ConnectOptions; use sqlx::{
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
ConnectOptions,
};
use std::path::Path; use std::path::Path;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -31,6 +34,9 @@ impl StorageManager {
} }
let opts = sqlx::sqlite::SqliteConnectOptions::new() let opts = sqlx::sqlite::SqliteConnectOptions::new()
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.filename(database_path) .filename(database_path)
.create_if_missing(fresh) .create_if_missing(fresh)
.disable_statement_logging(); .disable_statement_logging();
@@ -101,6 +101,10 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
// currently unused (but populated) // currently unused (but populated)
negotiated_protocol: Option<u8>, negotiated_protocol: Option<u8>,
// Callback on the fd as soon as the connection has been established
#[cfg(unix)]
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
/// Listen to shutdown messages and send notifications back to the task manager /// Listen to shutdown messages and send notifications back to the task manager
task_client: TaskClient, task_client: TaskClient,
} }
@@ -116,6 +120,7 @@ impl<C, St> GatewayClient<C, St> {
packet_router: PacketRouter, packet_router: PacketRouter,
bandwidth_controller: Option<BandwidthController<C, St>>, bandwidth_controller: Option<BandwidthController<C, St>>,
stats_reporter: ClientStatsSender, stats_reporter: ClientStatsSender,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
task_client: TaskClient, task_client: TaskClient,
) -> Self { ) -> Self {
GatewayClient { GatewayClient {
@@ -131,6 +136,8 @@ impl<C, St> GatewayClient<C, St> {
bandwidth_controller, bandwidth_controller,
stats_reporter, stats_reporter,
negotiated_protocol: None, negotiated_protocol: None,
#[cfg(unix)]
connection_fd_callback,
task_client, task_client,
} }
} }
@@ -205,6 +212,12 @@ impl<C, St> GatewayClient<C, St> {
}; };
self.connection = SocketState::Available(Box::new(ws_stream)); self.connection = SocketState::Available(Box::new(ws_stream));
#[cfg(unix)]
if let (Some(callback), Some(fd)) = (self.connection_fd_callback.as_ref(), self.ws_fd()) {
callback.as_ref()(fd);
}
Ok(()) Ok(())
} }
@@ -311,7 +324,7 @@ impl<C, St> GatewayClient<C, St> {
// If we want to send a message (with response), we need to have a full control over the socket, // If we want to send a message (with response), we need to have a full control over the socket,
// as we need to be able to write the request and read the subsequent response // as we need to be able to write the request and read the subsequent response
async fn send_websocket_message( pub async fn send_websocket_message(
&mut self, &mut self,
msg: impl Into<Message>, msg: impl Into<Message>,
) -> Result<ServerResponse, GatewayClientError> { ) -> Result<ServerResponse, GatewayClientError> {
@@ -1034,6 +1047,8 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
bandwidth_controller: None, bandwidth_controller: None,
stats_reporter: ClientStatsSender::new(None), stats_reporter: ClientStatsSender::new(None),
negotiated_protocol: None, negotiated_protocol: None,
#[cfg(unix)]
connection_fd_callback: None,
task_client, task_client,
} }
} }
@@ -1064,6 +1079,8 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
bandwidth_controller, bandwidth_controller,
stats_reporter, stats_reporter,
negotiated_protocol: self.negotiated_protocol, negotiated_protocol: self.negotiated_protocol,
#[cfg(unix)]
connection_fd_callback: self.connection_fd_callback,
task_client, task_client,
} }
} }
+3 -1
View File
@@ -8,10 +8,12 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dashmap = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
tokio = { workspace = true, features = ["time"] } tokio = { workspace = true, features = ["time", "sync"] }
tokio-util = { workspace = true, features = ["codec"], optional = true } tokio-util = { workspace = true, features = ["codec"], optional = true }
tokio-stream = { workspace = true }
# internal # internal
nym-sphinx = { path = "../../nymsphinx" } nym-sphinx = { path = "../../nymsphinx" }
+141 -86
View File
@@ -1,21 +1,24 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc; use dashmap::DashMap;
use futures::StreamExt; use futures::StreamExt;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress; use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx::framing::codec::NymCodec; use nym_sphinx::framing::codec::NymCodec;
use nym_sphinx::framing::packet::FramedNymPacket; use nym_sphinx::framing::packet::FramedNymPacket;
use nym_sphinx::params::PacketType; use nym_sphinx::params::PacketType;
use nym_sphinx::NymPacket; use nym_sphinx::NymPacket;
use std::collections::HashMap;
use std::io; use std::io;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::{AtomicU32, Ordering}; use std::ops::Deref;
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::sync::mpsc;
use tokio::sync::mpsc::error::TrySendError;
use tokio::time::sleep; use tokio::time::sleep;
use tokio_stream::wrappers::ReceiverStream;
use tokio_util::codec::Framed; use tokio_util::codec::Framed;
use tracing::*; use tracing::*;
@@ -55,11 +58,37 @@ pub trait SendWithoutResponse {
} }
pub struct Client { pub struct Client {
conn_new: HashMap<NymNodeRoutingAddress, ConnectionSender>, active_connections: ActiveConnections,
connections_count: Arc<AtomicUsize>,
config: Config, config: Config,
} }
struct ConnectionSender { #[derive(Default, Clone)]
pub struct ActiveConnections {
inner: Arc<DashMap<NymNodeRoutingAddress, ConnectionSender>>,
}
impl ActiveConnections {
pub fn pending_packets(&self) -> usize {
self.inner
.iter()
.map(|sender| {
let max_capacity = sender.channel.max_capacity();
let capacity = sender.channel.capacity();
max_capacity - capacity
})
.sum()
}
}
impl Deref for ActiveConnections {
type Target = DashMap<NymNodeRoutingAddress, ConnectionSender>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
pub struct ConnectionSender {
channel: mpsc::Sender<FramedNymPacket>, channel: mpsc::Sender<FramedNymPacket>,
current_reconnection_attempt: Arc<AtomicU32>, current_reconnection_attempt: Arc<AtomicU32>,
} }
@@ -73,46 +102,53 @@ impl ConnectionSender {
} }
} }
impl Client { struct ManagedConnection {
pub fn new(config: Config) -> Client { address: SocketAddr,
Client { message_receiver: ReceiverStream<FramedNymPacket>,
conn_new: HashMap::new(), connection_timeout: Duration,
config, current_reconnection: Arc<AtomicU32>,
}
impl ManagedConnection {
fn new(
address: SocketAddr,
message_receiver: mpsc::Receiver<FramedNymPacket>,
connection_timeout: Duration,
current_reconnection: Arc<AtomicU32>,
) -> Self {
ManagedConnection {
address,
message_receiver: ReceiverStream::new(message_receiver),
connection_timeout,
current_reconnection,
} }
} }
async fn manage_connection( async fn run(self) {
address: SocketAddr, let address = self.address;
receiver: mpsc::Receiver<FramedNymPacket>,
connection_timeout: Duration,
current_reconnection: &AtomicU32,
) {
let connection_fut = TcpStream::connect(address); let connection_fut = TcpStream::connect(address);
let conn = match tokio::time::timeout(connection_timeout, connection_fut).await { let conn = match tokio::time::timeout(self.connection_timeout, connection_fut).await {
Ok(stream_res) => match stream_res { Ok(stream_res) => match stream_res {
Ok(stream) => { Ok(stream) => {
debug!("Managed to establish connection to {}", address); debug!("Managed to establish connection to {}", self.address);
// if we managed to connect, reset the reconnection count (whatever it might have been) // if we managed to connect, reset the reconnection count (whatever it might have been)
current_reconnection.store(0, Ordering::Release); self.current_reconnection.store(0, Ordering::Release);
Framed::new(stream, NymCodec) Framed::new(stream, NymCodec)
} }
Err(err) => { Err(err) => {
debug!( debug!("failed to establish connection to {address} (err: {err})",);
"failed to establish connection to {} (err: {})",
address, err
);
return; return;
} }
}, },
Err(_) => { Err(_) => {
debug!( debug!(
"failed to connect to {} within {:?}", "failed to connect to {address} within {:?}",
address, connection_timeout self.connection_timeout
); );
// we failed to connect - increase reconnection attempt // we failed to connect - increase reconnection attempt
current_reconnection.fetch_add(1, Ordering::SeqCst); self.current_reconnection.fetch_add(1, Ordering::SeqCst);
return; return;
} }
}; };
@@ -120,15 +156,28 @@ impl Client {
// Take whatever the receiver channel produces and put it on the connection. // Take whatever the receiver channel produces and put it on the connection.
// We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care // We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care
// about neither receiver nor the connection, it doesn't matter which one gets consumed // about neither receiver nor the connection, it doesn't matter which one gets consumed
if let Err(err) = receiver.map(Ok).forward(conn).await { if let Err(err) = self.message_receiver.map(Ok).forward(conn).await {
warn!("Failed to forward packets to {} - {err}", address); warn!("Failed to forward packets to {address}: {err}");
} }
debug!( debug!(
"connection manager to {} is finished. Either the connection failed or mixnet client got dropped", "connection manager to {address} is finished. Either the connection failed or mixnet client got dropped",
address
); );
} }
}
impl Client {
pub fn new(config: Config, connections_count: Arc<AtomicUsize>) -> Client {
Client {
active_connections: Default::default(),
connections_count,
config,
}
}
pub fn active_connections(&self) -> ActiveConnections {
self.active_connections.clone()
}
/// If we're trying to reconnect, determine how long we should wait. /// If we're trying to reconnect, determine how long we should wait.
fn determine_backoff(&self, current_attempt: u32) -> Option<Duration> { fn determine_backoff(&self, current_attempt: u32) -> Option<Duration> {
@@ -148,7 +197,7 @@ impl Client {
} }
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) { fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size); let (sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
// this CAN'T fail because we just created the channel which has a non-zero capacity // this CAN'T fail because we just created the channel which has a non-zero capacity
if self.config.maximum_connection_buffer_size > 0 { if self.config.maximum_connection_buffer_size > 0 {
@@ -156,15 +205,16 @@ impl Client {
} }
// if we already tried to connect to `address` before, grab the current attempt count // if we already tried to connect to `address` before, grab the current attempt count
let current_reconnection_attempt = if let Some(existing) = self.conn_new.get_mut(&address) { let current_reconnection_attempt =
existing.channel = sender; if let Some(mut existing) = self.active_connections.get_mut(&address) {
Arc::clone(&existing.current_reconnection_attempt) existing.channel = sender;
} else { Arc::clone(&existing.current_reconnection_attempt)
let new_entry = ConnectionSender::new(sender); } else {
let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt); let new_entry = ConnectionSender::new(sender);
self.conn_new.insert(address, new_entry); let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt);
current_attempt self.active_connections.insert(address, new_entry);
}; current_attempt
};
// load the actual value. // load the actual value.
let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire); let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire);
@@ -173,6 +223,7 @@ impl Client {
// copy the value before moving into another task // copy the value before moving into another task
let initial_connection_timeout = self.config.initial_connection_timeout; let initial_connection_timeout = self.config.initial_connection_timeout;
let connections_count = self.connections_count.clone();
tokio::spawn(async move { tokio::spawn(async move {
// before executing the manager, wait for what was specified, if anything // before executing the manager, wait for what was specified, if anything
if let Some(backoff) = backoff { if let Some(backoff) = backoff {
@@ -180,13 +231,16 @@ impl Client {
sleep(backoff).await; sleep(backoff).await;
} }
Self::manage_connection( connections_count.fetch_add(1, Ordering::SeqCst);
ManagedConnection::new(
address.into(), address.into(),
receiver, receiver,
initial_connection_timeout, initial_connection_timeout,
&current_reconnection_attempt, current_reconnection_attempt,
) )
.await .run()
.await;
connections_count.fetch_sub(1, Ordering::SeqCst);
}); });
} }
} }
@@ -201,49 +255,47 @@ impl SendWithoutResponse for Client {
trace!("Sending packet to {address:?}"); trace!("Sending packet to {address:?}");
let framed_packet = FramedNymPacket::new(packet, packet_type); let framed_packet = FramedNymPacket::new(packet, packet_type);
if let Some(sender) = self.conn_new.get_mut(&address) { let Some(sender) = self.active_connections.get_mut(&address) else {
if let Err(err) = sender.channel.try_send(framed_packet) {
if err.is_full() {
debug!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address);
// it's not a 'big' error, but we did not manage to send the packet
// if the queue is full, we can't really do anything but to drop the packet
Err(io::Error::new(
io::ErrorKind::WouldBlock,
"connection queue is full",
))
} else if err.is_disconnected() {
debug!(
"Connection to {} seems to be dead. attempting to re-establish it...",
address
);
// it's not a 'big' error, but we did not manage to send the packet, but queue
// it up to send it as soon as the connection is re-established
self.make_connection(address, err.into_inner());
Err(io::Error::new(
io::ErrorKind::ConnectionAborted,
"reconnection attempt is in progress",
))
} else {
// this can't really happen, but let's safe-guard against it in case something changes in futures library
Err(io::Error::new(
io::ErrorKind::Other,
"unknown connection buffer error",
))
}
} else {
Ok(())
}
} else {
// there was never a connection to begin with // there was never a connection to begin with
debug!("establishing initial connection to {}", address); debug!("establishing initial connection to {}", address);
// it's not a 'big' error, but we did not manage to send the packet, but queue the packet // it's not a 'big' error, but we did not manage to send the packet, but queue the packet
// for sending for as soon as the connection is created // for sending for as soon as the connection is created
self.make_connection(address, framed_packet); self.make_connection(address, framed_packet);
Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::NotConnected, io::ErrorKind::NotConnected,
"connection is in progress", "connection is in progress",
)) ));
} };
let sending_res = sender.channel.try_send(framed_packet);
drop(sender);
sending_res.map_err(|err| {
match err {
TrySendError::Full(_) => {
debug!("Connection to {address} seems to not be able to handle all the traffic - dropping the current packet");
// it's not a 'big' error, but we did not manage to send the packet
// if the queue is full, we can't really do anything but to drop the packet
io::Error::new(
io::ErrorKind::WouldBlock,
"connection queue is full",
)
}
TrySendError::Closed(dropped) => {
debug!(
"Connection to {address} seems to be dead. attempting to re-establish it...",
);
// it's not a 'big' error, but we did not manage to send the packet, but queue
// it up to send it as soon as the connection is re-established
self.make_connection(address, dropped);
io::Error::new(
io::ErrorKind::ConnectionAborted,
"reconnection attempt is in progress",
)
}
}
} )
} }
} }
@@ -252,12 +304,15 @@ mod tests {
use super::*; use super::*;
fn dummy_client() -> Client { fn dummy_client() -> Client {
Client::new(Config { Client::new(
initial_reconnection_backoff: Duration::from_millis(10_000), Config {
maximum_reconnection_backoff: Duration::from_millis(300_000), initial_reconnection_backoff: Duration::from_millis(10_000),
initial_connection_timeout: Duration::from_millis(1_500), maximum_reconnection_backoff: Duration::from_millis(300_000),
maximum_connection_buffer_size: 128, initial_connection_timeout: Duration::from_millis(1_500),
}) maximum_connection_buffer_size: 128,
},
Default::default(),
)
} }
#[test] #[test]
@@ -863,11 +863,4 @@ pub enum QueryMsg {
pub struct MigrateMsg { pub struct MigrateMsg {
pub unsafe_skip_state_updates: Option<bool>, pub unsafe_skip_state_updates: Option<bool>,
pub vesting_contract_address: Option<String>, 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,
} }
@@ -23,6 +23,11 @@ impl SqliteEcashTicketbookManager {
SqliteEcashTicketbookManager { connection_pool } SqliteEcashTicketbookManager { connection_pool }
} }
/// Closes the connection pool.
pub async fn close(&self) {
self.connection_pool.close().await
}
pub(crate) async fn cleanup_expired(&self, deadline: Date) -> Result<(), sqlx::Error> { pub(crate) async fn cleanup_expired(&self, deadline: Date) -> Result<(), sqlx::Error> {
sqlx::query!( sqlx::query!(
"DELETE FROM ecash_ticketbook WHERE expiration_date <= ?", "DELETE FROM ecash_ticketbook WHERE expiration_date <= ?",
@@ -43,6 +43,10 @@ impl Debug for EphemeralStorage {
impl Storage for EphemeralStorage { impl Storage for EphemeralStorage {
type StorageError = StorageError; type StorageError = StorageError;
async fn close(&self) {
// nothing to do here
}
async fn cleanup_expired(&self) -> Result<(), Self::StorageError> { async fn cleanup_expired(&self) -> Result<(), Self::StorageError> {
self.storage_manager.cleanup_expired().await; self.storage_manager.cleanup_expired().await;
Ok(()) Ok(())
@@ -33,7 +33,10 @@ use nym_credentials::{
IssuanceTicketBook, IssuedTicketBook, IssuanceTicketBook, IssuedTicketBook,
}; };
use nym_ecash_time::{ecash_today, Date, EcashTime}; use nym_ecash_time::{ecash_today, Date, EcashTime};
use sqlx::ConnectOptions; use sqlx::{
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
ConnectOptions,
};
use std::path::Path; use std::path::Path;
use zeroize::Zeroizing; use zeroize::Zeroizing;
@@ -56,6 +59,9 @@ impl PersistentStorage {
); );
let opts = sqlx::sqlite::SqliteConnectOptions::new() let opts = sqlx::sqlite::SqliteConnectOptions::new()
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.filename(database_path) .filename(database_path)
.create_if_missing(true) .create_if_missing(true)
.disable_statement_logging(); .disable_statement_logging();
@@ -83,6 +89,10 @@ impl PersistentStorage {
impl Storage for PersistentStorage { impl Storage for PersistentStorage {
type StorageError = StorageError; type StorageError = StorageError;
async fn close(&self) {
self.storage_manager.close().await
}
/// remove all expired ticketbooks and expiration date signatures /// remove all expired ticketbooks and expiration date signatures
async fn cleanup_expired(&self) -> Result<(), Self::StorageError> { async fn cleanup_expired(&self) -> Result<(), Self::StorageError> {
let ecash_yesterday = ecash_today().date().previous_day().unwrap(); let ecash_yesterday = ecash_today().date().previous_day().unwrap();
+2
View File
@@ -22,6 +22,8 @@ use std::error::Error;
pub trait Storage: Send + Sync { pub trait Storage: Send + Sync {
type StorageError: Error; type StorageError: Error;
async fn close(&self);
/// remove all expired ticketbooks and expiration date signatures /// remove all expired ticketbooks and expiration date signatures
async fn cleanup_expired(&self) -> Result<(), Self::StorageError>; async fn cleanup_expired(&self) -> Result<(), Self::StorageError>;
@@ -9,7 +9,7 @@ use futures::{Sink, Stream};
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use tungstenite::Message as WsMessage; use tungstenite::Message as WsMessage;
impl<'a, S, R> State<'a, S, R> { impl<S, R> State<'_, S, R> {
async fn client_handshake_inner(&mut self) -> Result<(), HandshakeError> async fn client_handshake_inner(&mut self) -> Result<(), HandshakeError>
where where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin, S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
@@ -10,7 +10,7 @@ use crate::registration::handshake::{error::HandshakeError, WsItem};
use futures::{Sink, Stream}; use futures::{Sink, Stream};
use tungstenite::Message as WsMessage; use tungstenite::Message as WsMessage;
impl<'a, S, R> State<'a, S, R> { impl<S, R> State<'_, S, R> {
async fn gateway_handshake_inner( async fn gateway_handshake_inner(
&mut self, &mut self,
raw_init_message: Vec<u8>, raw_init_message: Vec<u8>,
@@ -20,6 +20,10 @@ pub enum ClientRequest {
hkdf_salt: Vec<u8>, hkdf_salt: Vec<u8>,
derived_key_digest: Vec<u8>, derived_key_digest: Vec<u8>,
}, },
ForgetMe {
client: bool,
stats: bool,
},
} }
impl ClientRequest { impl ClientRequest {
@@ -11,6 +11,7 @@ use tungstenite::Message;
#[non_exhaustive] #[non_exhaustive]
pub enum SensitiveServerResponse { pub enum SensitiveServerResponse {
KeyUpgradeAck {}, KeyUpgradeAck {},
ForgetMeAck {},
} }
impl SensitiveServerResponse { impl SensitiveServerResponse {
+17 -1
View File
@@ -6,7 +6,10 @@ use models::StoredFinishedSession;
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType}; use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
use nym_sphinx::DestinationAddressBytes; use nym_sphinx::DestinationAddressBytes;
use sessions::SessionManager; use sessions::SessionManager;
use sqlx::ConnectOptions; use sqlx::{
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
ConnectOptions,
};
use std::path::Path; use std::path::Path;
use time::Date; use time::Date;
use tracing::{debug, error}; use tracing::{debug, error};
@@ -36,6 +39,9 @@ impl PersistentStatsStorage {
// TODO: we can inject here more stuff based on our gateway global config // TODO: we can inject here more stuff based on our gateway global config
// struct. Maybe different pool size or timeout intervals? // struct. Maybe different pool size or timeout intervals?
let opts = sqlx::sqlite::SqliteConnectOptions::new() let opts = sqlx::sqlite::SqliteConnectOptions::new()
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.filename(database_path) .filename(database_path)
.create_if_missing(true) .create_if_missing(true)
.disable_statement_logging(); .disable_statement_logging();
@@ -116,6 +122,16 @@ impl PersistentStatsStorage {
.await?) .await?)
} }
pub async fn delete_unique_user(
&self,
client_address: DestinationAddressBytes,
) -> Result<(), StatsStorageError> {
Ok(self
.session_manager
.delete_unique_user(client_address.as_base58_string())
.await?)
}
pub async fn insert_active_session( pub async fn insert_active_session(
&self, &self,
client_address: DestinationAddressBytes, client_address: DestinationAddressBytes,
@@ -71,6 +71,16 @@ impl SessionManager {
Ok(()) Ok(())
} }
pub(crate) async fn delete_unique_user(&self, client_address_b58: String) -> Result<()> {
sqlx::query!(
"DELETE FROM sessions_unique_users WHERE client_address = ?",
client_address_b58
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn get_unique_users(&self, date: Date) -> Result<Vec<String>> { pub(crate) async fn get_unique_users(&self, date: Date) -> Result<Vec<String>> {
sqlx::query_scalar!( sqlx::query_scalar!(
"SELECT client_address as count FROM sessions_unique_users WHERE day = ?", "SELECT client_address as count FROM sessions_unique_users WHERE day = ?",
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM message_store WHERE client_address_bs58 = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM available_bandwidth WHERE client_id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118"
}
+10
View File
@@ -49,6 +49,16 @@ impl BandwidthManager {
Ok(()) Ok(())
} }
pub(crate) async fn remove_client(&self, client_id: i64) -> Result<(), sqlx::Error> {
sqlx::query!(
"DELETE FROM available_bandwidth WHERE client_id = ?",
client_id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Set the expiration date of the particular client to the provided date. /// Set the expiration date of the particular client to the provided date.
pub(crate) async fn set_expiration( pub(crate) async fn set_expiration(
&self, &self,
+13
View File
@@ -133,4 +133,17 @@ impl InboxManager {
.await?; .await?;
Ok(()) Ok(())
} }
pub(crate) async fn remove_messages_for_client(
&self,
client_address_bs58: &str,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"DELETE FROM message_store WHERE client_address_bs58 = ?",
client_address_bs58
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
} }
+49 -1
View File
@@ -12,7 +12,10 @@ use nym_credentials_interface::ClientTicket;
use nym_gateway_requests::shared_key::SharedGatewayKey; use nym_gateway_requests::shared_key::SharedGatewayKey;
use nym_sphinx::DestinationAddressBytes; use nym_sphinx::DestinationAddressBytes;
use shared_keys::SharedKeysManager; use shared_keys::SharedKeysManager;
use sqlx::ConnectOptions; use sqlx::{
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
ConnectOptions,
};
use std::path::Path; use std::path::Path;
use tickets::TicketStorageManager; use tickets::TicketStorageManager;
use time::OffsetDateTime; use time::OffsetDateTime;
@@ -41,6 +44,33 @@ pub struct GatewayStorage {
} }
impl GatewayStorage { impl GatewayStorage {
#[allow(dead_code)]
pub(crate) fn client_manager(&self) -> &ClientManager {
&self.client_manager
}
pub(crate) fn shared_key_manager(&self) -> &SharedKeysManager {
&self.shared_key_manager
}
pub(crate) fn inbox_manager(&self) -> &InboxManager {
&self.inbox_manager
}
pub(crate) fn bandwidth_manager(&self) -> &BandwidthManager {
&self.bandwidth_manager
}
#[allow(dead_code)]
pub(crate) fn ticket_manager(&self) -> &TicketStorageManager {
&self.ticket_manager
}
#[allow(dead_code)]
pub(crate) fn wireguard_peer_manager(&self) -> &wireguard_peers::WgPeerManager {
&self.wireguard_peer_manager
}
/// Initialises `PersistentStorage` using the provided path. /// Initialises `PersistentStorage` using the provided path.
/// ///
/// # Arguments /// # Arguments
@@ -59,6 +89,9 @@ impl GatewayStorage {
// TODO: we can inject here more stuff based on our gateway global config // TODO: we can inject here more stuff based on our gateway global config
// struct. Maybe different pool size or timeout intervals? // struct. Maybe different pool size or timeout intervals?
let opts = sqlx::sqlite::SqliteConnectOptions::new() let opts = sqlx::sqlite::SqliteConnectOptions::new()
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.filename(database_path) .filename(database_path)
.create_if_missing(true) .create_if_missing(true)
.disable_statement_logging(); .disable_statement_logging();
@@ -101,6 +134,21 @@ impl GatewayStorage {
.await?) .await?)
} }
pub async fn handle_forget_me(
&self,
client_address: DestinationAddressBytes,
) -> Result<(), GatewayStorageError> {
let client_id = self.get_mixnet_client_id(client_address).await?;
self.inbox_manager()
.remove_messages_for_client(&client_address.as_base58_string())
.await?;
self.bandwidth_manager().remove_client(client_id).await?;
self.shared_key_manager()
.remove_shared_keys(&client_address.as_base58_string())
.await?;
Ok(())
}
pub async fn insert_shared_keys( pub async fn insert_shared_keys(
&self, &self,
client_address: DestinationAddressBytes, client_address: DestinationAddressBytes,
+103
View File
@@ -324,6 +324,56 @@ impl Client {
} }
} }
pub fn create_patch_request<B, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> RequestBuilder
where
B: Serialize + ?Sized,
K: AsRef<str>,
V: AsRef<str>,
{
let url = sanitize_url(&self.base_url, path, params);
self.reqwest_client.patch(url).json(json_body)
}
pub async fn send_patch_request<B, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<Response, HttpClientError<E>>
where
B: Serialize + ?Sized,
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
{
Ok(wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.patch(url).json(json_body).send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(self
.reqwest_client
.patch(url)
.json(json_body)
.send()
.await?)
}
}
#[instrument(level = "debug", skip_all)] #[instrument(level = "debug", skip_all)]
pub async fn get_json<T, K, V, E>( pub async fn get_json<T, K, V, E>(
&self, &self,
@@ -372,6 +422,23 @@ impl Client {
parse_response(res, false).await parse_response(res, false).await
} }
pub async fn patch_json<B, T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized,
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
E: Display + DeserializeOwned,
{
let res = self.send_patch_request(path, params, json_body).await?;
parse_response(res, true).await
}
#[instrument(level = "debug", skip_all)] #[instrument(level = "debug", skip_all)]
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>> pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where where
@@ -466,6 +533,42 @@ impl Client {
parse_response(res, false).await parse_response(res, false).await
} }
pub async fn patch_json_endpoint<B, T, S, E>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str>,
{
#[cfg(target_arch = "wasm32")]
let res = {
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client
.patch(self.base_url.join(endpoint.as_ref())?)
.json(json_body)
.send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = {
self.reqwest_client
.patch(self.base_url.join(endpoint.as_ref())?)
.json(json_body)
.send()
.await?
};
parse_response(res, true).await
}
} }
// define those methods on the trait for nicer extensions (and not having to type the thing twice) // define those methods on the trait for nicer extensions (and not having to type the thing twice)
+11
View File
@@ -57,3 +57,14 @@ pub mod wireguard {
pub const WG_TUN_DEVICE_IP_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc01, 0, 0, 0, 0, 0, 0, 0x1); // fc01::1 pub const WG_TUN_DEVICE_IP_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc01, 0, 0, 0, 0, 0, 0, 0x1); // fc01::1
pub const WG_TUN_DEVICE_NETMASK_V6: u8 = 112; pub const WG_TUN_DEVICE_NETMASK_V6: u8 = 112;
} }
pub mod mixnet_vpn {
use std::net::{Ipv4Addr, Ipv6Addr};
// The interface used to route traffic
pub const NYM_TUN_BASE_NAME: &str = "nymtun";
pub const NYM_TUN_DEVICE_ADDRESS_V4: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1);
pub const NYM_TUN_DEVICE_NETMASK_V4: Ipv4Addr = Ipv4Addr::new(255, 255, 0, 0);
pub const NYM_TUN_DEVICE_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0x1); // fc00::1
pub const NYM_TUN_DEVICE_NETMASK_V6: &str = "112";
}
@@ -65,6 +65,14 @@ impl<T> NonExhaustiveDelayQueue<T> {
pub fn remove(&mut self, key: &QueueKey) -> Expired<T> { pub fn remove(&mut self, key: &QueueKey) -> Expired<T> {
self.inner.remove(key) self.inner.remove(key)
} }
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
} }
impl<T> Default for NonExhaustiveDelayQueue<T> { impl<T> Default for NonExhaustiveDelayQueue<T> {
+1 -1
View File
@@ -12,6 +12,6 @@ license.workspace = true
[dependencies] [dependencies]
prometheus = { workspace = true } prometheus = { workspace = true }
log = { workspace = true } tracing = { workspace = true }
dashmap = { workspace = true } dashmap = { workspace = true }
lazy_static = { workspace = true } lazy_static = { workspace = true }
+390 -81
View File
@@ -1,14 +1,18 @@
use dashmap::DashMap; use dashmap::DashMap;
pub use log::error;
use log::{debug, warn};
use std::fmt; use std::fmt;
pub use std::time::Instant; use tracing::{debug, error, warn};
use prometheus::{core::Collector, Encoder as _, IntCounter, IntGauge, Registry, TextEncoder}; use prometheus::{
core::Collector, Encoder as _, Gauge, Histogram, HistogramOpts, IntCounter, IntGauge, Registry,
TextEncoder,
};
pub use prometheus::HistogramTimer;
pub use std::time::Instant;
#[macro_export] #[macro_export]
macro_rules! prepend_package_name { macro_rules! prepend_package_name {
($name: literal) => { ($name: tt) => {
&format!( &format!(
"{}_{}", "{}_{}",
std::module_path!() std::module_path!()
@@ -23,15 +27,29 @@ macro_rules! prepend_package_name {
#[macro_export] #[macro_export]
macro_rules! inc_by { macro_rules! inc_by {
($name:literal, $x:expr, $help: expr) => {
$crate::REGISTRY.maybe_register_and_inc_by(
$crate::prepend_package_name!($name),
$x as i64,
$help,
);
};
($name:literal, $x:expr) => { ($name:literal, $x:expr) => {
$crate::REGISTRY.inc_by($crate::prepend_package_name!($name), $x as i64); $crate::REGISTRY.maybe_register_and_inc_by(
$crate::prepend_package_name!($name),
$x as i64,
None,
);
}; };
} }
#[macro_export] #[macro_export]
macro_rules! inc { macro_rules! inc {
($name:literal, $help: expr) => {
$crate::REGISTRY.maybe_register_and_inc($crate::prepend_package_name!($name), $help);
};
($name:literal) => { ($name:literal) => {
$crate::REGISTRY.inc($crate::prepend_package_name!($name)); $crate::REGISTRY.maybe_register_and_inc($crate::prepend_package_name!($name), None);
}; };
} }
@@ -42,6 +60,71 @@ macro_rules! metrics {
}; };
} }
#[macro_export]
macro_rules! set_metric {
($name:literal, $x:expr, $help: expr) => {
$crate::REGISTRY.maybe_register_and_set(
$crate::prepend_package_name!($name),
$x as i64,
$help,
);
};
($name:literal, $x:expr) => {
$crate::REGISTRY.maybe_register_and_set(
$crate::prepend_package_name!($name),
$x as i64,
None,
);
};
}
#[macro_export]
macro_rules! set_metric_float {
($name:literal, $x:expr, $help: expr) => {
$crate::REGISTRY.maybe_register_and_set_float(
$crate::prepend_package_name!($name),
$x as f64,
$help,
);
};
($name:literal, $x:expr) => {
$crate::REGISTRY.maybe_register_and_set_float(
$crate::prepend_package_name!($name),
$x as f64,
None,
);
};
}
#[macro_export]
macro_rules! add_histogram_obs {
($name:expr, $x:expr, $b:expr, $help:expr) => {
$crate::REGISTRY.maybe_register_and_add_to_histogram(
$crate::prepend_package_name!($name),
$x as f64,
Some($b),
$help,
);
};
($name:expr, $x:expr, $b:expr) => {
$crate::REGISTRY.maybe_register_and_add_to_histogram(
$crate::prepend_package_name!($name),
$x as f64,
Some($b),
None,
);
};
($name:expr, $x:expr) => {
$crate::REGISTRY.maybe_register_and_add_to_histogram(
$crate::prepend_package_name!($name),
$x as f64,
None,
None,
);
};
}
#[macro_export] #[macro_export]
macro_rules! nanos { macro_rules! nanos {
( $name:literal, $x:expr ) => {{ ( $name:literal, $x:expr ) => {{
@@ -50,7 +133,7 @@ macro_rules! nanos {
let r = $x; let r = $x;
let duration = start.elapsed().as_nanos() as i64; let duration = start.elapsed().as_nanos() as i64;
let name = $crate::prepend_package_name!($name); let name = $crate::prepend_package_name!($name);
$crate::REGISTRY.inc_by(&format!("{}_nanos", $name), duration); $crate::REGISTRY.maybe_register_and_inc_by(&format!("{}_nanos", $name), duration, None);
r r
}}; }};
} }
@@ -59,15 +142,100 @@ lazy_static::lazy_static! {
pub static ref REGISTRY: MetricsController = MetricsController::default(); pub static ref REGISTRY: MetricsController = MetricsController::default();
} }
pub fn metrics_registry() -> &'static MetricsController {
&REGISTRY
}
#[derive(Default)] #[derive(Default)]
pub struct MetricsController { pub struct MetricsController {
registry: Registry, registry: Registry,
registry_index: DashMap<String, Metric>, registry_index: DashMap<String, Metric>,
} }
enum Metric { pub enum Metric {
C(Box<IntCounter>), IntCounter(Box<IntCounter>),
G(Box<IntGauge>), IntGauge(Box<IntGauge>),
FloatGauge(Box<Gauge>),
Histogram(Box<Histogram>),
}
impl Metric {
pub fn new_int_counter(name: &str, help: &str) -> Option<Self> {
match IntCounter::new(sanitize_metric_name(name), help) {
Ok(c) => Some(c.into()),
Err(err) => {
error!("Failed to create counter {name:?}: {err}");
None
}
}
}
pub fn new_int_gauge(name: &str, help: &str) -> Option<Self> {
match IntGauge::new(sanitize_metric_name(name), help) {
Ok(g) => Some(g.into()),
Err(err) => {
error!("Failed to create gauge {name:?}: {err}");
None
}
}
}
pub fn new_float_gauge(name: &str, help: &str) -> Option<Self> {
match Gauge::new(sanitize_metric_name(name), help) {
Ok(g) => Some(g.into()),
Err(err) => {
error!("Failed to create gauge {name:?}: {err}");
None
}
}
}
pub fn new_histogram(name: &str, help: &str, buckets: Option<&[f64]>) -> Option<Self> {
let mut opts = HistogramOpts::new(sanitize_metric_name(name), help);
if let Some(buckets) = buckets {
opts = opts.buckets(buckets.to_vec())
}
match Histogram::with_opts(opts) {
Ok(h) => Some(Metric::Histogram(Box::new(h))),
Err(err) => {
error!("failed to create histogram {name:?}: {err}");
None
}
}
}
fn as_collector(&self) -> Box<dyn Collector> {
match self {
Metric::IntCounter(c) => c.clone(),
Metric::IntGauge(g) => g.clone(),
Metric::FloatGauge(g) => g.clone(),
Metric::Histogram(h) => h.clone(),
}
}
}
impl From<IntCounter> for Metric {
fn from(v: IntCounter) -> Self {
Metric::IntCounter(Box::new(v))
}
}
impl From<IntGauge> for Metric {
fn from(v: IntGauge) -> Self {
Metric::IntGauge(Box::new(v))
}
}
impl From<Gauge> for Metric {
fn from(v: Gauge) -> Self {
Metric::FloatGauge(Box::new(v))
}
}
impl From<Histogram> for Metric {
fn from(v: Histogram) -> Self {
Metric::Histogram(Box::new(v))
}
} }
fn fq_name(c: &dyn Collector) -> String { fn fq_name(c: &dyn Collector) -> String {
@@ -81,34 +249,92 @@ impl Metric {
#[inline(always)] #[inline(always)]
fn fq_name(&self) -> String { fn fq_name(&self) -> String {
match self { match self {
Metric::C(c) => fq_name(c.as_ref()), Metric::IntCounter(c) => fq_name(c.as_ref()),
Metric::G(g) => fq_name(g.as_ref()), Metric::IntGauge(g) => fq_name(g.as_ref()),
Metric::FloatGauge(g) => fq_name(g.as_ref()),
Metric::Histogram(h) => fq_name(h.as_ref()),
} }
} }
#[inline(always)] #[inline(always)]
fn inc(&self) { fn inc(&self) {
match self { match self {
Metric::C(c) => c.inc(), Metric::IntCounter(c) => c.inc(),
Metric::G(g) => g.inc(), Metric::IntGauge(g) => g.inc(),
Metric::FloatGauge(g) => g.inc(),
Metric::Histogram(_) => {
warn!("invalid operation: attempted to call increment on a histogram")
}
} }
} }
#[inline(always)] #[inline(always)]
fn inc_by(&self, value: i64) { fn inc_by(&self, value: i64) {
match self { match self {
Metric::C(c) => c.inc_by(value as u64), Metric::IntCounter(c) => c.inc_by(value as u64),
Metric::G(g) => g.add(value), Metric::IntGauge(g) => g.add(value),
Metric::FloatGauge(g) => {
warn!("attempted to increment a float gauge ('{}') by an integer - this is most likely a bug", self.fq_name());
g.add(value as f64)
}
Metric::Histogram(_) => {
warn!("invalid operation: attempted to call increment on a histogram")
}
} }
} }
#[inline(always)] #[inline(always)]
fn set(&self, value: i64) { fn set(&self, value: i64) {
match self { match self {
Metric::C(_c) => { Metric::IntCounter(_c) => {
warn!("Cannot set value for counter {:?}", self.fq_name()); warn!("Cannot set value for counter {:?}", self.fq_name());
} }
Metric::G(g) => g.set(value), Metric::IntGauge(g) => g.set(value),
Metric::FloatGauge(g) => {
warn!("attempted to set a float gauge ('{}') to an integer value - this is most likely a bug", self.fq_name());
g.set(value as f64)
}
Metric::Histogram(_) => {
warn!("invalid operation: attempted to call set on a histogram")
}
}
}
#[inline(always)]
fn set_float(&self, value: f64) {
match self {
Metric::IntCounter(_c) => {
warn!("Cannot set value for counter {:?}", self.fq_name());
}
Metric::IntGauge(g) => {
warn!("attempted to set a integer gauge ('{}') to a float value - this is most likely a bug", self.fq_name());
g.set(value as i64)
}
Metric::FloatGauge(g) => g.set(value),
Metric::Histogram(_) => {
warn!("invalid operation: attempted to call increment on a histogram")
}
}
}
#[inline(always)]
fn add_histogram_observation(&self, value: f64) {
match self {
Metric::Histogram(h) => {
h.observe(value);
}
_ => warn!("attempted to add histogram observation on a non-histogram metric"),
}
}
#[inline(always)]
fn start_timer(&self) -> Option<HistogramTimer> {
match self {
Metric::Histogram(h) => Some(h.start_timer()),
_ => {
warn!("attempted to start histogram observation on a non-histogram metric");
None
}
} }
} }
} }
@@ -145,93 +371,165 @@ impl MetricsController {
} }
} }
pub fn set(&self, name: &str, value: i64) { pub fn register_int_gauge<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
let Some(metric) = Metric::new_int_gauge(name, help.into().unwrap_or(name)) else {
return;
};
self.register_metric(metric);
}
pub fn register_float_gauge<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
let Some(metric) = Metric::new_float_gauge(name, help.into().unwrap_or(name)) else {
return;
};
self.register_metric(metric);
}
pub fn register_int_counter<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
let Some(metric) = Metric::new_int_counter(name, help.into().unwrap_or(name)) else {
return;
};
self.register_metric(metric);
}
pub fn register_histogram<'a>(
&self,
name: &str,
help: impl Into<Option<&'a str>>,
buckets: Option<&[f64]>,
) {
let Some(metric) = Metric::new_histogram(name, help.into().unwrap_or(name), buckets) else {
return;
};
self.register_metric(metric);
}
pub fn set(&self, name: &str, value: i64) -> bool {
if let Some(metric) = self.registry_index.get(name) { if let Some(metric) = self.registry_index.get(name) {
metric.set(value); metric.set(value);
true
} else { } else {
let gauge = match IntGauge::new(sanitize_metric_name(name), name) { false
Ok(g) => g,
Err(e) => {
debug!("Failed to create gauge {:?}:\n{}", name, e);
return;
}
};
self.register_gauge(Box::new(gauge));
self.set(name, value)
} }
} }
pub fn inc(&self, name: &str) { pub fn set_float(&self, name: &str, value: f64) -> bool {
if let Some(metric) = self.registry_index.get(name) {
metric.set_float(value);
true
} else {
false
}
}
pub fn add_to_histogram(&self, name: &str, value: f64) -> bool {
if let Some(metric) = self.registry_index.get(name) {
metric.add_histogram_observation(value);
true
} else {
false
}
}
pub fn start_timer(&self, name: &str) -> Option<HistogramTimer> {
self.registry_index
.get(name)
.and_then(|metric| metric.start_timer())
}
pub fn inc(&self, name: &str) -> bool {
if let Some(metric) = self.registry_index.get(name) { if let Some(metric) = self.registry_index.get(name) {
metric.inc(); metric.inc();
true
} else { } else {
let counter = match IntCounter::new(sanitize_metric_name(name), name) { false
Ok(c) => c,
Err(e) => {
debug!("Failed to create counter {:?}:\n{}", name, e);
return;
}
};
self.register_counter(Box::new(counter));
self.inc(name)
} }
} }
pub fn inc_by(&self, name: &str, value: i64) { pub fn inc_by(&self, name: &str, value: i64) -> bool {
if let Some(metric) = self.registry_index.get(name) { if let Some(metric) = self.registry_index.get(name) {
metric.inc_by(value); metric.inc_by(value);
true
} else { } else {
let counter = match IntCounter::new(sanitize_metric_name(name), name) { false
Ok(c) => c,
Err(e) => {
debug!("Failed to create counter {:?}:\n{}", name, e);
return;
}
};
self.register_counter(Box::new(counter));
self.inc_by(name, value)
} }
} }
fn register_gauge(&self, metric: Box<IntGauge>) { pub fn maybe_register_and_set<'a>(
let fq_name = metric &self,
.desc() name: &str,
.first() value: i64,
.map(|d| d.fq_name.clone()) help: impl Into<Option<&'a str>>,
.unwrap_or_default(); ) {
if !self.set(name, value) {
let help = help.into();
self.register_int_gauge(name, help);
self.set(name, value);
}
}
pub fn maybe_register_and_set_float<'a>(
&self,
name: &str,
value: f64,
help: impl Into<Option<&'a str>>,
) {
if !self.set_float(name, value) {
let help = help.into();
self.register_float_gauge(name, help);
self.set_float(name, value);
}
}
pub fn maybe_register_and_add_to_histogram<'a>(
&self,
name: &str,
value: f64,
buckets: Option<&[f64]>,
help: impl Into<Option<&'a str>>,
) {
if !self.add_to_histogram(name, value) {
let help = help.into();
self.register_histogram(name, help, buckets);
self.add_to_histogram(name, value);
}
}
pub fn maybe_register_and_inc<'a>(&self, name: &str, help: impl Into<Option<&'a str>>) {
if !self.inc(name) {
let help = help.into();
self.register_int_counter(name, help);
self.inc(name);
}
}
pub fn maybe_register_and_inc_by<'a>(
&self,
name: &str,
value: i64,
help: impl Into<Option<&'a str>>,
) {
if !self.inc_by(name, value) {
let help = help.into();
self.register_int_counter(name, help);
self.inc_by(name, value);
}
}
pub fn register_metric(&self, metric: impl Into<Metric>) {
let m = metric.into();
let fq_name = m.fq_name();
if self.registry_index.contains_key(&fq_name) { if self.registry_index.contains_key(&fq_name) {
return; return;
} }
match self.registry.register(metric.clone()) { match self.registry.register(m.as_collector()) {
Ok(_) => { Ok(_) => {
self.registry_index self.registry_index.insert(fq_name, m);
.insert(fq_name, Metric::G(metric.clone()));
} }
Err(e) => { Err(err) => {
debug!("Failed to register {:?}:\n{}", fq_name, e) debug!("Failed to register '{fq_name}': {err}")
}
}
}
fn register_counter(&self, metric: Box<IntCounter>) {
let fq_name = metric
.desc()
.first()
.map(|d| d.fq_name.clone())
.unwrap_or_default();
if self.registry_index.contains_key(&fq_name) {
return;
}
match self.registry.register(metric.clone()) {
Ok(_) => {
self.registry_index
.insert(fq_name, Metric::C(metric.clone()));
}
Err(e) => {
debug!("Failed to register {:?}:\n{}", fq_name, e)
} }
} }
} }
@@ -275,4 +573,15 @@ mod tests {
"packets_sent_34_242_65_133:1789" "packets_sent_34_242_65_133:1789"
) )
} }
#[test]
fn prepend_package_name() {
let literal = prepend_package_name!("foo");
assert_eq!(literal, "nym_metrics_foo");
let bar = "bar";
let format = format!("foomp_{}", bar);
let formatted = prepend_package_name!(format);
assert_eq!(formatted, "nym_metrics_foomp_bar");
}
} }
@@ -115,10 +115,7 @@ pub fn aggregate_signatures(
let params = ecash_group_parameters(); let params = ecash_group_parameters();
// aggregate the signature // aggregate the signature
let signature = match Aggregatable::aggregate(signatures, indices) { let signature = Aggregatable::aggregate(signatures, indices)?;
Ok(res) => res,
Err(err) => return Err(err),
};
// Ensure the aggregated signature is not an infinity point // Ensure the aggregated signature is not an infinity point
if bool::from(signature.is_at_infinity()) { if bool::from(signature.is_at_infinity()) {
+8 -1
View File
@@ -13,7 +13,11 @@ use crate::{
models::{CommitSignature, Validator}, models::{CommitSignature, Validator},
}, },
}; };
use sqlx::{types::time::OffsetDateTime, ConnectOptions, Sqlite, Transaction}; use sqlx::{
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
types::time::OffsetDateTime,
ConnectOptions, Sqlite, Transaction,
};
use std::{fmt::Debug, path::Path}; use std::{fmt::Debug, path::Path};
use tendermint::{ use tendermint::{
block::{Commit, CommitSig}, block::{Commit, CommitSig},
@@ -51,6 +55,9 @@ impl ScraperStorage {
#[instrument] #[instrument]
pub async fn init<P: AsRef<Path> + Debug>(database_path: P) -> Result<Self, ScraperError> { pub async fn init<P: AsRef<Path> + Debug>(database_path: P) -> Result<Self, ScraperError> {
let opts = sqlx::sqlite::SqliteConnectOptions::new() let opts = sqlx::sqlite::SqliteConnectOptions::new()
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.filename(database_path) .filename(database_path)
.create_if_missing(true) .create_if_missing(true)
.disable_statement_logging(); .disable_statement_logging();
@@ -428,7 +428,7 @@ impl PacketStatisticsControl {
while self while self
.history .history
.front() .front()
.map_or(false, |&(t, _)| t < recording_window) .is_some_and(|&(t, _)| t < recording_window)
{ {
self.history.pop_front(); self.history.pop_front();
} }
@@ -462,7 +462,7 @@ impl PacketStatisticsControl {
while self while self
.rates .rates
.front() .front()
.map_or(false, |&(t, _)| t < recording_window) .is_some_and(|&(t, _)| t < recording_window)
{ {
self.rates.pop_front(); self.rates.pop_front();
} }
+8
View File
@@ -58,6 +58,10 @@ pub enum GatewaySessionEvent {
/// Address of the remote client opening the connection /// Address of the remote client opening the connection
client: DestinationAddressBytes, client: DestinationAddressBytes,
}, },
SessionDelete {
/// Address of the remote client opening the connection
client: DestinationAddressBytes,
},
} }
impl GatewaySessionEvent { impl GatewaySessionEvent {
@@ -87,4 +91,8 @@ impl GatewaySessionEvent {
client, client,
} }
} }
pub fn new_session_delete(client: DestinationAddressBytes) -> GatewaySessionEvent {
GatewaySessionEvent::SessionDelete { client }
}
} }
+1
View File
@@ -36,3 +36,4 @@ nym-gateway-storage = { path = "../gateway-storage" }
nym-network-defaults = { path = "../network-defaults" } nym-network-defaults = { path = "../network-defaults" }
nym-task = { path = "../task" } nym-task = { path = "../task" }
nym-wireguard-types = { path = "../wireguard-types" } nym-wireguard-types = { path = "../wireguard-types" }
nym-node-metrics = { path = "../../nym-node/nym-node-metrics" }
+2
View File
@@ -85,6 +85,7 @@ pub struct WireguardData {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub async fn start_wireguard( pub async fn start_wireguard(
storage: nym_gateway_storage::GatewayStorage, storage: nym_gateway_storage::GatewayStorage,
metrics: nym_node_metrics::NymNodeMetrics,
all_peers: Vec<nym_gateway_storage::models::WireguardPeer>, all_peers: Vec<nym_gateway_storage::models::WireguardPeer>,
task_client: nym_task::TaskClient, task_client: nym_task::TaskClient,
wireguard_data: WireguardData, wireguard_data: WireguardData,
@@ -175,6 +176,7 @@ pub async fn start_wireguard(
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api)); let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
let mut controller = PeerController::new( let mut controller = PeerController::new(
storage, storage,
metrics,
wg_api.clone(), wg_api.clone(),
host, host,
peer_bandwidth_managers, peer_bandwidth_managers,
+61
View File
@@ -16,7 +16,9 @@ use nym_credential_verification::{
ClientBandwidth, ClientBandwidth,
}; };
use nym_gateway_storage::GatewayStorage; use nym_gateway_storage::GatewayStorage;
use nym_node_metrics::NymNodeMetrics;
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK; use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
use std::time::{Duration, SystemTime};
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use tokio::sync::{mpsc, RwLock}; use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt}; use tokio_stream::{wrappers::IntervalStream, StreamExt};
@@ -65,6 +67,11 @@ pub struct QueryBandwidthControlResponse {
pub struct PeerController { pub struct PeerController {
storage: GatewayStorage, storage: GatewayStorage,
// we have "all" metrics of a node, but they're behind a single Arc pointer,
// so the overhead is minimal
metrics: NymNodeMetrics,
// used to receive commands from individual handles too // used to receive commands from individual handles too
request_tx: mpsc::Sender<PeerControlRequest>, request_tx: mpsc::Sender<PeerControlRequest>,
request_rx: mpsc::Receiver<PeerControlRequest>, request_rx: mpsc::Receiver<PeerControlRequest>,
@@ -76,8 +83,10 @@ pub struct PeerController {
} }
impl PeerController { impl PeerController {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
storage: GatewayStorage, storage: GatewayStorage,
metrics: NymNodeMetrics,
wg_api: Arc<WgApiWrapper>, wg_api: Arc<WgApiWrapper>,
initial_host_information: Host, initial_host_information: Host,
bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager>, Peer)>, bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager>, Peer)>,
@@ -123,6 +132,7 @@ impl PeerController {
request_rx, request_rx,
timeout_check_interval, timeout_check_interval,
task_client, task_client,
metrics,
} }
} }
@@ -257,6 +267,55 @@ impl PeerController {
})) }))
} }
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 new_rx = 0;
let mut new_tx = 0;
for (peer_key, peer) in new_host.peers.iter() {
// only consider pre-existing peers,
// so that the value would always be increasing
if let Some(prior) = old_host.peers.get(peer_key) {
let delta_rx = peer.rx_bytes.saturating_sub(prior.rx_bytes);
let delta_tx = prior.tx_bytes.saturating_sub(prior.tx_bytes);
new_rx += delta_rx;
new_tx += delta_tx;
}
// if a peer hasn't performed a handshake in last minute,
// I think it's reasonable to assume it's no longer active
let Some(last_handshake) = peer.last_handshake else {
continue;
};
let Ok(elapsed) = now.duration_since(last_handshake) else {
continue;
};
if elapsed < ACTIVITY_THRESHOLD {
active_peers += 1;
}
}
self.metrics.wireguard.update(
// if the conversion fails it means we're running not running on a 64bit system
// and that's a reason enough for this failure.
new_rx.try_into().expect(
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
),
new_tx.try_into().expect(
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
),
total_peers,
active_peers,
);
}
pub async fn run(&mut self) { pub async fn run(&mut self) {
info!("started wireguard peer controller"); info!("started wireguard peer controller");
loop { loop {
@@ -266,6 +325,8 @@ impl PeerController {
log::error!("Can't read wireguard kernel data"); log::error!("Can't read wireguard kernel data");
continue; continue;
}; };
self.update_metrics(&host).await;
*self.host_information.write().await = host; *self.host_information.write().await = host;
} }
_ = self.task_client.recv() => { _ = self.task_client.recv() => {
@@ -464,10 +464,7 @@ pub fn instantiate_contracts(
mixnet_contract_address.clone(), mixnet_contract_address.clone(),
&nym_mixnet_contract_common::MigrateMsg { &nym_mixnet_contract_common::MigrateMsg {
vesting_contract_address: Some(vesting_contract_address.to_string()), 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), unsafe_skip_state_updates: Some(true),
version_score_params: Default::default(),
}, },
mixnet_code_id, mixnet_code_id,
) )
@@ -3470,43 +3470,13 @@
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "MigrateMsg", "title": "MigrateMsg",
"type": "object", "type": "object",
"required": [
"current_nym_node_semver"
],
"properties": { "properties": {
"current_nym_node_semver": {
"type": "string"
},
"unsafe_skip_state_updates": { "unsafe_skip_state_updates": {
"type": [ "type": [
"boolean", "boolean",
"null" "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": { "vesting_contract_address": {
"type": [ "type": [
"string", "string",
@@ -3514,63 +3484,7 @@
] ]
} }
}, },
"additionalProperties": false, "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
}
}
}, },
"sudo": null, "sudo": null,
"responses": { "responses": {
+1 -87
View File
@@ -2,43 +2,13 @@
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"title": "MigrateMsg", "title": "MigrateMsg",
"type": "object", "type": "object",
"required": [
"current_nym_node_semver"
],
"properties": { "properties": {
"current_nym_node_semver": {
"type": "string"
},
"unsafe_skip_state_updates": { "unsafe_skip_state_updates": {
"type": [ "type": [
"boolean", "boolean",
"null" "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": { "vesting_contract_address": {
"type": [ "type": [
"string", "string",
@@ -46,61 +16,5 @@
] ]
} }
}, },
"additionalProperties": false, "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
}
}
} }
+2 -2
View File
@@ -603,7 +603,7 @@ pub fn query(
#[entry_point] #[entry_point]
pub fn migrate( pub fn migrate(
mut deps: DepsMut<'_>, mut deps: DepsMut<'_>,
env: Env, _env: Env,
msg: MigrateMsg, msg: MigrateMsg,
) -> Result<Response, MixnetContractError> { ) -> Result<Response, MixnetContractError> {
set_build_information!(deps.storage)?; set_build_information!(deps.storage)?;
@@ -612,7 +612,7 @@ pub fn migrate(
let skip_state_updates = msg.unsafe_skip_state_updates.unwrap_or(false); let skip_state_updates = msg.unsafe_skip_state_updates.unwrap_or(false);
if !skip_state_updates { if !skip_state_updates {
crate::queued_migrations::add_config_score_params(deps.branch(), env, &msg)?; crate::queued_migrations::restore_node_version_history(deps.branch())?;
} }
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address // due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
@@ -34,9 +34,13 @@ impl NymNodeVersionHistory<'_> {
} }
fn next_id(&self, storage: &mut dyn Storage) -> Result<u32, MixnetContractError> { fn next_id(&self, storage: &mut dyn Storage) -> Result<u32, MixnetContractError> {
let next = self.id_counter.may_load(storage)?.unwrap_or_default(); let id = self
self.id_counter.save(storage, &next)?; .id_counter
Ok(next) .may_load(storage)?
.map(|current| current + 1)
.unwrap_or_default();
self.id_counter.save(storage, &id)?;
Ok(id)
} }
pub fn current_version( pub fn current_version(
@@ -56,10 +60,10 @@ impl NymNodeVersionHistory<'_> {
pub fn insert_new( pub fn insert_new(
&self, &self,
storage: &mut dyn Storage, storage: &mut dyn Storage,
entry: HistoricalNymNodeVersion, entry: &HistoricalNymNodeVersion,
) -> Result<u32, MixnetContractError> { ) -> Result<u32, MixnetContractError> {
let next_id = self.next_id(storage)?; 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) Ok(next_id)
} }
@@ -79,7 +83,7 @@ impl NymNodeVersionHistory<'_> {
// treat this as genesis // treat this as genesis
let genesis = let genesis =
HistoricalNymNodeVersion::genesis(raw_semver.to_string(), env.block.height); 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(); let current_semver = current.version_information.semver_unchecked();
@@ -99,7 +103,7 @@ impl NymNodeVersionHistory<'_> {
introduced_at_height: env.block.height, introduced_at_height: env.block.height,
difference_since_genesis: diff, 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))?; ADMIN.set(deps, Some(initial_admin))?;
Ok(()) 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); // let res = try_update_contract_settings(deps.as_mut(), info, new_params);
// assert_eq!(Err(MixnetContractError::ZeroActiveSet), res); // 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(())
}
}
}
} }
+135 -68
View File
@@ -1,86 +1,153 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net> // Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
mod config_score_params { mod node_version_history {
use crate::constants::CONTRACT_STATE_KEY;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnet_contract_settings::storage::NymNodeVersionHistory; use crate::mixnet_contract_settings::storage::NymNodeVersionHistory;
use cosmwasm_std::{Addr, Coin, DepsMut, Env}; use cosmwasm_std::DepsMut;
use cw_storage_plus::Item;
use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{ use mixnet_contract_common::{HistoricalNymNodeVersion, TotalVersionDifference};
ConfigScoreParams, ContractState, ContractStateParams, DelegationsParams, MigrateMsg,
OperatingCostRange, OperatorsParams, ProfitMarginRange,
};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
pub(crate) fn add_config_score_params( pub(crate) fn restore_node_version_history(
deps: DepsMut<'_>, deps: DepsMut<'_>,
env: Env,
msg: &MigrateMsg,
) -> Result<(), MixnetContractError> { ) -> Result<(), MixnetContractError> {
if semver::Version::from_str(&msg.current_nym_node_semver).is_err() { // sanity check:
return Err(MixnetContractError::InvalidNymNodeSemver { let storage = NymNodeVersionHistory::new();
provided: msg.current_nym_node_semver.to_string(), let Some(current) = storage.current_version(deps.storage)? else {
return Err(MixnetContractError::FailedMigration {
comment: "no node version history set".to_string(),
});
};
if current.version_information.semver != "1.2.1"
|| current.version_information.introduced_at_height != 15902170
{
return Err(MixnetContractError::FailedMigration {
comment: format!("unexpected current node version history. got: {current:?}"),
}); });
} }
let lost = HistoricalNymNodeVersion {
#[derive(Serialize, Deserialize)] semver: "1.1.12".to_string(),
pub struct OldContractState { introduced_at_height: 15779133,
pub owner: Option<Addr>, difference_since_genesis: TotalVersionDifference::default(),
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)?; #[allow(clippy::unwrap_used)]
// SAFETY: this information was already stored in the contract, so it must be a valid semver
let difference_since_genesis = lost.cumulative_difference_since_genesis(
&current.version_information.semver.parse().unwrap(),
);
let updated_current = HistoricalNymNodeVersion {
semver: current.version_information.semver,
introduced_at_height: current.version_information.introduced_at_height,
difference_since_genesis,
};
// initialise the version chain // restore overwritten entry for 1.1.12
NymNodeVersionHistory::new().try_insert_new( storage.version_history.save(deps.storage, 0, &lost)?;
deps.storage,
&env, // re-insert 1.2.1 as the current
&msg.current_nym_node_semver, storage
)?; .version_history
.save(deps.storage, 1, &updated_current)?;
storage.id_counter.save(deps.storage, &1)?;
Ok(()) Ok(())
} }
} }
pub(crate) use config_score_params::add_config_score_params; pub(crate) use node_version_history::restore_node_version_history;
#[cfg(test)]
mod tests {
use super::*;
use crate::mixnet_contract_settings::queries::query_nym_node_version_history_paged;
use crate::mixnet_contract_settings::storage::NymNodeVersionHistory;
use cosmwasm_std::testing::{mock_dependencies, mock_env};
use mixnet_contract_common::{
HistoricalNymNodeVersion, HistoricalNymNodeVersionEntry, TotalVersionDifference,
};
#[test]
fn fixing_history_storage() -> anyhow::Result<()> {
// current state on mainnet:
let mut deps = mock_dependencies();
let storage = NymNodeVersionHistory::new();
storage.id_counter.save(deps.as_mut().storage, &0)?;
storage.version_history.save(
deps.as_mut().storage,
0,
&HistoricalNymNodeVersion {
semver: "1.2.1".to_string(),
introduced_at_height: 15902170,
difference_since_genesis: Default::default(),
},
)?;
// run migration
restore_node_version_history(deps.as_mut())?;
let current = storage.current_version(deps.as_ref().storage)?.unwrap();
assert_eq!(current.version_information.semver, "1.2.1");
assert_eq!(current.version_information.introduced_at_height, 15902170);
assert_eq!(
current.version_information.difference_since_genesis,
TotalVersionDifference {
major: 0,
minor: 1,
patch: 0,
prerelease: 0,
}
);
let history = query_nym_node_version_history_paged(deps.as_ref(), None, None)?.history;
assert_eq!(history.len(), 2);
assert_eq!(
history,
vec![
HistoricalNymNodeVersionEntry {
id: 0,
version_information: HistoricalNymNodeVersion {
semver: "1.1.12".to_string(),
introduced_at_height: 15779133,
difference_since_genesis: Default::default(),
},
},
HistoricalNymNodeVersionEntry {
id: 1,
version_information: HistoricalNymNodeVersion {
semver: "1.2.1".to_string(),
introduced_at_height: 15902170,
difference_since_genesis: TotalVersionDifference {
major: 0,
minor: 1,
patch: 0,
prerelease: 0,
},
},
}
]
);
let counter = storage.id_counter.load(deps.as_ref().storage)?;
assert_eq!(counter, 1);
// make sure adding another version doesn't mess anything up
storage.try_insert_new(deps.as_mut().storage, &mock_env(), "1.3.0")?;
let current = storage.current_version(deps.as_ref().storage)?.unwrap();
assert_eq!(current.version_information.semver, "1.3.0");
assert_eq!(
current.version_information.difference_since_genesis,
TotalVersionDifference {
major: 0,
minor: 2,
patch: 0,
prerelease: 0,
}
);
let counter = storage.id_counter.load(deps.as_ref().storage)?;
assert_eq!(counter, 2);
Ok(())
}
}
+17 -1
View File
@@ -18,7 +18,7 @@ pub mod test_helpers {
}; };
use crate::interval::{pending_events, storage as interval_storage}; use crate::interval::{pending_events, storage as interval_storage};
use crate::mixnet_contract_settings::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::mixnet_contract_settings::storage::{rewarding_denom, rewarding_validator_address};
use crate::mixnodes::helpers::get_mixnode_details_by_id; 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)) 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 { pub fn random_address(&mut self) -> String {
format!("n1foomp{}", self.rng.next_u64()) format!("n1foomp{}", self.rng.next_u64())
} }
@@ -330,6 +334,14 @@ pub mod test_helpers {
self.deps.as_mut() 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 { pub fn env(&self) -> Env {
self.env.clone() self.env.clone()
} }
@@ -470,6 +482,10 @@ pub mod test_helpers {
.unwrap() .unwrap()
} }
pub fn admin_sender(&self) -> MessageInfo {
mock_info(self.admin().as_ref(), &[])
}
pub fn owner(&self) -> MessageInfo { pub fn owner(&self) -> MessageInfo {
self.owner.clone() self.owner.clone()
} }
@@ -1,7 +1,3 @@
---
description: Interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet.
---
# Introduction # Introduction
This site contains interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet. This site contains interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet.
@@ -1,7 +1,3 @@
---
description: Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.
---
# Introduction # Introduction
Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs. Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.
@@ -1,7 +1,3 @@
---
description: Nym's network documentation covering network architecture, node types, tokenomics, and cryptography.
---
# Introduction # Introduction
Nym's network documentation covering network architecture, node types, tokenomics, and crypto systems. Nym's network documentation covering network architecture, node types, tokenomics, and crypto systems.
@@ -1,7 +1,3 @@
---
description: Nym's Operators guide containing information and setup guides for the various components of Nym network and Nyx blockchain validators.
---
# Introduction # Introduction
This is **Nym's Operators guide**, containing information and setup guides for the various components of Nym network and Nyx blockchain validators. This is **Nym's Operators guide**, containing information and setup guides for the various components of Nym network and Nyx blockchain validators.
@@ -222,7 +222,7 @@ Good performance is much more essential than [total stake](../tokenomics.mdx#sta
For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them `config_score` = 1 for simplification): For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them `config_score` = 1 for simplification):
<br /> <br />
<AccordionTemplate name="✏️ Example: Performance ^ 20 \* total_stake calculation"> <AccordionTemplate name="✏️ Example: Performance ^ 20 * total_stake calculation">
> node_1 = 1.00 ^ 20 \* 1.0 = 1 <br /> > node_1 = 1.00 ^ 20 \* 1.0 = 1 <br />
> node_2 = 1.00 ^ 20 \* 0.5 = 0.5 <br /> > node_2 = 1.00 ^ 20 \* 0.5 = 0.5 <br />
> node_3 = 0.99 ^ 20 \* 1.0 = 0.818 <br /> > node_3 = 0.99 ^ 20 \* 1.0 = 0.818 <br />
+15 -2
View File
@@ -11,10 +11,23 @@ const config: DocsThemeConfig = {
const url = process.env.NEXT_PUBLIC_SITE_URL const url = process.env.NEXT_PUBLIC_SITE_URL
const image = url + '/nym_logo.jpg' const image = url + '/nym_logo.jpg'
// Define descriptions for different "books"
const bookDescriptions: Record<string, string> = {
'/developers': "Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.",
'/network': "Nym's network documentation covering network architecture, node types, tokenomics, and cryptography.",
'/operators': "Nym's Operators guide containing information and setup guides for the various components of Nym network and Nyx blockchain validators.",
'/apis': "Interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet."
}
const defaultDescription = 'Nym is a privacy platform. It provides strong network-level privacy against sophisticated end-to-end attackers, and anonymous access control using blinded, re-randomizable, decentralized credentials.'
const topLevel = '/' + route.split('/')[1]
const description = const description =
config.frontMatter.description || config.frontMatter.description ||
'Nym is a privacy platform. It provides strong network-level privacy against sophisticated end-to-end attackers, and anonymous access control using blinded, re-randomizable, decentralized credentials.' bookDescriptions[topLevel] ||
const title = config.title + (route === '/' ? '' : ' - Nym docs') defaultDescription
const title = config.title + (route === '/' ? '' : ' - Nym docs')
return ( return (
<> <>
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "explorer-api" name = "explorer-api"
version = "1.1.43" version = "1.1.44"
edition = "2021" edition = "2021"
license.workspace = true license.workspace = true
+1 -3
View File
@@ -120,9 +120,7 @@ impl ThreadsafeGatewayCache {
.read() .read()
.await .await
.get(&identity_key) .get(&identity_key)
.map_or(false, |cache_item| { .is_some_and(|cache_item| cache_item.valid_until > SystemTime::now())
cache_item.valid_until > SystemTime::now()
})
} }
pub(crate) async fn get_locations(&self) -> GatewayLocationCache { pub(crate) async fn get_locations(&self) -> GatewayLocationCache {
+1 -3
View File
@@ -106,9 +106,7 @@ impl ThreadsafeMixNodesCache {
.read() .read()
.await .await
.get(&mix_id) .get(&mix_id)
.map_or(false, |cache_item| { .is_some_and(|cache_item| cache_item.valid_until > SystemTime::now())
cache_item.valid_until > SystemTime::now()
})
} }
pub(crate) async fn get_locations(&self) -> MixnodeLocationCache { pub(crate) async fn get_locations(&self) -> MixnodeLocationCache {
+1 -3
View File
@@ -60,9 +60,7 @@ impl ThreadSafeNymNodesCache {
.read() .read()
.await .await
.get(&node_id) .get(&node_id)
.map_or(false, |cache_item| { .is_some_and(|cache_item| cache_item.valid_until > SystemTime::now())
cache_item.valid_until > SystemTime::now()
})
} }
pub(crate) async fn get_bonded_nymnodes( pub(crate) async fn get_bonded_nymnodes(
@@ -20,6 +20,7 @@ import { NYM_WEBSITE } from '@/app/api/constants'
import { useMainContext } from '@/app/context/main' import { useMainContext } from '@/app/context/main'
import { MobileDrawerClose } from '@/app/icons/MobileDrawerClose' import { MobileDrawerClose } from '@/app/icons/MobileDrawerClose'
import { NavOptionType, originalNavOptions } from '@/app/context/nav' import { NavOptionType, originalNavOptions } from '@/app/context/nav'
import { ReleaseAlert } from '@/app/components/ReleaseAlert'
import { DarkLightSwitchDesktop } from '@/app/components/Switch' import { DarkLightSwitchDesktop } from '@/app/components/Switch'
import { Footer } from '@/app/components/Footer' import { Footer } from '@/app/components/Footer'
import { ConnectKeplrWallet } from '@/app/components/Wallet/ConnectKeplrWallet' import { ConnectKeplrWallet } from '@/app/components/Wallet/ConnectKeplrWallet'
@@ -369,6 +370,7 @@ export const Nav: FCWithChildren = ({ children }) => {
style={{ width: `calc(100% - ${drawerWidth}px` }} style={{ width: `calc(100% - ${drawerWidth}px` }}
sx={{ py: 5, px: 6, mt: 7 }} sx={{ py: 5, px: 6, mt: 7 }}
> >
<ReleaseAlert />
{children} {children}
<Footer /> <Footer />
</Box> </Box>
@@ -22,6 +22,7 @@ import { ExpandableButton } from './DesktopNav'
import { ConnectKeplrWallet } from '../Wallet/ConnectKeplrWallet' import { ConnectKeplrWallet } from '../Wallet/ConnectKeplrWallet'
import { NetworkTitle } from '../NetworkTitle' import { NetworkTitle } from '../NetworkTitle'
import { originalNavOptions } from '@/app/context/nav' import { originalNavOptions } from '@/app/context/nav'
import { ReleaseAlert } from '@/app/components/ReleaseAlert'
import {SearchToolbar} from "@/app/components/Nav/Search"; import {SearchToolbar} from "@/app/components/Nav/Search";
export const MobileNav: FCWithChildren = ({ children }) => { export const MobileNav: FCWithChildren = ({ children }) => {
@@ -137,6 +138,7 @@ export const MobileNav: FCWithChildren = ({ children }) => {
</Drawer> </Drawer>
<Box sx={{ width: '100%', p: 4, mt: 7 }}> <Box sx={{ width: '100%', p: 4, mt: 7 }}>
<ReleaseAlert />
{children} {children}
<Footer /> <Footer />
</Box> </Box>
@@ -0,0 +1,9 @@
import { Alert, Box, Typography } from '@mui/material';
export const ReleaseAlert = () => (
<Alert severity="warning" sx={{ mb: 3, fontSize: 'medium', width: '100%' }}>
<Box>
<Typography>You are now viewing the legacy Nym mixnet explorer. Explorer 2.0 is coming soon.</Typography>
</Box>
</Alert>
);
@@ -163,4 +163,11 @@ impl ActiveClientsStore {
pub(crate) fn size(&self) -> usize { pub(crate) fn size(&self) -> usize {
self.inner.len() self.inner.len()
} }
pub fn pending_packets(&self) -> usize {
self.inner
.iter()
.map(|client| client.get_sender_ref().len())
.sum()
}
} }
@@ -7,6 +7,7 @@ use nym_crypto::asymmetric::identity;
use nym_gateway_storage::GatewayStorage; use nym_gateway_storage::GatewayStorage;
use nym_mixnet_client::forwarder::MixForwardingSender; use nym_mixnet_client::forwarder::MixForwardingSender;
use nym_node_metrics::events::MetricEventsSender; use nym_node_metrics::events::MetricEventsSender;
use nym_node_metrics::NymNodeMetrics;
use std::sync::Arc; use std::sync::Arc;
// I can see this being possible expanded with say storage or client store // I can see this being possible expanded with say storage or client store
@@ -17,7 +18,14 @@ pub(crate) struct CommonHandlerState {
pub(crate) local_identity: Arc<identity::KeyPair>, pub(crate) local_identity: Arc<identity::KeyPair>,
pub(crate) only_coconut_credentials: bool, pub(crate) only_coconut_credentials: bool,
pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig, pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig,
pub(crate) metrics: NymNodeMetrics,
pub(crate) metrics_sender: MetricEventsSender, pub(crate) metrics_sender: MetricEventsSender,
pub(crate) outbound_mix_sender: MixForwardingSender, pub(crate) outbound_mix_sender: MixForwardingSender,
pub(crate) active_clients_store: ActiveClientsStore, pub(crate) active_clients_store: ActiveClientsStore,
} }
impl CommonHandlerState {
pub(crate) fn storage(&self) -> &GatewayStorage {
&self.storage
}
}
@@ -157,6 +157,10 @@ impl<R, S> Drop for AuthenticatedHandler<R, S> {
} }
impl<R, S> AuthenticatedHandler<R, S> { impl<R, S> AuthenticatedHandler<R, S> {
pub(crate) fn inner(&self) -> &FreshHandler<R, S> {
&self.inner
}
/// Upgrades `FreshHandler` into the Authenticated variant implying the client is now authenticated /// Upgrades `FreshHandler` into the Authenticated variant implying the client is now authenticated
/// and thus allowed to perform more actions with the gateway, such as redeeming bandwidth or /// and thus allowed to perform more actions with the gateway, such as redeeming bandwidth or
/// sending sphinx packets. /// sending sphinx packets.
@@ -327,6 +331,24 @@ impl<R, S> AuthenticatedHandler<R, S> {
} }
} }
async fn handle_forget_me(
&mut self,
client: bool,
stats: bool,
) -> Result<ServerResponse, RequestHandlingError> {
if client {
self.inner()
.shared_state()
.storage()
.handle_forget_me(self.client.address)
.await?;
}
if stats {
self.send_metrics(GatewaySessionEvent::new_session_delete(self.client.address));
}
Ok(SensitiveServerResponse::ForgetMeAck {}.encrypt(&self.client.shared_keys)?)
}
async fn handle_key_upgrade( async fn handle_key_upgrade(
&mut self, &mut self,
hkdf_salt: Vec<u8>, hkdf_salt: Vec<u8>,
@@ -370,6 +392,7 @@ impl<R, S> AuthenticatedHandler<R, S> {
hkdf_salt, hkdf_salt,
derived_key_digest, derived_key_digest,
} => self.handle_key_upgrade(hkdf_salt, derived_key_digest).await, } => self.handle_key_upgrade(hkdf_salt, derived_key_digest).await,
ClientRequest::ForgetMe { client, stats } => self.handle_forget_me(client, stats).await,
_ => Err(RequestHandlingError::UnknownEncryptedTextRequest), _ => Err(RequestHandlingError::UnknownEncryptedTextRequest),
} }
} }
@@ -114,6 +114,10 @@ pub(crate) struct FreshHandler<R, S> {
} }
impl<R, S> FreshHandler<R, S> { impl<R, S> FreshHandler<R, S> {
pub(crate) fn shared_state(&self) -> &CommonHandlerState {
&self.shared_state
}
// for time being we assume handle is always constructed from raw socket. // for time being we assume handle is always constructed from raw socket.
// if we decide we want to change it, that's not too difficult // if we decide we want to change it, that's not too difficult
// also at this point I'm not entirely sure how to deal with this warning without // also at this point I'm not entirely sure how to deal with this warning without
@@ -61,7 +61,13 @@ impl Listener {
remote_addr, remote_addr,
shutdown, shutdown,
); );
tokio::spawn(handle.start_handling()); tokio::spawn(async move {
// TODO: refactor it similarly to the mixnet listener on the nym-node
let metrics_ref = handle.shared_state.metrics.clone();
metrics_ref.network.new_ingress_websocket_client();
handle.start_handling().await;
metrics_ref.network.disconnected_ingress_websocket_client();
});
} }
Err(err) => warn!("failed to get client: {err}"), Err(err) => warn!("failed to get client: {err}"),
} }
+9
View File
@@ -37,6 +37,7 @@ mod internal_service_providers;
pub use client_handling::active_clients::ActiveClientsStore; pub use client_handling::active_clients::ActiveClientsStore;
pub use nym_gateway_stats_storage::PersistentStatsStorage; pub use nym_gateway_stats_storage::PersistentStatsStorage;
pub use nym_gateway_storage::{error::GatewayStorageError, GatewayStorage}; pub use nym_gateway_storage::{error::GatewayStorageError, GatewayStorage};
use nym_node_metrics::NymNodeMetrics;
pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -81,6 +82,8 @@ pub struct GatewayTasksBuilder {
metrics_sender: MetricEventsSender, metrics_sender: MetricEventsSender,
metrics: NymNodeMetrics,
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>, mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
shutdown: TaskClient, shutdown: TaskClient,
@@ -102,12 +105,14 @@ impl Drop for GatewayTasksBuilder {
} }
impl GatewayTasksBuilder { impl GatewayTasksBuilder {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
config: Config, config: Config,
identity: Arc<ed25519::KeyPair>, identity: Arc<ed25519::KeyPair>,
storage: GatewayStorage, storage: GatewayStorage,
mix_packet_sender: MixForwardingSender, mix_packet_sender: MixForwardingSender,
metrics_sender: MetricEventsSender, metrics_sender: MetricEventsSender,
metrics: NymNodeMetrics,
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>, mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
shutdown: TaskClient, shutdown: TaskClient,
) -> GatewayTasksBuilder { ) -> GatewayTasksBuilder {
@@ -121,6 +126,7 @@ impl GatewayTasksBuilder {
storage, storage,
mix_packet_sender, mix_packet_sender,
metrics_sender, metrics_sender,
metrics,
mnemonic, mnemonic,
shutdown, shutdown,
ecash_manager: None, ecash_manager: None,
@@ -246,6 +252,7 @@ impl GatewayTasksBuilder {
local_identity: Arc::clone(&self.identity_keypair), local_identity: Arc::clone(&self.identity_keypair),
only_coconut_credentials: self.config.gateway.enforce_zk_nyms, only_coconut_credentials: self.config.gateway.enforce_zk_nyms,
bandwidth_cfg: (&self.config).into(), bandwidth_cfg: (&self.config).into(),
metrics: self.metrics.clone(),
metrics_sender: self.metrics_sender.clone(), metrics_sender: self.metrics_sender.clone(),
outbound_mix_sender: self.mix_packet_sender.clone(), outbound_mix_sender: self.mix_packet_sender.clone(),
active_clients_store: active_clients_store.clone(), active_clients_store: active_clients_store.clone(),
@@ -443,6 +450,7 @@ impl GatewayTasksBuilder {
pub async fn try_start_wireguard( pub async fn try_start_wireguard(
&mut self, &mut self,
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> { ) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
let _ = self.metrics.clone();
unimplemented!("wireguard is not supported on this platform") unimplemented!("wireguard is not supported on this platform")
} }
@@ -460,6 +468,7 @@ impl GatewayTasksBuilder {
let wg_handle = nym_wireguard::start_wireguard( let wg_handle = nym_wireguard::start_wireguard(
self.storage.clone(), self.storage.clone(),
self.metrics.clone(),
all_peers, all_peers,
self.shutdown.fork("wireguard"), self.shutdown.fork("wireguard"),
wireguard_data, wireguard_data,
+1 -1
View File
@@ -4,7 +4,7 @@
[package] [package]
name = "nym-api" name = "nym-api"
license = "GPL-3.0" license = "GPL-3.0"
version = "1.1.47" version = "1.1.48"
authors.workspace = true authors.workspace = true
edition = "2021" edition = "2021"
rust-version.workspace = true rust-version.workspace = true
@@ -183,6 +183,8 @@ impl PacketSender {
gateway_packet_router, gateway_packet_router,
Some(fresh_gateway_client_data.bandwidth_controller.clone()), Some(fresh_gateway_client_data.bandwidth_controller.clone()),
nym_statistics_common::clients::ClientStatsSender::new(None), nym_statistics_common::clients::ClientStatsSender::new(None),
#[cfg(unix)]
None,
task_client, task_client,
); );
+50 -15
View File
@@ -12,6 +12,7 @@ use futures::{stream, StreamExt};
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer}; use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer};
use nym_api_requests::models::{DescribedNodeType, NymNodeData, NymNodeDescription}; use nym_api_requests::models::{DescribedNodeType, NymNodeData, NymNodeDescription};
use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_crypto::asymmetric::ed25519;
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId, NymNodeDetails}; use nym_mixnet_contract_common::{LegacyMixLayer, NodeId, NymNodeDetails};
use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt}; use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt};
use nym_topology::gateway::GatewayConversionError; use nym_topology::gateway::GatewayConversionError;
@@ -58,6 +59,13 @@ pub enum NodeDescribeCacheError {
#[error("could not verify signed host information for node {node_id}")] #[error("could not verify signed host information for node {node_id}")]
MissignedHostInformation { node_id: NodeId }, MissignedHostInformation { node_id: NodeId },
#[error("identity of node {node_id} does not match. expected {expected} but got {got}")]
MismatchedIdentity {
node_id: NodeId,
expected: String,
got: String,
},
#[error("node {node_id} is announcing an illegal ip address")] #[error("node {node_id} is announcing an illegal ip address")]
IllegalIpAddress { node_id: NodeId }, IllegalIpAddress { node_id: NodeId },
} }
@@ -289,6 +297,15 @@ async fn try_get_description(
let host_info = client.get_host_information().await.map_err(map_query_err)?; let host_info = client.get_host_information().await.map_err(map_query_err)?;
// check if the identity key matches the information provided during bonding
if data.expected_identity != host_info.keys.ed25519_identity {
return Err(NodeDescribeCacheError::MismatchedIdentity {
node_id: data.node_id,
expected: data.expected_identity.to_base58_string(),
got: host_info.keys.ed25519_identity.to_base58_string(),
});
}
if !host_info.verify_host_information() { if !host_info.verify_host_information() {
return Err(NodeDescribeCacheError::MissignedHostInformation { return Err(NodeDescribeCacheError::MissignedHostInformation {
node_id: data.node_id, node_id: data.node_id,
@@ -315,47 +332,58 @@ async fn try_get_description(
pub(crate) struct RefreshData { pub(crate) struct RefreshData {
host: String, host: String,
node_id: NodeId, node_id: NodeId,
expected_identity: ed25519::PublicKey,
node_type: DescribedNodeType, node_type: DescribedNodeType,
port: Option<u16>, port: Option<u16>,
} }
impl<'a> From<&'a LegacyMixNodeDetailsWithLayer> for RefreshData { impl<'a> TryFrom<&'a LegacyMixNodeDetailsWithLayer> for RefreshData {
fn from(node: &'a LegacyMixNodeDetailsWithLayer) -> Self { type Error = ed25519::Ed25519RecoveryError;
RefreshData::new(
fn try_from(node: &'a LegacyMixNodeDetailsWithLayer) -> Result<Self, Self::Error> {
Ok(RefreshData::new(
&node.bond_information.mix_node.host, &node.bond_information.mix_node.host,
node.bond_information.identity().parse()?,
DescribedNodeType::LegacyMixnode, DescribedNodeType::LegacyMixnode,
node.mix_id(), node.mix_id(),
Some(node.bond_information.mix_node.http_api_port), Some(node.bond_information.mix_node.http_api_port),
) ))
} }
} }
impl<'a> From<&'a LegacyGatewayBondWithId> for RefreshData { impl<'a> TryFrom<&'a LegacyGatewayBondWithId> for RefreshData {
fn from(node: &'a LegacyGatewayBondWithId) -> Self { type Error = ed25519::Ed25519RecoveryError;
RefreshData::new(
fn try_from(node: &'a LegacyGatewayBondWithId) -> Result<Self, Self::Error> {
Ok(RefreshData::new(
&node.bond.gateway.host, &node.bond.gateway.host,
node.bond.identity().parse()?,
DescribedNodeType::LegacyGateway, DescribedNodeType::LegacyGateway,
node.node_id, node.node_id,
None, None,
) ))
} }
} }
impl<'a> From<&'a NymNodeDetails> for RefreshData { impl<'a> TryFrom<&'a NymNodeDetails> for RefreshData {
fn from(node: &'a NymNodeDetails) -> Self { type Error = ed25519::Ed25519RecoveryError;
RefreshData::new(
fn try_from(node: &'a NymNodeDetails) -> Result<Self, Self::Error> {
Ok(RefreshData::new(
&node.bond_information.node.host, &node.bond_information.node.host,
node.bond_information.identity().parse()?,
DescribedNodeType::NymNode, DescribedNodeType::NymNode,
node.node_id(), node.node_id(),
node.bond_information.node.custom_http_port, node.bond_information.node.custom_http_port,
) ))
} }
} }
impl RefreshData { impl RefreshData {
pub fn new( pub fn new(
host: impl Into<String>, host: impl Into<String>,
expected_identity: ed25519::PublicKey,
node_type: DescribedNodeType, node_type: DescribedNodeType,
node_id: NodeId, node_id: NodeId,
port: Option<u16>, port: Option<u16>,
@@ -363,6 +391,7 @@ impl RefreshData {
RefreshData { RefreshData {
host: host.into(), host: host.into(),
node_id, node_id,
expected_identity,
node_type, node_type,
port, port,
} }
@@ -404,7 +433,9 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain mixnodes information from the cache"), None => error!("failed to obtain mixnodes information from the cache"),
Some(legacy_mixnodes) => { Some(legacy_mixnodes) => {
for node in &**legacy_mixnodes { for node in &**legacy_mixnodes {
nodes_to_query.push(node.into()) if let Ok(data) = node.try_into() {
nodes_to_query.push(data);
}
} }
} }
} }
@@ -413,7 +444,9 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain gateways information from the cache"), None => error!("failed to obtain gateways information from the cache"),
Some(legacy_gateways) => { Some(legacy_gateways) => {
for node in &**legacy_gateways { for node in &**legacy_gateways {
nodes_to_query.push(node.into()) if let Ok(data) = node.try_into() {
nodes_to_query.push(data);
}
} }
} }
} }
@@ -422,7 +455,9 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain nym-nodes information from the cache"), None => error!("failed to obtain nym-nodes information from the cache"),
Some(nym_nodes) => { Some(nym_nodes) => {
for node in &**nym_nodes { for node in &**nym_nodes {
nodes_to_query.push(node.into()) if let Ok(data) = node.try_into() {
nodes_to_query.push(data);
}
} }
} }
} }
+3 -3
View File
@@ -385,7 +385,7 @@ impl NymContractCache {
.iter() .iter()
.find(|n| n.bond_information.identity() == encoded_identity) .find(|n| n.bond_information.identity() == encoded_identity)
{ {
return Some(nym_node.into()); return nym_node.try_into().ok();
} }
// 2. check legacy mixnodes // 2. check legacy mixnodes
@@ -394,7 +394,7 @@ impl NymContractCache {
.iter() .iter()
.find(|n| n.bond_information.identity() == encoded_identity) .find(|n| n.bond_information.identity() == encoded_identity)
{ {
return Some(mixnode.into()); return mixnode.try_into().ok();
} }
// 3. check legacy gateways // 3. check legacy gateways
@@ -403,7 +403,7 @@ impl NymContractCache {
.iter() .iter()
.find(|n| n.identity() == &encoded_identity) .find(|n| n.identity() == &encoded_identity)
{ {
return Some(gateway.into()); return gateway.try_into().ok();
} }
None None
+4
View File
@@ -18,6 +18,7 @@ use crate::support::storage::models::{
use dashmap::DashMap; use dashmap::DashMap;
use nym_mixnet_contract_common::NodeId; use nym_mixnet_contract_common::NodeId;
use nym_types::monitoring::NodeResult; use nym_types::monitoring::NodeResult;
use sqlx::sqlite::{SqliteAutoVacuum, SqliteSynchronous};
use sqlx::ConnectOptions; use sqlx::ConnectOptions;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
@@ -67,6 +68,9 @@ impl NymApiStorage {
// TODO: we can inject here more stuff based on our nym-api global config // TODO: we can inject here more stuff based on our nym-api global config
// struct. Maybe different pool size or timeout intervals? // struct. Maybe different pool size or timeout intervals?
let connect_opts = sqlx::sqlite::SqliteConnectOptions::new() let connect_opts = sqlx::sqlite::SqliteConnectOptions::new()
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.filename(database_path) .filename(database_path)
.create_if_missing(true) .create_if_missing(true)
.log_statements(LevelFilter::Trace) .log_statements(LevelFilter::Trace)
@@ -1,8 +1,22 @@
// Copyright 2024 Nym Technologies SA <contact@nymtech.net> // Copyright 2024 Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use nym_bin_common::bin_info;
use time::OffsetDateTime; use time::OffsetDateTime;
use tracing::{debug, info, warn}; use tokio_util::sync::CancellationToken;
use tracing::{debug, error, info, warn};
use crate::{
cli::Cli,
deposit_maker::DepositMaker,
error::VpnApiError,
http::{
state::{ApiState, ChainClient},
HttpServer,
},
storage::VpnApiStorage,
tasks::StoragePruner,
};
pub struct LockTimer { pub struct LockTimer {
created: OffsetDateTime, created: OffsetDateTime,
@@ -40,3 +54,88 @@ impl Default for LockTimer {
} }
} }
} }
pub async fn wait_for_signal() {
use tokio::signal::unix::{signal, SignalKind};
// if we fail to setup the signals, we should just blow up
#[allow(clippy::expect_used)]
let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel");
#[allow(clippy::expect_used)]
let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel");
tokio::select! {
_ = tokio::signal::ctrl_c() => {
info!("Received SIGINT");
},
_ = sigterm.recv() => {
info!("Received SIGTERM");
}
_ = sigquit.recv() => {
info!("Received SIGQUIT");
}
}
}
fn build_sha_short() -> &'static str {
let bin_info = bin_info!();
if bin_info.commit_sha.len() < 7 {
panic!("unavailable build commit sha")
}
if bin_info.commit_sha == "VERGEN_IDEMPOTENT_OUTPUT" {
error!("the binary hasn't been built correctly. it doesn't have a commit sha information");
return "unknown";
}
&bin_info.commit_sha[..7]
}
pub(crate) async fn run_api(cli: Cli) -> Result<(), VpnApiError> {
// create the tasks
let bind_address = cli.bind_address();
let storage = VpnApiStorage::init(cli.persistent_storage_path()).await?;
let mnemonic = cli.mnemonic;
let auth_token = cli.http_auth_token;
let webhook_cfg = cli.webhook;
let chain_client = ChainClient::new(mnemonic)?;
let cancellation_token = CancellationToken::new();
let deposit_maker = DepositMaker::new(
build_sha_short(),
chain_client.clone(),
cli.max_concurrent_deposits,
cancellation_token.clone(),
);
let deposit_request_sender = deposit_maker.deposit_request_sender();
let api_state = ApiState::new(
storage.clone(),
webhook_cfg,
chain_client,
deposit_request_sender,
cancellation_token.clone(),
)
.await?;
let http_server = HttpServer::new(
bind_address,
api_state.clone(),
auth_token,
cancellation_token.clone(),
);
let storage_pruner = StoragePruner::new(cancellation_token, storage);
// spawn all the tasks
api_state.try_spawn(http_server.run_forever());
api_state.try_spawn(storage_pruner.run_forever());
api_state.try_spawn(deposit_maker.run_forever());
// wait for cancel signal (SIGINT, SIGTERM or SIGQUIT)
wait_for_signal().await;
// cancel all the tasks and wait for all task to terminate
api_state.cancel_and_wait().await;
Ok(())
}
@@ -710,7 +710,7 @@ pub(crate) struct ChainWritePermit<'a> {
inner: RwLockWriteGuard<'a, DirectSigningHttpRpcNyxdClient>, inner: RwLockWriteGuard<'a, DirectSigningHttpRpcNyxdClient>,
} }
impl<'a> ChainWritePermit<'a> { impl ChainWritePermit<'_> {
pub(crate) async fn make_deposits( pub(crate) async fn make_deposits(
self, self,
short_sha: &'static str, short_sha: &'static str,
@@ -6,117 +6,30 @@
#![warn(clippy::todo)] #![warn(clippy::todo)]
#![warn(clippy::dbg_macro)] #![warn(clippy::dbg_macro)]
use crate::cli::Cli; cfg_if::cfg_if! {
use crate::deposit_maker::DepositMaker; if #[cfg(unix)] {
use crate::error::VpnApiError; use crate::cli::Cli;
use crate::http::state::{ApiState, ChainClient}; use clap::Parser;
use crate::http::HttpServer; use nym_bin_common::bin_info_owned;
use crate::storage::VpnApiStorage; use nym_bin_common::logging::setup_tracing_logger;
use crate::tasks::StoragePruner; use nym_network_defaults::setup_env;
use clap::Parser; use tracing::{info, trace};
use nym_bin_common::logging::setup_tracing_logger;
use nym_bin_common::{bin_info, bin_info_owned};
use nym_network_defaults::setup_env;
use tokio_util::sync::CancellationToken;
use tracing::{error, info, trace};
pub mod cli; pub mod cli;
pub mod config; pub mod config;
pub mod credentials; pub mod credentials;
mod deposit_maker; mod deposit_maker;
pub mod error; pub mod error;
pub mod helpers; pub mod helpers;
pub mod http; pub mod http;
pub mod nym_api_helpers; pub mod nym_api_helpers;
pub mod storage; pub mod storage;
pub mod tasks; pub mod tasks;
mod webhook; mod webhook;
pub async fn wait_for_signal() {
use tokio::signal::unix::{signal, SignalKind};
// if we fail to setup the signals, we should just blow up
#[allow(clippy::expect_used)]
let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel");
#[allow(clippy::expect_used)]
let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel");
tokio::select! {
_ = tokio::signal::ctrl_c() => {
info!("Received SIGINT");
},
_ = sigterm.recv() => {
info!("Received SIGTERM");
}
_ = sigquit.recv() => {
info!("Received SIGQUIT");
}
} }
} }
fn build_sha_short() -> &'static str { #[cfg(unix)]
let bin_info = bin_info!();
if bin_info.commit_sha.len() < 7 {
panic!("unavailable build commit sha")
}
if bin_info.commit_sha == "VERGEN_IDEMPOTENT_OUTPUT" {
error!("the binary hasn't been built correctly. it doesn't have a commit sha information");
return "unknown";
}
&bin_info.commit_sha[..7]
}
async fn run_api(cli: Cli) -> Result<(), VpnApiError> {
// create the tasks
let bind_address = cli.bind_address();
let storage = VpnApiStorage::init(cli.persistent_storage_path()).await?;
let mnemonic = cli.mnemonic;
let auth_token = cli.http_auth_token;
let webhook_cfg = cli.webhook;
let chain_client = ChainClient::new(mnemonic)?;
let cancellation_token = CancellationToken::new();
let deposit_maker = DepositMaker::new(
build_sha_short(),
chain_client.clone(),
cli.max_concurrent_deposits,
cancellation_token.clone(),
);
let deposit_request_sender = deposit_maker.deposit_request_sender();
let api_state = ApiState::new(
storage.clone(),
webhook_cfg,
chain_client,
deposit_request_sender,
cancellation_token.clone(),
)
.await?;
let http_server = HttpServer::new(
bind_address,
api_state.clone(),
auth_token,
cancellation_token.clone(),
);
let storage_pruner = StoragePruner::new(cancellation_token, storage);
// spawn all the tasks
api_state.try_spawn(http_server.run_forever());
api_state.try_spawn(storage_pruner.run_forever());
api_state.try_spawn(deposit_maker.run_forever());
// wait for cancel signal (SIGINT, SIGTERM or SIGQUIT)
wait_for_signal().await;
// cancel all the tasks and wait for all task to terminate
api_state.cancel_and_wait().await;
Ok(())
}
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
// std::env::set_var( // std::env::set_var(
@@ -134,6 +47,13 @@ async fn main() -> anyhow::Result<()> {
let bin_info = bin_info_owned!(); let bin_info = bin_info_owned!();
info!("using the following version: {bin_info}"); info!("using the following version: {bin_info}");
run_api(cli).await?; helpers::run_api(cli).await?;
Ok(()) Ok(())
} }
#[cfg(not(unix))]
#[tokio::main]
async fn main() -> anyhow::Result<()> {
eprintln!("This tool is only supported on Unix systems");
std::process::exit(1)
}
@@ -16,6 +16,7 @@ use nym_validator_client::ecash::BlindedSignatureResponse;
use nym_validator_client::nym_api::EpochId; use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::ecash_query_client::DepositId; use nym_validator_client::nyxd::contract_traits::ecash_query_client::DepositId;
use nym_validator_client::nyxd::Coin; use nym_validator_client::nyxd::Coin;
use sqlx::sqlite::{SqliteAutoVacuum, SqliteSynchronous};
use sqlx::ConnectOptions; use sqlx::ConnectOptions;
use std::fmt::Debug; use std::fmt::Debug;
use std::path::Path; use std::path::Path;
@@ -40,6 +41,9 @@ impl VpnApiStorage {
debug!("Attempting to connect to database"); debug!("Attempting to connect to database");
let opts = sqlx::sqlite::SqliteConnectOptions::new() let opts = sqlx::sqlite::SqliteConnectOptions::new()
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.filename(database_path) .filename(database_path)
.create_if_missing(true) .create_if_missing(true)
.log_statements(LevelFilter::Trace) .log_statements(LevelFilter::Trace)
+2
View File
@@ -27,12 +27,14 @@ tokio = { workspace = true, features = ["macros", "time"] }
tokio-util = { workspace = true } tokio-util = { workspace = true }
utoipa = { workspace = true, features = ["axum_extras"] } utoipa = { workspace = true, features = ["axum_extras"] }
utoipa-swagger-ui = { workspace = true, features = ["axum"] } utoipa-swagger-ui = { workspace = true, features = ["axum"] }
tokio-postgres = "0.7"
# internal # internal
nym-bin-common = { path = "../common/bin-common" } nym-bin-common = { path = "../common/bin-common" }
nym-client-core = { path = "../common/client-core" } nym-client-core = { path = "../common/client-core" }
nym-crypto = { path = "../common/crypto" } nym-crypto = { path = "../common/crypto" }
nym-network-defaults = { path = "../common/network-defaults" } nym-network-defaults = { path = "../common/network-defaults" }
nym-gateway-requests = { path = "../common/gateway-requests" }
nym-sdk = { path = "../sdk/rust/nym-sdk" } nym-sdk = { path = "../sdk/rust/nym-sdk" }
nym-sphinx = { path = "../common/nymsphinx" } nym-sphinx = { path = "../common/nymsphinx" }
nym-topology = { path = "../common/topology" } nym-topology = { path = "../common/topology" }
+3 -2
View File
@@ -9,13 +9,14 @@ network=${NYM_NETWORK:-mainnet}
timeout=${LOCUST_TIMEOUT:-600} timeout=${LOCUST_TIMEOUT:-600}
users=${LOCUST_USERS:-10} users=${LOCUST_USERS:-10}
processes=${LOCUST_PROCESSES:-4} processes=${LOCUST_PROCESSES:-4}
_database_url=${DATABASE_URL}
RUST_LOG=info nym-network-monitor --env envs/"${network}".env --private-key "${_private_key}" & RUST_LOG=info nym-network-monitor --env envs/"${network}".env --private-key "${_private_key}" --database-url "${_database_url}" &
nnm_pid=$! nnm_pid=$!
sleep 10 sleep 10
python -m locust -H http://${NYM_NETWORK_MONITOR_HOST}:${NYM_NETWORK_MONITOR_PORT} --processes "${processes}" --autostart --autoquit 60 -u "${users}" -t "${timeout}"s & python -m locust -H http://"${NYM_NETWORK_MONITOR_HOST}":"${NYM_NETWORK_MONITOR_PORT}" --processes "${processes}" --autostart --autoquit 60 -u "${users}" -t "${timeout}"s &
locust_pid=$! locust_pid=$!
wait $locust_pid wait $locust_pid
+123 -42
View File
@@ -1,7 +1,10 @@
use std::collections::{HashMap, HashSet}; use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use anyhow::Result; use anyhow::Result;
use futures::{stream::FuturesUnordered, StreamExt}; use futures::{pin_mut, stream::FuturesUnordered, StreamExt};
use log::{debug, info}; use log::{debug, info};
use nym_sphinx::chunking::{monitoring, SentFragment}; use nym_sphinx::chunking::{monitoring, SentFragment};
use nym_topology::{gateway, mix, NymTopology}; use nym_topology::{gateway, mix, NymTopology};
@@ -10,6 +13,7 @@ use nym_validator_client::nym_api::routes::{API_VERSION, STATUS, SUBMIT_GATEWAY,
use rand::SeedableRng; use rand::SeedableRng;
use rand_chacha::ChaCha8Rng; use rand_chacha::ChaCha8Rng;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio_postgres::{binary_copy::BinaryCopyInWriter, types::Type, Client};
use utoipa::ToSchema; use utoipa::ToSchema;
use crate::{NYM_API_URL, PRIVATE_KEY, TOPOLOGY}; use crate::{NYM_API_URL, PRIVATE_KEY, TOPOLOGY};
@@ -23,20 +27,20 @@ struct HydratedRoute {
struct GatewayStats(u32, u32); struct GatewayStats(u32, u32);
impl GatewayStats { impl GatewayStats {
fn new(sent: u32, recv: u32) -> Self { fn new(success: u32, failure: u32) -> Self {
GatewayStats(sent, recv) GatewayStats(success, failure)
} }
fn success(&self) -> u32 { fn success(&self) -> u32 {
self.0 self.0
} }
fn failed(&self) -> u32 { fn failure(&self) -> u32 {
self.1 self.1
} }
fn reliability(&self) -> f64 { fn reliability(&self) -> f64 {
self.success() as f64 / (self.success() + self.failed()) as f64 self.success() as f64 / (self.success() + self.failure()) as f64
} }
fn incr_success(&mut self) { fn incr_success(&mut self) {
@@ -321,48 +325,125 @@ pub async fn monitor_mixnode_results() -> anyhow::Result<Vec<NodeResult>> {
.collect()) .collect())
} }
pub async fn submit_metrics() -> anyhow::Result<()> { async fn submit_node_stats_to_db(client: Arc<Client>) -> anyhow::Result<()> {
let node_stats = monitor_mixnode_results().await?; let client = Arc::clone(&client);
let gateway_stats = monitor_gateway_results().await?; let node_stats = all_node_stats().await?;
info!("Submitting metrics to {}", *NYM_API_URL); let sink = client
let client = reqwest::Client::new(); .copy_in("COPY node_stats (node_id, identity, reliability, complete_routes, incomplete_routes) FROM STDIN BINARY")
.await?;
let node_submit_url = format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_NODE}", &*NYM_API_URL); let writer = BinaryCopyInWriter::new(
let gateway_submit_url = format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_GATEWAY}", &*NYM_API_URL); sink,
&[Type::INT4, Type::TEXT, Type::FLOAT8, Type::INT8, Type::INT8],
);
pin_mut!(writer);
info!("Submitting {} mixnode measurements", node_stats.len()); for stat in node_stats {
writer
.as_mut()
.write(&[
&(stat.mix_id as i32),
&stat.identity,
&stat.reliability,
&(stat.complete_routes as i64),
&(stat.incomplete_routes as i64),
])
.await?;
}
node_stats writer.finish().await?;
.chunks(10)
.map(|chunk| {
let monitor_message =
MonitorMessage::new(chunk.to_vec(), PRIVATE_KEY.get().expect("We've set this!"));
client.post(&node_submit_url).json(&monitor_message).send()
})
.collect::<FuturesUnordered<_>>()
.collect::<Vec<Result<_, _>>>()
.await
.into_iter()
.collect::<Result<Vec<_>, _>>()?;
info!("Submitting {} gateway measurements", gateway_stats.len()); Ok(())
}
gateway_stats async fn submit_gateway_stats_to_db(client: Arc<Client>) -> anyhow::Result<()> {
.chunks(10) let client = Arc::clone(&client);
.map(|chunk| { let network_account = NetworkAccount::finalize()?;
let monitor_message = let gateway_stats = network_account.gateway_stats;
MonitorMessage::new(chunk.to_vec(), PRIVATE_KEY.get().expect("We've set this!"));
client let sink = client
.post(&gateway_submit_url) .copy_in("COPY gateway_stats (identity, reliability, success, failure) FROM STDIN BINARY")
.json(&monitor_message) .await?;
.send()
}) let writer = BinaryCopyInWriter::new(sink, &[Type::TEXT, Type::FLOAT8, Type::INT8, Type::INT8]);
.collect::<FuturesUnordered<_>>() pin_mut!(writer);
.collect::<Vec<Result<_, _>>>()
.await for (key, stats) in gateway_stats {
.into_iter() writer
.collect::<Result<Vec<_>, _>>()?; .as_mut()
.write(&[
&key,
&stats.reliability(),
&(stats.success() as i64),
&(stats.failure() as i64),
])
.await?;
}
writer.finish().await?;
Ok(())
}
pub async fn submit_metrics_to_db(client: Arc<Client>) -> anyhow::Result<()> {
let client = Arc::clone(&client);
let client2 = Arc::clone(&client);
submit_node_stats_to_db(client).await?;
submit_gateway_stats_to_db(client2).await?;
Ok(())
}
pub async fn submit_metrics(client: Option<Arc<Client>>) -> anyhow::Result<()> {
if let Some(client) = client {
submit_metrics_to_db(client).await?;
}
if let Some(private_key) = PRIVATE_KEY.get() {
let node_stats = monitor_mixnode_results().await?;
let gateway_stats = monitor_gateway_results().await?;
info!("Submitting metrics to {}", *NYM_API_URL);
let client = reqwest::Client::new();
let node_submit_url = format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_NODE}", &*NYM_API_URL);
let gateway_submit_url =
format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_GATEWAY}", &*NYM_API_URL);
info!("Submitting {} mixnode measurements", node_stats.len());
node_stats
.chunks(10)
.map(|chunk| {
let monitor_message = MonitorMessage::new(chunk.to_vec(), private_key);
client.post(&node_submit_url).json(&monitor_message).send()
})
.collect::<FuturesUnordered<_>>()
.collect::<Vec<Result<_, _>>>()
.await
.into_iter()
.collect::<Result<Vec<_>, _>>()?;
info!("Submitting {} gateway measurements", gateway_stats.len());
gateway_stats
.chunks(10)
.map(|chunk| {
let monitor_message = MonitorMessage::new(
chunk.to_vec(),
PRIVATE_KEY.get().expect("We've set this!"),
);
client
.post(&gateway_submit_url)
.json(&monitor_message)
.send()
})
.collect::<FuturesUnordered<_>>()
.collect::<Vec<Result<_, _>>>()
.await
.into_iter()
.collect::<Result<Vec<_>, _>>()?;
}
NetworkAccount::empty_buffers(); NetworkAccount::empty_buffers();
+34 -7
View File
@@ -2,7 +2,8 @@ use crate::http::HttpServer;
use accounting::submit_metrics; use accounting::submit_metrics;
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::Parser;
use log::{info, warn}; use log::{error, info, warn};
use nym_client_core::ForgetMe;
use nym_crypto::asymmetric::ed25519::PrivateKey; use nym_crypto::asymmetric::ed25519::PrivateKey;
use nym_network_defaults::setup_env; use nym_network_defaults::setup_env;
use nym_network_defaults::var_names::NYM_API; use nym_network_defaults::var_names::NYM_API;
@@ -21,6 +22,7 @@ use std::{
}; };
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
use tokio::{signal::ctrl_c, sync::RwLock}; use tokio::{signal::ctrl_c, sync::RwLock};
use tokio_postgres::NoTls;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
static NYM_API_URL: LazyLock<String> = LazyLock::new(|| { static NYM_API_URL: LazyLock<String> = LazyLock::new(|| {
@@ -56,7 +58,11 @@ async fn make_clients(
loop { loop {
if Arc::strong_count(&dropped_client) == 1 { if Arc::strong_count(&dropped_client) == 1 {
if let Some(client) = Arc::into_inner(dropped_client) { if let Some(client) = Arc::into_inner(dropped_client) {
client.into_inner().disconnect().await; // let forget_me = ClientRequest::ForgetMe {
// also_from_stats: true,
// };
let client_handle = client.into_inner();
client_handle.disconnect().await;
} else { } else {
warn!("Failed to drop client, client had more then one strong ref") warn!("Failed to drop client, client had more then one strong ref")
} }
@@ -89,6 +95,7 @@ async fn make_client(topology: NymTopology) -> Result<MixnetClient> {
.network_details(net) .network_details(net)
.custom_topology_provider(topology_provider) .custom_topology_provider(topology_provider)
.debug_config(mixnet_debug_config(0)) .debug_config(mixnet_debug_config(0))
.with_forget_me(ForgetMe::new_all())
// .enable_credentials_mode() // .enable_credentials_mode()
.build()?; .build()?;
@@ -130,7 +137,10 @@ struct Args {
generate_key_pair: bool, generate_key_pair: bool,
#[arg(long)] #[arg(long)]
private_key: String, private_key: Option<String>,
#[arg(long, env = "DATABASE_URL")]
database_url: Option<String>,
} }
fn generate_key_pair() -> Result<()> { fn generate_key_pair() -> Result<()> {
@@ -168,8 +178,10 @@ async fn main() -> Result<()> {
std::process::exit(0); std::process::exit(0);
} }
let pk = PrivateKey::from_base58_string(&args.private_key)?; if let Some(private_key) = args.private_key {
PRIVATE_KEY.set(pk).ok(); let pk = PrivateKey::from_base58_string(&private_key)?;
PRIVATE_KEY.set(pk).ok();
}
TOPOLOGY TOPOLOGY
.set(if let Some(topology_file) = args.topology { .set(if let Some(topology_file) = args.topology {
@@ -197,16 +209,31 @@ async fn main() -> Result<()> {
info!("Waiting for message (ctrl-c to exit)"); info!("Waiting for message (ctrl-c to exit)");
let client = if let Some(database_url) = args.database_url {
let (client, connection) = tokio_postgres::connect(&database_url, NoTls).await?;
tokio::spawn(async move {
if let Err(e) = connection.await {
error!("Postgres connection error: {}", e);
}
});
Some(Arc::new(client))
} else {
None
};
loop { loop {
let client = client.as_ref().map(Arc::clone);
match tokio::time::timeout(Duration::from_secs(600), ctrl_c()).await { match tokio::time::timeout(Duration::from_secs(600), ctrl_c()).await {
Ok(_) => { Ok(_) => {
info!("Received kill signal, shutting down, submitting final batch of metrics"); info!("Received kill signal, shutting down, submitting final batch of metrics");
submit_metrics().await?; submit_metrics(client).await?;
break; break;
} }
Err(_) => { Err(_) => {
info!("Submitting metrics, cleaning metric buffers"); info!("Submitting metrics, cleaning metric buffers");
submit_metrics().await?; submit_metrics(client).await?;
} }
}; };
} }
@@ -3,7 +3,7 @@
set -eu set -eu
export ENVIRONMENT=${ENVIRONMENT:-"sandbox"} export ENVIRONMENT=${ENVIRONMENT:-"sandbox"}
probe_git_ref="nym-vpn-core-v1.0.0-rc.14" probe_git_ref="nym-vpn-core-v1.1.0"
crate_root=$(dirname $(realpath "$0")) crate_root=$(dirname $(realpath "$0"))
monorepo_root=$(realpath "${crate_root}/../..") monorepo_root=$(realpath "${crate_root}/../..")
@@ -54,7 +54,7 @@ function swarm() {
echo "All agents completed" echo "All agents completed"
} }
copy_gw_probe # copy_gw_probe
build_agent build_agent
swarm $workers swarm $workers
@@ -1,6 +1,8 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use sqlx::{Connection, SqliteConnection}; use sqlx::{Connection, SqliteConnection};
#[cfg(target_family = "unix")]
use std::fs::Permissions; use std::fs::Permissions;
#[cfg(target_family = "unix")]
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
use tokio::{fs::File, io::AsyncWriteExt}; use tokio::{fs::File, io::AsyncWriteExt};
@@ -39,7 +41,10 @@ async fn write_db_path_to_file(out_dir: &str, db_filename: &str) -> anyhow::Resu
file.write_all(format!("sqlite3 {}/{}", out_dir, db_filename).as_bytes()) file.write_all(format!("sqlite3 {}/{}", out_dir, db_filename).as_bytes())
.await?; .await?;
#[cfg(target_family = "unix")]
file.set_permissions(Permissions::from_mode(0o755)) file.set_permissions(Permissions::from_mode(0o755))
.await .await
.map_err(From::from) .map_err(anyhow::Error::from)?;
Ok(())
} }
@@ -2,6 +2,7 @@
set -e set -e
user_rust_log_preference=$RUST_LOG
export NYM_API_CLIENT_TIMEOUT=60 export NYM_API_CLIENT_TIMEOUT=60
export EXPLORER_CLIENT_TIMEOUT=60 export EXPLORER_CLIENT_TIMEOUT=60
export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=120 export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=120
@@ -20,7 +21,8 @@ function run_bare() {
set -a set -a
source "${monorepo_root}/envs/${ENVIRONMENT}.env" source "${monorepo_root}/envs/${ENVIRONMENT}.env"
set +a set +a
export RUST_LOG=debug export RUST_LOG=${user_rust_log_preference:-debug}
echo "RUST_LOG=${RUST_LOG}"
# --conection-url is provided in build.rs # --conection-url is provided in build.rs
cargo run --package nym-node-status-api cargo run --package nym-node-status-api
@@ -154,31 +154,39 @@ mod api_regression {
use super::*; use super::*;
use std::{env::var, sync::LazyLock}; use std::{env::var, sync::LazyLock};
static IPINFO_TOKEN: LazyLock<String> = LazyLock::new(|| var("IPINFO_API_TOKEN").unwrap()); static IPINFO_TOKEN: LazyLock<Option<String>> = LazyLock::new(|| var("IPINFO_API_TOKEN").ok());
static CI: LazyLock<Option<String>> = LazyLock::new(|| var("CI").ok());
#[tokio::test] #[tokio::test]
async fn should_parse_response() { async fn should_parse_response() {
let client = IpInfoClient::new(&(*IPINFO_TOKEN)); if CI.is_none() {
let my_ip = reqwest::get("https://api.ipify.org") return;
.await }
.expect("Couldn't get own IP") if let Some(token) = &*IPINFO_TOKEN {
.text() let client = IpInfoClient::new(token);
.await let my_ip = reqwest::get("https://api.ipify.org")
.unwrap(); .await
.expect("Couldn't get own IP")
.text()
.await
.unwrap();
let location_result = client.locate_ip(my_ip).await; let location_result = client.locate_ip(my_ip).await;
assert!(location_result.is_ok(), "Did ipinfo response change?"); assert!(location_result.is_ok(), "Did ipinfo response change?");
assert!( assert!(
client.check_remaining_bandwidth().await.is_ok(), client.check_remaining_bandwidth().await.is_ok(),
"Failed to check remaining bandwidth?" "Failed to check remaining bandwidth?"
); );
// when serialized, these fields should be present because they're exposed over API // when serialized, these fields should be present because they're exposed over API
let location_result = location_result.unwrap(); let location_result = location_result.unwrap();
let json = serde_json::to_value(&location_result).unwrap(); let json = serde_json::to_value(&location_result).unwrap();
assert!(json.get("two_letter_iso_country_code").is_some()); assert!(json.get("two_letter_iso_country_code").is_some());
assert!(json.get("latitude").is_some()); assert!(json.get("latitude").is_some());
assert!(json.get("longitude").is_some()); assert!(json.get("longitude").is_some());
} else {
panic!("IPINFO_API_TOKEN not set");
}
} }
} }
+1 -1
View File
@@ -3,7 +3,7 @@
[package] [package]
name = "nym-node" name = "nym-node"
version = "1.2.0" version = "1.3.1"
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
homepage.workspace = true homepage.workspace = true
+12
View File
@@ -1,9 +1,16 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net> // Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
#![warn(clippy::todo)]
#![warn(clippy::dbg_macro)]
use crate::entry::EntryStats; use crate::entry::EntryStats;
use crate::mixnet::MixingStats; use crate::mixnet::MixingStats;
use crate::network::NetworkStats; use crate::network::NetworkStats;
use crate::process::NodeStats;
use crate::wireguard::WireguardStats;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
@@ -11,6 +18,9 @@ pub mod entry;
pub mod events; pub mod events;
pub mod mixnet; pub mod mixnet;
pub mod network; pub mod network;
pub mod process;
pub mod prometheus_wrapper;
pub mod wireguard;
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct NymNodeMetrics { pub struct NymNodeMetrics {
@@ -34,6 +44,8 @@ impl Deref for NymNodeMetrics {
pub struct NymNodeMetricsInner { pub struct NymNodeMetricsInner {
pub mixnet: MixingStats, pub mixnet: MixingStats,
pub entry: EntryStats, pub entry: EntryStats,
pub wireguard: WireguardStats,
pub network: NetworkStats, pub network: NetworkStats,
pub process: NodeStats,
} }
+10 -7
View File
@@ -131,13 +131,6 @@ impl MixingStats {
.or_default() .or_default()
.dropped += 1; .dropped += 1;
} }
pub fn egress_dropped_final_hop_packet(&self) {
todo!()
// self.egress
// .final_hop_packets_dropped
// .fetch_add(1, Ordering::Relaxed);
}
} }
#[derive(Clone, Copy, Default, PartialEq)] #[derive(Clone, Copy, Default, PartialEq)]
@@ -148,6 +141,8 @@ pub struct EgressRecipientStats {
#[derive(Default)] #[derive(Default)]
pub struct EgressMixingStats { pub struct EgressMixingStats {
disk_persisted_packets: AtomicUsize,
// this includes ACKS! // this includes ACKS!
forward_hop_packets_sent: AtomicUsize, forward_hop_packets_sent: AtomicUsize,
@@ -159,6 +154,14 @@ pub struct EgressMixingStats {
} }
impl EgressMixingStats { impl EgressMixingStats {
pub fn add_disk_persisted_packet(&self) {
self.disk_persisted_packets.fetch_add(1, Ordering::Relaxed);
}
pub fn disk_persisted_packets(&self) -> usize {
self.disk_persisted_packets.load(Ordering::Relaxed)
}
pub fn forward_hop_packets_sent(&self) -> usize { pub fn forward_hop_packets_sent(&self) -> usize {
self.forward_hop_packets_sent.load(Ordering::Relaxed) self.forward_hop_packets_sent.load(Ordering::Relaxed)
} }
+32
View File
@@ -2,11 +2,19 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Default)] #[derive(Default)]
pub struct NetworkStats { pub struct NetworkStats {
// for now just experiment with basic data, we could always extend it // for now just experiment with basic data, we could always extend it
active_ingress_mixnet_connections: AtomicUsize, active_ingress_mixnet_connections: AtomicUsize,
active_ingress_websocket_connections: AtomicUsize,
// the reason for additional `Arc` on this one is that the handler wasn't
// designed with metrics in mind and this single counter has been woven through
// the call stack
active_egress_mixnet_connections: Arc<AtomicUsize>,
} }
impl NetworkStats { impl NetworkStats {
@@ -20,8 +28,32 @@ impl NetworkStats {
.fetch_sub(1, Ordering::Relaxed); .fetch_sub(1, Ordering::Relaxed);
} }
pub fn new_ingress_websocket_client(&self) {
self.active_ingress_websocket_connections
.fetch_add(1, Ordering::Relaxed);
}
pub fn disconnected_ingress_websocket_client(&self) {
self.active_ingress_websocket_connections
.fetch_sub(1, Ordering::Relaxed);
}
pub fn active_ingress_mixnet_connections_count(&self) -> usize { pub fn active_ingress_mixnet_connections_count(&self) -> usize {
self.active_ingress_mixnet_connections self.active_ingress_mixnet_connections
.load(Ordering::Relaxed) .load(Ordering::Relaxed)
} }
pub fn active_ingress_websocket_connections_count(&self) -> usize {
self.active_ingress_websocket_connections
.load(Ordering::Relaxed)
}
pub fn active_egress_mixnet_connections_counter(&self) -> Arc<AtomicUsize> {
self.active_egress_mixnet_connections.clone()
}
pub fn active_egress_mixnet_connections_count(&self) -> usize {
self.active_egress_mixnet_connections
.load(Ordering::Relaxed)
}
} }
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Default)]
pub struct NodeStats {
pub final_hop_packets_pending_delivery: AtomicUsize,
pub forward_hop_packets_pending_delivery: AtomicUsize,
pub forward_hop_packets_being_delayed: AtomicUsize,
// packets that haven't yet been delayed and are waiting for their chance
pub packet_forwarder_queue_size: AtomicUsize,
}
impl NodeStats {
pub fn update_final_hop_packets_pending_delivery(&self, current: usize) {
self.final_hop_packets_pending_delivery
.store(current, Ordering::Relaxed);
}
pub fn final_hop_packets_pending_delivery_count(&self) -> usize {
self.final_hop_packets_pending_delivery
.load(Ordering::Relaxed)
}
pub fn update_forward_hop_packets_pending_delivery(&self, current: usize) {
self.forward_hop_packets_pending_delivery
.store(current, Ordering::Relaxed);
}
pub fn forward_hop_packets_pending_delivery_count(&self) -> usize {
self.forward_hop_packets_pending_delivery
.load(Ordering::Relaxed)
}
pub fn update_forward_hop_packets_being_delayed(&self, current: usize) {
self.forward_hop_packets_being_delayed
.store(current, Ordering::Relaxed);
}
pub fn forward_hop_packets_being_delayed_count(&self) -> usize {
self.forward_hop_packets_being_delayed
.load(Ordering::Relaxed)
}
pub fn update_packet_forwarder_queue_size(&self, current: usize) {
self.packet_forwarder_queue_size
.store(current, Ordering::Relaxed);
}
pub fn packet_forwarder_queue_size(&self) -> usize {
self.packet_forwarder_queue_size.load(Ordering::Relaxed)
}
}
@@ -0,0 +1,390 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_metrics::{metrics_registry, HistogramTimer, Metric};
use std::sync::LazyLock;
use strum::{Display, EnumCount, EnumIter, EnumProperty, IntoEnumIterator};
pub static PROMETHEUS_METRICS: LazyLock<NymNodePrometheusMetrics> =
LazyLock::new(NymNodePrometheusMetrics::initialise);
const CLIENT_SESSION_DURATION_BUCKETS: &[f64] = &[
// sub 3s (implicitly)
3., // 3s - 15s
15., // 15s - 70s
70., // 70s - 2min
120., // 2 min - 5 min
300., // 5min - 15min
900., // 15min - 1h
3600., // 1h - 12h
43200., // 12h - 23.5h
84600., // 23.5h - 24.5h
88200., // 24.5h - 72h
259200., // 72h+ (implicitly)
];
#[derive(Clone, Debug, EnumIter, Display, EnumProperty, EnumCount, Eq, Hash, PartialEq)]
#[strum(serialize_all = "snake_case", prefix = "nym_node_")]
pub enum PrometheusMetric {
// # MIXNET
// ## INGRESS
#[strum(props(help = "The number of ingress forward hop sphinx packets received"))]
MixnetIngressForwardPacketsReceived,
#[strum(props(help = "The number of ingress final hop sphinx packets received"))]
MixnetIngressFinalHopPacketsReceived,
#[strum(props(help = "The number of ingress malformed sphinx packets received"))]
MixnetIngressMalformedPacketsReceived,
#[strum(props(
help = "The number of ingress forward sphinx packets that specified excessive delay received"
))]
MixnetIngressExcessiveDelayPacketsReceived,
#[strum(props(help = "The number of ingress forward hop sphinx packets dropped"))]
MixnetIngressForwardPacketsDropped,
#[strum(props(help = "The number of ingress final hop sphinx packets dropped"))]
MixnetIngressFinalHopPacketsDropped,
#[strum(props(help = "The current rate of receiving ingress forward hop sphinx packets"))]
MixnetIngressForwardPacketsReceivedRate,
#[strum(props(help = "The current rate of receiving ingress final hop sphinx packets"))]
MixnetIngressFinalHopPacketsReceivedRate,
#[strum(props(help = "The current rate of receiving ingress malformed sphinx packets"))]
MixnetIngressMalformedPacketsReceivedRate,
#[strum(props(
help = "The current rate of receiving ingress sphinx packets that specified excessive delay"
))]
MixnetIngressExcessiveDelayPacketsReceivedRate,
#[strum(props(help = "The current rate of dropping ingress forward hop sphinx packets"))]
MixnetIngressForwardPacketsDroppedRate,
#[strum(props(help = "The current rate of dropping ingress final hop sphinx packets"))]
MixnetIngressFinalHopPacketsDroppedRate,
// ## EGRESS
#[strum(props(
help = "The number of unwrapped final hop packets stored on disk for offline clients"
))]
MixnetEgressStoredOnDiskFinalHopPackets,
#[strum(props(help = "The number of egress forward hop sphinx packets sent/forwarded"))]
MixnetEgressForwardPacketsSent,
#[strum(props(
help = "The number of egress forward hop sphinx packets sent/forwarded (acks only)"
))]
MixnetEgressAckSent,
#[strum(props(help = "The number of egress forward hop sphinx packets dropped"))]
MixnetEgressForwardPacketsDropped,
#[strum(props(
help = "The current rate of sending/forwarding egress forward hop sphinx packets"
))]
MixnetEgressForwardPacketsSendRate,
#[strum(props(
help = "The current rate of sending/forwarding egress forward hop sphinx packets (acks only)"
))]
MixnetEgressAckSendRate,
#[strum(props(help = "The current rate of dropping egress forward hop sphinx packets"))]
MixnetEgressForwardPacketsDroppedRate,
// # ENTRY
#[strum(props(help = "The number of unique users"))]
EntryClientUniqueUsers,
#[strum(props(help = "The number of client sessions started"))]
EntryClientSessionsStarted,
#[strum(props(help = "The number of client sessions finished"))]
EntryClientSessionsFinished,
#[strum(to_string = "entry_client_sessions_durations_{typ}")]
#[strum(props(help = "The distribution of client sessions duration of the specified type"))]
EntryClientSessionsDurations { typ: String },
// # WIREGUARD
#[strum(props(help = "The amount of bytes transmitted via wireguard"))]
WireguardBytesTx,
#[strum(props(help = "The amount of bytes received via wireguard"))]
WireguardBytesRx,
#[strum(props(help = "The current number of all registered wireguard peers"))]
WireguardTotalPeers,
#[strum(props(help = "The current number of active wireguard peers"))]
WireguardActivePeers,
#[strum(props(help = "The current sending rate of wireguard"))]
WireguardBytesTxRate,
#[strum(props(help = "The current receiving rate of wireguard"))]
WireguardBytesRxRate,
// # NETWORK
#[strum(props(help = "The number of active ingress mixnet connections"))]
NetworkActiveIngressMixnetConnections,
#[strum(props(help = "The number of active ingress websocket connections"))]
NetworkActiveIngressWebSocketConnections,
#[strum(props(help = "The number of active egress mixnet connections"))]
NetworkActiveEgressMixnetConnections,
// # PROCESS
#[strum(props(help = "The current number of packets being delayed"))]
ProcessForwardHopPacketsBeingDelayed,
#[strum(props(
help = "The current number of packets waiting in the queue to get delayed and sent into the mixnet"
))]
ProcessPacketForwarderQueueSize,
#[strum(props(
help = "The latency distribution of attempting to retrieve network topology (from nym-api)"
))]
ProcessTopologyQueryResolutionLatency,
#[strum(props(
help = "The current number of final hop packets stuck in channels waiting to get delivered to appropriate websocket connections"
))]
ProcessFinalHopPacketsPendingDelivery,
#[strum(props(
help = "The current number of forward hop packets stuck in channels waiting to get delivered to appropriate TCP connections"
))]
ProcessForwardHopPacketsPendingDelivery,
}
impl PrometheusMetric {
fn name(&self) -> String {
self.to_string()
}
fn help(&self) -> &'static str {
// SAFETY: every variant has a `help` prop defined (and there's a unit test is checking for that)
#[allow(clippy::unwrap_used)]
self.get_str("help").unwrap()
}
fn is_complex(&self) -> bool {
matches!(self, PrometheusMetric::EntryClientSessionsDurations { .. })
// match self {
// PrometheusMetric::EntryClientSessionsDurations { .. } => true,
// _ => false,
// }
}
fn to_registrable_metric(&self) -> Option<Metric> {
let name = self.name();
let help = self.help();
match self {
PrometheusMetric::MixnetIngressForwardPacketsReceived => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressFinalHopPacketsReceived => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressMalformedPacketsReceived => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressExcessiveDelayPacketsReceived => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressForwardPacketsDropped => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressFinalHopPacketsDropped => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetIngressForwardPacketsReceivedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetIngressFinalHopPacketsReceivedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetIngressMalformedPacketsReceivedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetIngressExcessiveDelayPacketsReceivedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetIngressForwardPacketsDroppedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetIngressFinalHopPacketsDroppedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetEgressStoredOnDiskFinalHopPackets => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetEgressForwardPacketsSent => Metric::new_int_gauge(&name, help),
PrometheusMetric::MixnetEgressAckSent => Metric::new_int_gauge(&name, help),
PrometheusMetric::MixnetEgressForwardPacketsDropped => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::MixnetEgressForwardPacketsSendRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::MixnetEgressAckSendRate => Metric::new_float_gauge(&name, help),
PrometheusMetric::MixnetEgressForwardPacketsDroppedRate => {
Metric::new_float_gauge(&name, help)
}
PrometheusMetric::EntryClientUniqueUsers => Metric::new_int_gauge(&name, help),
PrometheusMetric::EntryClientSessionsStarted => Metric::new_int_gauge(&name, help),
PrometheusMetric::EntryClientSessionsFinished => Metric::new_int_gauge(&name, help),
PrometheusMetric::EntryClientSessionsDurations { .. } => {
Metric::new_histogram(&name, help, Some(CLIENT_SESSION_DURATION_BUCKETS))
}
PrometheusMetric::WireguardBytesTx => Metric::new_int_gauge(&name, help),
PrometheusMetric::WireguardBytesRx => Metric::new_int_gauge(&name, help),
PrometheusMetric::WireguardTotalPeers => Metric::new_int_gauge(&name, help),
PrometheusMetric::WireguardActivePeers => Metric::new_int_gauge(&name, help),
PrometheusMetric::WireguardBytesTxRate => Metric::new_float_gauge(&name, help),
PrometheusMetric::WireguardBytesRxRate => Metric::new_float_gauge(&name, help),
PrometheusMetric::NetworkActiveIngressMixnetConnections => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::NetworkActiveIngressWebSocketConnections => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::NetworkActiveEgressMixnetConnections => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::ProcessForwardHopPacketsBeingDelayed => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::ProcessPacketForwarderQueueSize => Metric::new_int_gauge(&name, help),
PrometheusMetric::ProcessTopologyQueryResolutionLatency => {
Metric::new_histogram(&name, help, None)
}
PrometheusMetric::ProcessFinalHopPacketsPendingDelivery => {
Metric::new_int_gauge(&name, help)
}
PrometheusMetric::ProcessForwardHopPacketsPendingDelivery => {
Metric::new_int_gauge(&name, help)
}
}
}
fn set(&self, value: i64) {
metrics_registry().set(&self.name(), value);
}
fn set_float(&self, value: f64) {
metrics_registry().set_float(&self.name(), value);
}
fn inc(&self) {
metrics_registry().inc(&self.name());
}
fn inc_by(&self, value: i64) {
metrics_registry().inc_by(&self.name(), value);
}
fn observe_histogram(&self, value: f64) {
let reg = metrics_registry();
if !reg.add_to_histogram(&self.name(), value) {
if let Some(registrable) = self.to_registrable_metric() {
reg.register_metric(registrable);
reg.add_to_histogram(&self.name(), value);
}
}
}
fn start_timer(&self) -> Option<HistogramTimer> {
metrics_registry().start_timer(&self.name())
}
}
#[non_exhaustive]
pub struct NymNodePrometheusMetrics {}
impl NymNodePrometheusMetrics {
// initialise all fields on startup with default values so that they'd be immediately available for query
pub(crate) fn initialise() -> Self {
let registry = metrics_registry();
// we can't initialise complex metrics as their names will only be fully known at runtime
for kind in PrometheusMetric::iter() {
if !kind.is_complex() {
if let Some(metric) = kind.to_registrable_metric() {
registry.register_metric(metric);
}
}
}
NymNodePrometheusMetrics {}
}
pub fn set(&self, metric: PrometheusMetric, value: i64) {
metric.set(value)
}
pub fn set_float(&self, metric: PrometheusMetric, value: f64) {
metric.set_float(value)
}
pub fn inc(&self, metric: PrometheusMetric) {
metric.inc()
}
pub fn inc_by(&self, metric: PrometheusMetric, value: i64) {
metric.inc_by(value)
}
pub fn observe_histogram(&self, metric: PrometheusMetric, value: f64) {
metric.observe_histogram(value)
}
pub fn start_timer(&self, metric: PrometheusMetric) -> Option<HistogramTimer> {
metric.start_timer()
}
}
#[cfg(test)]
mod tests {
use super::*;
use strum::IntoEnumIterator;
#[test]
fn prometheus_metrics() {
// a sanity check for anyone adding new metrics. if this test fails,
// make sure any methods on `PrometheusMetric` enum don't need updating
// or require custom Display impl
assert_eq!(37, PrometheusMetric::COUNT)
}
#[test]
fn every_variant_has_help_property() {
for variant in PrometheusMetric::iter() {
assert!(variant.get_str("help").is_some())
}
}
#[test]
fn prometheus_metrics_names() {
// make sure nothing changed in our serialisation
let simple = PrometheusMetric::MixnetIngressForwardPacketsReceived.to_string();
assert_eq!("nym_node_mixnet_ingress_forward_packets_received", simple);
let parameterised =
PrometheusMetric::EntryClientSessionsDurations { typ: "vpn".into() }.to_string();
assert_eq!(
"nym_node_entry_client_sessions_durations_vpn",
parameterised
)
}
}
@@ -0,0 +1,44 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Default)]
pub struct WireguardStats {
bytes_rx: AtomicUsize,
bytes_tx: AtomicUsize,
total_peers: AtomicUsize,
active_peers: AtomicUsize,
}
impl WireguardStats {
pub fn bytes_rx(&self) -> usize {
self.bytes_rx.load(Ordering::Relaxed)
}
pub fn bytes_tx(&self) -> usize {
self.bytes_tx.load(Ordering::Relaxed)
}
pub fn total_peers(&self) -> usize {
self.total_peers.load(Ordering::Relaxed)
}
pub fn active_peers(&self) -> usize {
self.active_peers.load(Ordering::Relaxed)
}
pub fn update(
&self,
new_bytes_rx: usize,
new_bytes_tx: usize,
total_peers: usize,
active_peers: usize,
) {
self.bytes_rx.fetch_add(new_bytes_rx, Ordering::Relaxed);
self.bytes_tx.fetch_add(new_bytes_tx, Ordering::Relaxed);
self.total_peers.store(total_peers, Ordering::Relaxed);
self.active_peers.store(active_peers, Ordering::Relaxed);
}
}
@@ -4,6 +4,19 @@
pub use mixing::*; pub use mixing::*;
pub use session::*; pub use session::*;
pub use verloc::*; pub use verloc::*;
pub use wireguard::*;
pub mod wireguard {
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct WireguardStats {
pub bytes_tx: usize,
pub bytes_rx: usize,
}
}
pub mod packets { pub mod packets {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
+6
View File
@@ -66,12 +66,18 @@ pub mod routes {
pub const LEGACY_MIXING: &str = "/mixing"; pub const LEGACY_MIXING: &str = "/mixing";
pub const PACKETS_STATS: &str = "/packets-stats"; pub const PACKETS_STATS: &str = "/packets-stats";
pub const WIREGUARD_STATS: &str = "/wireguard-stats";
pub const SESSIONS: &str = "/sessions"; pub const SESSIONS: &str = "/sessions";
pub const VERLOC: &str = "/verloc"; pub const VERLOC: &str = "/verloc";
pub const PROMETHEUS: &str = "/prometheus"; pub const PROMETHEUS: &str = "/prometheus";
absolute_route!(legacy_mixing_absolute, metrics_absolute(), LEGACY_MIXING); absolute_route!(legacy_mixing_absolute, metrics_absolute(), LEGACY_MIXING);
absolute_route!(packets_stats_absolute, metrics_absolute(), PACKETS_STATS); absolute_route!(packets_stats_absolute, metrics_absolute(), PACKETS_STATS);
absolute_route!(
wireguard_stats_absolute,
metrics_absolute(),
WIREGUARD_STATS
);
absolute_route!(sessions_absolute, metrics_absolute(), SESSIONS); absolute_route!(sessions_absolute, metrics_absolute(), SESSIONS);
absolute_route!(verloc_absolute, metrics_absolute(), VERLOC); absolute_route!(verloc_absolute, metrics_absolute(), VERLOC);
absolute_route!(prometheus_absolute, metrics_absolute(), PROMETHEUS); absolute_route!(prometheus_absolute, metrics_absolute(), PROMETHEUS);
+4
View File
@@ -83,6 +83,10 @@ pub(crate) async fn execute(mut args: Args) -> Result<(), NymNodeError> {
warn!("this node is going to run without mixnode or gateway support! consider providing `mode` value"); warn!("this node is going to run without mixnode or gateway support! consider providing `mode` value");
} }
if config.modes.standalone_exit() {
warn!("this node is going to run in EXIT gateway mode only - it will not be able to accept client traffic and thus will NOT be eligible for any rewards. consider running it alongside `entry` (or `full-gateway`) mode")
}
if config.host.public_ips.is_empty() { if config.host.public_ips.is_empty() {
return Err(NymNodeError::NoPublicIps); return Err(NymNodeError::NoPublicIps);
} }

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