Compare commits

..

224 Commits

Author SHA1 Message Date
Jon Häggblad bc59f94c1b Pass errors up the call stack 2024-05-06 11:56:30 +02:00
Jon Häggblad 8c28a12569 Start adding error return types 2024-05-06 10:24:50 +02:00
Jon Häggblad f7387c3229 wip: explore timeout on ws send 2024-05-05 22:42:56 +02:00
Jon Häggblad a3460d7f67 wip 2024-05-05 21:05:01 +02:00
Jon Häggblad d7b5f4f6d6 Exlicitly drop packet router when shutdown detected
In the vpn client I've observed that 2 out of 3 times the disconnect
procedure stalls out.

Investigating in the logs these 5 are still running when we hit shutdown
timeout.

[TaskClient-nym_vpn_lib-mixnet_client_main-real_traffic_controller-ack_control-retransmission_request_listener] Polling shutdown failed: channel closed
[TaskClient-nym_vpn_lib-mixnet_client_main-real_traffic_controller-out_queue_control] Polling shutdown failed: channel closed
[TaskClient-gateway-packet-router] Polling shutdown failed: channel closed
[TaskClient-nym_vpn_lib-mixnet_client_main-gateway_transceiver] Polling shutdown failed: channel closed
[TaskClient-nym_vpn_lib-mixnet_client_main-mix_traffic_controller] Polling shutdown failed: channel closed

I _think_ what was causing the problem here is that the task client in
the packet router is not being awaited in an event loop like other
client instances.

Explictly drop it in the socket state to make sure it's not blocking
shutdown.
2024-05-05 21:05:01 +02:00
Tommy Verrall c610389198 Merge pull request #4549 from nymtech/dependabot/go_modules/wasm/mix-fetch/go-mix-conn/golang.org/x/net-0.23.0
Bump golang.org/x/net from 0.17.0 to 0.23.0 in /wasm/mix-fetch/go-mix-conn
2024-04-19 17:46:36 +02:00
dependabot[bot] d283ecae22 Bump golang.org/x/net in /wasm/mix-fetch/go-mix-conn
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-19 12:20:51 +00:00
Jędrzej Stuczyński 2120acfdad Merge pull request #4547 from nymtech/feature/update-nymnode-bonding-version
Feature/update nymnode bonding version
2024-04-19 10:12:18 +01:00
Jędrzej Stuczyński b613775551 updated post-migration message 2024-04-18 15:16:18 +01:00
Jędrzej Stuczyński 73d4896b0b use {nym-node-binary-version}+nymnode rather than {nym-mixnode-binary-version}+nymnode for bonding info 2024-04-18 15:03:28 +01:00
Simon Wicky b8b66fa4ad rerandomize outfox's secret for each hop (#4544) 2024-04-18 13:48:08 +02:00
Jędrzej Stuczyński ba59022160 Merge pull request #4546 from nymtech/feature/disable-noise-key-announcement
don't announce noise keys
2024-04-18 12:18:24 +01:00
Jędrzej Stuczyński 8e2c64a867 don't announce noise keys 2024-04-18 11:25:29 +01:00
benedetta davico 73ae09cb76 Update Cargo.toml (#4543) 2024-04-17 14:00:58 +02:00
Simon Wicky fd238b1d1b fix dependency workspace typo (#4542) 2024-04-17 10:14:00 +02:00
Jon Häggblad 1f2d888626 Handle selected gateway in rust sdk (#4515)
* Set active gateway after setting up gateway in base client

* Elevate log statements in gateway setup

* Add debug implementations for some gateway setup types

* Rework gateway setup in rust sdk mixnet client

* Remove unused KeyMode type

* Remove pub from internal setters

* Remove pseudo builder methods

* Make create_bandwidth_client pub

* Downgrade log statement to debug

* Revert set_active_gateway in base_client

* Rename to set_active_gateway_if_previously_registered
2024-04-15 14:11:26 +02:00
Tommy Verrall 9f47b05ed6 Merge pull request #4537 from nymtech/dependabot/npm_and_yarn/wasm/mix-fetch/internal-dev-node/tar-6.2.1
Bump tar from 6.1.15 to 6.2.1 in /wasm/mix-fetch/internal-dev-node
2024-04-15 09:21:37 +01:00
Tommy Verrall f559a03732 Merge pull request #4538 from nymtech/dependabot/npm_and_yarn/wasm/client/internal-dev-node/tar-6.2.1
Bump tar from 6.1.15 to 6.2.1 in /wasm/client/internal-dev-node
2024-04-15 09:21:06 +01:00
Jędrzej Stuczyński 3e12766afd expose info whether entry gateway enforces zk-nym in self-described api (#4529) 2024-04-12 19:05:28 +02:00
Jędrzej Stuczyński 1c93cc8a68 Merge pull request #4437 from nymtech/feature/freepass-expiration
expire freepass bandwidth + stagger db flushes
2024-04-12 17:48:37 +01:00
Jędrzej Stuczyński 4865f7f205 [bugfix] Prevent generation of free passes with expiry date in the past (#4535) 2024-04-12 17:44:32 +02:00
Jędrzej Stuczyński a785950d76 [bugfix] don't shutdown NR on failed connection (#4539) 2024-04-12 17:43:22 +02:00
Jędrzej Stuczyński 328d1c25b7 removed old debug code 2024-04-12 15:23:37 +01:00
Jędrzej Stuczyński 862a248902 missing cli argument for controlling nyxd 2024-04-12 15:14:55 +01:00
Jędrzej Stuczyński f826904407 fix credential expiration + improve errors + naively stop client when out of bandwidth 2024-04-12 15:00:49 +01:00
Jędrzej Stuczyński 67467ca76d renamed 'shared_state' to 'common_state' 2024-04-12 15:00:49 +01:00
Jędrzej Stuczyński a53ae5b6b5 using lock guard in dedicated scope 2024-04-12 15:00:49 +01:00
Jędrzej Stuczyński bfc495ef29 expire freepass bandwidth + stagger db flushes 2024-04-12 15:00:48 +01:00
Jędrzej Stuczyński 9d74c22f9b Merge pull request #4536 from nymtech/bugfix/localnet
[bugfix] make sure localnet script works in post nym-node era
2024-04-12 12:41:53 +01:00
import this f978552a3a [DOC]: NymVPN testing Guides update (#4528)
* simplify cli - comment redundant pages

* unite mac and linux cli guide to one page

* finalise cli guide

* syntax edit

* book built

* syntax edit

* final version - ready to review

* final version - ready to review
2024-04-11 12:09:58 +00:00
dependabot[bot] b2477dd81b Bump tar from 6.1.15 to 6.2.1 in /wasm/client/internal-dev-node
Bumps [tar](https://github.com/isaacs/node-tar) from 6.1.15 to 6.2.1.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.1.15...v6.2.1)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 10:51:16 +00:00
dependabot[bot] dcd712430e Bump tar from 6.1.15 to 6.2.1 in /wasm/mix-fetch/internal-dev-node
Bumps [tar](https://github.com/isaacs/node-tar) from 6.1.15 to 6.2.1.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.1.15...v6.2.1)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 10:51:15 +00:00
Jędrzej Stuczyński 5dcad5ffd4 [bugfix] make sure localnet script works in post nym-node era 2024-04-11 10:25:04 +01:00
Jędrzej Stuczyński 210269cb9c [bugfix] restore old serialisation of 'NymNodeDescription' (#4533) 2024-04-11 10:11:46 +02:00
Jędrzej Stuczyński 8c59106add introduced /api-status routes for nym-api (#4527)
* introduced /api-status routes for nym-api

* added additional information to the signer-information endpoint
2024-04-11 09:28:42 +02:00
Tommy Verrall 264771f8cf Merge pull request #4428 from nymtech/sachin/delegate_from_file
nym-cli: delegations from a file
2024-04-10 15:36:11 +01:00
Tommy Verrall 28d27eb84e Merge pull request #4517 from nymtech/dependabot/cargo/sdk/ffi/shared/eyre-0.6.12
Bump eyre from 0.6.11 to 0.6.12 in /sdk/ffi/shared
2024-04-10 11:16:27 +01:00
Tommy Verrall d57757584c Merge pull request #4518 from nymtech/dependabot/cargo/sdk/ffi/cpp/eyre-0.6.12
Bump eyre from 0.6.11 to 0.6.12 in /sdk/ffi/cpp
2024-04-10 10:35:25 +01:00
Tommy Verrall 6da00513a7 Merge pull request #4519 from nymtech/dependabot/cargo/sdk/ffi/go/eyre-0.6.12
Bump eyre from 0.6.11 to 0.6.12 in /sdk/ffi/go
2024-04-10 10:25:38 +01:00
dependabot[bot] 035faf70e4 Bump eyre from 0.6.11 to 0.6.12 in /sdk/ffi/cpp
Bumps [eyre](https://github.com/eyre-rs/eyre) from 0.6.11 to 0.6.12.
- [Commits](https://github.com/eyre-rs/eyre/commits)

---
updated-dependencies:
- dependency-name: eyre
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-10 09:02:46 +00:00
Tommy Verrall b238d108e6 Merge pull request #4520 from nymtech/dependabot/cargo/sdk/ffi/cpp/h2-0.3.26
Bump h2 from 0.3.24 to 0.3.26 in /sdk/ffi/cpp
2024-04-10 10:01:47 +01:00
dependabot[bot] 5581b15094 Bump eyre from 0.6.11 to 0.6.12 in /sdk/ffi/go
Bumps [eyre](https://github.com/eyre-rs/eyre) from 0.6.11 to 0.6.12.
- [Commits](https://github.com/eyre-rs/eyre/commits)

---
updated-dependencies:
- dependency-name: eyre
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-10 08:34:13 +00:00
Tommy Verrall af19c4ecfd Merge pull request #4521 from nymtech/dependabot/cargo/sdk/ffi/go/h2-0.3.26
Bump h2 from 0.3.24 to 0.3.26 in /sdk/ffi/go
2024-04-10 09:31:31 +01:00
Jędrzej Stuczyński 1855423981 Merge pull request #4531 from nymtech/bugfix/self-described-backwards-compat
Bugfix/self described backwards compat
2024-04-09 16:49:18 +01:00
benedetta davico 229561bab9 update sandbox .env (#4530) 2024-04-09 17:47:30 +02:00
Jędrzej Stuczyński 7d2044c177 Merge pull request #4532 from nymtech/bugfix/standalone-node-http-api
run http api by default
2024-04-09 16:47:20 +01:00
Jędrzej Stuczyński 7885d3c986 removed dead code
removed dead code
2024-04-09 16:36:05 +01:00
Jędrzej Stuczyński 78fb3c2293 run http api by default 2024-04-09 16:26:54 +01:00
Jędrzej Stuczyński 0ed43d2439 allow nym-api to understand signatures on legacy host information 2024-04-09 16:17:39 +01:00
Jędrzej Stuczyński 69c1a32392 error prefix 2024-04-09 16:16:39 +01:00
Sachin Kamath 30ffea19a1 docs: add credential signing steps for nym-api (#4523)
* docs: add credential signing steps for nym-api

* review comments
2024-04-09 10:04:24 +02:00
Jon Häggblad 9e3bd8588d Re-export TransmissionLane type (#4525) 2024-04-08 16:05:12 +02:00
import this ac7b821782 [DOC]: Feature: cmdrun scripts (#4514)
* initialise cmdrun script dir

* initialise api_targets_config

* initialise api_targets

* first version of argparser cli

* create a token api stats table

* add format function

* initiate nym_vpn command

* add github to get api url

* add github api to config

* create nym_vpn releases dataframe

* menu headers tweak
2024-04-08 09:13:31 +00:00
dependabot[bot] bb372fb35c Bump eyre from 0.6.11 to 0.6.12 in /sdk/ffi/shared
Bumps [eyre](https://github.com/eyre-rs/eyre) from 0.6.11 to 0.6.12.
- [Commits](https://github.com/eyre-rs/eyre/commits)

---
updated-dependencies:
- dependency-name: eyre
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 08:28:10 +00:00
Tommy Verrall 5f4220867a Merge pull request #4522 from nymtech/dependabot/cargo/sdk/ffi/shared/h2-0.3.26
Bump h2 from 0.3.24 to 0.3.26 in /sdk/ffi/shared
2024-04-08 09:27:12 +01:00
Jon Häggblad bfa029f284 Use client-core default db names in sdk (#4524) 2024-04-06 09:54:53 +02:00
Jędrzej Stuczyński 4c5351ba60 Feature/one binary to rule them all (#4500) 2024-04-05 19:34:31 +02:00
dependabot[bot] 3a6058be60 Bump h2 from 0.3.24 to 0.3.26 in /sdk/ffi/shared
Bumps [h2](https://github.com/hyperium/h2) from 0.3.24 to 0.3.26.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.24...v0.3.26)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 16:24:23 +00:00
dependabot[bot] 7fcc8cdd19 Bump h2 from 0.3.24 to 0.3.26 in /sdk/ffi/go
Bumps [h2](https://github.com/hyperium/h2) from 0.3.24 to 0.3.26.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.24...v0.3.26)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 16:24:03 +00:00
dependabot[bot] 098bd4eb3c Bump h2 from 0.3.24 to 0.3.26 in /sdk/ffi/cpp
Bumps [h2](https://github.com/hyperium/h2) from 0.3.24 to 0.3.26.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.24...v0.3.26)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 16:18:54 +00:00
Jon Häggblad fa1e9988b3 Add credentials mode command to SDK mixnet builder (#4516) 2024-04-05 15:51:48 +02:00
Tommy Verrall 999b4f743e Merge pull request #4470 from nymtech/feature/explorer-delegation-mobile
Feature/explorer delegation mobile
2024-04-02 08:20:26 +01:00
Tommy Verrall cc55abf7fb Merge pull request #4510 from nymtech/dependabot/npm_and_yarn/clients/native/examples/js-examples/websocket/express-4.19.2
Bump express from 4.18.2 to 4.19.2 in /clients/native/examples/js-examples/websocket
2024-04-02 08:17:35 +01:00
Tommy Verrall 020edb8f84 Merge pull request #4512 from nymtech/dependabot/npm_and_yarn/wasm/node-tester/internal-dev/express-4.19.2
Bump express from 4.18.2 to 4.19.2 in /wasm/node-tester/internal-dev
2024-04-02 08:17:05 +01:00
Tommy Verrall ae2079272b Merge pull request #4511 from nymtech/dependabot/npm_and_yarn/wasm/client/internal-dev/express-4.19.2
Bump express from 4.18.2 to 4.19.2 in /wasm/client/internal-dev
2024-04-02 08:16:53 +01:00
Tommy Verrall a656429376 Merge pull request #4513 from nymtech/dependabot/npm_and_yarn/wasm/mix-fetch/internal-dev/express-4.19.2
Bump express from 4.18.2 to 4.19.2 in /wasm/mix-fetch/internal-dev
2024-04-02 08:13:49 +01:00
dependabot[bot] 2086946e16 Bump express from 4.18.2 to 4.19.2 in /wasm/mix-fetch/internal-dev
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-29 08:31:27 +00:00
dependabot[bot] 08e0892327 Bump express from 4.18.2 to 4.19.2 in /wasm/node-tester/internal-dev
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-29 08:31:25 +00:00
dependabot[bot] d28783cb2a Bump express from 4.18.2 to 4.19.2 in /wasm/client/internal-dev
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-29 08:31:24 +00:00
dependabot[bot] 1815a2d3a5 Bump express in /clients/native/examples/js-examples/websocket
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-28 18:44:49 +00:00
Tommy Verrall 1429f63d9d Merge pull request #4464 from nymtech/feature/remove-allowlist
removed the usage of allow.list
2024-03-28 16:10:59 +00:00
Jędrzej Stuczyński 1ed087e801 fixed NR template 2024-03-28 15:43:37 +00:00
Jędrzej Stuczyński 7fce0a9692 remove explicit deref 2024-03-28 15:43:37 +00:00
Jędrzej Stuczyński bef0a537b2 config migration for removing allow.list 2024-03-28 15:43:37 +00:00
Jędrzej Stuczyński fb2b2c963b moved and renamed NR old config structs 2024-03-28 15:43:37 +00:00
Jędrzej Stuczyński 2aa5e25532 removed the usage of allow.list 2024-03-28 15:43:36 +00:00
Tommy Verrall 588ed7dead Merge pull request #4509 from nymtech/bugfix/metrics-precondition
bugfix: use OptionFuture for creating metrics server
2024-03-28 15:39:33 +00:00
Jędrzej Stuczyński 519d419bbf bugfix: use OptionFuture for creating metrics server 2024-03-28 15:16:28 +00:00
fmtabbara f5e72cefcc Update dependencies in yarn.lock file 2024-03-28 10:15:16 +00:00
Jędrzej Stuczyński a5a9b5128f Merge pull request #4508 from nymtech/chore/beta-CI
fixed beta clippy: lints + updated cosmwasm to 1.4
2024-03-28 10:12:33 +00:00
Jędrzej Stuczyński 94cfac0bae Merge pull request #4463 from nymtech/feature/multigateway-config
Feature/multigateway config
2024-03-28 10:04:06 +00:00
import this 6fc4a06b3b [DOC]: Serinko/operators/fast-and-furious (#4466)
* initialise performance testing pages

* draft perfomance testing flow

* initialise metric template page

* add guides TOC

* spell check

* edit metrics monitoring page

* syntax edit

* draft initial  prometheus setup flow

* add prometheus config

* setup prometheus systemd service

* add self-hosted-monitor scripts

* steps to run explorenym self-hosted monitor

* add screenshots and explain steps

* changed test page url and syntax edit

* add grafana info

* finalise explorenym scripts page and initialise docker page

* initialise node setup page

* clarify remaining todos and sort summary

* add initial node setup steps

* update node-setup

* update docker setup layout

* syntax edit

* simplify node-setup and add troubleshooting

* fix typo

* implement feedback

* new release version

* remove sandbox from nymvpn gui guides

* cleaning after FF testing

* add blacklist troubleshooting

* added run command

* address review comments
2024-03-28 10:33:06 +01:00
Jędrzej Stuczyński 91b790accc fixed incorrect ArgGroup naming 2024-03-28 09:30:27 +00:00
Jędrzej Stuczyński c585753d02 removing unused code 2024-03-28 09:10:57 +00:00
Jędrzej Stuczyński 0b46d64869 cargo fmt 2024-03-28 08:57:17 +00:00
Jędrzej Stuczyński 4c43935862 change NR+IPR return types 2024-03-28 08:49:30 +00:00
Jędrzej Stuczyński 1804c66a96 CI: remove ephemera contract upload 2024-03-28 08:12:41 +00:00
Jędrzej Stuczyński 8fa213502d removed force register flag + improved error message on no gateways available 2024-03-28 08:11:57 +00:00
Jędrzej Stuczyński b869c30909 added extra alias 2024-03-27 17:22:30 +00:00
Jędrzej Stuczyński f012a1a069 fixed add gateway commands for nr and ipr 2024-03-27 17:22:30 +00:00
Jędrzej Stuczyński 74d03bfb3b moved for adding new gateways to separate command and made client init only first time setup related 2024-03-27 17:22:30 +00:00
Jędrzej Stuczyński ff499869d3 clippy 2024-03-27 17:22:30 +00:00
Jędrzej Stuczyński b8ab07020c fixed eda61b57dc8d349a9b0af70cbd02743eba1e10bf 2024-03-27 17:22:29 +00:00
Jędrzej Stuczyński dcd3d80b24 fixed post-rebasing clippy issues 2024-03-27 17:22:28 +00:00
Jędrzej Stuczyński ebd1eeb38d allow gateways to migrate configs of embedded NR/IPR 2024-03-27 17:20:39 +00:00
Jędrzej Stuczyński 36f84ca18f fixed typo in SQL query 2024-03-27 17:20:39 +00:00
Jędrzej Stuczyński 6259106b6b fixed embedded SP not correctly setting active gateway 2024-03-27 17:20:39 +00:00
Jędrzej Stuczyński 73d6e704c4 fixed gateway not generating keys for nr and ipr 2024-03-27 17:20:39 +00:00
Jędrzej Stuczyński c95e424981 missing licenses for new crates 2024-03-27 17:20:39 +00:00
Jędrzej Stuczyński 06abe53399 CT-80 2024-03-27 17:20:39 +00:00
Jędrzej Stuczyński 7c14a92baa decreased logging level 2024-03-27 17:20:38 +00:00
Jędrzej Stuczyński c07b782afa added bool to list gateways to indicate currently active gateway 2024-03-27 17:20:38 +00:00
Jędrzej Stuczyński 062f4517d6 added cli command for switching active gateway 2024-03-27 17:20:38 +00:00
Jędrzej Stuczyński 28b02c83db unified credential import cli + added it for ipr 2024-03-27 17:20:36 +00:00
Jędrzej Stuczyński 1b5a0f8cf2 list gateways cli command 2024-03-27 17:20:04 +00:00
Jędrzej Stuczyński 6c0573cb01 split up InitialisableClient trait 2024-03-27 17:20:04 +00:00
Jędrzej Stuczyński b59487cfb0 fixed ipr imports 2024-03-27 17:20:04 +00:00
Jędrzej Stuczyński 0ac8098dad ability to reuse free passes from the storage for new gateways 2024-03-27 17:20:03 +00:00
Jędrzej Stuczyński 9b10871efb ipr config migration 2024-03-27 17:20:03 +00:00
Jędrzej Stuczyński e48af11e8f clippy et. al 2024-03-27 17:20:03 +00:00
Jędrzej Stuczyński a4e5b2af93 further wasm fixes 2024-03-27 17:20:03 +00:00
Jędrzej Stuczyński 67c5a36894 building wasm-client-core 2024-03-27 17:20:03 +00:00
Jędrzej Stuczyński 765ac715c1 fixing builds of x86-based binaries 2024-03-27 17:20:03 +00:00
Jędrzej Stuczyński c4bc156cac actual sql 2024-03-27 17:20:03 +00:00
Jędrzej Stuczyński 40f08dcbb2 impl GatewaysDetailsStore for OnDiskGatewaysDetails
without the underlying sql
2024-03-27 17:20:02 +00:00
Jędrzej Stuczyński b4e45ef3ef fixed active gateway detection in rust sdk 2024-03-27 17:20:02 +00:00
Jędrzej Stuczyński fc43cb590b further adjusting storage and setup to allow for wg use 2024-03-27 17:20:02 +00:00
Jędrzej Stuczyński 6e1b869c99 ibid for network requester 2024-03-27 17:20:02 +00:00
Jędrzej Stuczyński 35d84b6e42 ibid for socks5 2024-03-27 17:20:02 +00:00
Jędrzej Stuczyński d31fddf940 gateway info migration + impl for native client
other clients are broken
2024-03-27 17:20:02 +00:00
Jędrzej Stuczyński 14ee279ec8 dead code 2024-03-27 17:20:02 +00:00
Jędrzej Stuczyński a3b1a7337d checking for already registered gateways 2024-03-27 17:20:01 +00:00
Jędrzej Stuczyński c7f7e77099 updated config templates 2024-03-27 17:20:01 +00:00
Jędrzej Stuczyński 5bb96bf42c additional fixes to key creation 2024-03-27 17:20:01 +00:00
Jędrzej Stuczyński c0ae924c58 move storage helpers 2024-03-27 17:20:01 +00:00
Jędrzej Stuczyński daa5f01683 further compilation fixes 2024-03-27 17:20:01 +00:00
Jędrzej Stuczyński 337d53b2ec ibid for network requester 2024-03-27 17:20:01 +00:00
Jędrzej Stuczyński cdc49e0749 ibid for rust sdk 2024-03-27 17:20:01 +00:00
Jędrzej Stuczyński 1916adedcc socks5 client compiling
but definitely not working yet
2024-03-27 17:20:00 +00:00
Jędrzej Stuczyński a3132b907a further propagation of new gateway/key types 2024-03-27 17:20:00 +00:00
Jędrzej Stuczyński 0e09826140 initial work on client config migration 2024-03-27 17:20:00 +00:00
Jędrzej Stuczyński bf4f16c7b9 adding additional methods to the trait 2024-03-27 17:20:00 +00:00
Jędrzej Stuczyński 6915fef99f removing generics from 'GatewaySelectionSpecification' 2024-03-27 17:20:00 +00:00
Jędrzej Stuczyński c80c0b88bf starting to weave in new storage trait 2024-03-27 17:20:00 +00:00
Jędrzej Stuczyński fe2edd56a8 adjusting the trait 2024-03-27 17:20:00 +00:00
Jędrzej Stuczyński 84a36c00b4 added registration timestamp 2024-03-27 17:19:59 +00:00
Jędrzej Stuczyński 4c9de843c5 types 2024-03-27 17:19:59 +00:00
Jędrzej Stuczyński f7edc223e9 initial db schema 2024-03-27 17:19:59 +00:00
Jędrzej Stuczyński f0072ce828 moved reply surb storage to separate subcrate 2024-03-27 17:19:58 +00:00
fmtabbara 1223f50fe8 Merge branch 'develop' into feature/explorer-delegation-mobile 2024-03-27 16:29:58 +00:00
Jędrzej Stuczyński 297cde513b turns out 'make' doesn't run cargo fmt 2024-03-27 16:11:21 +00:00
Jędrzej Stuczyński 7e6feb0cd2 fixed beta clippy: lints + updated cosmwasm to 1.4 2024-03-27 16:04:51 +00:00
Tommy Verrall 0a213990a7 Merge pull request #4490 from nymtech/feature-gate-client-metrics
Feature gate core-client metrics server
2024-03-27 15:42:07 +00:00
Tommy Verrall a44cd1cca9 Merge pull request #4506 from nymtech/int-prometheus
Switch to IntCounter and Gauge
2024-03-27 15:41:51 +00:00
durch 5fe6a2c63d Undo duplication 2024-03-27 16:31:26 +01:00
Drazen 8d46bba8eb Switch to IntCounter and Gauge 2024-03-27 16:28:34 +01:00
durch 90ff36afe8 Fix wasm 2024-03-27 16:24:41 +01:00
durch d2f482c743 Feature gate metrics-server 2024-03-27 16:23:30 +01:00
Tommy Verrall ebf9a98a1e Merge pull request #4444 from nymtech/jon/update-some-nym-prefixes
Update some nym crate prefixes
2024-03-27 15:09:40 +00:00
Tommy Verrall 578ae7f267 Merge pull request #4496 from nymtech/dependabot/npm_and_yarn/wasm/mix-fetch/internal-dev/webpack-dev-middleware-5.3.4
Bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /wasm/mix-fetch/internal-dev
2024-03-27 15:08:47 +00:00
Tommy Verrall deb5054357 Merge pull request #4495 from nymtech/dependabot/npm_and_yarn/wasm/node-tester/internal-dev/webpack-dev-middleware-5.3.4
Bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /wasm/node-tester/internal-dev
2024-03-27 15:08:10 +00:00
dependabot[bot] 089143cb3e Bump webpack-dev-middleware in /wasm/node-tester/internal-dev
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 15:51:35 +01:00
dependabot[bot] fa1a05886f Bump webpack-dev-middleware in /wasm/mix-fetch/internal-dev
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 15:48:12 +01:00
Tommy Verrall bec2cc951a Merge pull request #4507 from nymtech/bugfix/wasm-build
cherry-pick 80066d330527b7993cc85833b1b3ae96cc487bb9 to fix wasm build
2024-03-27 14:41:22 +00:00
Jędrzej Stuczyński 400f248e83 cherry-pick 80066d330527b7993cc85833b1b3ae96cc487bb9 to fix wasm build 2024-03-27 14:13:20 +00:00
Tommy Verrall 7554d0ac02 Merge pull request #4478 from nymtech/dependabot/npm_and_yarn/wasm/client/internal-dev/follow-redirects-1.15.6
Bump follow-redirects from 1.15.4 to 1.15.6 in /wasm/client/internal-dev
2024-03-27 12:05:20 +00:00
Tommy Verrall 74556ffb0f Merge pull request #4480 from nymtech/dependabot/npm_and_yarn/wasm/node-tester/internal-dev/follow-redirects-1.15.6
Bump follow-redirects from 1.15.4 to 1.15.6 in /wasm/node-tester/internal-dev
2024-03-27 12:04:46 +00:00
Tommy Verrall 91893ac44c Merge pull request #4499 from nymtech/feature/bonding-validation-update/TX-13
Bonding validation update
2024-03-27 12:01:09 +00:00
Tommy Verrall 033246268a Merge pull request #4503 from nymtech/dependabot/npm_and_yarn/express-4.19.2
Bump express from 4.18.2 to 4.19.2
2024-03-27 11:56:27 +00:00
dependabot[bot] 658b635509 Bump express from 4.18.2 to 4.19.2
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 09:46:12 +00:00
Jon Häggblad 3082cd5d32 Cargo.lock 2024-03-26 11:41:59 +01:00
Jon Häggblad ce08de28b5 Merge remote-tracking branch 'origin/develop' into jon/update-some-nym-prefixes 2024-03-26 11:39:29 +01:00
fmtabbara 9bc3a1d431 Bonding validation update
Update operator cost and profit margin validation to previous values (min 40 and min 4)
2024-03-25 10:07:26 +00:00
fmtabbara 635ae1118a Add mobile delegations beta banner 2024-03-22 12:19:54 +00:00
fmtabbara 68e0dd1f29 update success tx wording 2024-03-21 14:57:12 +00:00
fmtabbara cf201cba53 only use keplr wallet + add info banner 2024-03-21 14:27:16 +00:00
fmtabbara efe952fdf2 explorer package updates 2024-03-20 16:30:59 +00:00
fmtabbara 6d2e761319 Merge branch 'develop' into feature/explorer-delegation-mobile 2024-03-20 14:58:36 +00:00
fmtabbara e699f1ad79 explorer env configuration updates 2024-03-20 13:45:29 +00:00
Jon Häggblad c89273ab37 Update nym-wallet Cargo.lock 2024-03-20 10:53:09 +01:00
Jon Häggblad 6d66d769de Merge remote-tracking branch 'origin/develop' into jon/update-some-nym-prefixes 2024-03-20 10:51:58 +01:00
fmtabbara 01e4abc95f Update width of confirmation modal 2024-03-19 21:03:11 +00:00
fmtabbara 2195310d06 remove walletconnect (for now) 2024-03-19 18:20:17 +00:00
fmtabbara b6cd9b2bae add leap wallet 2024-03-19 14:37:10 +00:00
fmtabbara 682db85268 try to fix rpc endpoint 2024-03-19 12:46:17 +00:00
Sachin Kamath 1ebb0c7daa don't use bigdecimal 2024-03-18 20:28:55 +05:30
dependabot[bot] c3af84a952 Bump follow-redirects in /wasm/node-tester/internal-dev
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-16 23:12:48 +00:00
dependabot[bot] 2172644f98 Bump follow-redirects from 1.15.4 to 1.15.6 in /wasm/client/internal-dev
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-16 23:10:50 +00:00
fmtabbara 70b15c9226 test mainnet mixnodes 2024-03-15 21:08:14 +00:00
fmtabbara f7e6677878 update dotenv config 2024-03-15 16:28:01 +00:00
fmtabbara 73e87b2deb test wallet connect projectId 2024-03-15 16:17:02 +00:00
fmtabbara 6735c1ae58 append to env (dont overwrite) 2024-03-14 21:30:59 +00:00
fmtabbara b037c8fbeb test mobile delegations config 2024-03-14 14:15:41 +00:00
fmtabbara 3fcfbad4c0 add sandbox to cosmos-kit 2024-03-13 14:35:17 +00:00
fmtabbara 43e4cabb85 Merge branch 'develop' into feature/explorer-delegation 2024-03-12 14:28:16 +00:00
fmtabbara 496a1bc97d package updates 2024-03-12 10:20:38 +00:00
fmtabbara 7db4ddff56 use url as unique nav selection state 2024-03-12 10:20:26 +00:00
fmtabbara 6b25f83ee3 small UI updates
tidy up some text layouts + allow mixnode row selections
2024-03-12 10:19:03 +00:00
fmtabbara 7602f4b130 create delegations context and page 2024-03-12 10:17:29 +00:00
fmtabbara f762062f9f create wallet context + wallet components
create wallet context using cosmoskit + include nymclient + create wallet components
2024-03-12 10:16:06 +00:00
Jon Häggblad a9ff4ccb10 Update imports 2024-03-06 10:40:37 +01:00
Jon Häggblad 2f4f303223 Add nym- prefix to http-api-client 2024-03-06 10:38:41 +01:00
Jon Häggblad 3265d58052 Add nym- prefix to ledger crate 2024-03-06 10:27:42 +01:00
Jon Häggblad 30facbcec4 Update module imports 2024-03-06 10:26:15 +01:00
Jon Häggblad 0f5157f7d1 Update dependency names 2024-03-06 10:24:21 +01:00
Jon Häggblad 3369aa51bc Add nym- prefix to async-file-watcher 2024-03-06 10:20:13 +01:00
fmtabbara 28a0c3eb17 Add buffer module to webpack.common.js 2024-02-28 11:50:19 +00:00
fmtabbara a3d7b541e2 Fix currencyToString function call in Mixnodes page 2024-02-28 10:16:20 +00:00
Mark Sinclair 7861a0081a Add mixnet contract to env and add CI targets to package.json 2024-02-28 10:16:20 +00:00
Mark Sinclair c8b911d5d0 Make mixnet contract configurable 2024-02-28 10:16:20 +00:00
fmtabbara 22979ce245 add ledger intergration 2024-02-28 10:16:20 +00:00
fmtabbara b5a3a720a4 fix up delegate button in mobile view 2024-02-28 10:16:20 +00:00
fmtabbara 0ec5fb37c2 replace delegate icon button with button
fix linting
2024-02-28 10:16:20 +00:00
fmtabbara 2817c68e2e make mobile nav neater 2024-02-28 10:16:20 +00:00
fmtabbara c3050e5d02 allow clear search entry
fix react hooks error
2024-02-28 10:16:19 +00:00
fmtabbara 8f1901b6e0 tidy up mixnode and gateway tables 2024-02-28 10:16:19 +00:00
fmtabbara 96a42fa03d Fix table layouts
tidy up exports
2024-02-28 10:16:18 +00:00
fmtabbara 718de1f102 fix up mobile view
fix deps issues
2024-02-28 10:16:12 +00:00
fmtabbara cb9d730c16 display user message when wallet is not connected
fix linting
2024-02-28 10:16:12 +00:00
Yana 0e17fe5581 Add delegate button to each mixnode raw
relocate code

code refactor

wip

refactor

wip
2024-02-28 10:16:07 +00:00
Yana b25747dd60 Fix ModalError styles 2024-02-28 10:16:06 +00:00
Yana b16349f2e9 Add confirmation models 2024-02-28 10:16:06 +00:00
Yana f4b5693bcb Add CosmWasmSigningClient
WIP

wip
2024-02-28 10:16:06 +00:00
Yana 42a53a1c49 Remove identity key, mixId and amount validation
WIP
2024-02-28 10:16:06 +00:00
Yana 081fec72e5 Connect delegate modal to keplr balance
WIP

WIP

WIP

WIP
2024-02-28 10:16:06 +00:00
Yana f2223dddca Add Delegations Modal UI 2024-02-28 10:16:06 +00:00
Yana 9be7b8ab56 Add TokenSVG 2024-02-28 10:16:06 +00:00
Yana 40dc0d2cc7 fix ui bug in ts.config 2024-02-28 10:16:06 +00:00
Yana 19de5cb792 WIP
WIP
2024-02-28 10:16:06 +00:00
Sachin Kamath 377e06daab nym-cli: clippy fixes 2024-02-22 18:10:41 +05:30
Sachin Kamath b54d82a01a nym-cli: batch transactions 2024-02-22 17:55:35 +05:30
Sachin Kamath ad052ef498 fmt + clippy 2024-02-21 19:14:12 +05:30
Sachin Kamath a3bc4af8fe fmt + clippy 2024-02-21 19:12:02 +05:30
Sachin Kamath b6d57e2862 nym-cli: delegate to mixnodes from input csv file
nym-cli: delegate to mixnodes from input csv file
2024-02-21 18:44:15 +05:30
640 changed files with 24901 additions and 10500 deletions
@@ -112,6 +112,7 @@ jobs:
target/release/nym-network-statistics
target/release/nym-cli
target/release/nymvisor
target/release/nym-node
retention-days: 30
# If this was a pull_request or nightly, upload to build server
@@ -130,6 +131,7 @@ jobs:
cp target/release/nym-network-requester $OUTPUT_DIR
cp target/release/nym-network-statistics $OUTPUT_DIR
cp target/release/nymvisor $OUTPUT_DIR
cp target/release/nym-node $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/explorer-api $OUTPUT_DIR
if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then
@@ -60,7 +60,6 @@ jobs:
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_service_provider_directory.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_name_service.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_ephemera.wasm $OUTPUT_DIR
- name: Deploy branch to CI www
continue-on-error: true
Generated
+476 -176
View File
File diff suppressed because it is too large Load Diff
+17 -10
View File
@@ -24,6 +24,9 @@ members = [
"common/bandwidth-controller",
"common/bin-common",
"common/client-core",
"common/client-core/config-types",
"common/client-core/surb-storage",
"common/client-core/gateways-storage",
"common/client-libs/gateway-client",
"common/client-libs/mixnet-client",
"common/client-libs/validator-client",
@@ -39,6 +42,7 @@ members = [
"common/cosmwasm-smart-contracts/name-service",
"common/cosmwasm-smart-contracts/service-provider-directory",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/country-group",
"common/credential-storage",
"common/credentials",
"common/credential-utils",
@@ -48,6 +52,7 @@ members = [
"common/execute",
"common/exit-policy",
"common/http-api-client",
"common/http-api-common",
"common/inclusion-probability",
"common/ip-packet-requests",
"common/ledger",
@@ -102,6 +107,7 @@ members = [
"nym-browser-extension/storage",
"nym-api/nym-api-requests",
"nym-node",
"nym-node/nym-node-http-api",
"nym-node/nym-node-requests",
"nym-outfox",
"nym-validator-rewarder",
@@ -116,7 +122,6 @@ members = [
# "wasm/full-nym-wasm",
"wasm/mix-fetch",
"wasm/node-tester",
"common/nym-metrics",
]
default-members = [
@@ -130,6 +135,7 @@ default-members = [
"tools/nymvisor",
"explorer-api",
"nym-validator-rewarder",
"nym-node"
]
exclude = [
@@ -165,6 +171,7 @@ dotenvy = "0.15.6"
futures = "0.3.28"
generic-array = "0.14.7"
getrandom = "0.2.10"
humantime-serde = "1.1.1"
hyper = "0.14.27"
k256 = "0.13"
lazy_static = "1.4.0"
@@ -202,19 +209,19 @@ ff = "0.13.0"
# cosmwasm-related
cosmwasm-derive = "=1.3.0"
cosmwasm-schema = "=1.3.0"
cosmwasm-std = "=1.3.0"
# use 0.5.0 as that's the version used by cosmwasm-std 1.3.0
cosmwasm-derive = "=1.4.3"
cosmwasm-schema = "=1.4.3"
cosmwasm-std = "=1.4.3"
# use 0.5.0 as that's the version used by cosmwasm-std 1.4.3
# (and ideally we don't want to pull the same dependency twice)
serde-json-wasm = "=0.5.0"
cosmwasm-storage = "=1.3.0"
cosmwasm-storage = "=1.4.3"
# same version as used by cosmwasm
cw-utils = "=1.0.1"
cw-storage-plus = "=1.1.0"
cw2 = { version = "=1.1.0" }
cw3 = { version = "=1.1.0" }
cw4 = { version = "=1.1.0" }
cw-storage-plus = "=1.2.0"
cw2 = { version = "=1.1.2" }
cw3 = { version = "=1.1.2" }
cw4 = { version = "=1.1.2" }
cw-controllers = { version = "=1.1.0" }
# cosmrs-related
+2 -2
View File
@@ -4,7 +4,7 @@ version = "1.1.33"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
rust-version = "1.65"
rust-version = "1.70"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -38,7 +38,7 @@ zeroize = { workspace = true }
## internal
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] }
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "fs-gateways-storage", "cli"] }
nym-config = { path = "../../common/config" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-credentials = { path = "../../common/credentials" }
+291 -91
View File
@@ -674,13 +674,13 @@
}
},
"node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dev": true,
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@@ -688,7 +688,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@@ -793,13 +793,19 @@
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -1030,18 +1036,18 @@
}
},
"node_modules/content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true,
"engines": {
"node": ">= 0.6"
@@ -1137,6 +1143,23 @@
"node": ">= 10"
}
},
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -1358,6 +1381,27 @@
"node": ">=4"
}
},
"node_modules/es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-module-lexer": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
@@ -1463,17 +1507,17 @@
}
},
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dev": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.1",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -1731,20 +1775,28 @@
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -1824,6 +1876,18 @@
"node": ">=0.10.0"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.9",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
@@ -1855,10 +1919,22 @@
"node": ">=8"
}
},
"node_modules/has-symbols": {
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-proto": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
"dev": true,
"engines": {
"node": ">= 0.4"
@@ -1867,6 +1943,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -2650,9 +2750,9 @@
}
},
"node_modules/object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -3038,9 +3138,9 @@
}
},
"node_modules/raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dev": true,
"dependencies": {
"bytes": "3.1.2",
@@ -3413,6 +3513,23 @@
"node": ">= 0.8.0"
}
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -3453,14 +3570,18 @@
}
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
"call-bind": "^1.0.7",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -5030,13 +5151,13 @@
"dev": true
},
"body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dev": true,
"requires": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@@ -5044,7 +5165,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@@ -5122,13 +5243,16 @@
"dev": true
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"set-function-length": "^1.2.1"
}
},
"camel-case": {
@@ -5294,15 +5418,15 @@
}
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true
},
"cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true
},
"cookie-signature": {
@@ -5377,6 +5501,17 @@
"execa": "^5.0.0"
}
},
"define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"requires": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
}
},
"define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -5546,6 +5681,21 @@
"integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==",
"dev": true
},
"es-define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
"dev": true,
"requires": {
"get-intrinsic": "^1.2.4"
}
},
"es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true
},
"es-module-lexer": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz",
@@ -5626,17 +5776,17 @@
}
},
"express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dev": true,
"requires": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.1",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -5837,20 +5987,22 @@
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true
},
"get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
"hasown": "^2.0.0"
}
},
"get-stream": {
@@ -5908,6 +6060,15 @@
}
}
},
"gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dev": true,
"requires": {
"get-intrinsic": "^1.1.3"
}
},
"graceful-fs": {
"version": "4.2.9",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
@@ -5933,12 +6094,36 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"has-symbols": {
"has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"requires": {
"es-define-property": "^1.0.0"
}
},
"has-proto": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
"dev": true
},
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"dev": true
},
"hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"requires": {
"function-bind": "^1.1.2"
}
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -6515,9 +6700,9 @@
"dev": true
},
"object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"dev": true
},
"object-is": {
@@ -6803,9 +6988,9 @@
"dev": true
},
"raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dev": true,
"requires": {
"bytes": "3.1.2",
@@ -7090,6 +7275,20 @@
"send": "0.18.0"
}
},
"set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"dev": true,
"requires": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
}
},
"setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -7121,14 +7320,15 @@
"dev": true
},
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
"dev": true,
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
"call-bind": "^1.0.7",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.4",
"object-inspect": "^1.13.1"
}
},
"signal-exit": {
+4 -3
View File
@@ -4,7 +4,7 @@
use crate::client::config::persistence::ClientPaths;
use crate::client::config::template::CONFIG_TEMPLATE;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::cli_helpers::client_init::ClientConfig;
use nym_client_core::cli_helpers::CliClientConfig;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use nym_config::{
@@ -19,11 +19,12 @@ use std::path::{Path, PathBuf};
use std::str::FromStr;
pub use nym_client_core::config::Config as BaseClientConfig;
pub use nym_client_core::config::{DebugConfig, GatewayEndpointConfig};
pub use nym_client_core::config::DebugConfig;
pub mod old_config_v1_1_13;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_33;
mod persistence;
mod template;
@@ -74,7 +75,7 @@ impl NymConfigTemplate for Config {
}
}
impl ClientConfig for Config {
impl CliClientConfig for Config {
fn common_paths(&self) -> &CommonClientPaths {
&self.storage_paths.common_paths
}
@@ -5,8 +5,8 @@ use crate::client::config::old_config_v1_1_20_2::{
ClientPathsV1_1_20_2, ConfigV1_1_20_2, SocketTypeV1_1_20_2, SocketV1_1_20_2,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::disk_persistence::old_v1_1_33::ClientKeysPathsV1_1_33;
use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20;
use nym_client_core::config::old_config_v1_1_20_2::{
ClientV1_1_20_2, ConfigV1_1_20_2 as BaseConfigV1_1_20_2,
@@ -60,7 +60,7 @@ impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
socket: value.socket.into(),
storage_paths: ClientPathsV1_1_20_2 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPaths {
keys: ClientKeysPathsV1_1_33 {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
private_encryption_key_file: value.base.client.private_encryption_key_file,
@@ -1,18 +1,15 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
client::config::{
default_config_filepath, persistence::ClientPaths, Config, Socket, SocketType,
},
error::ClientError,
use crate::client::config::old_config_v1_1_33::{
ClientPathsV1_1_33, ConfigV1_1_33, SocketTypeV1_1_33, SocketV1_1_33,
};
use crate::{client::config::default_config_filepath, error::ClientError};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseConfigV1_1_20_2;
use nym_client_core::config::old_config_v1_1_30::ConfigV1_1_30 as BaseConfigV1_1_30;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
use nym_config::read_config_from_toml_file;
use nym_network_defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use serde::{Deserialize, Serialize};
@@ -49,12 +46,12 @@ impl ConfigV1_1_20_2 {
// in this upgrade, gateway endpoint configuration was moved out of the config file,
// so its returned to be stored elsewhere.
pub fn upgrade(self) -> Result<(Config, GatewayEndpointConfig), ClientError> {
pub fn upgrade(self) -> Result<(ConfigV1_1_33, OldGatewayEndpointConfigV1_1_33), ClientError> {
let gateway_details = self.base.client.gateway_endpoint.clone().into();
let config = Config {
let config = ConfigV1_1_33 {
base: BaseConfigV1_1_30::from(self.base).into(),
socket: self.socket.into(),
storage_paths: ClientPaths {
storage_paths: ClientPathsV1_1_33 {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
},
logging: self.logging,
@@ -71,11 +68,11 @@ pub enum SocketTypeV1_1_20_2 {
None,
}
impl From<SocketTypeV1_1_20_2> for SocketType {
impl From<SocketTypeV1_1_20_2> for SocketTypeV1_1_33 {
fn from(value: SocketTypeV1_1_20_2) -> Self {
match value {
SocketTypeV1_1_20_2::WebSocket => SocketType::WebSocket,
SocketTypeV1_1_20_2::None => SocketType::None,
SocketTypeV1_1_20_2::WebSocket => SocketTypeV1_1_33::WebSocket,
SocketTypeV1_1_20_2::None => SocketTypeV1_1_33::None,
}
}
}
@@ -88,9 +85,9 @@ pub struct SocketV1_1_20_2 {
pub listening_port: u16,
}
impl From<SocketV1_1_20_2> for Socket {
impl From<SocketV1_1_20_2> for SocketV1_1_33 {
fn from(value: SocketV1_1_20_2) -> Self {
Socket {
SocketV1_1_33 {
socket_type: value.socket_type.into(),
host: value.host,
listening_port: value.listening_port,
@@ -0,0 +1,99 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::persistence::ClientPaths;
use crate::client::config::{default_config_filepath, Config, Socket, SocketType};
use crate::error::ClientError;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
use nym_client_core::config::old_config_v1_1_33::ConfigV1_1_33 as BaseConfigV1_1_33;
use nym_config::read_config_from_toml_file;
use nym_network_defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use serde::{Deserialize, Serialize};
use std::io;
use std::net::{IpAddr, Ipv4Addr};
use std::path::Path;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct ClientPathsV1_1_33 {
#[serde(flatten)]
pub common_paths: CommonClientPathsV1_1_33,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct ConfigV1_1_33 {
#[serde(flatten)]
pub base: BaseConfigV1_1_33,
pub socket: SocketV1_1_33,
// \/ CHANGED
pub storage_paths: ClientPathsV1_1_33,
// /\ CHANGED
pub logging: LoggingSettings,
}
impl ConfigV1_1_33 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, ClientError> {
Ok(Config {
base: self.base.into(),
socket: self.socket.into(),
storage_paths: ClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
},
logging: self.logging,
})
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub enum SocketTypeV1_1_33 {
WebSocket,
None,
}
impl From<SocketTypeV1_1_33> for SocketType {
fn from(value: SocketTypeV1_1_33) -> Self {
match value {
SocketTypeV1_1_33::WebSocket => SocketType::WebSocket,
SocketTypeV1_1_33::None => SocketType::None,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct SocketV1_1_33 {
pub socket_type: SocketTypeV1_1_33,
pub host: IpAddr,
pub listening_port: u16,
}
impl From<SocketV1_1_33> for Socket {
fn from(value: SocketV1_1_33) -> Self {
Socket {
socket_type: value.socket_type.into(),
host: value.host,
listening_port: value.listening_port,
}
}
}
impl Default for SocketV1_1_33 {
fn default() -> Self {
SocketV1_1_33 {
socket_type: SocketTypeV1_1_33::WebSocket,
host: IpAddr::V4(Ipv4Addr::LOCALHOST),
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
}
}
}
+3 -7
View File
@@ -50,10 +50,6 @@ keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key
# Path to file containing public encryption key.
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
# A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway.
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
# Path to file containing key used for encrypting and decrypting the content of an
# acknowledgement so that nobody besides the client knows which packet it refers to.
keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}'
@@ -64,9 +60,9 @@ credentials_database = '{{ storage_paths.credentials_database }}'
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
reply_surb_database = '{{ storage_paths.reply_surb_database }}'
# Path to the file containing information about gateway used by this client,
# i.e. details such as its public key, owner address or the network information.
gateway_details = '{{ storage_paths.gateway_details }}'
# Path to the file containing information about gateways used by this client,
# i.e. details such as their public keys, owner addresses or the network information.
gateway_registrations = '{{ storage_paths.gateway_registrations }}'
##### socket config options #####
@@ -0,0 +1,30 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::CliNativeClient;
use crate::error::ClientError;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::cli_helpers::client_add_gateway::{add_gateway, CommonClientAddGatewayArgs};
#[derive(clap::Args)]
pub(crate) struct Args {
#[command(flatten)]
common_args: CommonClientAddGatewayArgs,
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
impl AsRef<CommonClientAddGatewayArgs> for Args {
fn as_ref(&self) -> &CommonClientAddGatewayArgs {
&self.common_args
}
}
pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
let output = args.output;
let res = add_gateway::<CliNativeClient, _>(args).await?;
println!("{}", output.format(&res));
Ok(())
}
@@ -1,54 +1,14 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::try_load_current_config;
use crate::commands::CliNativeClient;
use crate::error::ClientError;
use clap::ArgGroup;
use nym_client_core::cli_helpers::client_import_credential::{
import_credential, CommonClientImportCredentialArgs,
};
use nym_id::import_credential;
use std::fs;
use std::path::PathBuf;
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
bs58::decode(raw).into_vec()
}
#[derive(clap::Args)]
#[clap(group(ArgGroup::new("cred_data").required(true)))]
pub(crate) struct Args {
/// Id of client that is going to import the credential
#[clap(long)]
pub id: String,
/// Explicitly provide the encoded credential data (as base58)
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
pub(crate) credential_data: Option<Vec<u8>>,
/// Specifies the path to file containing binary credential data
#[clap(long, group = "cred_data")]
pub(crate) credential_path: Option<PathBuf>,
// currently hidden as there exists only a single serialization standard
#[clap(long, hide = true)]
pub(crate) version: Option<u8>,
}
pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
let config = try_load_current_config(&args.id)?;
let credentials_store = nym_credential_storage::initialise_persistent_storage(
&config.storage_paths.common_paths.credentials_database,
)
.await;
let raw_credential = match args.credential_data {
Some(data) => data,
None => {
// SAFETY: one of those arguments must have been set
fs::read(args.credential_path.unwrap())?
}
};
import_credential(credentials_store, raw_credential, args.version).await?;
pub(crate) async fn execute(args: CommonClientImportCredentialArgs) -> Result<(), ClientError> {
import_credential::<CliNativeClient, _>(args).await?;
println!("successfully imported credential!");
Ok(())
}
+3 -12
View File
@@ -4,7 +4,7 @@
use crate::client::config::{
default_config_directory, default_config_filepath, default_data_directory,
};
use crate::commands::try_upgrade_config;
use crate::commands::CliNativeClient;
use crate::{
client::config::Config,
commands::{override_config, OverrideConfig},
@@ -21,17 +21,8 @@ use std::fs;
use std::net::IpAddr;
use std::path::PathBuf;
struct NativeClientInit;
impl InitialisableClient for NativeClientInit {
const NAME: &'static str = "native";
type Error = ClientError;
impl InitialisableClient for CliNativeClient {
type InitArgs = Init;
type Config = Config;
fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error> {
try_upgrade_config(id)
}
fn initialise_storage_paths(id: &str) -> Result<(), Self::Error> {
fs::create_dir_all(default_data_directory(id))?;
@@ -124,7 +115,7 @@ pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
eprintln!("Initialising client...");
let output = args.output;
let res = initialise_client::<NativeClientInit>(args).await?;
let res = initialise_client::<CliNativeClient>(args).await?;
let init_results = InitResults::new(res);
println!("{}", output.format(&init_results));
@@ -0,0 +1,32 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::CliNativeClient;
use crate::error::ClientError;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::cli_helpers::client_list_gateways::{
list_gateways, CommonClientListGatewaysArgs,
};
#[derive(clap::Args)]
pub(crate) struct Args {
#[command(flatten)]
common_args: CommonClientListGatewaysArgs,
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
impl AsRef<CommonClientListGatewaysArgs> for Args {
fn as_ref(&self) -> &CommonClientListGatewaysArgs {
&self.common_args
}
}
pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
let output = args.output;
let res = list_gateways::<CliNativeClient, _>(args).await?;
println!("{}", output.format(&res));
Ok(())
}
+100 -45
View File
@@ -4,6 +4,7 @@
use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::client::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::client::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::client::config::old_config_v1_1_33::ConfigV1_1_33;
use crate::client::config::{BaseClientConfig, Config};
use crate::error::ClientError;
use clap::CommandFactory;
@@ -11,21 +12,37 @@ use clap::{Parser, Subcommand};
use log::{error, info};
use nym_bin_common::bin_info;
use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_client_core::client::base_client::storage::gateway_details::{
OnDiskGatewayDetails, PersistedGatewayDetails,
};
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_client_core::cli_helpers::client_import_credential::CommonClientImportCredentialArgs;
use nym_client_core::cli_helpers::CliClient;
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
use nym_config::OptionalSet;
use std::error::Error;
use std::net::IpAddr;
use std::sync::OnceLock;
mod add_gateway;
pub(crate) mod build_info;
pub(crate) mod import_credential;
pub(crate) mod init;
mod list_gateways;
pub(crate) mod run;
mod switch_gateway;
pub(crate) struct CliNativeClient;
impl CliClient for CliNativeClient {
const NAME: &'static str = "native";
type Error = ClientError;
type Config = Config;
async fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error> {
try_upgrade_config(id).await
}
async fn try_load_current_config(id: &str) -> Result<Self::Config, Self::Error> {
try_load_current_config(id).await
}
}
fn pretty_build_info_static() -> &'static str {
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
@@ -56,7 +73,16 @@ pub(crate) enum Commands {
Run(run::Run),
/// Import a pre-generated credential
ImportCredential(import_credential::Args),
ImportCredential(CommonClientImportCredentialArgs),
/// List all registered with gateways
ListGateways(list_gateways::Args),
/// Add new gateway to this client
AddGateway(add_gateway::Args),
/// Change the currently active gateway. Note that you must have already registered with the new gateway!
SwitchGateway(switch_gateway::Args),
/// Show build information of this binary
BuildInfo(build_info::BuildInfo),
@@ -87,6 +113,9 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
Commands::Init(m) => init::execute(m).await?,
Commands::Run(m) => run::execute(m).await?,
Commands::ImportCredential(m) => import_credential::execute(m).await?,
Commands::ListGateways(args) => list_gateways::execute(args).await?,
Commands::AddGateway(args) => add_gateway::execute(args).await?,
Commands::SwitchGateway(args) => switch_gateway::execute(args).await?,
Commands::BuildInfo(m) => build_info::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
@@ -122,29 +151,7 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
)
}
fn persist_gateway_details(
config: &Config,
details: GatewayEndpointConfig,
) -> Result<(), ClientError> {
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let keys_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
let shared_keys = keys_store.ephemeral_load_gateway_keys().map_err(|source| {
ClientError::ClientCoreError(ClientCoreError::KeyStoreError {
source: Box::new(source),
})
})?;
let persisted_details = PersistedGatewayDetails::new(details.into(), Some(&shared_keys))?;
details_store
.store_to_disk(&persisted_details)
.map_err(|source| {
ClientError::ClientCoreError(ClientCoreError::GatewayDetailsStoreError {
source: Box::new(source),
})
})
}
fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
@@ -158,14 +165,22 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
let updated_step1: ConfigV1_1_20 = old_config.into();
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated, gateway_config) = updated_step2.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
let (updated_step3, gateway_config) = updated_step2.upgrade()?;
let old_paths = updated_step3.storage_paths.clone();
let updated = updated_step3.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
async fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
@@ -178,14 +193,21 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated, gateway_config) = updated_step1.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
let (updated_step2, gateway_config) = updated_step1.upgrade()?;
let old_paths = updated_step2.storage_paths.clone();
let updated = updated_step2.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
async fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
// explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20_2::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
@@ -195,28 +217,62 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
info!("It seems the client is using <= v1.1.20_2 config template.");
info!("It is going to get updated to the current specification.");
let (updated, gateway_config) = old_config.upgrade()?;
persist_gateway_details(&updated, gateway_config)?;
let (updated_step1, gateway_config) = old_config.upgrade()?;
let old_paths = updated_step1.storage_paths.clone();
let updated = updated_step1.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
updated.save_to_default_location()?;
Ok(true)
}
async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, ClientError> {
// explicitly load it as v1.1.33 (which is incompatible with the current one, i.e. +1.1.34)
let Ok(old_config) = ConfigV1_1_33::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the client is using <= v1.1.33 config template.");
info!("It is going to get updated to the current specification.");
let old_paths = old_config.storage_paths.clone();
let updated = old_config.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
None,
)
.await?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
if try_upgrade_v1_1_13_config(id)? {
async fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
if try_upgrade_v1_1_13_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_20_config(id)? {
if try_upgrade_v1_1_20_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_20_2_config(id)? {
if try_upgrade_v1_1_20_2_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_33_config(id).await? {
return Ok(());
}
Ok(())
}
fn try_load_current_config(id: &str) -> Result<Config, ClientError> {
async fn try_load_current_config(id: &str) -> Result<Config, ClientError> {
// try to load the config as is
if let Ok(cfg) = Config::read_from_default_path(id) {
return if !cfg.validate() {
@@ -227,7 +283,7 @@ fn try_load_current_config(id: &str) -> Result<Config, ClientError> {
}
// we couldn't load it - try upgrading it from older revisions
try_upgrade_config(id)?;
try_upgrade_config(id).await?;
let config = match Config::read_from_default_path(id) {
Ok(cfg) => cfg,
@@ -247,7 +303,6 @@ fn try_load_current_config(id: &str) -> Result<Config, ClientError> {
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
+1 -1
View File
@@ -69,7 +69,7 @@ fn version_check(cfg: &Config) -> bool {
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn Error + Send + Sync>> {
eprintln!("Starting client {}...", args.common_args.id);
let mut config = try_load_current_config(&args.common_args.id)?;
let mut config = try_load_current_config(&args.common_args.id).await?;
config = override_config(config, OverrideConfig::from(args.clone()));
if !version_check(&config) {
@@ -0,0 +1,24 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::CliNativeClient;
use crate::error::ClientError;
use nym_client_core::cli_helpers::client_switch_gateway::{
switch_gateway, CommonClientSwitchGatewaysArgs,
};
#[derive(clap::Args, Clone, Debug)]
pub struct Args {
#[command(flatten)]
common_args: CommonClientSwitchGatewaysArgs,
}
impl AsRef<CommonClientSwitchGatewaysArgs> for Args {
fn as_ref(&self) -> &CommonClientSwitchGatewaysArgs {
&self.common_args
}
}
pub(crate) async fn execute(args: Args) -> Result<(), ClientError> {
switch_gateway::<CliNativeClient, _>(args).await
}
+3
View File
@@ -23,6 +23,9 @@ pub enum ClientError {
#[error("Attempted to start the client in invalid socket mode")]
InvalidSocketMode,
#[error(transparent)]
ConfigUpgradeFailure(#[from] nym_client_core::config::ConfigUpgradeFailure),
#[error(transparent)]
NymIdError(#[from] NymIdError),
}
@@ -8,7 +8,7 @@ use crate::error::{self, ErrorKind};
use crate::text::ClientRequestText;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE};
use std::convert::{TryFrom, TryInto};
use std::mem::size_of;
#[repr(u8)]
@@ -9,7 +9,7 @@ use crate::text::ServerResponseText;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE};
use nym_sphinx::receiver::ReconstructedMessage;
use std::convert::TryInto;
use std::mem::size_of;
#[repr(u8)]
@@ -7,7 +7,6 @@ use crate::responses::ServerResponse;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
// local text equivalent of `ClientRequest` for easier serialization + deserialization with serde
// TODO: figure out if there's an easy way to avoid defining it
+2 -2
View File
@@ -4,7 +4,7 @@ version = "1.1.33"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
rust-version = "1.56"
rust-version = "1.70"
license.workspace = true
[dependencies]
@@ -23,7 +23,7 @@ zeroize = { workspace = true }
# internal
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] }
nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "fs-gateways-storage", "cli"] }
nym-config = { path = "../../common/config" }
nym-credentials = { path = "../../common/credentials" }
nym-crypto = { path = "../../common/crypto" }
@@ -0,0 +1,30 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::CliSocks5Client;
use crate::error::Socks5ClientError;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::cli_helpers::client_add_gateway::{add_gateway, CommonClientAddGatewayArgs};
#[derive(clap::Args)]
pub(crate) struct Args {
#[command(flatten)]
common_args: CommonClientAddGatewayArgs,
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
impl AsRef<CommonClientAddGatewayArgs> for Args {
fn as_ref(&self) -> &CommonClientAddGatewayArgs {
&self.common_args
}
}
pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
let output = args.output;
let res = add_gateway::<CliSocks5Client, _>(args).await?;
println!("{}", output.format(&res));
Ok(())
}
@@ -1,54 +1,16 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::try_load_current_config;
use crate::commands::CliSocks5Client;
use crate::error::Socks5ClientError;
use clap::ArgGroup;
use nym_client_core::cli_helpers::client_import_credential::{
import_credential, CommonClientImportCredentialArgs,
};
use nym_id::import_credential;
use std::fs;
use std::path::PathBuf;
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
bs58::decode(raw).into_vec()
}
#[derive(clap::Args)]
#[clap(group(ArgGroup::new("cred_data").required(true)))]
pub(crate) struct Args {
/// Id of client that is going to import the credential
#[clap(long)]
pub id: String,
/// Explicitly provide the encoded credential data (as base58)
#[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)]
pub(crate) credential_data: Option<Vec<u8>>,
/// Specifies the path to file containing binary credential data
#[clap(long, group = "cred_data")]
pub(crate) credential_path: Option<PathBuf>,
// currently hidden as there exists only a single serialization standard
#[clap(long, hide = true)]
pub(crate) version: Option<u8>,
}
pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
let config = try_load_current_config(&args.id)?;
let credentials_store = nym_credential_storage::initialise_persistent_storage(
&config.storage_paths.common_paths.credentials_database,
)
.await;
let raw_credential = match args.credential_data {
Some(data) => data,
None => {
// SAFETY: one of those arguments must have been set
fs::read(args.credential_path.unwrap())?
}
};
import_credential(credentials_store, raw_credential, args.version).await?;
pub(crate) async fn execute(
args: CommonClientImportCredentialArgs,
) -> Result<(), Socks5ClientError> {
import_credential::<CliSocks5Client, _>(args).await?;
println!("successfully imported credential!");
Ok(())
}
+4 -13
View File
@@ -1,7 +1,7 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::try_upgrade_config;
use crate::commands::CliSocks5Client;
use crate::config::{
default_config_directory, default_config_filepath, default_data_directory, Config,
};
@@ -21,17 +21,8 @@ use std::fs;
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
struct Socks5ClientInit;
impl InitialisableClient for Socks5ClientInit {
const NAME: &'static str = "socks5";
type Error = Socks5ClientError;
impl InitialisableClient for CliSocks5Client {
type InitArgs = Init;
type Config = Config;
fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error> {
try_upgrade_config(id)
}
fn initialise_storage_paths(id: &str) -> Result<(), Self::Error> {
fs::create_dir_all(default_data_directory(id))?;
@@ -118,7 +109,7 @@ impl InitResults {
Self {
client_address: res.init_results.address.to_string(),
client_core: res.init_results,
socks5_listening_address: res.config.core.socks5.bind_adddress,
socks5_listening_address: res.config.core.socks5.bind_address,
}
}
}
@@ -139,7 +130,7 @@ pub(crate) async fn execute(args: Init) -> Result<(), Socks5ClientError> {
eprintln!("Initialising client...");
let output = args.output;
let res = initialise_client::<Socks5ClientInit>(args).await?;
let res = initialise_client::<CliSocks5Client>(args).await?;
let init_results = InitResults::new(res);
println!("{}", output.format(&init_results));
@@ -0,0 +1,32 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::CliSocks5Client;
use crate::error::Socks5ClientError;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::cli_helpers::client_list_gateways::{
list_gateways, CommonClientListGatewaysArgs,
};
#[derive(clap::Args)]
pub(crate) struct Args {
#[command(flatten)]
common_args: CommonClientListGatewaysArgs,
#[arg(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
impl AsRef<CommonClientListGatewaysArgs> for Args {
fn as_ref(&self) -> &CommonClientListGatewaysArgs {
&self.common_args
}
}
pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
let output = args.output;
let res = list_gateways::<CliSocks5Client, _>(args).await?;
println!("{}", output.format(&res));
Ok(())
}
+122 -48
View File
@@ -5,30 +5,48 @@ use crate::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::config::old_config_v1_1_30::ConfigV1_1_30;
use crate::config::{BaseClientConfig, Config, SocksClientPaths};
use crate::config::old_config_v1_1_33::ConfigV1_1_33;
use crate::config::{BaseClientConfig, Config};
use crate::error::Socks5ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use log::{error, info};
use nym_bin_common::bin_info;
use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_client_core::client::base_client::storage::gateway_details::{
OnDiskGatewayDetails, PersistedGatewayDetails,
};
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::cli_helpers::client_import_credential::CommonClientImportCredentialArgs;
use nym_client_core::cli_helpers::CliClient;
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
use nym_client_core::config::{GatewayEndpointConfig, GroupBy, TopologyStructure};
use nym_client_core::error::ClientCoreError;
use nym_client_core::config::{GroupBy, TopologyStructure};
use nym_config::OptionalSet;
use nym_sphinx::params::{PacketSize, PacketType};
use std::error::Error;
use std::net::IpAddr;
use std::sync::OnceLock;
mod add_gateway;
pub(crate) mod build_info;
mod import_credential;
pub mod init;
mod list_gateways;
pub(crate) mod run;
mod switch_gateway;
pub(crate) struct CliSocks5Client;
impl CliClient for CliSocks5Client {
const NAME: &'static str = "socks5";
type Error = Socks5ClientError;
type Config = Config;
async fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error> {
try_upgrade_config(id).await
}
async fn try_load_current_config(id: &str) -> Result<Self::Config, Self::Error> {
try_load_current_config(id).await
}
}
fn pretty_build_info_static() -> &'static str {
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
@@ -59,7 +77,16 @@ pub(crate) enum Commands {
Run(run::Run),
/// Import a pre-generated credential
ImportCredential(import_credential::Args),
ImportCredential(CommonClientImportCredentialArgs),
/// List all registered with gateways
ListGateways(list_gateways::Args),
/// Add new gateway to this client
AddGateway(add_gateway::Args),
/// Change the currently active gateway. Note that you must have already registered with the new gateway!
SwitchGateway(switch_gateway::Args),
/// Show build information of this binary
BuildInfo(build_info::BuildInfo),
@@ -93,6 +120,9 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
Commands::Init(m) => init::execute(m).await?,
Commands::Run(m) => run::execute(m).await?,
Commands::ImportCredential(m) => import_credential::execute(m).await?,
Commands::ListGateways(args) => list_gateways::execute(args).await?,
Commands::AddGateway(args) => add_gateway::execute(args).await?,
Commands::SwitchGateway(args) => switch_gateway::execute(args).await?,
Commands::BuildInfo(m) => build_info::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
@@ -168,28 +198,7 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
)
}
fn persist_gateway_details(
storage_paths: &SocksClientPaths,
details: GatewayEndpointConfig,
) -> Result<(), Socks5ClientError> {
let details_store = OnDiskGatewayDetails::new(&storage_paths.common_paths.gateway_details);
let keys_store = OnDiskKeys::new(storage_paths.common_paths.keys.clone());
let shared_keys = keys_store.ephemeral_load_gateway_keys().map_err(|source| {
Socks5ClientError::ClientCoreError(ClientCoreError::KeyStoreError {
source: Box::new(source),
})
})?;
let persisted_details = PersistedGatewayDetails::new(details.into(), Some(&shared_keys))?;
details_store
.store_to_disk(&persisted_details)
.map_err(|source| {
Socks5ClientError::ClientCoreError(ClientCoreError::GatewayDetailsStoreError {
source: Box::new(source),
})
})
}
fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
@@ -204,14 +213,23 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
let updated_step1: ConfigV1_1_20 = old_config.into();
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated_step3, gateway_config) = updated_step2.upgrade()?;
persist_gateway_details(&updated_step3.storage_paths, gateway_config)?;
let old_paths = updated_step3.storage_paths.clone();
let updated_step4: ConfigV1_1_33 = updated_step3.into();
let updated = updated_step4.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated: Config = updated_step3.into();
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError> {
async fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
@@ -225,14 +243,23 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError> {
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated_step2, gateway_config) = updated_step1.upgrade()?;
persist_gateway_details(&updated_step2.storage_paths, gateway_config)?;
let old_paths = updated_step2.storage_paths.clone();
let updated_step3: ConfigV1_1_33 = updated_step2.into();
let updated = updated_step3.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated: Config = updated_step2.into();
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientError> {
async fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientError> {
// explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20_2::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
@@ -243,14 +270,23 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientError> {
info!("It is going to get updated to the current specification.");
let (updated_step1, gateway_config) = old_config.upgrade()?;
persist_gateway_details(&updated_step1.storage_paths, gateway_config)?;
let old_paths = updated_step1.storage_paths.clone();
let updated_step2: ConfigV1_1_33 = updated_step1.into();
let updated = updated_step2.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated: Config = updated_step1.into();
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_30_config(id: &str) -> Result<bool, Socks5ClientError> {
async fn try_upgrade_v1_1_30_config(id: &str) -> Result<bool, Socks5ClientError> {
// explicitly load it as v1.1.30 (which is incompatible with the current one, i.e. +1.1.31)
let Ok(old_config) = ConfigV1_1_30::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
@@ -260,29 +296,68 @@ fn try_upgrade_v1_1_30_config(id: &str) -> Result<bool, Socks5ClientError> {
info!("It seems the client is using <= v1.1.30 config template.");
info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into();
let old_paths = old_config.storage_paths.clone();
let updated_step1: ConfigV1_1_33 = old_config.into();
let updated = updated_step1.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
None,
)
.await?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_config(id: &str) -> Result<(), Socks5ClientError> {
if try_upgrade_v1_1_13_config(id)? {
async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, Socks5ClientError> {
// explicitly load it as v1.1.33 (which is incompatible with the current one, i.e. +1.1.34)
let Ok(old_config) = ConfigV1_1_33::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the client is using <= v1.1.33 config template.");
info!("It is going to get updated to the current specification.");
let old_paths = old_config.storage_paths.clone();
let updated = old_config.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
None,
)
.await?;
updated.save_to_default_location()?;
Ok(true)
}
async fn try_upgrade_config(id: &str) -> Result<(), Socks5ClientError> {
if try_upgrade_v1_1_13_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_20_config(id)? {
if try_upgrade_v1_1_20_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_20_2_config(id)? {
if try_upgrade_v1_1_20_2_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_30_config(id)? {
if try_upgrade_v1_1_30_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_33_config(id).await? {
return Ok(());
}
Ok(())
}
fn try_load_current_config(id: &str) -> Result<Config, Socks5ClientError> {
async fn try_load_current_config(id: &str) -> Result<Config, Socks5ClientError> {
// try to load the config as is
if let Ok(cfg) = Config::read_from_default_path(id) {
return if !cfg.validate() {
@@ -293,7 +368,7 @@ fn try_load_current_config(id: &str) -> Result<Config, Socks5ClientError> {
}
// we couldn't load it - try upgrading it from older revisions
try_upgrade_config(id)?;
try_upgrade_config(id).await?;
let config = match Config::read_from_default_path(id) {
Ok(cfg) => cfg,
@@ -313,7 +388,6 @@ fn try_load_current_config(id: &str) -> Result<Config, Socks5ClientError> {
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
+1 -1
View File
@@ -105,7 +105,7 @@ fn version_check(cfg: &Config) -> bool {
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
eprintln!("Starting client {}...", args.common_args.id);
let mut config = try_load_current_config(&args.common_args.id)?;
let mut config = try_load_current_config(&args.common_args.id).await?;
config = override_config(config, OverrideConfig::from(args.clone()));
if !version_check(&config) {
@@ -0,0 +1,24 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::CliSocks5Client;
use crate::error::Socks5ClientError;
use nym_client_core::cli_helpers::client_switch_gateway::{
switch_gateway, CommonClientSwitchGatewaysArgs,
};
#[derive(clap::Args, Clone, Debug)]
pub struct Args {
#[command(flatten)]
common_args: CommonClientSwitchGatewaysArgs,
}
impl AsRef<CommonClientSwitchGatewaysArgs> for Args {
fn as_ref(&self) -> &CommonClientSwitchGatewaysArgs {
&self.common_args
}
}
pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> {
switch_gateway::<CliSocks5Client, _>(args).await
}
+3 -2
View File
@@ -3,7 +3,7 @@
use crate::config::template::CONFIG_TEMPLATE;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::cli_helpers::client_init::ClientConfig;
use nym_client_core::cli_helpers::CliClientConfig;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_config::{
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
@@ -24,6 +24,7 @@ pub mod old_config_v1_1_13;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_30;
pub mod old_config_v1_1_33;
mod persistence;
mod template;
@@ -71,7 +72,7 @@ impl NymConfigTemplate for Config {
}
}
impl ClientConfig for Config {
impl CliClientConfig for Config {
fn common_paths(&self) -> &CommonClientPaths {
&self.storage_paths.common_paths
}
@@ -5,8 +5,8 @@ use crate::config::old_config_v1_1_20_2::{
ConfigV1_1_20_2, CoreConfigV1_1_20_2, SocksClientPathsV1_1_20_2,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::disk_persistence::old_v1_1_33::ClientKeysPathsV1_1_33;
use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20;
use nym_client_core::config::old_config_v1_1_20_2::ClientV1_1_20_2;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
@@ -50,7 +50,7 @@ impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
},
storage_paths: SocksClientPathsV1_1_20_2 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPaths {
keys: ClientKeysPathsV1_1_33 {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
private_encryption_key_file: value.base.client.private_encryption_key_file,
@@ -2,13 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_30::ConfigV1_1_30;
use crate::{
config::{default_config_filepath, persistence::SocksClientPaths},
error::Socks5ClientError,
};
use crate::config::old_config_v1_1_33::SocksClientPathsV1_1_33;
use crate::{config::default_config_filepath, error::Socks5ClientError};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
use nym_config::read_config_from_toml_file;
use serde::{Deserialize, Serialize};
use std::io;
@@ -43,11 +41,13 @@ impl ConfigV1_1_20_2 {
// in this upgrade, gateway endpoint configuration was moved out of the config file,
// so its returned to be stored elsewhere.
pub fn upgrade(self) -> Result<(ConfigV1_1_30, GatewayEndpointConfig), Socks5ClientError> {
pub fn upgrade(
self,
) -> Result<(ConfigV1_1_30, OldGatewayEndpointConfigV1_1_33), Socks5ClientError> {
let gateway_details = self.core.base.client.gateway_endpoint.clone().into();
let config = ConfigV1_1_30 {
core: self.core.into(),
storage_paths: SocksClientPaths {
storage_paths: SocksClientPathsV1_1_33 {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
},
logging: self.logging,
@@ -1,8 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::SocksClientPaths;
use crate::config::{default_config_filepath, Config};
use crate::config::default_config_filepath;
use crate::config::old_config_v1_1_33::{ConfigV1_1_33, SocksClientPathsV1_1_33};
use nym_bin_common::logging::LoggingSettings;
use nym_config::read_config_from_toml_file;
use nym_socks5_client_core::config::old_config_v1_1_30::ConfigV1_1_30 as CoreConfigV1_1_30;
@@ -15,17 +15,14 @@ use std::path::Path;
pub struct ConfigV1_1_30 {
pub core: CoreConfigV1_1_30,
// I'm leaving a landmine here for when the paths actually do change the next time,
// but propagating the change right now (in ALL clients) would be such a hassle...,
// so sorry for the next person looking at it : )
pub storage_paths: SocksClientPaths,
pub storage_paths: SocksClientPathsV1_1_33,
pub logging: LoggingSettings,
}
impl From<ConfigV1_1_30> for Config {
impl From<ConfigV1_1_30> for ConfigV1_1_33 {
fn from(value: ConfigV1_1_30) -> Self {
Config {
ConfigV1_1_33 {
core: value.core.into(),
storage_paths: value.storage_paths,
logging: LoggingSettings::default(),
@@ -0,0 +1,49 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{default_config_filepath, Config, SocksClientPaths};
use crate::error::Socks5ClientError;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
use nym_config::read_config_from_toml_file;
use nym_socks5_client_core::config::old_config_v1_1_33::ConfigV1_1_33 as CoreConfigV1_1_33;
use serde::{Deserialize, Serialize};
use std::io;
use std::path::Path;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct SocksClientPathsV1_1_33 {
#[serde(flatten)]
pub common_paths: CommonClientPathsV1_1_33,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_33 {
pub core: CoreConfigV1_1_33,
// \/ CHANGED
pub storage_paths: SocksClientPathsV1_1_33,
// /\ CHANGED
pub logging: LoggingSettings,
}
impl ConfigV1_1_33 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, Socks5ClientError> {
Ok(Config {
core: self.core.into(),
storage_paths: SocksClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
},
logging: self.logging,
})
}
}
+4 -8
View File
@@ -50,10 +50,6 @@ keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key
# Path to file containing public encryption key.
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
# A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway.
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
# Path to file containing key used for encrypting and decrypting the content of an
# acknowledgement so that nobody besides the client knows which packet it refers to.
keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}'
@@ -64,9 +60,9 @@ credentials_database = '{{ storage_paths.credentials_database }}'
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
reply_surb_database = '{{ storage_paths.reply_surb_database }}'
# Path to the file containing information about gateway used by this client,
# i.e. details such as its public key, owner address or the network information.
gateway_details = '{{ storage_paths.gateway_details }}'
# Path to the file containing information about gateways used by this client,
# i.e. details such as their public keys, owner addresses or the network information.
gateway_registrations = '{{ storage_paths.gateway_registrations }}'
##### socket config options #####
@@ -77,7 +73,7 @@ provider_mix_address = '{{ core.socks5.provider_mix_address }}'
# The address on which the client will be listening for incoming requests
# (default: 127.0.0.1:1080)
bind_adddress = '{{ core.socks5.bind_adddress }}'
bind_address = '{{ core.socks5.bind_address }}'
# Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
# While this is going to hide its actual address information, it will make the actual communication
+3
View File
@@ -23,6 +23,9 @@ pub enum Socks5ClientError {
#[error(transparent)]
ClientCoreError(#[from] ClientCoreError),
#[error(transparent)]
ConfigUpgradeFailure(#[from] nym_client_core::config::ConfigUpgradeFailure),
#[error(transparent)]
NymIdError(#[from] NymIdError),
}
+4 -4
View File
@@ -1,13 +1,13 @@
[package]
name = "async-file-watcher"
name = "nym-async-file-watcher"
version = "0.1.0"
edition = "2021"
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4"
tokio = { workspace = true, features = ["time"] }
futures = { workspace = true }
log = "0.4"
notify = "5.1.0"
tokio = { workspace = true, features = ["time"] }
+11 -6
View File
@@ -18,6 +18,7 @@ pub mod acquire;
pub mod error;
mod utils;
#[derive(Debug)]
pub struct BandwidthController<C, St> {
storage: St,
client: C,
@@ -49,6 +50,7 @@ impl<C, St: Storage> BandwidthController<C, St> {
/// It marks any retrieved intermediate credentials as expired.
pub async fn get_next_usable_credential(
&self,
gateway_id: &str,
) -> Result<RetrievedCredential, BandwidthControllerError>
where
<St as Storage>::StorageError: Send + Sync + 'static,
@@ -56,7 +58,7 @@ impl<C, St: Storage> BandwidthController<C, St> {
loop {
let Some(maybe_next) = self
.storage
.get_next_unspent_credential()
.get_next_unspent_credential(gateway_id)
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?
else {
@@ -114,12 +116,13 @@ impl<C, St: Storage> BandwidthController<C, St> {
pub async fn prepare_bandwidth_credential(
&self,
gateway_id: &str,
) -> Result<PreparedCredential, BandwidthControllerError>
where
C: DkgQueryClient + Sync + Send,
<St as Storage>::StorageError: Send + Sync + 'static,
{
let retrieved_credential = self.get_next_usable_credential().await?;
let retrieved_credential = self.get_next_usable_credential(gateway_id).await?;
let epoch_id = retrieved_credential.credential.epoch_id();
let credential_id = retrieved_credential.credential_id;
@@ -137,14 +140,16 @@ impl<C, St: Storage> BandwidthController<C, St> {
})
}
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError>
pub async fn consume_credential(
&self,
id: i64,
gateway_id: &str,
) -> Result<(), BandwidthControllerError>
where
<St as Storage>::StorageError: Send + Sync + 'static,
{
// JS: shouldn't we send some contract/validator/gateway message here to actually, you know,
// consume it?
self.storage
.consume_coconut_credential(id)
.consume_coconut_credential(id, gateway_id)
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
}
-1
View File
@@ -8,7 +8,6 @@ license = { workspace = true }
repository = { workspace = true }
[dependencies]
atty = "0.2"
const-str = "0.5.6"
clap = { workspace = true, features = ["derive"] }
clap_complete = "4.0"
+3 -2
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::io::IsTerminal;
#[cfg(feature = "tracing")]
pub use opentelemetry;
@@ -14,7 +15,7 @@ pub use tracing_subscriber;
#[cfg(feature = "tracing")]
pub use tracing_tree;
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LoggingSettings {
// well, we need to implement something here at some point...
@@ -120,7 +121,7 @@ pub fn banner(crate_name: &str, crate_version: &str) -> String {
}
pub fn maybe_print_banner(crate_name: &str, crate_version: &str) {
if atty::is(atty::Stream::Stdout) {
if std::io::stdout().is_terminal() {
println!("{}", banner(crate_name, crate_version))
}
}
+12 -21
View File
@@ -11,29 +11,30 @@ license.workspace = true
[dependencies]
async-trait = { workspace = true }
base64 = "0.21.2"
bs58 = { workspace = true }
cfg-if = "1.0.0"
clap = { workspace = true, optional = true }
dashmap = { workspace = true }
dirs = "4.0"
futures = { workspace = true }
humantime-serde = "1.0"
humantime-serde = { workspace = true }
log = { workspace = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
reqwest = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sha2 = "0.10.6"
si-scale = "0.2.2"
tap = "1.0.1"
thiserror = { workspace = true }
url = { workspace = true, features = ["serde"] }
tungstenite = { workspace = true, default-features = false }
tokio = { workspace = true, features = ["macros"] }
time = "0.3.17"
time = { workspace = true }
zeroize = { workspace = true }
# internal
nym-id = { path = "../nym-id" }
nym-bandwidth-controller = { path = "../bandwidth-controller" }
nym-config = { path = "../config" }
nym-country-group = { path = "../country-group" }
nym-crypto = { path = "../crypto" }
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
nym-gateway-client = { path = "../client-libs/gateway-client" }
@@ -47,7 +48,9 @@ nym-validator-client = { path = "../client-libs/validator-client", default-featu
nym-task = { path = "../task" }
nym-credential-storage = { path = "../credential-storage" }
nym-network-defaults = { path = "../network-defaults" }
si-scale = "0.2.2"
nym-client-core-config-types = { path = "./config-types", features = ["disk-persistence"]}
nym-client-core-surb-storage = { path = "./surb-storage" }
nym-client-core-gateways-storage = { path = "./gateways-storage" }
### For serving prometheus metrics
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.hyper]
@@ -74,11 +77,6 @@ features = ["time"]
version = "0.20.1"
features = ["rustls-tls-native-roots"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
workspace = true
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
optional = true
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
workspace = true
@@ -104,17 +102,10 @@ features = ["wasm-bindgen"]
[dev-dependencies]
tempfile = "3.1.0"
[build-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
] }
[features]
default = []
cli = ["clap"]
fs-surb-storage = ["sqlx"]
fs-surb-storage = ["nym-client-core-surb-storage/fs-surb-storage"]
fs-gateways-storage = ["nym-client-core-gateways-storage/fs-gateways-storage"]
wasm = ["nym-gateway-client/wasm"]
metrics-server = []
@@ -0,0 +1,26 @@
[package]
name = "nym-client-core-config-types"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
humantime-serde = { workspace = true }
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true
url = { workspace = true, features = ["serde"] }
nym-config = { path = "../../config" }
nym-country-group = { path = "../../country-group" }
nym-pemstore = { path = "../../pemstore", optional = true }
# those are pulling so many deps T.T
nym-sphinx-params = { path = "../../nymsphinx/params" }
nym-sphinx-addressing = { path = "../../nymsphinx/addressing" }
[features]
disk-persistence = ["nym-pemstore"]
@@ -1,16 +1,53 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub mod old;
// preserve old structure for easier migration
pub use old::{old_v1_1_20_2, old_v1_1_33};
pub const DEFAULT_REPLY_SURB_DB_FILENAME: &str = "persistent_reply_store.sqlite";
pub const DEFAULT_CREDENTIALS_DB_FILENAME: &str = "credentials_database.db";
pub const DEFAULT_GATEWAYS_DETAILS_DB_FILENAME: &str = "gateways_registrations.sqlite";
pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem";
pub const DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME: &str = "private_encryption.pem";
pub const DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME: &str = "public_encryption.pem";
pub const DEFAULT_GATEWAY_SHARED_KEY_FILENAME: &str = "gateway_shared.pem";
pub const DEFAULT_ACK_KEY_FILENAME: &str = "ack_key.pem";
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CommonClientPaths {
pub keys: ClientKeysPaths,
/// Path to the file containing information about gateways used by this client,
/// i.e. details such as their public keys, owner addresses or the network information.
pub gateway_registrations: PathBuf,
/// Path to the database containing bandwidth credentials of this client.
pub credentials_database: PathBuf,
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
pub reply_surb_database: PathBuf,
}
impl CommonClientPaths {
pub fn new_base<P: AsRef<Path>>(base_data_directory: P) -> Self {
let base_dir = base_data_directory.as_ref();
CommonClientPaths {
credentials_database: base_dir.join(DEFAULT_CREDENTIALS_DB_FILENAME),
reply_surb_database: base_dir.join(DEFAULT_REPLY_SURB_DB_FILENAME),
gateway_registrations: base_dir.join(DEFAULT_GATEWAYS_DETAILS_DB_FILENAME),
keys: ClientKeysPaths::new_base(base_data_directory),
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct ClientKeysPaths {
/// Path to file containing private identity key.
@@ -25,10 +62,6 @@ pub struct ClientKeysPaths {
/// Path to file containing public encryption key.
pub public_encryption_key_file: PathBuf,
/// Path to file containing shared key derived with the specified gateway that is used
/// for all communication with it.
pub gateway_shared_key_file: PathBuf,
/// Path to file containing key used for encrypting and decrypting the content of an
/// acknowledgement so that nobody besides the client knows which packet it refers to.
pub ack_key_file: PathBuf,
@@ -43,7 +76,6 @@ impl ClientKeysPaths {
public_identity_key_file: base_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME),
private_encryption_key_file: base_dir.join(DEFAULT_PRIVATE_ENCRYPTION_KEY_FILENAME),
public_encryption_key_file: base_dir.join(DEFAULT_PUBLIC_ENCRYPTION_KEY_FILENAME),
gateway_shared_key_file: base_dir.join(DEFAULT_GATEWAY_SHARED_KEY_FILENAME),
ack_key_file: base_dir.join(DEFAULT_ACK_KEY_FILENAME),
}
}
@@ -67,7 +99,6 @@ impl ClientKeysPaths {
|| matches!(self.private_identity_key_file.try_exists(), Ok(true))
|| matches!(self.public_encryption_key_file.try_exists(), Ok(true))
|| matches!(self.private_encryption_key_file.try_exists(), Ok(true))
|| matches!(self.gateway_shared_key_file.try_exists(), Ok(true))
|| matches!(self.ack_key_file.try_exists(), Ok(true))
}
@@ -76,14 +107,9 @@ impl ClientKeysPaths {
.or_else(|| file_exists(&self.private_identity_key_file))
.or_else(|| file_exists(&self.public_encryption_key_file))
.or_else(|| file_exists(&self.private_encryption_key_file))
.or_else(|| file_exists(&self.gateway_shared_key_file))
.or_else(|| file_exists(&self.ack_key_file))
}
pub fn gateway_key_file_exists(&self) -> bool {
matches!(self.gateway_shared_key_file.try_exists(), Ok(true))
}
pub fn private_identity_key(&self) -> &Path {
&self.private_identity_key_file
}
@@ -100,10 +126,6 @@ impl ClientKeysPaths {
&self.public_encryption_key_file
}
pub fn gateway_shared_key(&self) -> &Path {
&self.gateway_shared_key_file
}
pub fn ack_key(&self) -> &Path {
&self.ack_key_file
}
@@ -0,0 +1,9 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod v1;
pub mod v2;
// aliases for backwards compatibility
pub use v1 as old_v1_1_20_2;
pub use v2 as old_v1_1_33;
@@ -0,0 +1,37 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::disk_persistence::old::v2::{
ClientKeysPathsV2, CommonClientPathsV2, DEFAULT_GATEWAY_DETAILS_FILENAME,
};
use crate::error::ConfigUpgradeFailure;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
// aliases for backwards compatibility
pub type CommonClientPathsV1_1_20_2 = CommonClientPathsV1;
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CommonClientPathsV1 {
pub keys: ClientKeysPathsV2,
pub credentials_database: PathBuf,
pub reply_surb_database: PathBuf,
}
impl CommonClientPathsV1 {
pub fn upgrade_default(self) -> Result<CommonClientPathsV2, ConfigUpgradeFailure> {
let data_dir = self
.reply_surb_database
.parent()
.ok_or_else(|| ConfigUpgradeFailure {
current_version: "1.1.20-2".to_string(),
})?;
Ok(CommonClientPathsV2 {
keys: self.keys,
gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
credentials_database: self.credentials_database,
reply_surb_database: self.reply_surb_database,
})
}
}
@@ -0,0 +1,85 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::disk_persistence::ClientKeysPaths;
use crate::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAYS_DETAILS_DB_FILENAME};
use crate::error::ConfigUpgradeFailure;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub const DEFAULT_GATEWAY_DETAILS_FILENAME: &str = "gateway_details.json";
// aliases for backwards compatibility
pub type CommonClientPathsV1_1_33 = CommonClientPathsV2;
pub type ClientKeysPathsV1_1_33 = ClientKeysPathsV2;
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct ClientKeysPathsV2 {
/// Path to file containing private identity key.
pub private_identity_key_file: PathBuf,
/// Path to file containing public identity key.
pub public_identity_key_file: PathBuf,
/// Path to file containing private encryption key.
pub private_encryption_key_file: PathBuf,
/// Path to file containing public encryption key.
pub public_encryption_key_file: PathBuf,
/// Path to file containing shared key derived with the specified gateway that is used
/// for all communication with it.
pub gateway_shared_key_file: PathBuf,
/// Path to file containing key used for encrypting and decrypting the content of an
/// acknowledgement so that nobody besides the client knows which packet it refers to.
pub ack_key_file: PathBuf,
}
impl ClientKeysPathsV2 {
pub fn upgrade(self) -> ClientKeysPaths {
ClientKeysPaths {
private_identity_key_file: self.private_identity_key_file,
public_identity_key_file: self.public_identity_key_file,
private_encryption_key_file: self.private_encryption_key_file,
public_encryption_key_file: self.public_encryption_key_file,
ack_key_file: self.ack_key_file,
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CommonClientPathsV2 {
pub keys: ClientKeysPathsV2,
/// Path to the file containing information about gateway used by this client,
/// i.e. details such as its public key, owner address or the network information.
pub gateway_details: PathBuf,
/// Path to the database containing bandwidth credentials of this client.
pub credentials_database: PathBuf,
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
pub reply_surb_database: PathBuf,
}
impl CommonClientPathsV2 {
// note that during the upgrade process, the caller will need to extract the key and gateway details
// manually and resave them in the new database
pub fn upgrade_default(self) -> Result<CommonClientPaths, ConfigUpgradeFailure> {
let data_dir = self
.gateway_details
.parent()
.ok_or_else(|| ConfigUpgradeFailure {
current_version: "1.1.33".to_string(),
})?;
Ok(CommonClientPaths {
keys: self.keys.upgrade(),
gateway_registrations: data_dir.join(DEFAULT_GATEWAYS_DETAILS_DB_FILENAME),
credentials_database: self.credentials_database,
reply_surb_database: self.reply_surb_database,
})
}
}
@@ -0,0 +1,10 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
#[derive(Debug, Error)]
#[error("unable to upgrade config file from `{current_version}`")]
pub struct ConfigUpgradeFailure {
pub current_version: String,
}
+602
View File
@@ -0,0 +1,602 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_config::defaults::NymNetworkDetails;
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
#[cfg(feature = "disk-persistence")]
pub mod disk_persistence;
pub mod error;
pub mod old;
pub use error::ConfigUpgradeFailure;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
// reply-surbs related:
// define when to request
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
// define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10);
const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60);
// 12 hours
const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60);
// 24 hours
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
pub use nym_country_group::CountryGroup;
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
pub client: Client,
#[serde(default)]
pub debug: DebugConfig,
}
impl Config {
pub fn new<S1, S2>(id: S1, version: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
Config {
client: Client::new_default(id, version),
debug: Default::default(),
}
}
pub fn from_client_config(client: Client, debug: DebugConfig) -> Self {
Config { client, debug }
}
pub fn validate(&self) -> bool {
self.debug.validate()
}
pub fn with_debug_config(mut self, debug: DebugConfig) -> Self {
self.debug = debug;
self
}
pub fn with_disabled_credentials(mut self, disabled_credentials_mode: bool) -> Self {
self.client.disabled_credentials_mode = disabled_credentials_mode;
self
}
pub fn with_custom_nyxd(mut self, urls: Vec<Url>) -> Self {
self.client.nyxd_urls = urls;
self
}
pub fn set_custom_nyxd(&mut self, nyxd_urls: Vec<Url>) {
self.client.nyxd_urls = nyxd_urls;
}
pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec<Url>) -> Self {
self.client.nym_api_urls = nym_api_urls;
self
}
pub fn set_custom_nym_apis(&mut self, nym_api_urls: Vec<Url>) {
self.client.nym_api_urls = nym_api_urls;
}
pub fn with_high_default_traffic_volume(mut self, enabled: bool) -> Self {
if enabled {
self.set_high_default_traffic_volume();
}
self
}
pub fn with_packet_type(mut self, packet_type: PacketType) -> Self {
self.debug.traffic.packet_type = packet_type;
self
}
pub fn set_high_default_traffic_volume(&mut self) {
self.debug.traffic.average_packet_delay = Duration::from_millis(10);
// basically don't really send cover messages
self.debug.cover_traffic.loop_cover_traffic_average_delay =
Duration::from_millis(2_000_000);
// 250 "real" messages / s
self.debug.traffic.message_sending_average_delay = Duration::from_millis(4);
}
pub fn with_disabled_poisson_process(mut self, disabled: bool) -> Self {
if disabled {
self.set_no_poisson_process()
}
self
}
pub fn set_no_poisson_process(&mut self) {
self.debug.traffic.disable_main_poisson_packet_distribution = true;
}
pub fn with_disabled_cover_traffic(mut self, disabled: bool) -> Self {
if disabled {
self.set_no_cover_traffic()
}
self
}
pub fn set_no_cover_traffic(&mut self) {
self.debug.cover_traffic.disable_loop_cover_traffic_stream = true;
self.debug.traffic.disable_main_poisson_packet_distribution = true;
}
pub fn with_disabled_cover_traffic_with_keepalive(mut self, disabled: bool) -> Self {
if disabled {
self.set_no_cover_traffic_with_keepalive()
}
self
}
pub fn set_no_cover_traffic_with_keepalive(&mut self) {
self.debug.traffic.disable_main_poisson_packet_distribution = true;
self.debug.cover_traffic.loop_cover_traffic_average_delay = Duration::from_secs(5);
}
pub fn with_disabled_topology_refresh(mut self, disable_topology_refresh: bool) -> Self {
self.debug.topology.disable_refreshing = disable_topology_refresh;
self
}
pub fn with_topology_structure(mut self, topology_structure: TopologyStructure) -> Self {
self.set_topology_structure(topology_structure);
self
}
pub fn set_topology_structure(&mut self, topology_structure: TopologyStructure) {
self.debug.topology.topology_structure = topology_structure;
}
pub fn with_no_per_hop_delays(mut self, no_per_hop_delays: bool) -> Self {
if no_per_hop_delays {
self.set_no_per_hop_delays()
}
self
}
pub fn set_no_per_hop_delays(&mut self) {
self.debug.traffic.average_packet_delay = Duration::ZERO;
self.debug.acknowledgements.average_ack_delay = Duration::ZERO;
}
pub fn with_secondary_packet_size(mut self, secondary_packet_size: Option<PacketSize>) -> Self {
self.set_secondary_packet_size(secondary_packet_size);
self
}
pub fn set_secondary_packet_size(&mut self, secondary_packet_size: Option<PacketSize>) {
self.debug.traffic.secondary_packet_size = secondary_packet_size;
}
pub fn set_custom_version(&mut self, version: &str) {
self.client.version = version.to_string();
}
pub fn get_id(&self) -> String {
self.client.id.clone()
}
pub fn get_disabled_credentials_mode(&self) -> bool {
self.client.disabled_credentials_mode
}
pub fn get_validator_endpoints(&self) -> Vec<Url> {
self.client.nyxd_urls.clone()
}
pub fn get_nym_api_endpoints(&self) -> Vec<Url> {
self.client.nym_api_urls.clone()
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2
#[serde(deny_unknown_fields)]
pub struct Client {
/// Version of the client for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular client.
pub id: String,
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
// TODO: this should be moved to `debug.gateway_connection`
#[serde(default)]
pub disabled_credentials_mode: bool,
/// Addresses to nyxd validators via which the client can communicate with the chain.
#[serde(alias = "validator_urls")]
pub nyxd_urls: Vec<Url>,
/// Addresses to APIs running on validator from which the client gets the view of the network.
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
}
impl Client {
pub fn new_default<S1, S2>(id: S1, version: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
let network = NymNetworkDetails::new_mainnet();
let nyxd_urls = network
.endpoints
.iter()
.map(|validator| validator.nyxd_url())
.collect();
let nym_api_urls = network
.endpoints
.iter()
.filter_map(|validator| validator.api_url())
.collect::<Vec<_>>();
Client {
version: version.into(),
id: id.into(),
disabled_credentials_mode: true,
nyxd_urls,
nym_api_urls,
}
}
pub fn new<S: Into<String>>(
id: S,
version: S,
disabled_credentials_mode: bool,
nyxd_urls: Vec<Url>,
nym_api_urls: Vec<Url>,
) -> Self {
Client {
version: version.into(),
id: id.into(),
disabled_credentials_mode,
nyxd_urls,
nym_api_urls,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Traffic {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Specifies the packet size used for sent messages.
/// Do not override it unless you understand the consequences of that change.
pub primary_packet_size: PacketSize,
/// Specifies the optional auxiliary packet size for optimizing message streams.
/// Note that its use decreases overall anonymity.
/// Do not set it it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>,
pub packet_type: PacketType,
}
impl Traffic {
pub fn validate(&self) -> bool {
if let Some(secondary_packet_size) = self.secondary_packet_size {
if secondary_packet_size == PacketSize::AckPacket
|| secondary_packet_size == self.primary_packet_size
{
return false;
}
}
true
}
}
impl Default for Traffic {
fn default() -> Self {
Traffic {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: PacketType::Mix,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTraffic {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
/// Only applicable if `secondary_packet_size` is enabled.
pub cover_traffic_primary_size_ratio: f64,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
}
impl Default for CoverTraffic {
fn default() -> Self {
CoverTraffic {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnection {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl Default for GatewayConnection {
fn default() -> Self {
GatewayConnection {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Acknowledgements {
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 1.
pub ack_wait_multiplier: f64,
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
#[serde(with = "humantime_serde")]
pub ack_wait_addition: Duration,
}
impl Default for Acknowledgements {
fn default() -> Self {
Acknowledgements {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Topology {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
/// Defines how long the client is going to wait on startup for its gateway to come online,
/// before abandoning the procedure.
#[serde(with = "humantime_serde")]
pub max_startup_gateway_waiting_period: Duration,
/// Specifies the mixnode topology to be used for sending packets.
pub topology_structure: TopologyStructure,
}
#[allow(clippy::large_enum_variant)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TopologyStructure {
#[default]
NymApi,
GeoAware(GroupBy),
}
#[allow(clippy::large_enum_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupBy {
CountryGroup(CountryGroup),
NymAddress(Recipient),
}
impl std::fmt::Display for GroupBy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GroupBy::CountryGroup(group) => write!(f, "group: {group}"),
GroupBy::NymAddress(address) => write!(f, "address: {address}"),
}
}
}
impl Default for Topology {
fn default() -> Self {
Topology {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
topology_structure: TopologyStructure::default(),
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbs {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
/// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize,
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs the client would request.
pub maximum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
pub maximum_allowed_reply_surb_request_size: u32,
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
/// for more even though in theory they wouldn't need to.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_rerequest_waiting_period: Duration,
/// Defines maximum amount of time the client is going to wait for reply surbs before
/// deciding it's never going to get them and would drop all pending messages
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_drop_waiting_period: Duration,
/// Defines maximum amount of time given reply surb is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_age: Duration,
/// Defines maximum amount of time given reply key is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
/// the default value is used.
pub surb_mix_hops: Option<u8>,
}
impl Default for ReplySurbs {
fn default() -> Self {
ReplySurbs {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
maximum_reply_surb_rerequest_waiting_period:
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
surb_mix_hops: None,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfig {
/// Defines all configuration options related to traffic streams.
pub traffic: Traffic,
/// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTraffic,
/// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnection,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: Acknowledgements,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: Topology,
/// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbs,
}
impl DebugConfig {
pub fn validate(&self) -> bool {
// no other sections have explicit requirements (yet)
self.traffic.validate()
}
}
// it could be derived, sure, but I'd rather have an explicit implementation in case we had to change
// something manually at some point
#[allow(clippy::derivable_impls)]
impl Default for DebugConfig {
fn default() -> Self {
DebugConfig {
traffic: Default::default(),
cover_traffic: Default::default(),
gateway_connection: Default::default(),
acknowledgements: Default::default(),
topology: Default::default(),
reply_surbs: Default::default(),
}
}
}
@@ -0,0 +1,15 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod v1;
pub mod v2;
pub mod v3;
pub mod v4;
pub mod v5;
// aliases for backwards compatibility
pub use v1 as old_config_v1_1_13;
pub use v2 as old_config_v1_1_20;
pub use v3 as old_config_v1_1_20_2;
pub use v4 as old_config_v1_1_30;
pub use v5 as old_config_v1_1_33;
@@ -1,23 +1,28 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_20::{
AcknowledgementsV1_1_20, ClientV1_1_20, ConfigV1_1_20, CoverTrafficV1_1_20, DebugConfigV1_1_20,
GatewayConnectionV1_1_20, LoggingV1_1_20, ReplySurbsV1_1_20, TopologyV1_1_20, TrafficV1_1_20,
DEFAULT_ACK_WAIT_ADDITION, DEFAULT_ACK_WAIT_MULTIPLIER, DEFAULT_AVERAGE_PACKET_DELAY,
DEFAULT_GATEWAY_RESPONSE_TIMEOUT, DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE, DEFAULT_MAXIMUM_REPLY_KEY_AGE,
DEFAULT_MAXIMUM_REPLY_SURB_AGE, DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
use crate::old::v2::{
AcknowledgementsV2, ClientV2, ConfigV2, CoverTrafficV2, DebugConfigV2, GatewayConnectionV2,
LoggingV2, ReplySurbsV2, TopologyV2, TrafficV2, DEFAULT_ACK_WAIT_ADDITION,
DEFAULT_ACK_WAIT_MULTIPLIER, DEFAULT_AVERAGE_PACKET_DELAY, DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
DEFAULT_MAXIMUM_REPLY_KEY_AGE, DEFAULT_MAXIMUM_REPLY_SURB_AGE,
DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
DEFAULT_TOPOLOGY_REFRESH_RATE, DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
};
use nym_sphinx::params::PacketSize;
use nym_sphinx_params::PacketSize;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::time::Duration;
// aliases for backwards compatibility
pub type OldConfigV1_1_13<T> = ConfigV1<T>;
pub type OldLoggingV1_1_13 = LoggingV1;
pub type OldDebugConfigV1_1_13 = DebugConfigV1;
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ExtendedPacketSize {
@@ -38,28 +43,28 @@ impl From<ExtendedPacketSize> for PacketSize {
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13<T> {
pub client: ClientV1_1_20<T>,
pub struct ConfigV1<T> {
pub client: ClientV2<T>,
#[serde(default)]
pub logging: OldLoggingV1_1_13,
pub logging: LoggingV1,
#[serde(default)]
pub debug: OldDebugConfigV1_1_13,
pub debug: DebugConfigV1,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OldLoggingV1_1_13 {}
pub struct LoggingV1 {}
impl From<OldLoggingV1_1_13> for LoggingV1_1_20 {
fn from(_value: OldLoggingV1_1_13) -> Self {
LoggingV1_1_20 {}
impl From<LoggingV1> for LoggingV2 {
fn from(_value: LoggingV1) -> Self {
LoggingV2 {}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct OldDebugConfigV1_1_13 {
pub struct DebugConfigV1 {
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
@@ -114,10 +119,10 @@ pub struct OldDebugConfigV1_1_13 {
pub maximum_reply_key_age: Duration,
}
impl From<OldDebugConfigV1_1_13> for DebugConfigV1_1_20 {
fn from(value: OldDebugConfigV1_1_13) -> Self {
DebugConfigV1_1_20 {
traffic: TrafficV1_1_20 {
impl From<DebugConfigV1> for DebugConfigV2 {
fn from(value: DebugConfigV1) -> Self {
DebugConfigV2 {
traffic: TrafficV2 {
average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
@@ -125,25 +130,25 @@ impl From<OldDebugConfigV1_1_13> for DebugConfigV1_1_20 {
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: value.use_extended_packet_size.map(Into::into),
},
cover_traffic: CoverTrafficV1_1_20 {
cover_traffic: CoverTrafficV2 {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream,
..CoverTrafficV1_1_20::default()
..CoverTrafficV2::default()
},
gateway_connection: GatewayConnectionV1_1_20 {
gateway_connection: GatewayConnectionV2 {
gateway_response_timeout: value.gateway_response_timeout,
},
acknowledgements: AcknowledgementsV1_1_20 {
acknowledgements: AcknowledgementsV2 {
average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition,
},
topology: TopologyV1_1_20 {
topology: TopologyV2 {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: false,
},
reply_surbs: ReplySurbsV1_1_20 {
reply_surbs: ReplySurbsV2 {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value.minimum_reply_surb_request_size,
@@ -161,9 +166,9 @@ impl From<OldDebugConfigV1_1_13> for DebugConfigV1_1_20 {
}
}
impl Default for OldDebugConfigV1_1_13 {
impl Default for DebugConfigV1 {
fn default() -> Self {
OldDebugConfigV1_1_13 {
DebugConfigV1 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
@@ -190,10 +195,10 @@ impl Default for OldDebugConfigV1_1_13 {
}
}
impl<T, U> From<OldConfigV1_1_13<T>> for ConfigV1_1_20<U> {
fn from(value: OldConfigV1_1_13<T>) -> Self {
ConfigV1_1_20 {
client: ClientV1_1_20 {
impl<T, U> From<ConfigV1<T>> for ConfigV2<U> {
fn from(value: ConfigV1<T>) -> Self {
ConfigV2 {
client: ClientV2 {
version: value.client.version,
id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode,
@@ -1,12 +1,11 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_20_2::{
AcknowledgementsV1_1_20_2, CoverTrafficV1_1_20_2, DebugConfigV1_1_20_2,
GatewayConnectionV1_1_20_2, GatewayEndpointConfigV1_1_20_2, ReplySurbsV1_1_20_2,
TopologyV1_1_20_2, TrafficV1_1_20_2,
use crate::old::v3::{
AcknowledgementsV3, CoverTrafficV3, DebugConfigV3, GatewayConnectionV3,
GatewayEndpointConfigV3, ReplySurbsV3, TopologyV3, TrafficV3,
};
use nym_sphinx::params::{PacketSize, PacketType};
use nym_sphinx_params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::path::PathBuf;
@@ -54,27 +53,41 @@ pub(crate) const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(
// 24 hours
pub(crate) const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
// aliases for backwards compatibility
pub type ConfigV1_1_20<T> = ConfigV2<T>;
pub type ClientV1_1_20<T> = ClientV2<T>;
pub type LoggingV1_1_20 = LoggingV2;
pub type DebugConfigV1_1_20 = DebugConfigV2;
pub type GatewayEndpointConfigV1_1_20 = GatewayEndpointConfigV2;
pub type TrafficV1_1_20 = TrafficV2;
pub type CoverTrafficV1_1_20 = CoverTrafficV2;
pub type GatewayConnectionV1_1_20 = GatewayConnectionV2;
pub type AcknowledgementsV1_1_20 = AcknowledgementsV2;
pub type TopologyV1_1_20 = TopologyV2;
pub type ReplySurbsV1_1_20 = ReplySurbsV2;
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20<T> {
pub client: ClientV1_1_20<T>,
pub struct ConfigV2<T> {
pub client: ClientV2<T>,
#[serde(default)]
pub logging: LoggingV1_1_20,
pub logging: LoggingV2,
#[serde(default)]
pub debug: DebugConfigV1_1_20,
pub debug: DebugConfigV2,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayEndpointConfigV1_1_20 {
pub struct GatewayEndpointConfigV2 {
pub gateway_id: String,
pub gateway_owner: String,
pub gateway_listener: String,
}
impl From<GatewayEndpointConfigV1_1_20> for GatewayEndpointConfigV1_1_20_2 {
fn from(value: GatewayEndpointConfigV1_1_20) -> Self {
GatewayEndpointConfigV1_1_20_2 {
impl From<GatewayEndpointConfigV2> for GatewayEndpointConfigV3 {
fn from(value: GatewayEndpointConfigV2) -> Self {
GatewayEndpointConfigV3 {
gateway_id: value.gateway_id,
gateway_owner: value.gateway_owner,
gateway_listener: value.gateway_listener,
@@ -83,7 +96,7 @@ impl From<GatewayEndpointConfigV1_1_20> for GatewayEndpointConfigV1_1_20_2 {
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct ClientV1_1_20<T> {
pub struct ClientV2<T> {
pub version: String,
pub id: String,
#[serde(default)]
@@ -98,7 +111,7 @@ pub struct ClientV1_1_20<T> {
pub public_encryption_key_file: PathBuf,
pub gateway_shared_key_file: PathBuf,
pub ack_key_file: PathBuf,
pub gateway_endpoint: GatewayEndpointConfigV1_1_20,
pub gateway_endpoint: GatewayEndpointConfigV2,
pub database_path: PathBuf,
#[serde(default)]
pub reply_surb_database_path: PathBuf,
@@ -110,11 +123,11 @@ pub struct ClientV1_1_20<T> {
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LoggingV1_1_20 {}
pub struct LoggingV2 {}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct TrafficV1_1_20 {
pub struct TrafficV2 {
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
#[serde(with = "humantime_serde")]
@@ -124,9 +137,9 @@ pub struct TrafficV1_1_20 {
pub secondary_packet_size: Option<PacketSize>,
}
impl From<TrafficV1_1_20> for TrafficV1_1_20_2 {
fn from(value: TrafficV1_1_20) -> Self {
TrafficV1_1_20_2 {
impl From<TrafficV2> for TrafficV3 {
fn from(value: TrafficV2) -> Self {
TrafficV3 {
average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
@@ -138,9 +151,9 @@ impl From<TrafficV1_1_20> for TrafficV1_1_20_2 {
}
}
impl Default for TrafficV1_1_20 {
impl Default for TrafficV2 {
fn default() -> Self {
TrafficV1_1_20 {
TrafficV2 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
@@ -152,16 +165,16 @@ impl Default for TrafficV1_1_20 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTrafficV1_1_20 {
pub struct CoverTrafficV2 {
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
pub cover_traffic_primary_size_ratio: f64,
pub disable_loop_cover_traffic_stream: bool,
}
impl From<CoverTrafficV1_1_20> for CoverTrafficV1_1_20_2 {
fn from(value: CoverTrafficV1_1_20) -> Self {
CoverTrafficV1_1_20_2 {
impl From<CoverTrafficV2> for CoverTrafficV3 {
fn from(value: CoverTrafficV2) -> Self {
CoverTrafficV3 {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
cover_traffic_primary_size_ratio: value.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream,
@@ -169,9 +182,9 @@ impl From<CoverTrafficV1_1_20> for CoverTrafficV1_1_20_2 {
}
}
impl Default for CoverTrafficV1_1_20 {
impl Default for CoverTrafficV2 {
fn default() -> Self {
CoverTrafficV1_1_20 {
CoverTrafficV2 {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
@@ -181,22 +194,22 @@ impl Default for CoverTrafficV1_1_20 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnectionV1_1_20 {
pub struct GatewayConnectionV2 {
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl From<GatewayConnectionV1_1_20> for GatewayConnectionV1_1_20_2 {
fn from(value: GatewayConnectionV1_1_20) -> Self {
GatewayConnectionV1_1_20_2 {
impl From<GatewayConnectionV2> for GatewayConnectionV3 {
fn from(value: GatewayConnectionV2) -> Self {
GatewayConnectionV3 {
gateway_response_timeout: value.gateway_response_timeout,
}
}
}
impl Default for GatewayConnectionV1_1_20 {
impl Default for GatewayConnectionV2 {
fn default() -> Self {
GatewayConnectionV1_1_20 {
GatewayConnectionV2 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
@@ -204,7 +217,7 @@ impl Default for GatewayConnectionV1_1_20 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV1_1_20 {
pub struct AcknowledgementsV2 {
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
pub ack_wait_multiplier: f64,
@@ -212,9 +225,9 @@ pub struct AcknowledgementsV1_1_20 {
pub ack_wait_addition: Duration,
}
impl From<AcknowledgementsV1_1_20> for AcknowledgementsV1_1_20_2 {
fn from(value: AcknowledgementsV1_1_20) -> Self {
AcknowledgementsV1_1_20_2 {
impl From<AcknowledgementsV2> for AcknowledgementsV3 {
fn from(value: AcknowledgementsV2) -> Self {
AcknowledgementsV3 {
average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition,
@@ -222,9 +235,9 @@ impl From<AcknowledgementsV1_1_20> for AcknowledgementsV1_1_20_2 {
}
}
impl Default for AcknowledgementsV1_1_20 {
impl Default for AcknowledgementsV2 {
fn default() -> Self {
AcknowledgementsV1_1_20 {
AcknowledgementsV2 {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
@@ -234,7 +247,7 @@ impl Default for AcknowledgementsV1_1_20 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TopologyV1_1_20 {
pub struct TopologyV2 {
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
#[serde(with = "humantime_serde")]
@@ -242,9 +255,9 @@ pub struct TopologyV1_1_20 {
pub disable_refreshing: bool,
}
impl From<TopologyV1_1_20> for TopologyV1_1_20_2 {
fn from(value: TopologyV1_1_20) -> Self {
TopologyV1_1_20_2 {
impl From<TopologyV2> for TopologyV3 {
fn from(value: TopologyV2) -> Self {
TopologyV3 {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: value.disable_refreshing,
@@ -252,9 +265,9 @@ impl From<TopologyV1_1_20> for TopologyV1_1_20_2 {
}
}
impl Default for TopologyV1_1_20 {
impl Default for TopologyV2 {
fn default() -> Self {
TopologyV1_1_20 {
TopologyV2 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
@@ -264,7 +277,7 @@ impl Default for TopologyV1_1_20 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV1_1_20 {
pub struct ReplySurbsV2 {
pub minimum_reply_surb_storage_threshold: usize,
pub maximum_reply_surb_storage_threshold: usize,
pub minimum_reply_surb_request_size: u32,
@@ -280,9 +293,9 @@ pub struct ReplySurbsV1_1_20 {
pub maximum_reply_key_age: Duration,
}
impl From<ReplySurbsV1_1_20> for ReplySurbsV1_1_20_2 {
fn from(value: ReplySurbsV1_1_20) -> Self {
ReplySurbsV1_1_20_2 {
impl From<ReplySurbsV2> for ReplySurbsV3 {
fn from(value: ReplySurbsV2) -> Self {
ReplySurbsV3 {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value.minimum_reply_surb_request_size,
@@ -297,9 +310,9 @@ impl From<ReplySurbsV1_1_20> for ReplySurbsV1_1_20_2 {
}
}
impl Default for ReplySurbsV1_1_20 {
impl Default for ReplySurbsV2 {
fn default() -> Self {
ReplySurbsV1_1_20 {
ReplySurbsV2 {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
@@ -316,18 +329,18 @@ impl Default for ReplySurbsV1_1_20 {
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfigV1_1_20 {
pub traffic: TrafficV1_1_20,
pub cover_traffic: CoverTrafficV1_1_20,
pub gateway_connection: GatewayConnectionV1_1_20,
pub acknowledgements: AcknowledgementsV1_1_20,
pub topology: TopologyV1_1_20,
pub reply_surbs: ReplySurbsV1_1_20,
pub struct DebugConfigV2 {
pub traffic: TrafficV2,
pub cover_traffic: CoverTrafficV2,
pub gateway_connection: GatewayConnectionV2,
pub acknowledgements: AcknowledgementsV2,
pub topology: TopologyV2,
pub reply_surbs: ReplySurbsV2,
}
impl From<DebugConfigV1_1_20> for DebugConfigV1_1_20_2 {
fn from(value: DebugConfigV1_1_20) -> Self {
DebugConfigV1_1_20_2 {
impl From<DebugConfigV2> for DebugConfigV3 {
fn from(value: DebugConfigV2) -> Self {
DebugConfigV3 {
traffic: value.traffic.into(),
cover_traffic: value.cover_traffic.into(),
gateway_connection: value.gateway_connection.into(),
@@ -1,12 +1,12 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_30::{
AcknowledgementsV1_1_30, ClientV1_1_30, ConfigV1_1_30, CoverTrafficV1_1_30, DebugConfigV1_1_30,
GatewayConnectionV1_1_30, ReplySurbsV1_1_30, TopologyV1_1_30, TrafficV1_1_30,
use crate::old::v4::{
AcknowledgementsV4, ClientV4, ConfigV4, CoverTrafficV4, DebugConfigV4, GatewayConnectionV4,
ReplySurbsV4, TopologyV4, TrafficV4,
};
use crate::config::GatewayEndpointConfig;
use nym_sphinx::params::{PacketSize, PacketType};
use crate::old::v5::GatewayEndpointConfigV5;
use nym_sphinx_params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
@@ -50,18 +50,31 @@ const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 6
// 24 hours
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
// aliases for backwards compatibility
pub type ConfigV1_1_20_2 = ConfigV3;
pub type ClientV1_1_20_2 = ClientV3;
pub type DebugConfigV1_1_20_2 = DebugConfigV3;
pub type GatewayEndpointConfigV1_1_20_2 = GatewayEndpointConfigV3;
pub type TrafficV1_1_20_2 = TrafficV3;
pub type CoverTrafficV1_1_20_2 = CoverTrafficV3;
pub type GatewayConnectionV1_1_20_2 = GatewayConnectionV3;
pub type AcknowledgementsV1_1_20_2 = AcknowledgementsV3;
pub type TopologyV1_1_20_2 = TopologyV3;
pub type ReplySurbsV1_1_20_2 = ReplySurbsV3;
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20_2 {
pub client: ClientV1_1_20_2,
pub struct ConfigV3 {
pub client: ClientV3,
#[serde(default)]
pub debug: DebugConfigV1_1_20_2,
pub debug: DebugConfigV3,
}
impl From<ConfigV1_1_20_2> for ConfigV1_1_30 {
fn from(value: ConfigV1_1_20_2) -> Self {
ConfigV1_1_30 {
impl From<ConfigV3> for ConfigV4 {
fn from(value: ConfigV3) -> Self {
ConfigV4 {
client: value.client.into(),
debug: value.debug.into(),
}
@@ -69,7 +82,7 @@ impl From<ConfigV1_1_20_2> for ConfigV1_1_30 {
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayEndpointConfigV1_1_20_2 {
pub struct GatewayEndpointConfigV3 {
/// gateway_id specifies ID of the gateway to which the client should send messages.
/// If initially omitted, a random gateway will be chosen from the available topology.
pub gateway_id: String,
@@ -81,9 +94,9 @@ pub struct GatewayEndpointConfigV1_1_20_2 {
pub gateway_listener: String,
}
impl From<GatewayEndpointConfigV1_1_20_2> for GatewayEndpointConfig {
fn from(value: GatewayEndpointConfigV1_1_20_2) -> Self {
GatewayEndpointConfig {
impl From<GatewayEndpointConfigV3> for GatewayEndpointConfigV5 {
fn from(value: GatewayEndpointConfigV3) -> Self {
GatewayEndpointConfigV5 {
gateway_id: value.gateway_id,
gateway_owner: value.gateway_owner,
gateway_listener: value.gateway_listener,
@@ -92,7 +105,7 @@ impl From<GatewayEndpointConfigV1_1_20_2> for GatewayEndpointConfig {
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct ClientV1_1_20_2 {
pub struct ClientV3 {
pub version: String,
pub id: String,
@@ -105,12 +118,12 @@ pub struct ClientV1_1_20_2 {
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
pub gateway_endpoint: GatewayEndpointConfigV1_1_20_2,
pub gateway_endpoint: GatewayEndpointConfigV3,
}
impl From<ClientV1_1_20_2> for ClientV1_1_30 {
fn from(value: ClientV1_1_20_2) -> Self {
ClientV1_1_30 {
impl From<ClientV3> for ClientV4 {
fn from(value: ClientV3) -> Self {
ClientV4 {
version: value.version,
id: value.id,
disabled_credentials_mode: value.disabled_credentials_mode,
@@ -122,7 +135,7 @@ impl From<ClientV1_1_20_2> for ClientV1_1_30 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct TrafficV1_1_20_2 {
pub struct TrafficV3 {
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
#[serde(with = "humantime_serde")]
@@ -133,9 +146,9 @@ pub struct TrafficV1_1_20_2 {
pub packet_type: PacketType,
}
impl From<TrafficV1_1_20_2> for TrafficV1_1_30 {
fn from(value: TrafficV1_1_20_2) -> Self {
TrafficV1_1_30 {
impl From<TrafficV3> for TrafficV4 {
fn from(value: TrafficV3) -> Self {
TrafficV4 {
average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
@@ -147,9 +160,9 @@ impl From<TrafficV1_1_20_2> for TrafficV1_1_30 {
}
}
impl Default for TrafficV1_1_20_2 {
impl Default for TrafficV3 {
fn default() -> Self {
TrafficV1_1_20_2 {
TrafficV3 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
@@ -162,16 +175,16 @@ impl Default for TrafficV1_1_20_2 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTrafficV1_1_20_2 {
pub struct CoverTrafficV3 {
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
pub cover_traffic_primary_size_ratio: f64,
pub disable_loop_cover_traffic_stream: bool,
}
impl From<CoverTrafficV1_1_20_2> for CoverTrafficV1_1_30 {
fn from(value: CoverTrafficV1_1_20_2) -> Self {
CoverTrafficV1_1_30 {
impl From<CoverTrafficV3> for CoverTrafficV4 {
fn from(value: CoverTrafficV3) -> Self {
CoverTrafficV4 {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
cover_traffic_primary_size_ratio: value.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream,
@@ -179,9 +192,9 @@ impl From<CoverTrafficV1_1_20_2> for CoverTrafficV1_1_30 {
}
}
impl Default for CoverTrafficV1_1_20_2 {
impl Default for CoverTrafficV3 {
fn default() -> Self {
CoverTrafficV1_1_20_2 {
CoverTrafficV3 {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
@@ -191,22 +204,22 @@ impl Default for CoverTrafficV1_1_20_2 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnectionV1_1_20_2 {
pub struct GatewayConnectionV3 {
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl From<GatewayConnectionV1_1_20_2> for GatewayConnectionV1_1_30 {
fn from(value: GatewayConnectionV1_1_20_2) -> Self {
GatewayConnectionV1_1_30 {
impl From<GatewayConnectionV3> for GatewayConnectionV4 {
fn from(value: GatewayConnectionV3) -> Self {
GatewayConnectionV4 {
gateway_response_timeout: value.gateway_response_timeout,
}
}
}
impl Default for GatewayConnectionV1_1_20_2 {
impl Default for GatewayConnectionV3 {
fn default() -> Self {
GatewayConnectionV1_1_20_2 {
GatewayConnectionV3 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
@@ -214,7 +227,7 @@ impl Default for GatewayConnectionV1_1_20_2 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV1_1_20_2 {
pub struct AcknowledgementsV3 {
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
pub ack_wait_multiplier: f64,
@@ -222,9 +235,9 @@ pub struct AcknowledgementsV1_1_20_2 {
pub ack_wait_addition: Duration,
}
impl From<AcknowledgementsV1_1_20_2> for AcknowledgementsV1_1_30 {
fn from(value: AcknowledgementsV1_1_20_2) -> Self {
AcknowledgementsV1_1_30 {
impl From<AcknowledgementsV3> for AcknowledgementsV4 {
fn from(value: AcknowledgementsV3) -> Self {
AcknowledgementsV4 {
average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition,
@@ -232,9 +245,9 @@ impl From<AcknowledgementsV1_1_20_2> for AcknowledgementsV1_1_30 {
}
}
impl Default for AcknowledgementsV1_1_20_2 {
impl Default for AcknowledgementsV3 {
fn default() -> Self {
AcknowledgementsV1_1_20_2 {
AcknowledgementsV3 {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
@@ -244,7 +257,7 @@ impl Default for AcknowledgementsV1_1_20_2 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TopologyV1_1_20_2 {
pub struct TopologyV3 {
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
#[serde(with = "humantime_serde")]
@@ -252,9 +265,9 @@ pub struct TopologyV1_1_20_2 {
pub disable_refreshing: bool,
}
impl Default for TopologyV1_1_20_2 {
impl Default for TopologyV3 {
fn default() -> Self {
TopologyV1_1_20_2 {
TopologyV3 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
@@ -262,9 +275,9 @@ impl Default for TopologyV1_1_20_2 {
}
}
impl From<TopologyV1_1_20_2> for TopologyV1_1_30 {
fn from(value: TopologyV1_1_20_2) -> Self {
TopologyV1_1_30 {
impl From<TopologyV3> for TopologyV4 {
fn from(value: TopologyV3) -> Self {
TopologyV4 {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: value.disable_refreshing,
@@ -275,7 +288,7 @@ impl From<TopologyV1_1_20_2> for TopologyV1_1_30 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV1_1_20_2 {
pub struct ReplySurbsV3 {
pub minimum_reply_surb_storage_threshold: usize,
pub maximum_reply_surb_storage_threshold: usize,
pub minimum_reply_surb_request_size: u32,
@@ -291,9 +304,9 @@ pub struct ReplySurbsV1_1_20_2 {
pub maximum_reply_key_age: Duration,
}
impl Default for ReplySurbsV1_1_20_2 {
impl Default for ReplySurbsV3 {
fn default() -> Self {
ReplySurbsV1_1_20_2 {
ReplySurbsV3 {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
@@ -308,9 +321,9 @@ impl Default for ReplySurbsV1_1_20_2 {
}
}
impl From<ReplySurbsV1_1_20_2> for ReplySurbsV1_1_30 {
fn from(value: ReplySurbsV1_1_20_2) -> Self {
ReplySurbsV1_1_30 {
impl From<ReplySurbsV3> for ReplySurbsV4 {
fn from(value: ReplySurbsV3) -> Self {
ReplySurbsV4 {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value.minimum_reply_surb_request_size,
@@ -327,18 +340,18 @@ impl From<ReplySurbsV1_1_20_2> for ReplySurbsV1_1_30 {
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfigV1_1_20_2 {
pub traffic: TrafficV1_1_20_2,
pub cover_traffic: CoverTrafficV1_1_20_2,
pub gateway_connection: GatewayConnectionV1_1_20_2,
pub acknowledgements: AcknowledgementsV1_1_20_2,
pub topology: TopologyV1_1_20_2,
pub reply_surbs: ReplySurbsV1_1_20_2,
pub struct DebugConfigV3 {
pub traffic: TrafficV3,
pub cover_traffic: CoverTrafficV3,
pub gateway_connection: GatewayConnectionV3,
pub acknowledgements: AcknowledgementsV3,
pub topology: TopologyV3,
pub reply_surbs: ReplySurbsV3,
}
impl From<DebugConfigV1_1_20_2> for DebugConfigV1_1_30 {
fn from(value: DebugConfigV1_1_20_2) -> Self {
DebugConfigV1_1_30 {
impl From<DebugConfigV3> for DebugConfigV4 {
fn from(value: DebugConfigV3) -> Self {
DebugConfigV4 {
traffic: value.traffic.into(),
cover_traffic: value.cover_traffic.into(),
gateway_connection: value.gateway_connection.into(),
@@ -1,15 +1,13 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::topology_control::geo_aware_provider::CountryGroup;
use crate::config::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection, GroupBy,
ReplySurbs, Topology, TopologyStructure, Traffic,
};
use nym_sphinx::{
addressing::clients::Recipient,
params::{PacketSize, PacketType},
use crate::old::v5::{
AcknowledgementsV5, ClientV5, ConfigV5, CoverTrafficV5, DebugConfigV5, GatewayConnectionV5,
GroupByV5, ReplySurbsV5, TopologyStructureV5, TopologyV5, TrafficV5,
};
use crate::CountryGroup;
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
@@ -55,27 +53,39 @@ const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 6
// 24 hours
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
// aliases for backwards compatibility
pub type ConfigV1_1_30 = ConfigV4;
pub type ClientV1_1_30 = ClientV4;
pub type DebugConfigV1_1_30 = DebugConfigV4;
pub type TrafficV1_1_30 = TrafficV4;
pub type CoverTrafficV1_1_30 = CoverTrafficV4;
pub type GatewayConnectionV1_1_30 = GatewayConnectionV4;
pub type AcknowledgementsV1_1_30 = AcknowledgementsV4;
pub type TopologyV1_1_30 = TopologyV4;
pub type ReplySurbsV1_1_30 = ReplySurbsV4;
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_30 {
pub client: ClientV1_1_30,
pub struct ConfigV4 {
pub client: ClientV4,
#[serde(default)]
pub debug: DebugConfigV1_1_30,
pub debug: DebugConfigV4,
}
impl From<ConfigV1_1_30> for Config {
fn from(value: ConfigV1_1_30) -> Self {
Config {
client: Client {
impl From<ConfigV4> for ConfigV5 {
fn from(value: ConfigV4) -> Self {
ConfigV5 {
client: ClientV5 {
version: value.client.version,
id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode,
nyxd_urls: value.client.nyxd_urls,
nym_api_urls: value.client.nym_api_urls,
},
debug: DebugConfig {
traffic: Traffic {
debug: DebugConfigV5 {
traffic: TrafficV5 {
average_packet_delay: value.debug.traffic.average_packet_delay,
message_sending_average_delay: value
.debug
@@ -89,7 +99,7 @@ impl From<ConfigV1_1_30> for Config {
secondary_packet_size: value.debug.traffic.secondary_packet_size,
packet_type: value.debug.traffic.packet_type,
},
cover_traffic: CoverTraffic {
cover_traffic: CoverTrafficV5 {
loop_cover_traffic_average_delay: value
.debug
.cover_traffic
@@ -103,18 +113,18 @@ impl From<ConfigV1_1_30> for Config {
.cover_traffic
.disable_loop_cover_traffic_stream,
},
gateway_connection: GatewayConnection {
gateway_connection: GatewayConnectionV5 {
gateway_response_timeout: value
.debug
.gateway_connection
.gateway_response_timeout,
},
acknowledgements: Acknowledgements {
acknowledgements: AcknowledgementsV5 {
average_ack_delay: value.debug.acknowledgements.average_ack_delay,
ack_wait_multiplier: value.debug.acknowledgements.ack_wait_multiplier,
ack_wait_addition: value.debug.acknowledgements.ack_wait_addition,
},
topology: Topology {
topology: TopologyV5 {
topology_refresh_rate: value.debug.topology.topology_refresh_rate,
topology_resolution_timeout: value.debug.topology.topology_resolution_timeout,
disable_refreshing: value.debug.topology.disable_refreshing,
@@ -124,7 +134,7 @@ impl From<ConfigV1_1_30> for Config {
.max_startup_gateway_waiting_period,
topology_structure: value.debug.topology.topology_structure.into(),
},
reply_surbs: ReplySurbs {
reply_surbs: ReplySurbsV5 {
minimum_reply_surb_storage_threshold: value
.debug
.reply_surbs
@@ -155,7 +165,10 @@ impl From<ConfigV1_1_30> for Config {
.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
// \/ ADDED
surb_mix_hops: None,
// /\ ADDED
},
},
}
@@ -165,7 +178,7 @@ impl From<ConfigV1_1_30> for Config {
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2
#[serde(deny_unknown_fields)]
pub struct ClientV1_1_30 {
pub struct ClientV4 {
/// Version of the client for which this configuration was created.
pub version: String,
@@ -189,7 +202,7 @@ pub struct ClientV1_1_30 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TrafficV1_1_30 {
pub struct TrafficV4 {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
@@ -220,9 +233,9 @@ pub struct TrafficV1_1_30 {
pub packet_type: PacketType,
}
impl Default for TrafficV1_1_30 {
impl Default for TrafficV4 {
fn default() -> Self {
TrafficV1_1_30 {
TrafficV4 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
@@ -235,7 +248,7 @@ impl Default for TrafficV1_1_30 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTrafficV1_1_30 {
pub struct CoverTrafficV4 {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
#[serde(with = "humantime_serde")]
@@ -250,9 +263,9 @@ pub struct CoverTrafficV1_1_30 {
pub disable_loop_cover_traffic_stream: bool,
}
impl Default for CoverTrafficV1_1_30 {
impl Default for CoverTrafficV4 {
fn default() -> Self {
CoverTrafficV1_1_30 {
CoverTrafficV4 {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
@@ -262,16 +275,16 @@ impl Default for CoverTrafficV1_1_30 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnectionV1_1_30 {
pub struct GatewayConnectionV4 {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl Default for GatewayConnectionV1_1_30 {
impl Default for GatewayConnectionV4 {
fn default() -> Self {
GatewayConnectionV1_1_30 {
GatewayConnectionV4 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
@@ -279,7 +292,7 @@ impl Default for GatewayConnectionV1_1_30 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV1_1_30 {
pub struct AcknowledgementsV4 {
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
@@ -299,9 +312,9 @@ pub struct AcknowledgementsV1_1_30 {
pub ack_wait_addition: Duration,
}
impl Default for AcknowledgementsV1_1_30 {
impl Default for AcknowledgementsV4 {
fn default() -> Self {
AcknowledgementsV1_1_30 {
AcknowledgementsV4 {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
@@ -311,7 +324,7 @@ impl Default for AcknowledgementsV1_1_30 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TopologyV1_1_30 {
pub struct TopologyV4 {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
#[serde(with = "humantime_serde")]
@@ -334,23 +347,23 @@ pub struct TopologyV1_1_30 {
pub max_startup_gateway_waiting_period: Duration,
/// Specifies the mixnode topology to be used for sending packets.
pub topology_structure: TopologyStructureV1_1_30,
pub topology_structure: TopologyStructureV4,
}
#[allow(clippy::large_enum_variant)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TopologyStructureV1_1_30 {
pub enum TopologyStructureV4 {
#[default]
NymApi,
GeoAware(GroupByV1_1_30),
GeoAware(GroupByV4),
}
impl From<TopologyStructureV1_1_30> for TopologyStructure {
fn from(value: TopologyStructureV1_1_30) -> Self {
impl From<TopologyStructureV4> for TopologyStructureV5 {
fn from(value: TopologyStructureV4) -> Self {
match value {
TopologyStructureV1_1_30::NymApi => TopologyStructure::NymApi,
TopologyStructureV1_1_30::GeoAware(group_by) => {
TopologyStructure::GeoAware(group_by.into())
TopologyStructureV4::NymApi => TopologyStructureV5::NymApi,
TopologyStructureV4::GeoAware(group_by) => {
TopologyStructureV5::GeoAware(group_by.into())
}
}
}
@@ -358,44 +371,44 @@ impl From<TopologyStructureV1_1_30> for TopologyStructure {
#[allow(clippy::large_enum_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupByV1_1_30 {
pub enum GroupByV4 {
CountryGroup(CountryGroup),
NymAddress(Recipient),
}
impl From<GroupByV1_1_30> for GroupBy {
fn from(value: GroupByV1_1_30) -> Self {
impl From<GroupByV4> for GroupByV5 {
fn from(value: GroupByV4) -> Self {
match value {
GroupByV1_1_30::CountryGroup(country) => GroupBy::CountryGroup(country),
GroupByV1_1_30::NymAddress(addr) => GroupBy::NymAddress(addr),
GroupByV4::CountryGroup(country) => GroupByV5::CountryGroup(country),
GroupByV4::NymAddress(addr) => GroupByV5::NymAddress(addr),
}
}
}
impl std::fmt::Display for GroupByV1_1_30 {
impl std::fmt::Display for GroupByV4 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GroupByV1_1_30::CountryGroup(group) => write!(f, "group: {}", group),
GroupByV1_1_30::NymAddress(address) => write!(f, "address: {}", address),
GroupByV4::CountryGroup(group) => write!(f, "group: {}", group),
GroupByV4::NymAddress(address) => write!(f, "address: {}", address),
}
}
}
impl Default for TopologyV1_1_30 {
impl Default for TopologyV4 {
fn default() -> Self {
TopologyV1_1_30 {
TopologyV4 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
topology_structure: TopologyStructureV1_1_30::default(),
topology_structure: TopologyStructureV4::default(),
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV1_1_30 {
pub struct ReplySurbsV4 {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
/// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize,
@@ -433,9 +446,9 @@ pub struct ReplySurbsV1_1_30 {
pub maximum_reply_key_age: Duration,
}
impl Default for ReplySurbsV1_1_30 {
impl Default for ReplySurbsV4 {
fn default() -> Self {
ReplySurbsV1_1_30 {
ReplySurbsV4 {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
@@ -452,22 +465,22 @@ impl Default for ReplySurbsV1_1_30 {
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfigV1_1_30 {
pub struct DebugConfigV4 {
/// Defines all configuration options related to traffic streams.
pub traffic: TrafficV1_1_30,
pub traffic: TrafficV4,
/// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTrafficV1_1_30,
pub cover_traffic: CoverTrafficV4,
/// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnectionV1_1_30,
pub gateway_connection: GatewayConnectionV4,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: AcknowledgementsV1_1_30,
pub acknowledgements: AcknowledgementsV4,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: TopologyV1_1_30,
pub topology: TopologyV4,
/// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbsV1_1_30,
pub reply_surbs: ReplySurbsV4,
}
@@ -0,0 +1,499 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
Acknowledgements, Client, Config, CountryGroup, CoverTraffic, DebugConfig, GatewayConnection,
GroupBy, ReplySurbs, Topology, TopologyStructure, Traffic,
};
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
// reply-surbs related:
// define when to request
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
// define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10);
const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60);
// 12 hours
const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60);
// 24 hours
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
// aliases for backwards compatibility
pub type ConfigV1_1_33 = ConfigV5;
pub type ClientV1_1_33 = ClientV5;
pub type DebugConfigV1_1_33 = DebugConfigV5;
pub type OldGatewayEndpointConfigV1_1_33 = GatewayEndpointConfigV5;
pub type TrafficV1_1_33 = TrafficV5;
pub type CoverTrafficV1_1_33 = CoverTrafficV5;
pub type GatewayConnectionV1_1_33 = GatewayConnectionV5;
pub type AcknowledgementsV1_1_33 = AcknowledgementsV5;
pub type TopologyV1_1_33 = TopologyV5;
pub type ReplySurbsV1_1_33 = ReplySurbsV5;
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayEndpointConfigV5 {
/// gateway_id specifies ID of the gateway to which the client should send messages.
/// If initially omitted, a random gateway will be chosen from the available topology.
pub gateway_id: String,
/// Address of the gateway owner to which the client should send messages.
pub gateway_owner: String,
/// Address of the gateway listener to which all client requests should be sent.
pub gateway_listener: String,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV5 {
pub client: ClientV5,
#[serde(default)]
pub debug: DebugConfigV5,
}
impl From<ConfigV5> for Config {
fn from(value: ConfigV5) -> Self {
Config {
client: Client {
version: value.client.version,
id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode,
nyxd_urls: value.client.nyxd_urls,
nym_api_urls: value.client.nym_api_urls,
},
debug: DebugConfig {
traffic: Traffic {
average_packet_delay: value.debug.traffic.average_packet_delay,
message_sending_average_delay: value
.debug
.traffic
.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
.debug
.traffic
.disable_main_poisson_packet_distribution,
primary_packet_size: value.debug.traffic.primary_packet_size,
secondary_packet_size: value.debug.traffic.secondary_packet_size,
packet_type: value.debug.traffic.packet_type,
},
cover_traffic: CoverTraffic {
loop_cover_traffic_average_delay: value
.debug
.cover_traffic
.loop_cover_traffic_average_delay,
cover_traffic_primary_size_ratio: value
.debug
.cover_traffic
.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: value
.debug
.cover_traffic
.disable_loop_cover_traffic_stream,
},
gateway_connection: GatewayConnection {
gateway_response_timeout: value
.debug
.gateway_connection
.gateway_response_timeout,
},
acknowledgements: Acknowledgements {
average_ack_delay: value.debug.acknowledgements.average_ack_delay,
ack_wait_multiplier: value.debug.acknowledgements.ack_wait_multiplier,
ack_wait_addition: value.debug.acknowledgements.ack_wait_addition,
},
topology: Topology {
topology_refresh_rate: value.debug.topology.topology_refresh_rate,
topology_resolution_timeout: value.debug.topology.topology_resolution_timeout,
disable_refreshing: value.debug.topology.disable_refreshing,
max_startup_gateway_waiting_period: value
.debug
.topology
.max_startup_gateway_waiting_period,
topology_structure: value.debug.topology.topology_structure.into(),
},
reply_surbs: ReplySurbs {
minimum_reply_surb_storage_threshold: value
.debug
.reply_surbs
.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value
.debug
.reply_surbs
.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value
.debug
.reply_surbs
.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: value
.debug
.reply_surbs
.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: value
.debug
.reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: value
.debug
.reply_surbs
.maximum_reply_surb_rerequest_waiting_period,
maximum_reply_surb_drop_waiting_period: value
.debug
.reply_surbs
.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
},
},
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2
#[serde(deny_unknown_fields)]
pub struct ClientV5 {
/// Version of the client for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular client.
pub id: String,
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
// TODO: this should be moved to `debug.gateway_connection`
#[serde(default)]
pub disabled_credentials_mode: bool,
/// Addresses to nyxd validators via which the client can communicate with the chain.
#[serde(alias = "validator_urls")]
pub nyxd_urls: Vec<Url>,
/// Addresses to APIs running on validator from which the client gets the view of the network.
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TrafficV5 {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Specifies the packet size used for sent messages.
/// Do not override it unless you understand the consequences of that change.
pub primary_packet_size: PacketSize,
/// Specifies the optional auxiliary packet size for optimizing message streams.
/// Note that its use decreases overall anonymity.
/// Do not set it it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>,
pub packet_type: PacketType,
}
impl Default for TrafficV5 {
fn default() -> Self {
TrafficV5 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: PacketType::Mix,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTrafficV5 {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
/// Only applicable if `secondary_packet_size` is enabled.
pub cover_traffic_primary_size_ratio: f64,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
}
impl Default for CoverTrafficV5 {
fn default() -> Self {
CoverTrafficV5 {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnectionV5 {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl Default for GatewayConnectionV5 {
fn default() -> Self {
GatewayConnectionV5 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV5 {
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 1.
pub ack_wait_multiplier: f64,
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
#[serde(with = "humantime_serde")]
pub ack_wait_addition: Duration,
}
impl Default for AcknowledgementsV5 {
fn default() -> Self {
AcknowledgementsV5 {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TopologyV5 {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
/// Defines how long the client is going to wait on startup for its gateway to come online,
/// before abandoning the procedure.
#[serde(with = "humantime_serde")]
pub max_startup_gateway_waiting_period: Duration,
/// Specifies the mixnode topology to be used for sending packets.
pub topology_structure: TopologyStructureV5,
}
#[allow(clippy::large_enum_variant)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TopologyStructureV5 {
#[default]
NymApi,
GeoAware(GroupByV5),
}
impl From<TopologyStructureV5> for TopologyStructure {
fn from(value: TopologyStructureV5) -> Self {
match value {
TopologyStructureV5::NymApi => TopologyStructure::NymApi,
TopologyStructureV5::GeoAware(group_by) => TopologyStructure::GeoAware(group_by.into()),
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupByV5 {
CountryGroup(CountryGroup),
NymAddress(Recipient),
}
impl From<GroupByV5> for GroupBy {
fn from(value: GroupByV5) -> Self {
match value {
GroupByV5::CountryGroup(country) => GroupBy::CountryGroup(country),
GroupByV5::NymAddress(addr) => GroupBy::NymAddress(addr),
}
}
}
impl std::fmt::Display for GroupByV5 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GroupByV5::CountryGroup(group) => write!(f, "group: {}", group),
GroupByV5::NymAddress(address) => write!(f, "address: {}", address),
}
}
}
impl Default for TopologyV5 {
fn default() -> Self {
TopologyV5 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
topology_structure: TopologyStructureV5::default(),
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV5 {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
/// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize,
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs the client would request.
pub maximum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
pub maximum_allowed_reply_surb_request_size: u32,
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
/// for more even though in theory they wouldn't need to.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_rerequest_waiting_period: Duration,
/// Defines maximum amount of time the client is going to wait for reply surbs before
/// deciding it's never going to get them and would drop all pending messages
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_drop_waiting_period: Duration,
/// Defines maximum amount of time given reply surb is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_age: Duration,
/// Defines maximum amount of time given reply key is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
/// the default value is used.
pub surb_mix_hops: Option<u8>,
}
impl Default for ReplySurbsV5 {
fn default() -> Self {
ReplySurbsV5 {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
maximum_reply_surb_rerequest_waiting_period:
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
surb_mix_hops: None,
}
}
}
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfigV5 {
/// Defines all configuration options related to traffic streams.
pub traffic: TrafficV5,
/// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTrafficV5,
/// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnectionV5,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: AcknowledgementsV5,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: TopologyV5,
/// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbsV5,
}
@@ -0,0 +1,33 @@
[package]
name = "nym-client-core-gateways-storage"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait.workspace = true
cosmrs.workspace = true
log.workspace = true
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true
time.workspace = true
tokio = { workspace = true, features = ["sync"] }
url.workspace = true
zeroize = { workspace = true, features = ["zeroize_derive"] }
nym-crypto = { path = "../../crypto", features = ["asymmetric"] }
nym-gateway-requests = { path = "../../../gateway/gateway-requests" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
workspace = true
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"]
optional = true
[build-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
[features]
fs-gateways-storage = ["sqlx"]
@@ -0,0 +1,31 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[tokio::main]
async fn main() {
#[cfg(feature = "fs-gateways-storage")]
{
use sqlx::{Connection, SqliteConnection};
use std::env;
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{out_dir}/gateways-storage-example.sqlite");
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.expect("Failed to create SQLx database connection");
sqlx::migrate!("./fs_gateways_migrations")
.run(&mut conn)
.await
.expect("Failed to perform SQLx migrations");
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
#[cfg(target_family = "windows")]
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
CREATE TABLE active_gateway
(
id INTEGER PRIMARY KEY CHECK (id = 0),
active_gateway_id_bs58 TEXT REFERENCES registered_gateway (gateway_id_bs58)
);
CREATE TABLE registered_gateway
(
gateway_id_bs58 TEXT NOT NULL UNIQUE PRIMARY KEY,
registration_timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
gateway_type TEXT CHECK ( gateway_type IN ('remote', 'custom') ) NOT NULL DEFAULT 'remote'
);
-- TODO: perhaps keep additional metadata such as bandwidth, credential usage, etc
CREATE TABLE remote_gateway_details
(
gateway_id_bs58 TEXT NOT NULL UNIQUE PRIMARY KEY REFERENCES registered_gateway (gateway_id_bs58),
derived_aes128_ctr_blake3_hmac_keys_bs58 TEXT NOT NULL,
gateway_owner_address TEXT NOT NULL,
gateway_listener TEXT NOT NULL,
wg_tun_address TEXT
);
CREATE TABLE custom_gateway_details
(
gateway_id_bs58 TEXT NOT NULL UNIQUE PRIMARY KEY REFERENCES registered_gateway (gateway_id_bs58),
data BLOB
);
INSERT INTO active_gateway(id, active_gateway_id_bs58)
values (0, NULL);
@@ -0,0 +1,45 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::BadGateway;
use std::io;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum StorageError {
#[error("the provided database path doesn't have a filename defined")]
DatabasePathWithoutFilename { provided_path: PathBuf },
#[error("unable to create the directory for the database at {}: {source}", provided_path.display())]
DatabasePathUnableToCreateParentDirectory {
provided_path: PathBuf,
source: io::Error,
},
#[error("failed to perform sqlx migration: {source}")]
MigrationError {
#[source]
#[from]
source: sqlx::migrate::MigrateError,
},
#[error("failed to connect to the underlying connection pool: {source}")]
DatabaseConnectionError {
#[source]
source: sqlx::error::Error,
},
#[error("failed to run the SQL query: {source}")]
QueryError {
#[source]
#[from]
source: sqlx::error::Error,
},
#[error(transparent)]
MalformedGateway(#[from] BadGateway),
#[error("gateway {gateway_id} does not exist in the storage")]
GatewayDoesNotExist { gateway_id: String },
}
@@ -0,0 +1,234 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
backend::fs_backend::error::StorageError,
types::{
RawActiveGateway, RawCustomGatewayDetails, RawRegisteredGateway, RawRemoteGatewayDetails,
},
};
use log::{debug, error};
use sqlx::ConnectOptions;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct StorageManager {
pub connection_pool: sqlx::SqlitePool,
}
// all SQL goes here
impl StorageManager {
pub async fn init<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
// ensure the whole directory structure exists
if let Some(parent_dir) = database_path.as_ref().parent() {
std::fs::create_dir_all(parent_dir).map_err(|source| {
StorageError::DatabasePathUnableToCreateParentDirectory {
provided_path: database_path.as_ref().to_path_buf(),
source,
}
})?;
}
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true);
opts.disable_statement_logging();
let connection_pool = sqlx::SqlitePool::connect_with(opts)
.await
.map_err(|source| {
error!("Failed to connect to SQLx database: {source}");
StorageError::DatabaseConnectionError { source }
})?;
sqlx::migrate!("./fs_gateways_migrations")
.run(&connection_pool)
.await
.inspect_err(|err| {
error!("Failed to initialize SQLx database: {err}");
})?;
debug!("Database migration finished!");
Ok(StorageManager { connection_pool })
}
pub(crate) async fn get_active_gateway(&self) -> Result<RawActiveGateway, sqlx::Error> {
sqlx::query_as!(
RawActiveGateway,
"SELECT active_gateway_id_bs58 FROM active_gateway"
)
.fetch_one(&self.connection_pool)
.await
}
pub(crate) async fn set_active_gateway(
&self,
gateway_id: Option<&str>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"UPDATE active_gateway SET active_gateway_id_bs58 = ?",
gateway_id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn has_registered_gateway(
&self,
gateway_id: &str,
) -> Result<bool, sqlx::Error> {
sqlx::query!("SELECT EXISTS (SELECT 1 FROM registered_gateway WHERE gateway_id_bs58 = ?) AS 'exists'", gateway_id)
.fetch_one(&self.connection_pool)
.await
.map(|result| result.exists == 1)
}
pub(crate) async fn maybe_get_registered_gateway(
&self,
gateway_id: &str,
) -> Result<Option<RawRegisteredGateway>, sqlx::Error> {
sqlx::query_as("SELECT * FROM registered_gateway WHERE gateway_id_bs58 = ?")
.bind(gateway_id)
.fetch_optional(&self.connection_pool)
.await
}
pub(crate) async fn must_get_registered_gateway(
&self,
gateway_id: &str,
) -> Result<RawRegisteredGateway, sqlx::Error> {
sqlx::query_as("SELECT * FROM registered_gateway WHERE gateway_id_bs58 = ?")
.bind(gateway_id)
.fetch_one(&self.connection_pool)
.await
}
pub(crate) async fn set_registered_gateway(
&self,
registered_gateway: &RawRegisteredGateway,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type)
VALUES (?, ?, ?)
"#,
registered_gateway.gateway_id_bs58,
registered_gateway.registration_timestamp,
registered_gateway.gateway_type,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn remove_registered_gateway(
&self,
gateway_id: &str,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"DELETE FROM registered_gateway WHERE gateway_id_bs58 = ?",
gateway_id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn get_remote_gateway_details(
&self,
gateway_id: &str,
) -> Result<RawRemoteGatewayDetails, sqlx::Error> {
sqlx::query_as!(
RawRemoteGatewayDetails,
"SELECT * FROM remote_gateway_details WHERE gateway_id_bs58 = ?",
gateway_id
)
.fetch_one(&self.connection_pool)
.await
}
pub(crate) async fn set_remote_gateway_details(
&self,
remote: &RawRemoteGatewayDetails,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58, gateway_owner_address, gateway_listener, wg_tun_address)
VALUES (?, ?, ?, ?, ?)
"#,
remote.gateway_id_bs58,
remote.derived_aes128_ctr_blake3_hmac_keys_bs58,
remote.gateway_owner_address,
remote.gateway_listener,
remote.wg_tun_address,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn remove_remote_gateway_details(
&self,
gateway_id: &str,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"DELETE FROM remote_gateway_details WHERE gateway_id_bs58 = ?",
gateway_id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn get_custom_gateway_details(
&self,
gateway_id: &str,
) -> Result<RawCustomGatewayDetails, sqlx::Error> {
sqlx::query_as!(
RawCustomGatewayDetails,
"SELECT * FROM custom_gateway_details WHERE gateway_id_bs58 = ?",
gateway_id
)
.fetch_one(&self.connection_pool)
.await
}
pub(crate) async fn set_custom_gateway_details(
&self,
custom: &RawCustomGatewayDetails,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO custom_gateway_details(gateway_id_bs58, data)
VALUES (?, ?)
"#,
custom.gateway_id_bs58,
custom.data,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn remove_custom_gateway_details(
&self,
gateway_id: &str,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"DELETE FROM custom_gateway_details WHERE gateway_id_bs58 = ?",
gateway_id
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn registered_gateways(&self) -> Result<Vec<String>, sqlx::Error> {
sqlx::query!("SELECT gateway_id_bs58 FROM registered_gateway")
.fetch_all(&self.connection_pool)
.await
.map(|records| records.into_iter().map(|r| r.gateway_id_bs58).collect())
}
}
@@ -0,0 +1,155 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
ActiveGateway, BadGateway, GatewayDetails, GatewayRegistration, GatewayType,
GatewaysDetailsStore, StorageError,
};
use async_trait::async_trait;
use manager::StorageManager;
use nym_crypto::asymmetric::identity::PublicKey;
use std::path::Path;
pub mod error;
mod manager;
mod models;
pub struct OnDiskGatewaysDetails {
manager: StorageManager,
}
impl OnDiskGatewaysDetails {
pub async fn init<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
Ok(OnDiskGatewaysDetails {
manager: StorageManager::init(database_path).await?,
})
}
}
#[async_trait]
impl GatewaysDetailsStore for OnDiskGatewaysDetails {
type StorageError = error::StorageError;
async fn has_gateway_details(&self, gateway_id: &str) -> Result<bool, Self::StorageError> {
Ok(self
.manager
.maybe_get_registered_gateway(gateway_id)
.await?
.is_some())
}
async fn active_gateway(&self) -> Result<ActiveGateway, Self::StorageError> {
let raw_active = self.manager.get_active_gateway().await?;
let registration = match raw_active.active_gateway_id_bs58 {
None => None,
Some(gateway_id) => Some(self.load_gateway_details(&gateway_id).await?),
};
Ok(ActiveGateway { registration })
}
async fn set_active_gateway(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
if !self.manager.has_registered_gateway(gateway_id).await? {
return Err(StorageError::GatewayDoesNotExist {
gateway_id: gateway_id.to_string(),
});
}
Ok(self.manager.set_active_gateway(Some(gateway_id)).await?)
}
async fn all_gateways(&self) -> Result<Vec<GatewayRegistration>, Self::StorageError> {
let identities = self.manager.registered_gateways().await?;
let mut registered = Vec::with_capacity(identities.len());
for gateway_id in identities {
registered.push(self.load_gateway_details(&gateway_id).await?)
}
Ok(registered)
}
async fn all_gateways_identities(&self) -> Result<Vec<PublicKey>, Self::StorageError> {
Ok(self
.manager
.registered_gateways()
.await?
.into_iter()
.map(|gateway_id| {
gateway_id
.as_str()
.parse()
.map_err(|source| BadGateway::MalformedGatewayIdentity { gateway_id, source })
})
.collect::<Result<_, _>>()?)
}
async fn load_gateway_details(
&self,
gateway_id: &str,
) -> Result<GatewayRegistration, Self::StorageError> {
let raw_registration = self.manager.must_get_registered_gateway(gateway_id).await?;
let typ: GatewayType = raw_registration.gateway_type.parse()?;
let details = match typ {
GatewayType::Remote => {
let raw_details = self.manager.get_remote_gateway_details(gateway_id).await?;
GatewayDetails::Remote(raw_details.try_into()?)
}
GatewayType::Custom => {
let raw_details = self.manager.get_custom_gateway_details(gateway_id).await?;
GatewayDetails::Custom(raw_details.try_into()?)
}
};
Ok(GatewayRegistration {
details,
registration_timestamp: raw_registration.registration_timestamp,
})
}
async fn store_gateway_details(
&self,
details: &GatewayRegistration,
) -> Result<(), Self::StorageError> {
let raw_registration = details.into();
self.manager
.set_registered_gateway(&raw_registration)
.await?;
match &details.details {
GatewayDetails::Remote(remote_details) => {
let raw_details = remote_details.into();
self.manager
.set_remote_gateway_details(&raw_details)
.await?;
}
GatewayDetails::Custom(custom_details) => {
let raw_details = custom_details.into();
self.manager
.set_custom_gateway_details(&raw_details)
.await?;
}
}
Ok(())
}
// ideally all of those should be run under a storage tx to ensure storage consistency,
// but at that point it's fine
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
let active = self.manager.get_active_gateway().await?;
if let Some(currently_active) = &active.active_gateway_id_bs58 {
if currently_active == gateway_id {
self.manager.set_active_gateway(None).await?;
}
}
// just try remove it from all tables even if it doesn't actually exist
self.manager.remove_registered_gateway(gateway_id).await?;
self.manager
.remove_remote_gateway_details(gateway_id)
.await?;
self.manager
.remove_custom_gateway_details(gateway_id)
.await?;
Ok(())
}
}
@@ -0,0 +1,2 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,108 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{ActiveGateway, GatewayRegistration};
use crate::{BadGateway, GatewaysDetailsStore};
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::Arc;
use thiserror::Error;
use tokio::sync::RwLock;
#[derive(Debug, Error)]
pub enum InMemStorageError {
#[error("gateway {gateway_id} does not exist")]
GatewayDoesNotExist { gateway_id: String },
#[error(transparent)]
MalformedGateway(#[from] BadGateway),
}
#[derive(Debug, Default)]
pub struct InMemGatewaysDetails {
inner: Arc<RwLock<InMemStorageInner>>,
}
#[derive(Debug, Default)]
struct InMemStorageInner {
active_gateway: Option<String>,
gateways: HashMap<String, GatewayRegistration>,
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GatewaysDetailsStore for InMemGatewaysDetails {
type StorageError = InMemStorageError;
async fn has_gateway_details(&self, gateway_id: &str) -> Result<bool, Self::StorageError> {
Ok(self.inner.read().await.gateways.contains_key(gateway_id))
}
async fn active_gateway(&self) -> Result<ActiveGateway, Self::StorageError> {
let guard = self.inner.read().await;
let registration = guard.active_gateway.as_ref().map(|id| {
// SAFETY: if particular gateway is set as active, its details MUST exist
#[allow(clippy::unwrap_used)]
guard.gateways.get(id).unwrap().clone()
});
Ok(ActiveGateway { registration })
}
async fn set_active_gateway(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
// ensure the gateway with provided id exists
let mut guard = self.inner.write().await;
if !guard.gateways.contains_key(gateway_id) {
return Err(InMemStorageError::GatewayDoesNotExist {
gateway_id: gateway_id.to_string(),
});
}
guard.active_gateway = Some(gateway_id.to_string());
Ok(())
}
async fn all_gateways(&self) -> Result<Vec<GatewayRegistration>, Self::StorageError> {
Ok(self.inner.read().await.gateways.values().cloned().collect())
}
async fn load_gateway_details(
&self,
gateway_id: &str,
) -> Result<GatewayRegistration, Self::StorageError> {
self.inner
.read()
.await
.gateways
.get(gateway_id)
.cloned()
.ok_or(InMemStorageError::GatewayDoesNotExist {
gateway_id: gateway_id.to_string(),
})
}
async fn store_gateway_details(
&self,
details: &GatewayRegistration,
) -> Result<(), Self::StorageError> {
self.inner.write().await.gateways.insert(
details.details.gateway_id().to_base58_string(),
details.clone(),
);
Ok(())
}
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
let mut guard = self.inner.write().await;
if let Some(active) = guard.active_gateway.as_ref() {
if active == gateway_id {
guard.active_gateway = None
}
}
guard.gateways.remove(gateway_id);
Ok(())
}
}
@@ -0,0 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-gateways-storage"))]
pub mod fs_backend;
pub mod mem_backend;
@@ -0,0 +1,50 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_gateway_requests::registration::handshake::shared_key::SharedKeyConversionError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum BadGateway {
#[error("{typ} is not a valid gateway type")]
InvalidGatewayType { typ: String },
#[error("the provided gateway identity {gateway_id} is malformed: {source}")]
MalformedGatewayIdentity {
gateway_id: String,
#[source]
source: Ed25519RecoveryError,
},
#[error("the account owner of gateway {gateway_id} ({raw_owner}) is malformed: {source}")]
MalformedGatewayOwnerAccountAddress {
gateway_id: String,
raw_owner: String,
#[source]
source: cosmrs::ErrorReport,
},
#[error("the shared keys provided for gateway {gateway_id} are malformed: {source}")]
MalformedSharedKeys {
gateway_id: String,
#[source]
source: SharedKeyConversionError,
},
#[error(
"the listening address of gateway {gateway_id} ({raw_listener}) is malformed: {source}"
)]
MalformedListener {
gateway_id: String,
raw_listener: String,
#[source]
source: url::ParseError,
},
}
@@ -0,0 +1,66 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
use async_trait::async_trait;
use std::error::Error;
pub mod backend;
pub mod error;
pub mod types;
// todo: export port types
pub use crate::types::*;
pub use backend::mem_backend::{InMemGatewaysDetails, InMemStorageError};
pub use error::BadGateway;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-gateways-storage"))]
pub use backend::fs_backend::{error::StorageError, OnDiskGatewaysDetails};
use nym_crypto::asymmetric::identity;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait GatewaysDetailsStore {
type StorageError: Error + From<error::BadGateway>;
/// Returns details of the currently active gateway, if available.
async fn active_gateway(&self) -> Result<ActiveGateway, Self::StorageError>;
/// Set the provided gateway as the currently active gateway.
async fn set_active_gateway(&self, gateway_id: &str) -> Result<(), Self::StorageError>;
/// Returns details of all registered gateways.
async fn all_gateways(&self) -> Result<Vec<GatewayRegistration>, Self::StorageError>;
/// Return identity keys of all registered gateways.
async fn all_gateways_identities(
&self,
) -> Result<Vec<identity::PublicKey>, Self::StorageError> {
Ok(self
.all_gateways()
.await?
.into_iter()
.map(|gateway| gateway.details.gateway_id())
.collect())
}
/// Check if the gateway with the provided id already exists in the store.
async fn has_gateway_details(&self, gateway_id: &str) -> Result<bool, Self::StorageError>;
/// Returns details of the particular gateway.
async fn load_gateway_details(
&self,
gateway_id: &str,
) -> Result<GatewayRegistration, Self::StorageError>;
/// Store the provided gateway details.
async fn store_gateway_details(
&self,
details: &GatewayRegistration,
) -> Result<(), Self::StorageError>;
/// Remove given gateway details from the underlying store.
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError>;
}
@@ -0,0 +1,315 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::BadGateway;
use cosmrs::AccountId;
use nym_crypto::asymmetric::identity;
use nym_gateway_requests::registration::handshake::SharedKeys;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use std::sync::Arc;
use time::OffsetDateTime;
use url::Url;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub const REMOTE_GATEWAY_TYPE: &str = "remote";
pub const CUSTOM_GATEWAY_TYPE: &str = "custom";
#[derive(Debug, Clone, Default)]
pub struct ActiveGateway {
pub registration: Option<GatewayRegistration>,
}
#[derive(Debug, Clone)]
pub struct GatewayRegistration {
pub details: GatewayDetails,
pub registration_timestamp: OffsetDateTime,
}
impl GatewayRegistration {
pub fn gateway_id(&self) -> identity::PublicKey {
self.details.gateway_id()
}
}
impl<'a> From<&'a GatewayRegistration> for RawRegisteredGateway {
fn from(value: &'a GatewayRegistration) -> Self {
RawRegisteredGateway {
gateway_id_bs58: value.details.gateway_id().to_base58_string(),
registration_timestamp: value.registration_timestamp,
gateway_type: value.details.typ().to_string(),
}
}
}
#[derive(Debug, Clone)]
pub enum GatewayDetails {
/// Standard details of a remote gateway
Remote(RemoteGatewayDetails),
/// Custom gateway setup, such as for a client embedded inside gateway itself
Custom(CustomGatewayDetails),
}
impl From<GatewayDetails> for GatewayRegistration {
fn from(details: GatewayDetails) -> Self {
GatewayRegistration {
details,
registration_timestamp: OffsetDateTime::now_utc(),
}
}
}
impl GatewayDetails {
pub fn new_remote(
gateway_id: identity::PublicKey,
derived_aes128_ctr_blake3_hmac_keys: Arc<SharedKeys>,
gateway_owner_address: AccountId,
gateway_listener: Url,
wg_tun_address: Option<Url>,
) -> Self {
GatewayDetails::Remote(RemoteGatewayDetails {
gateway_id,
derived_aes128_ctr_blake3_hmac_keys,
gateway_owner_address,
gateway_listener,
wg_tun_address,
})
}
pub fn new_custom(gateway_id: identity::PublicKey, data: Option<Vec<u8>>) -> Self {
GatewayDetails::Custom(CustomGatewayDetails { gateway_id, data })
}
pub fn gateway_id(&self) -> identity::PublicKey {
match self {
GatewayDetails::Remote(details) => details.gateway_id,
GatewayDetails::Custom(details) => details.gateway_id,
}
}
pub fn shared_key(&self) -> Option<&SharedKeys> {
match self {
GatewayDetails::Remote(details) => Some(&details.derived_aes128_ctr_blake3_hmac_keys),
GatewayDetails::Custom(_) => None,
}
}
pub fn is_custom(&self) -> bool {
matches!(self, GatewayDetails::Custom(..))
}
pub fn typ(&self) -> GatewayType {
match self {
GatewayDetails::Remote(_) => GatewayType::Remote,
GatewayDetails::Custom(_) => GatewayType::Custom,
}
}
}
#[derive(Debug, Copy, Clone, Default)]
pub enum GatewayType {
#[default]
Remote,
Custom,
}
impl FromStr for GatewayType {
type Err = BadGateway;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
REMOTE_GATEWAY_TYPE => Ok(GatewayType::Remote),
CUSTOM_GATEWAY_TYPE => Ok(GatewayType::Custom),
other => Err(BadGateway::InvalidGatewayType {
typ: other.to_string(),
}),
}
}
}
impl Display for GatewayType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
GatewayType::Remote => REMOTE_GATEWAY_TYPE.fmt(f),
GatewayType::Custom => CUSTOM_GATEWAY_TYPE.fmt(f),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct RawActiveGateway {
pub active_gateway_id_bs58: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct RawRegisteredGateway {
pub gateway_id_bs58: String,
// not necessarily needed but is nice for display purposes
pub registration_timestamp: OffsetDateTime,
pub gateway_type: String,
}
#[derive(Debug, Clone, Copy)]
pub struct RegisteredGateway {
pub gateway_id: identity::PublicKey,
pub registration_timestamp: OffsetDateTime,
pub gateway_type: GatewayType,
}
#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct RawRemoteGatewayDetails {
pub gateway_id_bs58: String,
pub derived_aes128_ctr_blake3_hmac_keys_bs58: String,
pub gateway_owner_address: String,
pub gateway_listener: String,
pub wg_tun_address: Option<String>,
}
impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
type Error = BadGateway;
fn try_from(value: RawRemoteGatewayDetails) -> Result<Self, Self::Error> {
let gateway_id =
identity::PublicKey::from_base58_string(&value.gateway_id_bs58).map_err(|source| {
BadGateway::MalformedGatewayIdentity {
gateway_id: value.gateway_id_bs58.clone(),
source,
}
})?;
let derived_aes128_ctr_blake3_hmac_keys = Arc::new(
SharedKeys::try_from_base58_string(&value.derived_aes128_ctr_blake3_hmac_keys_bs58)
.map_err(|source| BadGateway::MalformedSharedKeys {
gateway_id: value.gateway_id_bs58.clone(),
source,
})?,
);
let gateway_owner_address =
AccountId::from_str(&value.gateway_owner_address).map_err(|source| {
BadGateway::MalformedGatewayOwnerAccountAddress {
gateway_id: value.gateway_id_bs58.clone(),
raw_owner: value.gateway_owner_address.clone(),
source,
}
})?;
let gateway_listener = Url::parse(&value.gateway_listener).map_err(|source| {
BadGateway::MalformedListener {
gateway_id: value.gateway_id_bs58.clone(),
raw_listener: value.gateway_listener.clone(),
source,
}
})?;
let wg_tun_address = value
.wg_tun_address
.as_ref()
.map(|addr| {
Url::parse(addr).map_err(|source| BadGateway::MalformedListener {
gateway_id: value.gateway_id_bs58.clone(),
raw_listener: addr.clone(),
source,
})
})
.transpose()?;
Ok(RemoteGatewayDetails {
gateway_id,
derived_aes128_ctr_blake3_hmac_keys,
gateway_owner_address,
gateway_listener,
wg_tun_address,
})
}
}
impl<'a> From<&'a RemoteGatewayDetails> for RawRemoteGatewayDetails {
fn from(value: &'a RemoteGatewayDetails) -> Self {
RawRemoteGatewayDetails {
gateway_id_bs58: value.gateway_id.to_base58_string(),
derived_aes128_ctr_blake3_hmac_keys_bs58: value
.derived_aes128_ctr_blake3_hmac_keys
.to_base58_string(),
gateway_owner_address: value.gateway_owner_address.to_string(),
gateway_listener: value.gateway_listener.to_string(),
wg_tun_address: value.wg_tun_address.as_ref().map(|addr| addr.to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct RemoteGatewayDetails {
pub gateway_id: identity::PublicKey,
// note: `SharedKeys` implement ZeroizeOnDrop, meaning when `RemoteGatewayDetails` is dropped,
// the keys will be zeroized
pub derived_aes128_ctr_blake3_hmac_keys: Arc<SharedKeys>,
pub gateway_owner_address: AccountId,
pub gateway_listener: Url,
pub wg_tun_address: Option<Url>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct RawCustomGatewayDetails {
pub gateway_id_bs58: String,
pub data: Option<Vec<u8>>,
}
impl TryFrom<RawCustomGatewayDetails> for CustomGatewayDetails {
type Error = BadGateway;
fn try_from(value: RawCustomGatewayDetails) -> Result<Self, Self::Error> {
let gateway_id =
identity::PublicKey::from_base58_string(&value.gateway_id_bs58).map_err(|source| {
BadGateway::MalformedGatewayIdentity {
gateway_id: value.gateway_id_bs58.clone(),
source,
}
})?;
Ok(CustomGatewayDetails {
gateway_id,
data: value.data,
})
}
}
impl<'a> From<&'a CustomGatewayDetails> for RawCustomGatewayDetails {
fn from(value: &'a CustomGatewayDetails) -> Self {
RawCustomGatewayDetails {
gateway_id_bs58: value.gateway_id.to_base58_string(),
// I don't know what to feel about that clone here given it might contain possibly sensitive data
data: value.data.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct CustomGatewayDetails {
pub gateway_id: identity::PublicKey,
pub data: Option<Vec<u8>>,
}
impl CustomGatewayDetails {
pub fn new(gateway_id: identity::PublicKey) -> CustomGatewayDetails {
Self {
gateway_id,
data: None,
}
}
}
@@ -0,0 +1,162 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::cli_helpers::types::GatewayInfo;
use crate::cli_helpers::{CliClient, CliClientConfig};
use crate::client::base_client::non_wasm_helpers::setup_fs_gateways_storage;
use crate::{
client::{
base_client::storage::helpers::{get_all_registered_identities, set_active_gateway},
key_manager::persistence::OnDiskKeys,
},
error::ClientCoreError,
init::types::{GatewaySelectionSpecification, GatewaySetup},
};
use log::info;
use nym_client_core_gateways_storage::GatewayDetails;
use nym_crypto::asymmetric::identity;
use nym_topology::NymTopology;
use std::path::PathBuf;
#[cfg_attr(feature = "cli", derive(clap::Args))]
#[derive(Debug, Clone)]
pub struct CommonClientAddGatewayArgs {
/// Id of client we want to add gateway for.
#[cfg_attr(feature = "cli", clap(long))]
pub id: String,
/// Explicitly specify id of the gateway to register with.
/// If unspecified, a random gateway will be chosen instead.
#[cfg_attr(feature = "cli", clap(long, alias = "gateway"))]
pub gateway_id: Option<identity::PublicKey>,
/// Specifies whether the client will attempt to enforce tls connection to the desired gateway.
#[cfg_attr(feature = "cli", clap(long))]
pub force_tls_gateway: bool,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[cfg_attr(feature = "cli", clap(long, conflicts_with = "gateway_id"))]
pub latency_based_selection: bool,
/// Specify whether this new gateway should be set as the active one
#[cfg_attr(feature = "cli", clap(long, default_value_t = true))]
pub set_active: bool,
/// Comma separated list of rest endpoints of the API validators
#[cfg_attr(
feature = "cli",
clap(
long,
alias = "api_validators",
value_delimiter = ',',
group = "network"
)
)]
pub nym_apis: Option<Vec<url::Url>>,
/// Path to .json file containing custom network specification.
#[cfg_attr(feature = "cli", clap(long, group = "network", hide = true))]
pub custom_mixnet: Option<PathBuf>,
}
pub async fn add_gateway<C, A>(args: A) -> Result<GatewayInfo, C::Error>
where
A: AsRef<CommonClientAddGatewayArgs>,
C: CliClient,
{
let common_args = args.as_ref();
let id = &common_args.id;
let config = C::try_load_current_config(id).await?;
let core = config.core_config();
let paths = config.common_paths();
let key_store = OnDiskKeys::new(paths.keys.clone());
let details_store = setup_fs_gateways_storage(&paths.gateway_registrations).await?;
// Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = common_args.gateway_id;
log::debug!("User chosen gateway id: {user_chosen_gateway_id:?}");
let selection_spec = GatewaySelectionSpecification::new(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(common_args.latency_based_selection),
common_args.force_tls_gateway,
);
log::debug!("Gateway selection specification: {selection_spec:?}");
let registered_gateways = get_all_registered_identities(&details_store).await?;
// if user provided gateway id (and we can't overwrite data), make sure we're not trying to register
// with a known gateway
if let Some(user_chosen) = user_chosen_gateway_id {
if registered_gateways.contains(&user_chosen) {
return Err(ClientCoreError::AlreadyRegistered {
gateway_id: user_chosen.to_base58_string(),
}
.into());
}
}
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let available_gateways = if let Some(custom_mixnet) = common_args.custom_mixnet.as_ref() {
let hardcoded_topology = NymTopology::new_from_file(custom_mixnet).map_err(|source| {
ClientCoreError::CustomTopologyLoadFailure {
file_path: custom_mixnet.clone(),
source,
}
})?;
hardcoded_topology.get_gateways()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::current_gateways(&mut rng, &core.client.nym_api_urls).await?
};
// since we're registering with a brand new gateway,
// make sure the list of available gateways doesn't overlap the list of known gateways
let available_gateways = available_gateways
.into_iter()
.filter(|g| !registered_gateways.contains(g.identity()))
.collect::<Vec<_>>();
if available_gateways.is_empty() {
return Err(ClientCoreError::NoNewGatewaysAvailable.into());
}
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
wg_tun_address: None,
};
let init_details =
crate::init::setup_gateway(gateway_setup, &key_store, &details_store).await?;
let address = init_details.client_address();
let gateway_registration = init_details.gateway_registration;
let GatewayDetails::Remote(ref gateway_details) = gateway_registration.details else {
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails)?;
};
if common_args.set_active {
set_active_gateway(
&details_store,
&gateway_details.gateway_id.to_base58_string(),
)
.await?;
} else {
info!("registered with new gateway {} (under address {address}), but this will not be our default address", gateway_details.gateway_id);
}
Ok(GatewayInfo {
registration: gateway_registration.registration_timestamp,
identity: gateway_details.gateway_id,
active: common_args.set_active,
typ: gateway_registration.details.typ().to_string(),
endpoint: Some(gateway_details.gateway_listener.clone()),
wg_tun_address: gateway_details.wg_tun_address.clone(),
})
}
@@ -0,0 +1,59 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::cli_helpers::{CliClient, CliClientConfig};
use std::fs;
use std::path::PathBuf;
#[cfg(feature = "cli")]
fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result<Vec<u8>> {
bs58::decode(raw).into_vec()
}
#[cfg_attr(feature = "cli", derive(clap::Args))]
#[cfg_attr(feature = "cli", clap(group(clap::ArgGroup::new("cred_data").required(true))))]
#[derive(Debug, Clone)]
pub struct CommonClientImportCredentialArgs {
/// Id of client that is going to import the credential
#[cfg_attr(feature = "cli", clap(long))]
pub id: String,
/// Explicitly provide the encoded credential data (as base58)
#[cfg_attr(feature = "cli", clap(long, group = "cred_data", value_parser = parse_encoded_credential_data))]
pub(crate) credential_data: Option<Vec<u8>>,
/// Specifies the path to file containing binary credential data
#[cfg_attr(feature = "cli", clap(long, group = "cred_data"))]
pub(crate) credential_path: Option<PathBuf>,
// currently hidden as there exists only a single serialization standard
#[cfg_attr(feature = "cli", clap(long, hide = true))]
pub(crate) version: Option<u8>,
}
pub async fn import_credential<C, A>(args: A) -> Result<(), C::Error>
where
A: Into<CommonClientImportCredentialArgs>,
C: CliClient,
C::Error: From<std::io::Error> + From<nym_id::NymIdError>,
{
let common_args = args.into();
let id = &common_args.id;
let config = C::try_load_current_config(id).await?;
let paths = config.common_paths();
let credentials_store =
nym_credential_storage::initialise_persistent_storage(&paths.credentials_database).await;
let raw_credential = match common_args.credential_data {
Some(data) => data,
None => {
// SAFETY: one of those arguments must have been set
fs::read(common_args.credential_path.unwrap())?
}
};
nym_id::import_credential(credentials_store, raw_credential, common_args.version).await?;
Ok(())
}
@@ -1,27 +1,28 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::disk_persistence::CommonClientPaths;
use crate::cli_helpers::traits::{CliClient, CliClientConfig};
use crate::error::ClientCoreError;
use crate::{
client::{
base_client::storage::gateway_details::OnDiskGatewayDetails,
base_client::{
non_wasm_helpers::setup_fs_gateways_storage, storage::helpers::set_active_gateway,
},
key_manager::persistence::OnDiskKeys,
},
init::types::{GatewayDetails, GatewaySelectionSpecification, GatewaySetup, InitResults},
init::types::{GatewaySelectionSpecification, GatewaySetup, InitResults},
};
use log::info;
use nym_client_core_gateways_storage::GatewayDetails;
use nym_crypto::asymmetric::identity;
use nym_topology::NymTopology;
use std::path::{Path, PathBuf};
use rand::rngs::OsRng;
use std::path::PathBuf;
pub trait InitialisableClient {
const NAME: &'static str;
type Error: From<ClientCoreError>;
// we can suppress this warning (as suggested by linter itself) since we're only using it in our own code
#[allow(async_fn_in_trait)]
pub trait InitialisableClient: CliClient {
type InitArgs: AsRef<CommonClientInitArgs>;
type Config: ClientConfig;
fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error>;
fn initialise_storage_paths(id: &str) -> Result<(), Self::Error>;
@@ -30,16 +31,6 @@ pub trait InitialisableClient {
fn construct_config(init_args: &Self::InitArgs) -> Self::Config;
}
pub trait ClientConfig {
fn common_paths(&self) -> &CommonClientPaths;
fn core_config(&self) -> &crate::config::Config;
fn default_store_location(&self) -> PathBuf;
fn save_to<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()>;
}
#[cfg_attr(feature = "cli", derive(clap::Args))]
#[derive(Debug, Clone)]
pub struct CommonClientInitArgs {
@@ -51,16 +42,15 @@ pub struct CommonClientInitArgs {
#[cfg_attr(feature = "cli", clap(long))]
pub gateway: Option<identity::PublicKey>,
/// Specifies whether the client will attempt to enforce tls connection to the desired gateway.
#[cfg_attr(feature = "cli", clap(long))]
pub force_tls_gateway: bool,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[cfg_attr(feature = "cli", clap(long, conflicts_with = "gateway"))]
pub latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[cfg_attr(feature = "cli", clap(long))]
pub force_register_gateway: bool,
/// Comma separated list of rest endpoints of the nyxd validators
#[cfg_attr(
feature = "cli",
@@ -109,7 +99,7 @@ pub async fn initialise_client<C>(
) -> Result<InitResultsWithConfig<C::Config>, C::Error>
where
C: InitialisableClient,
<C as InitialisableClient>::Config: std::fmt::Debug,
<C as CliClient>::Config: std::fmt::Debug,
<C as InitialisableClient>::InitArgs: std::fmt::Debug,
{
info!("initialising {} client", C::NAME);
@@ -117,28 +107,15 @@ where
let common_args = init_args.as_ref();
let id = &common_args.id;
let already_init = if C::default_config_path(id).exists() {
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
C::try_upgrade_outdated_config(id)?;
if C::default_config_path(id).exists() {
eprintln!("{} client \"{id}\" was already initialised before", C::NAME);
true
} else {
C::initialise_storage_paths(id)?;
false
};
// Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted.
let user_wants_force_register = common_args.force_register_gateway;
if user_wants_force_register {
eprintln!("Instructed to force registering gateway. This might overwrite keys!");
return Err(ClientCoreError::AlreadyInitialised {
client_id: id.to_string(),
}
.into());
}
// If the client was already initialized, don't generate new keys and don't re-register with
// the gateway (because this would create a new shared key).
// Unless the user really wants to.
let register_gateway = !already_init || user_wants_force_register;
C::initialise_storage_paths(id)?;
// Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = common_args.gateway;
@@ -147,7 +124,7 @@ where
let selection_spec = GatewaySelectionSpecification::new(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(common_args.latency_based_selection),
false,
common_args.force_tls_gateway,
);
log::debug!("Gateway selection specification: {selection_spec:?}");
@@ -168,11 +145,14 @@ where
.join(",")
);
let key_store = OnDiskKeys::new(paths.keys.clone());
let details_store = setup_fs_gateways_storage(&paths.gateway_registrations).await?;
let mut rng = OsRng;
crate::init::generate_new_client_keys(&mut rng, &key_store).await?;
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let key_store = OnDiskKeys::new(paths.keys.clone());
let details_store = OnDiskGatewayDetails::new(&paths.gateway_details);
let available_gateways = if let Some(custom_mixnet) = common_args.custom_mixnet.as_ref() {
let hardcoded_topology = NymTopology::new_from_file(custom_mixnet).map_err(|source| {
ClientCoreError::CustomTopologyLoadFailure {
@@ -189,14 +169,13 @@ where
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
overwrite_data: register_gateway,
wg_tun_address: None,
};
let init_details =
crate::init::setup_gateway(gateway_setup, &key_store, &details_store).await?;
// TODO: ask the service provider we specified for its interface version and set it in the config
let config_save_location = config.default_store_location();
if let Err(err) = config.save_to(&config_save_location) {
return Err(ClientCoreError::ConfigSaveFailure {
@@ -213,12 +192,20 @@ where
config_save_location.display()
);
let address = init_details.client_address()?;
let address = init_details.client_address();
let GatewayDetails::Configured(gateway_details) = init_details.gateway_details else {
let GatewayDetails::Remote(gateway_details) = init_details.gateway_registration.details else {
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails)?;
};
let init_results = InitResults::new(config.core_config(), address, &gateway_details);
let init_results = InitResults::new(
config.core_config(),
address,
&gateway_details,
init_details.gateway_registration.registration_timestamp,
);
set_active_gateway(&details_store, &init_results.gateway_id).await?;
Ok(InitResultsWithConfig {
config,
@@ -0,0 +1,74 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::types::GatewayInfo;
use crate::cli_helpers::{CliClient, CliClientConfig};
use crate::client::base_client::non_wasm_helpers::setup_fs_gateways_storage;
use crate::client::base_client::storage::helpers::{
get_active_gateway_identity, get_gateway_registrations,
};
use nym_client_core_gateways_storage::{GatewayDetails, GatewayType};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
#[cfg_attr(feature = "cli", derive(clap::Args))]
#[derive(Debug, Clone)]
pub struct CommonClientListGatewaysArgs {
/// Id of client we want to list gateways for.
#[cfg_attr(feature = "cli", clap(long))]
pub id: String,
}
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct RegisteredGateways(Vec<GatewayInfo>);
impl Display for RegisteredGateways {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for (i, gateway) in self.0.iter().enumerate() {
writeln!(f, "[{i}]: {gateway}")?;
}
Ok(())
}
}
pub async fn list_gateways<C, A>(args: A) -> Result<RegisteredGateways, C::Error>
where
A: AsRef<CommonClientListGatewaysArgs>,
C: CliClient,
{
let common_args = args.as_ref();
let id = &common_args.id;
let config = C::try_load_current_config(id).await?;
let paths = config.common_paths();
let details_store = setup_fs_gateways_storage(&paths.gateway_registrations).await?;
let active_gateway = get_active_gateway_identity(&details_store).await?;
let gateways = get_gateway_registrations(&details_store).await?;
let mut info = Vec::with_capacity(gateways.len());
for gateway in gateways {
match gateway.details {
GatewayDetails::Remote(remote_details) => info.push(GatewayInfo {
registration: gateway.registration_timestamp,
identity: remote_details.gateway_id,
active: active_gateway == Some(remote_details.gateway_id),
typ: GatewayType::Remote.to_string(),
endpoint: Some(remote_details.gateway_listener),
wg_tun_address: remote_details.wg_tun_address,
}),
GatewayDetails::Custom(_) => info.push(GatewayInfo {
registration: gateway.registration_timestamp,
identity: gateway.details.gateway_id(),
active: active_gateway == Some(gateway.details.gateway_id()),
typ: gateway.details.typ().to_string(),
endpoint: None,
wg_tun_address: None,
}),
};
}
Ok(RegisteredGateways(info))
}
@@ -0,0 +1,37 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::cli_helpers::{CliClient, CliClientConfig};
use crate::client::base_client::non_wasm_helpers::setup_fs_gateways_storage;
use crate::client::base_client::storage::helpers::set_active_gateway;
use nym_crypto::asymmetric::identity;
#[cfg_attr(feature = "cli", derive(clap::Args))]
#[derive(Debug, Clone)]
pub struct CommonClientSwitchGatewaysArgs {
/// Id of client we want to list gateways for.
#[cfg_attr(feature = "cli", clap(long))]
pub id: String,
/// Id of the gateway we want to switch to.
#[cfg_attr(feature = "cli", clap(long))]
pub gateway_id: identity::PublicKey,
}
pub async fn switch_gateway<C, A>(args: A) -> Result<(), C::Error>
where
A: AsRef<CommonClientSwitchGatewaysArgs>,
C: CliClient,
{
let common_args = args.as_ref();
let id = &common_args.id;
let config = C::try_load_current_config(id).await?;
let paths = config.common_paths();
let details_store = setup_fs_gateways_storage(&paths.gateway_registrations).await?;
set_active_gateway(&details_store, &common_args.gateway_id.to_base58_string()).await?;
Ok(())
}
+10 -1
View File
@@ -1,5 +1,14 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod client_add_gateway;
pub mod client_import_credential;
pub mod client_init;
pub mod client_list_gateways;
pub mod client_run;
pub mod client_switch_gateway;
pub mod traits;
mod types;
pub use client_init::InitialisableClient;
pub use traits::{CliClient, CliClientConfig};
@@ -0,0 +1,28 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::disk_persistence::CommonClientPaths;
use crate::error::ClientCoreError;
use std::path::{Path, PathBuf};
// we can suppress this warning (as suggested by linter itself) since we're only using it in our own code
#[allow(async_fn_in_trait)]
pub trait CliClient {
const NAME: &'static str;
type Error: From<ClientCoreError>;
type Config: CliClientConfig;
async fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error>;
async fn try_load_current_config(id: &str) -> Result<Self::Config, Self::Error>;
}
pub trait CliClientConfig {
fn common_paths(&self) -> &CommonClientPaths;
fn core_config(&self) -> &crate::config::Config;
fn default_store_location(&self) -> PathBuf;
fn save_to<P: AsRef<Path>>(&self, path: P) -> std::io::Result<()>;
}
@@ -0,0 +1,40 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_crypto::asymmetric::identity;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use time::OffsetDateTime;
use url::Url;
#[derive(Serialize, Deserialize)]
pub struct GatewayInfo {
pub registration: OffsetDateTime,
pub identity: identity::PublicKey,
pub active: bool,
pub typ: String,
pub endpoint: Option<Url>,
pub wg_tun_address: Option<Url>,
}
impl Display for GatewayInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.active {
write!(f, "[ACTIVE] ")?;
}
write!(
f,
"{} gateway '{}' registered at: {}",
self.typ, self.identity, self.registration
)?;
if let Some(endpoint) = &self.endpoint {
write!(f, " endpoint: {endpoint}")?;
}
if let Some(wg_tun_address) = &self.wg_tun_address {
write!(f, " wg tun address: {wg_tun_address}")?;
}
Ok(())
}
}
@@ -4,11 +4,12 @@
use super::packet_statistics_control::PacketStatisticsReporter;
use super::received_buffer::ReceivedBufferMessage;
use super::topology_control::geo_aware_provider::GeoAwareTopologyProvider;
use crate::client::base_client::storage::gateway_details::GatewayDetailsStore;
use crate::client::base_client::storage::helpers::store_client_keys;
use crate::client::base_client::storage::MixnetClientStorage;
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ClientKeys;
use crate::client::mix_traffic::transceiver::{GatewayReceiver, GatewayTransceiver, RemoteGateway};
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use crate::client::packet_statistics_control::PacketStatisticsControl;
@@ -30,17 +31,19 @@ use crate::config::{Config, DebugConfig};
use crate::error::ClientCoreError;
use crate::init::{
setup_gateway,
types::{GatewayDetails, GatewaySetup, InitialisationResult},
types::{GatewaySetup, InitialisationResult},
};
use crate::{config, spawn_future};
use futures::channel::mpsc;
use log::{debug, error, info};
use log::{debug, error, info, warn};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::encryption;
use nym_gateway_client::{
AcknowledgementReceiver, GatewayClient, MixnetMessageReceiver, PacketRouter,
AcknowledgementReceiver, GatewayClient, GatewayConfig, MixnetMessageReceiver, PacketRouter,
};
use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, WG_TUN_DEVICE_ADDRESS};
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::addressing::nodes::NodeIdentity;
@@ -51,13 +54,18 @@ use nym_task::{TaskClient, TaskHandle};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use rand::rngs::OsRng;
use std::fmt::Debug;
use std::os::raw::c_int as RawFd;
use std::path::Path;
use std::sync::Arc;
use url::Url;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
#[cfg(all(
not(target_arch = "wasm32"),
feature = "fs-surb-storage",
feature = "fs-gateways-storage"
))]
pub mod non_wasm_helpers;
pub mod helpers;
@@ -172,6 +180,7 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
dkg_query_client: Option<C>,
wait_for_gateway: bool,
wireguard_connection: bool,
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
shutdown: Option<TaskClient>,
@@ -194,10 +203,11 @@ where
client_store,
dkg_query_client,
wait_for_gateway: false,
wireguard_connection: false,
custom_topology_provider: None,
custom_gateway_transceiver: None,
shutdown: None,
setup_method: GatewaySetup::MustLoad,
setup_method: GatewaySetup::MustLoad { gateway_id: None },
}
}
@@ -213,6 +223,12 @@ where
self
}
#[must_use]
pub fn with_wireguard_connection(mut self, wireguard_connection: bool) -> Self {
self.wireguard_connection = wireguard_connection;
self
}
#[must_use]
pub fn with_topology_provider(
mut self,
@@ -246,13 +262,7 @@ where
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
// because it relies on the crypto keys being already loaded
fn mix_address(details: &InitialisationResult) -> Recipient {
Recipient::new(
*details.managed_keys.identity_public_key(),
*details.managed_keys.encryption_public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(details.gateway_details.gateway_id()).unwrap(),
)
details.client_address()
}
// future constantly pumping loop cover traffic at some specified average rate
@@ -342,6 +352,7 @@ where
async fn start_gateway_client(
config: &Config,
wireguard_connection: bool,
initialisation_result: InitialisationResult,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
packet_router: PacketRouter,
@@ -351,50 +362,65 @@ where
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
{
let managed_keys = initialisation_result.managed_keys;
let GatewayDetails::Configured(gateway_config) = initialisation_result.gateway_details
let managed_keys = initialisation_result.client_keys;
let GatewayDetails::Remote(details) = initialisation_result.gateway_registration.details
else {
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails);
};
let mut gateway_client =
if let Some(existing_client) = initialisation_result.authenticated_ephemeral_client {
existing_client.upgrade(packet_router, bandwidth_controller, shutdown)
let mut gateway_client = if let Some(existing_client) =
initialisation_result.authenticated_ephemeral_client
{
existing_client.upgrade(packet_router, bandwidth_controller, shutdown)
} else {
let gateway_listener = if wireguard_connection {
if let Some(tun_address) = details.wg_tun_address {
tun_address.to_string()
} else {
let default =
format!("ws://{WG_TUN_DEVICE_ADDRESS}:{DEFAULT_CLIENT_LISTENING_PORT}");
warn!("gateway {} does not have tun device address set. defaulting to '{default}'", details.gateway_id);
default
}
} else {
let cfg = gateway_config.try_into()?;
GatewayClient::new(
cfg,
managed_keys.identity_keypair(),
Some(managed_keys.must_get_gateway_shared_key()),
packet_router,
bandwidth_controller,
shutdown,
)
.with_disabled_credentials_mode(config.client.disabled_credentials_mode)
.with_response_timeout(config.debug.gateway_connection.gateway_response_timeout)
details.gateway_listener.to_string()
};
let gateway_id = gateway_client.gateway_identity();
let cfg = GatewayConfig::new(
details.gateway_id,
Some(details.gateway_owner_address.to_string()),
gateway_listener,
);
GatewayClient::new(
cfg,
managed_keys.identity_keypair(),
Some(details.derived_aes128_ctr_blake3_hmac_keys),
packet_router,
bandwidth_controller,
shutdown,
)
.with_disabled_credentials_mode(config.client.disabled_credentials_mode)
.with_response_timeout(config.debug.gateway_connection.gateway_response_timeout)
};
let shared_key = gateway_client
gateway_client
.authenticate_and_start()
.await
.map_err(|err| {
log::error!("Could not authenticate and start up the gateway connection - {err}");
ClientCoreError::GatewayClientError {
gateway_id: gateway_id.to_base58_string(),
gateway_id: details.gateway_id.to_base58_string(),
source: err,
}
})?;
managed_keys.ensure_gateway_key(Some(shared_key));
Ok(gateway_client)
}
async fn setup_gateway_transceiver(
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
config: &Config,
wireguard_connection: bool,
initialisation_result: InitialisationResult,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
packet_router: PacketRouter,
@@ -406,7 +432,11 @@ where
{
// if we have setup custom gateway sender and persisted details agree with it, return it
if let Some(mut custom_gateway_transceiver) = custom_gateway_transceiver {
return if !initialisation_result.gateway_details.is_custom() {
return if !initialisation_result
.gateway_registration
.details
.is_custom()
{
Err(ClientCoreError::CustomGatewaySelectionExpected)
} else {
// and make sure to invalidate the task client so we wouldn't cause premature shutdown
@@ -419,6 +449,7 @@ where
// otherwise, setup normal gateway client, etc
let gateway_client = Self::start_gateway_client(
config,
wireguard_connection,
initialisation_result,
bandwidth_controller,
packet_router,
@@ -569,12 +600,20 @@ where
async fn initialise_keys_and_gateway(
setup_method: GatewaySetup,
key_store: &S::KeyStore,
details_store: &S::GatewayDetailsStore,
details_store: &S::GatewaysDetailsStore,
) -> Result<InitialisationResult, ClientCoreError>
where
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
<S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
{
// if client keys do not exist already, create and persist them
if key_store.load_keys().await.is_err() {
info!("could not find valid client keys - a new set will be generated");
let mut rng = OsRng;
let keys = ClientKeys::generate_new(&mut rng);
store_client_keys(keys, key_store).await?;
}
setup_gateway(setup_method, key_store, details_store).await
}
@@ -584,7 +623,7 @@ where
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
<S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
{
info!("Starting nym client");
@@ -629,8 +668,8 @@ where
reply_controller::requests::new_control_channels();
let self_address = Self::mix_address(&init_res);
let ack_key = init_res.managed_keys.ack_key();
let encryption_keys = init_res.managed_keys.encryption_keypair();
let ack_key = init_res.client_keys.ack_key();
let encryption_keys = init_res.client_keys.encryption_keypair();
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
@@ -667,6 +706,7 @@ where
let gateway_transceiver = Self::setup_gateway_transceiver(
self.custom_gateway_transceiver,
self.config,
self.wireguard_connection,
init_res,
bandwidth_controller,
gateway_packet_router,
@@ -1,4 +1,4 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::replies::reply_storage::{
@@ -7,8 +7,9 @@ use crate::client::replies::reply_storage::{
use crate::config;
use crate::config::Config;
use crate::error::ClientCoreError;
use log::{error, info};
use log::{error, info, trace};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core_gateways_storage::OnDiskGatewaysDetails;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_validator_client::nyxd;
use nym_validator_client::QueryHttpRpcNyxdClient;
@@ -101,6 +102,17 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
}
}
pub async fn setup_fs_gateways_storage<P: AsRef<Path>>(
db_path: P,
) -> Result<OnDiskGatewaysDetails, ClientCoreError> {
trace!("setting up gateways details storage");
OnDiskGatewaysDetails::init(db_path)
.await
.map_err(|source| ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
})
}
pub fn create_bandwidth_controller<St: CredentialStorage>(
config: &Config,
storage: St,
@@ -1,296 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::GatewayEndpointConfig;
use crate::error::ClientCoreError;
use crate::init::types::{EmptyCustomDetails, GatewayDetails};
use async_trait::async_trait;
use log::error;
use nym_gateway_requests::registration::handshake::SharedKeys;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::error::Error;
use std::ops::Deref;
use tokio::sync::Mutex;
use zeroize::Zeroizing;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait GatewayDetailsStore<T = EmptyCustomDetails> {
type StorageError: Error;
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails<T>, Self::StorageError>
where
T: DeserializeOwned + Send + Sync;
async fn store_gateway_details(
&self,
details: &PersistedGatewayDetails<T>,
) -> Result<(), Self::StorageError>
where
T: Serialize + Send + Sync;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PersistedGatewayDetails<T = EmptyCustomDetails> {
/// Standard details of a remote gateway
Default(PersistedGatewayConfig),
/// Custom gateway setup, such as for a client embedded inside gateway itself
Custom(PersistedCustomGatewayDetails<T>),
}
impl<T> PersistedGatewayDetails<T> {
// TODO: this should probably allow for custom verification over T
pub fn validate(&self, shared_key: Option<&SharedKeys>) -> Result<(), ClientCoreError> {
match self {
PersistedGatewayDetails::Default(details) => {
if !details.verify(shared_key.ok_or(ClientCoreError::UnavailableSharedKey)?) {
Err(ClientCoreError::MismatchedGatewayDetails {
gateway_id: details.details.gateway_id.clone(),
})
} else {
Ok(())
}
}
PersistedGatewayDetails::Custom(_) => {
if shared_key.is_some() {
error!("using custom persisted gateway setup with shared key present - are you sure that's what you want?");
// but technically we could still continue. just ignore the key
}
Ok(())
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PersistedGatewayConfig {
// TODO: should we also verify correctness of the details themselves?
// i.e. we could include a checksum or tag (via the shared keys)
// counterargument: if we wanted to modify, say, the host information in the stored file on disk,
// in order to actually use it, we'd have to recompute the whole checksum which would be a huge pain.
/// The hash of the shared keys to ensure the correct ones are used with those gateway details.
#[serde(with = "base64")]
key_hash: Vec<u8>,
/// Actual gateway details being persisted.
pub details: GatewayEndpointConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistedCustomGatewayDetails<T> {
// whatever custom method is used, gateway's identity must be known
pub gateway_id: String,
#[serde(flatten)]
pub additional_data: T,
}
impl PersistedGatewayConfig {
pub fn new(details: GatewayEndpointConfig, shared_key: &SharedKeys) -> Self {
let key_bytes = Zeroizing::new(shared_key.to_bytes());
let mut key_hasher = Sha256::new();
key_hasher.update(&key_bytes);
let key_hash = key_hasher.finalize().to_vec();
PersistedGatewayConfig { key_hash, details }
}
pub fn verify(&self, shared_key: &SharedKeys) -> bool {
let key_bytes = Zeroizing::new(shared_key.to_bytes());
let mut key_hasher = Sha256::new();
key_hasher.update(&key_bytes);
let key_hash = key_hasher.finalize();
self.key_hash == key_hash.deref()
}
}
impl<T> PersistedGatewayDetails<T> {
pub fn new(
details: GatewayDetails<T>,
shared_key: Option<&SharedKeys>,
) -> Result<Self, ClientCoreError> {
match details {
GatewayDetails::Configured(cfg) => {
let shared_key = shared_key.ok_or(ClientCoreError::UnavailableSharedKey)?;
Ok(PersistedGatewayDetails::Default(
PersistedGatewayConfig::new(cfg, shared_key),
))
}
GatewayDetails::Custom(custom) => Ok(PersistedGatewayDetails::Custom(custom.into())),
}
}
pub fn is_custom(&self) -> bool {
matches!(self, PersistedGatewayDetails::Custom(..))
}
pub fn matches(&self, other: &GatewayDetails<T>) -> bool
where
T: PartialEq,
{
match self {
PersistedGatewayDetails::Default(default) => {
if let GatewayDetails::Configured(other_configured) = other {
&default.details == other_configured
} else {
false
}
}
PersistedGatewayDetails::Custom(custom) => {
if let GatewayDetails::Custom(other_custom) = other {
custom.gateway_id == other_custom.gateway_id
&& custom.additional_data == other_custom.additional_data
} else {
false
}
}
}
}
}
// helper to make Vec<u8> serialization use base64 representation to make it human readable
// so that it would be easier for users to copy contents from the disk if they wanted to use it elsewhere
mod base64 {
use base64::{engine::general_purpose::STANDARD, Engine as _};
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&STANDARD.encode(bytes))
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
let s = <String>::deserialize(deserializer)?;
STANDARD.decode(s).map_err(serde::de::Error::custom)
}
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, thiserror::Error)]
pub enum OnDiskGatewayDetailsError {
#[error("JSON failure: {0}")]
SerializationFailure(#[from] serde_json::Error),
#[error("failed to store gateway details to {path}: {err}")]
StoreFailure {
path: String,
#[source]
err: std::io::Error,
},
#[error("failed to load gateway details from {path}: {err}")]
LoadFailure {
path: String,
#[source]
err: std::io::Error,
},
}
#[cfg(not(target_arch = "wasm32"))]
pub struct OnDiskGatewayDetails {
file_location: std::path::PathBuf,
}
#[cfg(not(target_arch = "wasm32"))]
impl OnDiskGatewayDetails {
pub fn new<P: AsRef<std::path::Path>>(path: P) -> Self {
OnDiskGatewayDetails {
file_location: path.as_ref().to_owned(),
}
}
pub fn load_from_disk<T>(&self) -> Result<PersistedGatewayDetails<T>, OnDiskGatewayDetailsError>
where
T: DeserializeOwned,
{
let file = std::fs::File::open(&self.file_location).map_err(|err| {
OnDiskGatewayDetailsError::LoadFailure {
path: self.file_location.display().to_string(),
err,
}
})?;
Ok(serde_json::from_reader(file)?)
}
pub fn store_to_disk<T>(
&self,
details: &PersistedGatewayDetails<T>,
) -> Result<(), OnDiskGatewayDetailsError>
where
T: Serialize,
{
// ensure the whole directory structure exists
if let Some(parent_dir) = &self.file_location.parent() {
std::fs::create_dir_all(parent_dir).map_err(|err| {
OnDiskGatewayDetailsError::StoreFailure {
path: self.file_location.display().to_string(),
err,
}
})?
}
let file = std::fs::File::create(&self.file_location).map_err(|err| {
OnDiskGatewayDetailsError::StoreFailure {
path: self.file_location.display().to_string(),
err,
}
})?;
Ok(serde_json::to_writer_pretty(file, details)?)
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GatewayDetailsStore for OnDiskGatewayDetails {
type StorageError = OnDiskGatewayDetailsError;
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError> {
self.load_from_disk()
}
async fn store_gateway_details(
&self,
gateway_details: &PersistedGatewayDetails,
) -> Result<(), Self::StorageError> {
self.store_to_disk(gateway_details)
}
}
#[derive(Default)]
pub struct InMemGatewayDetails<T = EmptyCustomDetails> {
details: Mutex<Option<PersistedGatewayDetails<T>>>,
}
#[derive(Debug, thiserror::Error)]
#[error("old ephemeral gateway details can't be loaded from storage")]
pub struct EphemeralGatewayDetailsError;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GatewayDetailsStore for InMemGatewayDetails {
type StorageError = EphemeralGatewayDetailsError;
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError> {
self.details
.lock()
.await
.clone()
.ok_or(EphemeralGatewayDetailsError)
}
async fn store_gateway_details(
&self,
gateway_details: &PersistedGatewayDetails,
) -> Result<(), Self::StorageError> {
*self.details.lock().await = Some(gateway_details.clone());
Ok(())
}
}
@@ -0,0 +1,157 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ClientKeys;
use crate::error::ClientCoreError;
use nym_client_core_gateways_storage::{ActiveGateway, GatewayRegistration, GatewaysDetailsStore};
use nym_crypto::asymmetric::identity;
// helpers for error wrapping
pub async fn set_active_gateway<D>(
details_store: &D,
gateway_id: &str,
) -> Result<(), ClientCoreError>
where
D: GatewaysDetailsStore,
D::StorageError: Send + Sync + 'static,
{
details_store
.set_active_gateway(gateway_id)
.await
.map_err(|source| ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
})
}
pub async fn get_active_gateway_identity<D>(
details_store: &D,
) -> Result<Option<identity::PublicKey>, ClientCoreError>
where
D: GatewaysDetailsStore,
D::StorageError: Send + Sync + 'static,
{
details_store
.active_gateway()
.await
.map_err(|source| ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
})
.map(|a| a.registration.map(|r| r.details.gateway_id()))
}
pub async fn get_all_registered_identities<D>(
details_store: &D,
) -> Result<Vec<identity::PublicKey>, ClientCoreError>
where
D: GatewaysDetailsStore + Sync,
D::StorageError: Send + Sync + 'static,
{
details_store
.all_gateways_identities()
.await
.map_err(|source| ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
})
}
pub async fn get_gateway_registrations<D>(
details_store: &D,
) -> Result<Vec<GatewayRegistration>, ClientCoreError>
where
D: GatewaysDetailsStore + Sync,
D::StorageError: Send + Sync + 'static,
{
details_store.all_gateways().await.map_err(|source| {
ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
}
})
}
pub async fn store_gateway_details<D>(
details_store: &D,
details: &GatewayRegistration,
) -> Result<(), ClientCoreError>
where
D: GatewaysDetailsStore,
D::StorageError: Send + Sync + 'static,
{
details_store
.store_gateway_details(details)
.await
.map_err(|source| ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
})
}
pub async fn load_active_gateway_details<D>(
details_store: &D,
) -> Result<ActiveGateway, ClientCoreError>
where
D: GatewaysDetailsStore,
D::StorageError: Send + Sync + 'static,
{
details_store.active_gateway().await.map_err(|source| {
ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
}
})
}
pub async fn load_gateway_details<D>(
details_store: &D,
gateway_id: &str,
) -> Result<GatewayRegistration, ClientCoreError>
where
D: GatewaysDetailsStore,
D::StorageError: Send + Sync + 'static,
{
details_store
.load_gateway_details(gateway_id)
.await
.map_err(|source| ClientCoreError::UnavailableGatewayDetails {
gateway_id: gateway_id.to_string(),
source: Box::new(source),
})
}
pub async fn has_gateway_details<D>(
details_store: &D,
gateway_id: &str,
) -> Result<bool, ClientCoreError>
where
D: GatewaysDetailsStore,
D::StorageError: Send + Sync + 'static,
{
details_store
.has_gateway_details(gateway_id)
.await
.map_err(|source| ClientCoreError::GatewaysDetailsStoreError {
source: Box::new(source),
})
}
pub async fn load_client_keys<K>(key_store: &K) -> Result<ClientKeys, ClientCoreError>
where
K: KeyStore,
K::StorageError: Send + Sync + 'static,
{
ClientKeys::load_keys(key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})
}
pub async fn store_client_keys<K>(keys: ClientKeys, key_store: &K) -> Result<(), ClientCoreError>
where
K: KeyStore,
K::StorageError: Send + Sync + 'static,
{
keys.persist_keys(key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})
}
@@ -0,0 +1,212 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod v1_1_33 {
use crate::client::base_client::{
non_wasm_helpers::setup_fs_gateways_storage,
storage::helpers::{set_active_gateway, store_gateway_details},
};
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
use crate::config::disk_persistence::CommonClientPaths;
use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
use crate::error::ClientCoreError;
use nym_client_core_gateways_storage::{
CustomGatewayDetails, GatewayDetails, GatewayRegistration, RemoteGatewayDetails,
};
use nym_gateway_requests::registration::handshake::SharedKeys;
use serde::{Deserialize, Serialize};
use sha2::{digest::Digest, Sha256};
use std::ops::Deref;
use std::path::Path;
use std::sync::Arc;
use zeroize::Zeroizing;
mod base64 {
use base64::{engine::general_purpose::STANDARD, Engine as _};
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&STANDARD.encode(bytes))
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Vec<u8>, D::Error> {
let s = <String>::deserialize(deserializer)?;
STANDARD.decode(s).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
enum PersistedGatewayDetails {
/// Standard details of a remote gateway
Default(PersistedGatewayConfig),
/// Custom gateway setup, such as for a client embedded inside gateway itself
Custom(PersistedCustomGatewayDetails),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
struct PersistedGatewayConfig {
/// The hash of the shared keys to ensure the correct ones are used with those gateway details.
#[serde(with = "base64")]
key_hash: Vec<u8>,
/// Actual gateway details being persisted.
details: OldGatewayEndpointConfigV1_1_33,
}
impl PersistedGatewayConfig {
fn verify(&self, shared_key: &SharedKeys) -> bool {
let key_bytes = Zeroizing::new(shared_key.to_bytes());
let mut key_hasher = Sha256::new();
key_hasher.update(&key_bytes);
let key_hash = key_hasher.finalize();
self.key_hash == key_hash.deref()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PersistedCustomGatewayDetails {
gateway_id: String,
}
fn load_shared_key<P: AsRef<Path>>(path: P) -> Result<SharedKeys, ClientCoreError> {
// the shared key was a simple pem file
Ok(nym_pemstore::load_key(path)?)
}
fn gateway_details_from_raw(
gateway_id: String,
gateway_owner: String,
gateway_listener: String,
gateway_shared_key: SharedKeys,
) -> Result<GatewayDetails, ClientCoreError> {
Ok(GatewayDetails::Remote(RemoteGatewayDetails {
gateway_id: gateway_id
.parse()
.map_err(|err| ClientCoreError::UpgradeFailure {
message: format!("the stored gateway id was malformed: {err}"),
})?,
derived_aes128_ctr_blake3_hmac_keys: Arc::new(gateway_shared_key),
gateway_owner_address: gateway_owner.parse().map_err(|err| {
ClientCoreError::UpgradeFailure {
message: format!("the stored gateway owner address was malformed: {err}"),
}
})?,
gateway_listener: gateway_listener.parse().map_err(|err| {
ClientCoreError::UpgradeFailure {
message: format!("the stored gateway listener address was malformed: {err}"),
}
})?,
wg_tun_address: None,
}))
}
// helper to extract shared key and gateway details into the new GatewayRegistration
fn extract_gateway_registration(
storage_paths: &CommonClientPathsV1_1_33,
) -> Result<GatewayRegistration, ClientCoreError> {
let details_file = std::fs::File::open(&storage_paths.gateway_details).map_err(|err| {
ClientCoreError::UpgradeFailure {
message: format!(
"failed to open gateway details file at {}: {err}",
storage_paths.gateway_details.display()
),
}
})?;
// in v1.1.33 of the clients, the gateway details struct was saved as json
let details: PersistedGatewayDetails =
serde_json::from_reader(details_file).map_err(|err| {
ClientCoreError::UpgradeFailure {
message: format!(
"failed to deserialize gateway details from {}: {err}",
storage_paths.gateway_details.display()
),
}
})?;
let details = match details {
PersistedGatewayDetails::Default(config) => {
let gateway_shared_key =
load_shared_key(&storage_paths.keys.gateway_shared_key_file)?;
if !config.verify(&gateway_shared_key) {
return Err(ClientCoreError::UpgradeFailure {
message: "failed to verify consistency of the existing gateway details"
.to_string(),
});
}
gateway_details_from_raw(
config.details.gateway_id,
config.details.gateway_owner,
config.details.gateway_listener,
gateway_shared_key,
)?
}
PersistedGatewayDetails::Custom(custom) => {
GatewayDetails::Custom(CustomGatewayDetails {
gateway_id: custom.gateway_id.parse().map_err(|err| {
ClientCoreError::UpgradeFailure {
message: format!("the stored gateway id was malformed: {err}"),
}
})?,
data: None,
})
}
};
Ok(details.into())
}
// it's responsibility of the caller to ensure this is called **after** new registration has already been saved
fn remove_old_gateway_details(storage_paths: &CommonClientPathsV1_1_33) -> std::io::Result<()> {
std::fs::remove_file(&storage_paths.gateway_details)?;
if storage_paths.keys.gateway_shared_key_file.exists() {
std::fs::remove_file(&storage_paths.keys.gateway_shared_key_file)?;
}
Ok(())
}
pub async fn migrate_gateway_details(
old_storage_paths: &CommonClientPathsV1_1_33,
new_storage_paths: &CommonClientPaths,
preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
) -> Result<(), ClientCoreError> {
let gateway_registration = match preloaded_config {
Some(config) => {
let gateway_shared_key =
load_shared_key(&old_storage_paths.keys.gateway_shared_key_file)?;
gateway_details_from_raw(
config.gateway_id,
config.gateway_owner,
config.gateway_listener,
gateway_shared_key,
)?
.into()
}
None => extract_gateway_registration(old_storage_paths)?,
};
// since we're migrating to a brand new store, the store should be empty
// and thus set the 'new' gateway as the active one
let details_store =
setup_fs_gateways_storage(&new_storage_paths.gateway_registrations).await?;
store_gateway_details(&details_store, &gateway_registration).await?;
set_active_gateway(
&details_store,
&gateway_registration.details.gateway_id().to_base58_string(),
)
.await?;
remove_old_gateway_details(old_storage_paths).map_err(|err| {
ClientCoreError::UpgradeFailure {
message: format!("failed to remove old data: {err}"),
}
})
}
}
@@ -4,46 +4,57 @@
// TODO: combine those more closely. Perhaps into a single underlying store.
// Like for persistent, on-disk, storage, what's the point of having 3 different databases?
use crate::client::base_client::storage::gateway_details::{
GatewayDetailsStore, InMemGatewayDetails,
};
use crate::client::key_manager::persistence::{InMemEphemeralKeys, KeyStore};
use crate::client::replies::reply_storage;
use crate::client::replies::reply_storage::ReplyStorageBackend;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_credential_storage::storage::Storage as CredentialStorage;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::base_client::non_wasm_helpers;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::key_manager::persistence::OnDiskKeys;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::replies::reply_storage::fs_backend;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::config::{self, disk_persistence::CommonClientPaths};
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::error::ClientCoreError;
#[cfg(all(
not(target_arch = "wasm32"),
feature = "fs-surb-storage",
feature = "fs-gateways-storage"
))]
use crate::{
client::{
base_client::non_wasm_helpers, key_manager::persistence::OnDiskKeys,
replies::reply_storage::fs_backend,
},
config::{self, disk_persistence::CommonClientPaths},
error::ClientCoreError,
};
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use nym_credential_storage::persistent_storage::PersistentStorage as PersistentCredentialStorage;
pub mod gateway_details;
pub use nym_client_core_gateways_storage as gateways_storage;
pub use nym_client_core_gateways_storage::{GatewaysDetailsStore, InMemGatewaysDetails};
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-gateways-storage"))]
pub use nym_client_core_gateways_storage::{OnDiskGatewaysDetails, StorageError};
pub mod helpers;
#[cfg(all(
not(target_arch = "wasm32"),
feature = "fs-surb-storage",
feature = "fs-gateways-storage"
))]
pub mod migration_helpers;
// TODO: ideally this should be changed into
// `MixnetClientStorage: KeyStore + ReplyStorageBackend + CredentialStorage + GatewayDetailsStore`
// `MixnetClientStorage: KeyStore + ReplyStorageBackend + CredentialStorage + GatewaysDetailsStore`
pub trait MixnetClientStorage {
type KeyStore: KeyStore;
type ReplyStore: ReplyStorageBackend;
type CredentialStore: CredentialStorage;
type GatewayDetailsStore: GatewayDetailsStore;
type GatewaysDetailsStore: GatewaysDetailsStore;
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore);
fn key_store(&self) -> &Self::KeyStore;
fn reply_store(&self) -> &Self::ReplyStore;
fn credential_store(&self) -> &Self::CredentialStore;
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore;
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
}
#[derive(Default)]
@@ -51,7 +62,7 @@ pub struct Ephemeral {
key_store: InMemEphemeralKeys,
reply_store: reply_storage::Empty,
credential_store: EphemeralCredentialStorage,
gateway_details_store: InMemGatewayDetails,
gateway_details_store: InMemGatewaysDetails,
}
impl Ephemeral {
@@ -64,7 +75,7 @@ impl MixnetClientStorage for Ephemeral {
type KeyStore = InMemEphemeralKeys;
type ReplyStore = reply_storage::Empty;
type CredentialStore = EphemeralCredentialStorage;
type GatewayDetailsStore = InMemGatewayDetails;
type GatewaysDetailsStore = InMemGatewaysDetails;
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
(self.reply_store, self.credential_store)
@@ -82,26 +93,34 @@ impl MixnetClientStorage for Ephemeral {
&self.credential_store
}
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore {
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore {
&self.gateway_details_store
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
#[cfg(all(
not(target_arch = "wasm32"),
feature = "fs-surb-storage",
feature = "fs-gateways-storage"
))]
pub struct OnDiskPersistent {
pub(crate) key_store: OnDiskKeys,
pub(crate) reply_store: fs_backend::Backend,
pub(crate) credential_store: PersistentCredentialStorage,
pub(crate) gateway_details_store: OnDiskGatewayDetails,
pub(crate) gateway_details_store: OnDiskGatewaysDetails,
}
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
#[cfg(all(
not(target_arch = "wasm32"),
feature = "fs-surb-storage",
feature = "fs-gateways-storage"
))]
impl OnDiskPersistent {
pub fn new(
key_store: OnDiskKeys,
reply_store: fs_backend::Backend,
credential_store: PersistentCredentialStorage,
gateway_details_store: OnDiskGatewayDetails,
gateway_details_store: OnDiskGatewaysDetails,
) -> Self {
Self {
key_store,
@@ -126,7 +145,8 @@ impl OnDiskPersistent {
let credential_store =
nym_credential_storage::initialise_persistent_storage(paths.credentials_database).await;
let gateway_details_store = OnDiskGatewayDetails::new(paths.gateway_details);
let gateway_details_store =
non_wasm_helpers::setup_fs_gateways_storage(paths.gateway_registrations).await?;
Ok(OnDiskPersistent {
key_store,
@@ -137,12 +157,16 @@ impl OnDiskPersistent {
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
#[cfg(all(
not(target_arch = "wasm32"),
feature = "fs-surb-storage",
feature = "fs-gateways-storage"
))]
impl MixnetClientStorage for OnDiskPersistent {
type KeyStore = OnDiskKeys;
type ReplyStore = fs_backend::Backend;
type CredentialStore = PersistentCredentialStorage;
type GatewayDetailsStore = OnDiskGatewayDetails;
type GatewaysDetailsStore = OnDiskGatewaysDetails;
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
(self.reply_store, self.credential_store)
@@ -160,7 +184,7 @@ impl MixnetClientStorage for OnDiskPersistent {
&self.credential_store
}
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore {
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore {
&self.gateway_details_store
}
}
+14 -251
View File
@@ -6,225 +6,11 @@ use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use rand::{CryptoRng, RngCore};
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use zeroize::ZeroizeOnDrop;
pub mod persistence;
pub enum ManagedKeys {
Initial(KeyManagerBuilder),
FullyDerived(KeyManager),
// I really hate the existence of this variant, but I couldn't come up with a better way to handle
// `Self::deal_with_gateway_key` otherwise.
Invalidated,
}
impl Debug for ManagedKeys {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ManagedKeys::Initial(_) => write!(f, "initial"),
ManagedKeys::FullyDerived(_) => write!(f, "fully derived"),
ManagedKeys::Invalidated => write!(f, "invalidated"),
}
}
}
impl From<KeyManagerBuilder> for ManagedKeys {
fn from(value: KeyManagerBuilder) -> Self {
ManagedKeys::Initial(value)
}
}
impl From<KeyManager> for ManagedKeys {
fn from(value: KeyManager) -> Self {
ManagedKeys::FullyDerived(value)
}
}
impl ManagedKeys {
pub fn is_valid(&self) -> bool {
!matches!(self, ManagedKeys::Invalidated)
}
pub async fn try_load<S: KeyStore>(key_store: &S) -> Result<Self, S::StorageError> {
Ok(ManagedKeys::FullyDerived(
KeyManager::load_keys(key_store).await?,
))
}
pub fn generate_new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
ManagedKeys::Initial(KeyManagerBuilder::new(rng))
}
pub async fn load_or_generate<R, S>(rng: &mut R, key_store: &S) -> Self
where
R: RngCore + CryptoRng,
S: KeyStore,
{
Self::try_load(key_store)
.await
.unwrap_or_else(|_| Self::generate_new(rng))
}
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
match self {
ManagedKeys::Initial(keys) => keys.identity_keypair(),
ManagedKeys::FullyDerived(keys) => keys.identity_keypair(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
match self {
ManagedKeys::Initial(keys) => keys.encryption_keypair(),
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn ack_key(&self) -> Arc<AckKey> {
match self {
ManagedKeys::Initial(keys) => keys.ack_key(),
ManagedKeys::FullyDerived(keys) => keys.ack_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn must_get_gateway_shared_key(&self) -> Arc<SharedKeys> {
self.gateway_shared_key()
.expect("failed to extract gateway shared key")
}
pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> {
match self {
ManagedKeys::Initial(_) => None,
ManagedKeys::FullyDerived(keys) => keys.gateway_shared_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn identity_public_key(&self) -> &identity::PublicKey {
match self {
ManagedKeys::Initial(keys) => keys.identity_keypair.public_key(),
ManagedKeys::FullyDerived(keys) => keys.identity_keypair.public_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn encryption_public_key(&self) -> &encryption::PublicKey {
match self {
ManagedKeys::Initial(keys) => keys.encryption_keypair.public_key(),
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair.public_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn ensure_gateway_key(&self, gateway_shared_key: Option<Arc<SharedKeys>>) {
if let ManagedKeys::FullyDerived(key_manager) = &self {
if self.gateway_shared_key().is_none() && gateway_shared_key.is_none() {
// the key doesn't exist in either state
return;
}
if gateway_shared_key.is_some() && self.gateway_shared_key().is_none()
|| gateway_shared_key.is_none() && self.gateway_shared_key().is_some()
{
// if one is provided whilst the other is not...
// TODO: should this actually panic or return an error? would this branch be possible
// under normal operation?
panic!("inconsistent re-derived gateway key")
}
// here we know both keys MUST exist
let provided = gateway_shared_key.unwrap();
if !Arc::ptr_eq(key_manager.must_get_gateway_shared_key(), &provided)
|| *key_manager.must_get_gateway_shared_key() != provided
{
// this should NEVER happen thus panic here
panic!("derived fresh gateway shared key whilst already holding one!")
}
}
}
pub async fn deal_with_gateway_key<S: KeyStore>(
&mut self,
gateway_shared_key: Option<Arc<SharedKeys>>,
key_store: &S,
) -> Result<(), S::StorageError> {
let key_manager = match std::mem::replace(self, ManagedKeys::Invalidated) {
ManagedKeys::Initial(keys) => {
let key_manager = keys.insert_maybe_gateway_shared_key(gateway_shared_key);
key_manager.persist_keys(key_store).await?;
key_manager
}
ManagedKeys::FullyDerived(key_manager) => {
self.ensure_gateway_key(gateway_shared_key);
key_manager
}
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
};
*self = ManagedKeys::FullyDerived(key_manager);
Ok(())
}
}
// all of the keys really shouldn't be wrapped in `Arc`, but due to how the gateway client is currently
// constructed, changing that would require more work than what it's worth
pub struct KeyManagerBuilder {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
impl KeyManagerBuilder {
/// Creates new instance of a [`KeyManager`]
pub fn new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
KeyManagerBuilder {
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
ack_key: Arc::new(AckKey::new(rng)),
}
}
pub fn insert_maybe_gateway_shared_key(
self,
gateway_shared_key: Option<Arc<SharedKeys>>,
) -> KeyManager {
KeyManager {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
gateway_shared_key,
ack_key: self.ack_key,
}
}
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
Arc::clone(&self.identity_keypair)
}
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
}
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
@@ -233,35 +19,38 @@ impl KeyManagerBuilder {
// Remember that Arc<T> has Deref implementation for T
#[derive(Clone)]
pub struct KeyManager {
pub struct ClientKeys {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// shared key derived with the gateway during "registration handshake"
// I'm not a fan of how we broke the nice transition of `KeyManagerBuilder` -> `KeyManager`
// by making this field optional.
// However, it has to be optional for when we use embedded NR inside a gateway,
// since it won't have a shared key (because why would it?)
gateway_shared_key: Option<Arc<SharedKeys>>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
impl KeyManager {
impl ClientKeys {
/// Creates new instance of a [`ClientKeys`]
pub fn generate_new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
ClientKeys {
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
ack_key: Arc::new(AckKey::new(rng)),
}
}
pub fn from_keys(
id_keypair: identity::KeyPair,
enc_keypair: encryption::KeyPair,
gateway_shared_key: Option<SharedKeys>,
ack_key: AckKey,
) -> Self {
Self {
identity_keypair: Arc::new(id_keypair),
encryption_keypair: Arc::new(enc_keypair),
gateway_shared_key: gateway_shared_key.map(Arc::new),
ack_key: Arc::new(ack_key),
}
}
@@ -287,32 +76,6 @@ impl KeyManager {
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
fn must_get_gateway_shared_key(&self) -> &Arc<SharedKeys> {
self.gateway_shared_key
.as_ref()
.expect("gateway shared key is unavailable")
}
pub fn uses_custom_gateway(&self) -> bool {
self.gateway_shared_key.is_none()
}
/// Gets an atomically reference counted pointer to [`SharedKey`].
pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> {
self.gateway_shared_key.clone()
}
pub fn remove_gateway_key(self) -> KeyManagerBuilder {
if Arc::strong_count(self.must_get_gateway_shared_key()) > 1 {
panic!("attempted to remove gateway key whilst still holding multiple references!")
}
KeyManagerBuilder {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
ack_key: self.ack_key,
}
}
}
fn _assert_keys_zeroize_on_drop() {
@@ -1,18 +1,16 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::key_manager::KeyManager;
use crate::client::key_manager::ClientKeys;
use async_trait::async_trait;
use std::error::Error;
use tokio::sync::Mutex;
#[cfg(not(target_arch = "wasm32"))]
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
use crate::config::disk_persistence::ClientKeysPaths;
#[cfg(not(target_arch = "wasm32"))]
use nym_crypto::asymmetric::{encryption, identity};
#[cfg(not(target_arch = "wasm32"))]
use nym_gateway_requests::registration::handshake::SharedKeys;
#[cfg(not(target_arch = "wasm32"))]
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
#[cfg(not(target_arch = "wasm32"))]
use nym_pemstore::KeyPairPath;
@@ -25,9 +23,9 @@ use nym_sphinx::acknowledgements::AckKey;
pub trait KeyStore {
type StorageError: Error;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError>;
async fn load_keys(&self) -> Result<ClientKeys, Self::StorageError>;
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError>;
async fn store_keys(&self, keys: &ClientKeys) -> Result<(), Self::StorageError>;
}
#[cfg(not(target_arch = "wasm32"))]
@@ -84,14 +82,6 @@ impl OnDiskKeys {
OnDiskKeys { paths }
}
#[doc(hidden)]
pub fn ephemeral_load_gateway_keys(
&self,
) -> Result<zeroize::Zeroizing<SharedKeys>, OnDiskKeysError> {
self.load_key(self.paths.gateway_shared_key(), "gateway shared")
.map(zeroize::Zeroizing::new)
}
#[doc(hidden)]
pub fn load_encryption_keypair(&self) -> Result<encryption::KeyPair, OnDiskKeysError> {
let encryption_paths = self.paths.encryption_key_pair_path();
@@ -156,26 +146,19 @@ impl OnDiskKeys {
})
}
fn load_keys(&self) -> Result<KeyManager, OnDiskKeysError> {
fn load_keys(&self) -> Result<ClientKeys, OnDiskKeysError> {
let identity_keypair = self.load_identity_keypair()?;
let encryption_keypair = self.load_encryption_keypair()?;
let ack_key: AckKey = self.load_key(self.paths.ack_key(), "ack key")?;
let gateway_shared_key: Option<SharedKeys> = self
.load_key(self.paths.gateway_shared_key(), "gateway shared keys")
.ok();
Ok(KeyManager::from_keys(
Ok(ClientKeys::from_keys(
identity_keypair,
encryption_keypair,
gateway_shared_key,
ack_key,
))
}
fn store_keys(&self, keys: &KeyManager) -> Result<(), OnDiskKeysError> {
use std::ops::Deref;
fn store_keys(&self, keys: &ClientKeys) -> Result<(), OnDiskKeysError> {
let identity_paths = self.paths.identity_key_pair_path();
let encryption_paths = self.paths.encryption_key_pair_path();
@@ -192,14 +175,6 @@ impl OnDiskKeys {
self.store_key(keys.ack_key.as_ref(), self.paths.ack_key(), "ack key")?;
if let Some(shared_keys) = &keys.gateway_shared_key {
self.store_key(
shared_keys.deref(),
self.paths.gateway_shared_key(),
"gateway shared keys",
)?;
}
Ok(())
}
}
@@ -209,18 +184,18 @@ impl OnDiskKeys {
impl KeyStore for OnDiskKeys {
type StorageError = OnDiskKeysError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
async fn load_keys(&self) -> Result<ClientKeys, Self::StorageError> {
self.load_keys()
}
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
async fn store_keys(&self, keys: &ClientKeys) -> Result<(), Self::StorageError> {
self.store_keys(keys)
}
}
#[derive(Default)]
pub struct InMemEphemeralKeys {
keys: Mutex<Option<KeyManager>>,
keys: Mutex<Option<ClientKeys>>,
}
#[derive(Debug, thiserror::Error)]
@@ -232,11 +207,11 @@ pub struct EphemeralKeysError;
impl KeyStore for InMemEphemeralKeys {
type StorageError = EphemeralKeysError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
async fn load_keys(&self) -> Result<ClientKeys, Self::StorageError> {
self.keys.lock().await.clone().ok_or(EphemeralKeysError)
}
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
async fn store_keys(&self, keys: &ClientKeys) -> Result<(), Self::StorageError> {
*self.keys.lock().await = Some(keys.clone());
Ok(())
}
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
use crate::error::ClientCoreError;
use crate::spawn_future;
use log::*;
use nym_sphinx::forwarding::packet::MixPacket;
@@ -60,8 +61,15 @@ impl MixTrafficController {
)
}
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
async fn on_messages(
&mut self,
mut mix_packets: Vec<MixPacket>,
) -> Result<(), ClientCoreError> {
debug_assert!(!mix_packets.is_empty());
info!(
"JON: MixTrafficController: Sending {} sphinx packets to the gateway",
mix_packets.len()
);
let result = if mix_packets.len() == 1 {
let mix_packet = mix_packets.pop().unwrap();
@@ -72,42 +80,56 @@ impl MixTrafficController {
.await
};
match result {
let r = match result {
Err(err) => {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
self.consecutive_gateway_failure_count += 1;
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// todo: in the future this should initiate a 'graceful' shutdown or try
// to reconnect?
panic!("failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead. Can't do anything about it yet :(")
Err(ClientCoreError::GatewayMaxRetriesExceeded)
} else {
Err(ClientCoreError::GatewayClientSendError {
gateway_client_error: err.to_string(),
})
}
}
Ok(_) => {
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
self.consecutive_gateway_failure_count = 0;
Ok(())
}
}
};
info!("JON: MixTrafficController: done sending sphinx packets to the gateway");
r
}
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
spawn_future(async move {
debug!("Started MixTrafficController with graceful shutdown support");
// let mut shutdown0 = shutdown.recv_with_delay();
// tokio::pin!(shutdown0);
loop {
tokio::select! {
biased;
_ = shutdown.recv_with_delay() => {
// _ = &mut shutdown0 => {
log::trace!("MixTrafficController: Received shutdown");
break;
}
mix_packets = self.mix_rx.recv() => match mix_packets {
Some(mix_packets) => {
self.on_messages(mix_packets).await;
log::info!("JON: MixTrafficController: mix_rx recv");
if let Err(err) = self.on_messages(mix_packets).await {
log::error!("MixTrafficController: failed to send mix packets to the gateway: {err}");
}
log::info!("JON: MixTrafficController: done with mix_rx recv");
},
None => {
log::trace!("MixTrafficController: Stopping since channel closed");
break;
}
},
_ = shutdown.recv_with_delay() => {
log::trace!("MixTrafficController: Received shutdown");
break;
}
}
}
shutdown.recv_timeout().await;
@@ -19,6 +19,12 @@ use futures::channel::{mpsc, oneshot};
#[error(transparent)]
pub struct ErasedGatewayError(Box<dyn std::error::Error + Send + Sync>);
impl ErasedGatewayError {
pub fn downcast<T: std::error::Error + 'static>(&self) -> Option<&T> {
self.0.downcast_ref::<T>()
}
}
fn erase_err<E: std::error::Error + Send + Sync + 'static>(err: E) -> ErasedGatewayError {
ErasedGatewayError(Box::new(err))
}
@@ -40,6 +46,7 @@ pub trait GatewaySender {
&mut self,
packets: Vec<MixPacket>,
) -> Result<(), ErasedGatewayError> {
log::info!("GatewaySender::batch_send_mix_packets - sending {} packets", packets.len());
// allow for optimisation when sending multiple packets
for packet in packets {
self.send_mix_packet(packet).await?;
@@ -78,7 +85,10 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
impl<G: GatewaySender + ?Sized + Send> GatewaySender for Box<G> {
#[inline]
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
(**self).send_mix_packet(packet).await
log::info!("JON: Box<GatewaySender>::send_mix_packet - sending a packet");
let r = (**self).send_mix_packet(packet).await;
log::info!("JON: Box<GatewaySender>::send_mix_packet - sent a packet");
r
}
#[inline]
@@ -86,7 +96,10 @@ impl<G: GatewaySender + ?Sized + Send> GatewaySender for Box<G> {
&mut self,
packets: Vec<MixPacket>,
) -> Result<(), ErasedGatewayError> {
(**self).batch_send_mix_packets(packets).await
log::info!("JON: Box<GatewaySender>::batch_send_mix_packets - sending {} packets", packets.len());
let r = (**self).batch_send_mix_packets(packets).await;
log::info!("JON: Box<GatewaySender>::batch_send_mix_packets - sent packets");
r
}
}
@@ -130,20 +143,26 @@ where
St: Send,
{
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
self.gateway_client
log::info!("JON: RemoteGateway::send_mix_packet - sending a packet");
let r = self.gateway_client
.send_mix_packet(packet)
.await
.map_err(erase_err)
.map_err(erase_err);
log::info!("JON: RemoteGateway::send_mix_packet - sent a packet");
r
}
async fn batch_send_mix_packets(
&mut self,
packets: Vec<MixPacket>,
) -> Result<(), ErasedGatewayError> {
self.gateway_client
log::info!("JON: RemoteGateway::batch_send_mix_packets - sending {} packets", packets.len());
let r = self.gateway_client
.batch_send_mix_packets(packets)
.await
.map_err(erase_err)
.map_err(erase_err);
log::info!("JON: RemoteGateway::batch_send_mix_packets - sent packets");
r
}
}
@@ -203,10 +222,13 @@ mod nonwasm_sealed {
#[async_trait]
impl GatewaySender for LocalGateway {
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
self.packet_forwarder
log::info!("JON: LocalGateway::send_mix_packet - sending a packet");
let r = self.packet_forwarder
.unbounded_send(packet)
.map_err(|err| err.into_send_error())
.map_err(erase_err)
.map_err(erase_err);
log::info!("JON: LocalGateway::send_mix_packet - sent a packet");
r
}
}
@@ -261,6 +283,7 @@ impl GatewayReceiver for MockGateway {
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl GatewaySender for MockGateway {
async fn send_mix_packet(&mut self, packet: MixPacket) -> Result<(), ErasedGatewayError> {
log::info!("MockGateway::send_mix_packet - sending a packet");
self.sent.push(packet);
Ok(())
}
@@ -3,11 +3,12 @@ use std::{
time::{Duration, Instant},
};
use log::{info, warn};
use nym_metrics::{inc, inc_by, metrics};
use nym_metrics::{inc, inc_by};
use si_scale::helpers::bibytes2;
// Metrics server
use futures::future::{FusedFuture, OptionFuture};
use futures::FutureExt;
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
use http_body_util::Full;
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
@@ -23,6 +24,7 @@ use hyper_util::rt::TokioIo;
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
use std::convert::Infallible;
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
#[cfg(feature = "metrics-server")]
use std::net::SocketAddr;
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
use tokio::net::TcpListener;
@@ -508,17 +510,17 @@ impl PacketStatisticsControl {
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
log::warn!("Metrics server is not supported on wasm32-unknown-unknown");
let listener = None;
} else {
let listener: Option<WasmEmpty> = None;
} else if #[cfg(feature = "metrics-server")] {
let mut metrics_port = 18000;
let listener: Option<TcpListener>;
loop {
let addr = SocketAddr::from(([0, 0, 0, 0], metrics_port));
match TcpListener::bind(addr).await {
Ok(l) => {
info!("###############################");
info!("Metrics endpoint is at: {:?}", l.local_addr());
info!("###############################");
log::info!("###############################");
log::info!("Metrics endpoint is at: {:?}", l.local_addr());
log::info!("###############################");
listener = Some(l);
break;
},
@@ -528,11 +530,21 @@ impl PacketStatisticsControl {
}
};
}
} else {
log::info!("Metrics server is disabled!");
let listener: Option<TcpListener> = None;
}
}
loop {
// it seems at some point tokio changed its select precondition evaluation,
// and it's no longer checked before the future is evaluated.
let accept_future: OptionFuture<_> = listener
.as_ref()
.map(|l| l.accept())
.map(FutureExt::fuse)
.into();
tokio::select! {
stats_event = self.stats_rx.recv() => match stats_event {
Some(stats_event) => {
@@ -545,10 +557,11 @@ impl PacketStatisticsControl {
}
},
// conditional will disable the branch if we're in wasm32-unknown-unknown
result = listener.as_ref().unwrap().accept(), if listener.is_some() => {
// use `_` to calm down clippy when running for wasm
_result = accept_future, if !accept_future.is_terminated() => {
cfg_if::cfg_if! {
if #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] {
if let Ok((stream, _)) = result {
if let Some(Ok((stream, _))) = _result {
let io = TokioIo::new(stream);
tokio::task::spawn(async move {
@@ -556,11 +569,11 @@ impl PacketStatisticsControl {
.serve_connection(io, service_fn(serve_metrics))
.await
{
warn!("Error serving connection: {:?}", err);
log::warn!("Error serving connection: {:?}", err);
}
});
} else {
warn!("Error accepting connection");
log::warn!("Error accepting connection");
}
}
}
@@ -590,8 +603,19 @@ impl PacketStatisticsControl {
}
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
async fn serve_metrics(
_: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
use nym_metrics::metrics;
Ok(Response::new(Full::new(Bytes::from(metrics!()))))
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
struct WasmEmpty;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
impl WasmEmpty {
async fn accept(&self) {}
}
@@ -632,15 +632,23 @@ where
}
pub(crate) fn update_ack_delay(&self, id: FragmentIdentifier, new_delay: Delay) {
self.action_sender
if self
.action_sender
.unbounded_send(Action::UpdateDelay(id, new_delay))
.expect("action control task has died")
.is_err()
{
log::debug!("action control task has died");
}
}
pub(crate) fn insert_pending_acks(&self, pending_acks: Vec<PendingAcknowledgement>) {
self.action_sender
if self
.action_sender
.unbounded_send(Action::new_insert(pending_acks))
.expect("action control task has died")
.is_err()
{
log::debug!("action control task has died");
}
}
// tells real message sender (with the poisson timer) to send this to the mix network
@@ -198,7 +198,9 @@ where
// queues and client load rather than the required delay. So realistically we can treat
// whatever is about to happen as negligible additional delay.
trace!("{} is about to get sent to the mixnet", frag_id);
self.sent_notifier.unbounded_send(frag_id).unwrap();
if self.sent_notifier.unbounded_send(frag_id).is_err() {
debug!("Failed to notify about sent packet");
}
}
fn loop_cover_message_size(&mut self) -> PacketSize {
@@ -270,7 +272,8 @@ where
};
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
log::error!("Failed to send: {err}");
log::debug!("Failed to send: {err}");
return;
} else {
let event = if fragment_id.is_some() {
PacketStatisticsEvent::RealPacketSent(packet_size)
+3 -1
View File
@@ -2,4 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
pub mod reply_controller;
pub mod reply_storage;
// re-export it under the old name to preserve import paths
pub use nym_client_core_surb_storage as reply_storage;
@@ -1,6 +1,5 @@
use std::{collections::HashMap, fmt};
use log::{debug, error, info};
use crate::config::GroupBy;
use log::{debug, error};
use nym_explorer_client::{ExplorerClient, PrettyDetailedMixNodeBond};
use nym_network_defaults::var_names::EXPLORER_API;
use nym_topology::{
@@ -10,11 +9,11 @@ use nym_topology::{
};
use nym_validator_client::client::MixId;
use rand::{prelude::SliceRandom, thread_rng};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tap::TapOptional;
use url::Url;
use crate::config::GroupBy;
pub use nym_country_group::CountryGroup;
const MIN_NODES_PER_LAYER: usize = 1;
@@ -38,158 +37,6 @@ fn create_explorer_client() -> Option<ExplorerClient> {
Some(client)
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub enum CountryGroup {
Europe,
NorthAmerica,
SouthAmerica,
Oceania,
Asia,
Africa,
Unknown,
}
impl CountryGroup {
// We map contry codes into group, which initially are continent codes to a first approximation,
// but we do it manually to reserve the right to tweak this distribution for our purposes.
// NOTE: I did this quickly and it's not a complete list of all countries, but only those that
// were present in the network at the time. Please add more as needed.
fn new(country_code: &str) -> Self {
let country_code = country_code.to_uppercase();
use CountryGroup::*;
match country_code.as_ref() {
// Europe
"AT" => Europe,
"BG" => Europe,
"CH" => Europe,
"CY" => Europe,
"CZ" => Europe,
"DE" => Europe,
"DK" => Europe,
"ES" => Europe,
"FI" => Europe,
"FR" => Europe,
"GB" => Europe,
"GR" => Europe,
"IE" => Europe,
"IT" => Europe,
"LT" => Europe,
"LU" => Europe,
"LV" => Europe,
"MD" => Europe,
"MT" => Europe,
"NL" => Europe,
"NO" => Europe,
"PL" => Europe,
"RO" => Europe,
"SE" => Europe,
"SK" => Europe,
"TR" => Europe,
"UA" => Europe,
// North America
"CA" => NorthAmerica,
"MX" => NorthAmerica,
"US" => NorthAmerica,
// South America
"AR" => SouthAmerica,
"BR" => SouthAmerica,
"CL" => SouthAmerica,
"CO" => SouthAmerica,
"CR" => SouthAmerica,
"GT" => SouthAmerica,
// Oceania
"AU" => Oceania,
// Asia
"AM" => Asia,
"BH" => Asia,
"CN" => Asia,
"GE" => Asia,
"HK" => Asia,
"ID" => Asia,
"IL" => Asia,
"IN" => Asia,
"JP" => Asia,
"KH" => Asia,
"KR" => Asia,
"KZ" => Asia,
"MY" => Asia,
"RU" => Asia,
"SG" => Asia,
"TH" => Asia,
"VN" => Asia,
// Africa
"SC" => Africa,
"UG" => Africa,
"ZA" => Africa,
// And group level codes work too
"EU" => Europe,
"NA" => NorthAmerica,
"SA" => SouthAmerica,
"OC" => Oceania,
"AS" => Asia,
"AF" => Africa,
// And some aliases
"EUROPE" => Europe,
"NORTHAMERICA" => NorthAmerica,
"SOUTHAMERICA" => SouthAmerica,
"OCEANIA" => Oceania,
"ASIA" => Asia,
"AFRICA" => Africa,
_ => {
info!("Unknown country code: {}", country_code);
Unknown
}
}
}
}
impl fmt::Display for CountryGroup {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use CountryGroup::*;
match self {
Europe => write!(f, "EU"),
NorthAmerica => write!(f, "NA"),
SouthAmerica => write!(f, "SA"),
Oceania => write!(f, "OC"),
Asia => write!(f, "AS"),
Africa => write!(f, "AF"),
Unknown => write!(f, "Unknown"),
}
}
}
impl std::str::FromStr for CountryGroup {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let group = CountryGroup::new(s);
if group == CountryGroup::Unknown {
Err(())
} else {
Ok(group)
}
}
}
impl CountryGroup {
#[allow(unused)]
fn known(self) -> Option<CountryGroup> {
use CountryGroup::*;
match self {
Europe | NorthAmerica | SouthAmerica | Oceania | Asia | Africa => Some(self),
Unknown => None,
}
}
}
fn group_mixnodes_by_country_code(
mixnodes: Vec<PrettyDetailedMixNodeBond>,
) -> HashMap<CountryGroup, Vec<MixId>> {
@@ -16,6 +16,8 @@ const OLDEST_LANE_SET_SIZE: usize = 4;
// As a way of prune connections we also check for timeouts.
const MSG_CONSIDERED_STALE_AFTER_SECS: u64 = 10 * 60;
// this trait is apparently not used in wasm
#[allow(dead_code)]
pub(crate) trait SizedData {
fn data_size(&self) -> usize;
}
@@ -1,42 +0,0 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
pub mod keys_paths;
pub mod old_v1_1_20_2;
pub const DEFAULT_GATEWAY_DETAILS_FILENAME: &str = "gateway_details.json";
pub const DEFAULT_REPLY_SURB_DB_FILENAME: &str = "persistent_reply_store.sqlite";
pub const DEFAULT_CREDENTIALS_DB_FILENAME: &str = "credentials_database.db";
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CommonClientPaths {
pub keys: ClientKeysPaths,
/// Path to the file containing information about gateway used by this client,
/// i.e. details such as its public key, owner address or the network information.
pub gateway_details: PathBuf,
/// Path to the database containing bandwidth credentials of this client.
pub credentials_database: PathBuf,
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
pub reply_surb_database: PathBuf,
}
impl CommonClientPaths {
pub fn new_base<P: AsRef<Path>>(base_data_directory: P) -> Self {
let base_dir = base_data_directory.as_ref();
CommonClientPaths {
credentials_database: base_dir.join(DEFAULT_CREDENTIALS_DB_FILENAME),
reply_surb_database: base_dir.join(DEFAULT_REPLY_SURB_DB_FILENAME),
gateway_details: base_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
keys: ClientKeysPaths::new_base(base_data_directory),
}
}
}
@@ -1,32 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
use crate::config::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAY_DETAILS_FILENAME};
use crate::error::ClientCoreError;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CommonClientPathsV1_1_20_2 {
pub keys: ClientKeysPaths,
pub credentials_database: PathBuf,
pub reply_surb_database: PathBuf,
}
impl CommonClientPathsV1_1_20_2 {
pub fn upgrade_default(self) -> Result<CommonClientPaths, ClientCoreError> {
let data_dir = self.reply_surb_database.parent().ok_or_else(|| {
ClientCoreError::UnableToUpgradeConfigFile {
new_version: "1.1.20-2".to_string(),
}
})?;
Ok(CommonClientPaths {
keys: self.keys,
gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
credentials_database: self.credentials_database,
reply_surb_database: self.reply_surb_database,
})
}
}
+6 -674
View File
@@ -1,677 +1,9 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{client::topology_control::geo_aware_provider::CountryGroup, error::ClientCoreError};
use nym_config::defaults::NymNetworkDetails;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::client::GatewayConfig;
use nym_sphinx::{
addressing::clients::Recipient,
params::{PacketSize, PacketType},
pub use nym_client_core_config_types::disk_persistence;
pub use nym_client_core_config_types::old::{
old_config_v1_1_13, old_config_v1_1_20, old_config_v1_1_20_2, old_config_v1_1_30,
old_config_v1_1_33,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
pub mod disk_persistence;
pub mod old_config_v1_1_13;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_30;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
// reply-surbs related:
// define when to request
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
// define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10);
const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60);
// 12 hours
const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60);
// 24 hours
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
pub client: Client,
#[serde(default)]
pub debug: DebugConfig,
}
impl Config {
pub fn new<S1, S2>(id: S1, version: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
Config {
client: Client::new_default(id, version),
debug: Default::default(),
}
}
pub fn from_client_config(client: Client, debug: DebugConfig) -> Self {
Config { client, debug }
}
pub fn validate(&self) -> bool {
self.debug.validate()
}
pub fn with_debug_config(mut self, debug: DebugConfig) -> Self {
self.debug = debug;
self
}
pub fn with_disabled_credentials(mut self, disabled_credentials_mode: bool) -> Self {
self.client.disabled_credentials_mode = disabled_credentials_mode;
self
}
pub fn with_custom_nyxd(mut self, urls: Vec<Url>) -> Self {
self.client.nyxd_urls = urls;
self
}
pub fn set_custom_nyxd(&mut self, nyxd_urls: Vec<Url>) {
self.client.nyxd_urls = nyxd_urls;
}
pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec<Url>) -> Self {
self.client.nym_api_urls = nym_api_urls;
self
}
pub fn set_custom_nym_apis(&mut self, nym_api_urls: Vec<Url>) {
self.client.nym_api_urls = nym_api_urls;
}
pub fn with_high_default_traffic_volume(mut self, enabled: bool) -> Self {
if enabled {
self.set_high_default_traffic_volume();
}
self
}
pub fn with_packet_type(mut self, packet_type: PacketType) -> Self {
self.debug.traffic.packet_type = packet_type;
self
}
pub fn set_high_default_traffic_volume(&mut self) {
self.debug.traffic.average_packet_delay = Duration::from_millis(10);
// basically don't really send cover messages
self.debug.cover_traffic.loop_cover_traffic_average_delay =
Duration::from_millis(2_000_000);
// 250 "real" messages / s
self.debug.traffic.message_sending_average_delay = Duration::from_millis(4);
}
pub fn with_disabled_poisson_process(mut self, disabled: bool) -> Self {
if disabled {
self.set_no_poisson_process()
}
self
}
pub fn set_no_poisson_process(&mut self) {
self.debug.traffic.disable_main_poisson_packet_distribution = true;
}
pub fn with_disabled_cover_traffic(mut self, disabled: bool) -> Self {
if disabled {
self.set_no_cover_traffic()
}
self
}
pub fn set_no_cover_traffic(&mut self) {
self.debug.cover_traffic.disable_loop_cover_traffic_stream = true;
self.debug.traffic.disable_main_poisson_packet_distribution = true;
}
pub fn with_disabled_cover_traffic_with_keepalive(mut self, disabled: bool) -> Self {
if disabled {
self.set_no_cover_traffic_with_keepalive()
}
self
}
pub fn set_no_cover_traffic_with_keepalive(&mut self) {
self.debug.traffic.disable_main_poisson_packet_distribution = true;
self.debug.cover_traffic.loop_cover_traffic_average_delay = Duration::from_secs(5);
}
pub fn with_disabled_topology_refresh(mut self, disable_topology_refresh: bool) -> Self {
self.debug.topology.disable_refreshing = disable_topology_refresh;
self
}
pub fn with_topology_structure(mut self, topology_structure: TopologyStructure) -> Self {
self.set_topology_structure(topology_structure);
self
}
pub fn set_topology_structure(&mut self, topology_structure: TopologyStructure) {
self.debug.topology.topology_structure = topology_structure;
}
pub fn with_no_per_hop_delays(mut self, no_per_hop_delays: bool) -> Self {
if no_per_hop_delays {
self.set_no_per_hop_delays()
}
self
}
pub fn set_no_per_hop_delays(&mut self) {
self.debug.traffic.average_packet_delay = Duration::ZERO;
self.debug.acknowledgements.average_ack_delay = Duration::ZERO;
}
pub fn with_secondary_packet_size(mut self, secondary_packet_size: Option<PacketSize>) -> Self {
self.set_secondary_packet_size(secondary_packet_size);
self
}
pub fn set_secondary_packet_size(&mut self, secondary_packet_size: Option<PacketSize>) {
self.debug.traffic.secondary_packet_size = secondary_packet_size;
}
pub fn set_custom_version(&mut self, version: &str) {
self.client.version = version.to_string();
}
pub fn get_id(&self) -> String {
self.client.id.clone()
}
pub fn get_disabled_credentials_mode(&self) -> bool {
self.client.disabled_credentials_mode
}
pub fn get_validator_endpoints(&self) -> Vec<Url> {
self.client.nyxd_urls.clone()
}
pub fn get_nym_api_endpoints(&self) -> Vec<Url> {
self.client.nym_api_urls.clone()
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
pub struct GatewayEndpointConfig {
/// gateway_id specifies ID of the gateway to which the client should send messages.
/// If initially omitted, a random gateway will be chosen from the available topology.
pub gateway_id: String,
/// Address of the gateway owner to which the client should send messages.
pub gateway_owner: String,
/// Address of the gateway listener to which all client requests should be sent.
pub gateway_listener: String,
}
impl TryFrom<GatewayEndpointConfig> for GatewayConfig {
type Error = ClientCoreError;
fn try_from(value: GatewayEndpointConfig) -> Result<Self, Self::Error> {
Ok(GatewayConfig {
gateway_identity: identity::PublicKey::from_base58_string(value.gateway_id)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?,
gateway_owner: Some(value.gateway_owner),
gateway_listener: value.gateway_listener,
})
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
impl GatewayEndpointConfig {
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(constructor))]
pub fn new(
gateway_id: String,
gateway_owner: String,
gateway_listener: String,
) -> GatewayEndpointConfig {
GatewayEndpointConfig {
gateway_id,
gateway_owner,
gateway_listener,
}
}
}
// separate block so it wouldn't be exported via wasm bindgen
impl GatewayEndpointConfig {
pub fn try_get_gateway_identity_key(&self) -> Result<identity::PublicKey, ClientCoreError> {
identity::PublicKey::from_base58_string(&self.gateway_id)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)
}
pub fn from_node(
node: nym_topology::gateway::Node,
must_use_tls: bool,
) -> Result<Self, ClientCoreError> {
let gateway_listener = if must_use_tls {
node.clients_address_tls()
.ok_or(ClientCoreError::UnsupportedWssProtocol {
gateway: node.identity_key.to_base58_string(),
})?
} else {
node.clients_address()
};
Ok(GatewayEndpointConfig {
gateway_id: node.identity_key.to_base58_string(),
gateway_listener,
gateway_owner: node.owner,
})
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2
#[serde(deny_unknown_fields)]
pub struct Client {
/// Version of the client for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular client.
pub id: String,
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
// TODO: this should be moved to `debug.gateway_connection`
#[serde(default)]
pub disabled_credentials_mode: bool,
/// Addresses to nyxd validators via which the client can communicate with the chain.
#[serde(alias = "validator_urls")]
pub nyxd_urls: Vec<Url>,
/// Addresses to APIs running on validator from which the client gets the view of the network.
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
}
impl Client {
pub fn new_default<S1, S2>(id: S1, version: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
let network = NymNetworkDetails::new_mainnet();
let nyxd_urls = network
.endpoints
.iter()
.map(|validator| validator.nyxd_url())
.collect();
let nym_api_urls = network
.endpoints
.iter()
.filter_map(|validator| validator.api_url())
.collect::<Vec<_>>();
Client {
version: version.into(),
id: id.into(),
disabled_credentials_mode: true,
nyxd_urls,
nym_api_urls,
}
}
pub fn new<S: Into<String>>(
id: S,
disabled_credentials_mode: bool,
nyxd_urls: Vec<Url>,
nym_api_urls: Vec<Url>,
) -> Self {
Client {
version: env!("CARGO_PKG_VERSION").to_string(),
id: id.into(),
disabled_credentials_mode,
nyxd_urls,
nym_api_urls,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Traffic {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Specifies the packet size used for sent messages.
/// Do not override it unless you understand the consequences of that change.
pub primary_packet_size: PacketSize,
/// Specifies the optional auxiliary packet size for optimizing message streams.
/// Note that its use decreases overall anonymity.
/// Do not set it it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>,
pub packet_type: PacketType,
}
impl Traffic {
pub fn validate(&self) -> bool {
if let Some(secondary_packet_size) = self.secondary_packet_size {
if secondary_packet_size == PacketSize::AckPacket
|| secondary_packet_size == self.primary_packet_size
{
return false;
}
}
true
}
}
impl Default for Traffic {
fn default() -> Self {
Traffic {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: PacketType::Mix,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTraffic {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
/// Only applicable if `secondary_packet_size` is enabled.
pub cover_traffic_primary_size_ratio: f64,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
}
impl Default for CoverTraffic {
fn default() -> Self {
CoverTraffic {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnection {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl Default for GatewayConnection {
fn default() -> Self {
GatewayConnection {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Acknowledgements {
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 1.
pub ack_wait_multiplier: f64,
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
#[serde(with = "humantime_serde")]
pub ack_wait_addition: Duration,
}
impl Default for Acknowledgements {
fn default() -> Self {
Acknowledgements {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Topology {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
/// Defines how long the client is going to wait on startup for its gateway to come online,
/// before abandoning the procedure.
#[serde(with = "humantime_serde")]
pub max_startup_gateway_waiting_period: Duration,
/// Specifies the mixnode topology to be used for sending packets.
pub topology_structure: TopologyStructure,
}
#[allow(clippy::large_enum_variant)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TopologyStructure {
#[default]
NymApi,
GeoAware(GroupBy),
}
#[allow(clippy::large_enum_variant)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GroupBy {
CountryGroup(CountryGroup),
NymAddress(Recipient),
}
impl std::fmt::Display for GroupBy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GroupBy::CountryGroup(group) => write!(f, "group: {}", group),
GroupBy::NymAddress(address) => write!(f, "address: {}", address),
}
}
}
impl Default for Topology {
fn default() -> Self {
Topology {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
topology_structure: TopologyStructure::default(),
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbs {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
/// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize,
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs the client would request.
pub maximum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
pub maximum_allowed_reply_surb_request_size: u32,
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
/// for more even though in theory they wouldn't need to.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_rerequest_waiting_period: Duration,
/// Defines maximum amount of time the client is going to wait for reply surbs before
/// deciding it's never going to get them and would drop all pending messages
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_drop_waiting_period: Duration,
/// Defines maximum amount of time given reply surb is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_age: Duration,
/// Defines maximum amount of time given reply key is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
/// the default value is used.
pub surb_mix_hops: Option<u8>,
}
impl Default for ReplySurbs {
fn default() -> Self {
ReplySurbs {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
maximum_reply_surb_rerequest_waiting_period:
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
surb_mix_hops: None,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfig {
/// Defines all configuration options related to traffic streams.
pub traffic: Traffic,
/// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTraffic,
/// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnection,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: Acknowledgements,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: Topology,
/// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbs,
}
impl DebugConfig {
pub fn validate(&self) -> bool {
// no other sections have explicit requirements (yet)
self.traffic.validate()
}
}
// it could be derived, sure, but I'd rather have an explicit implementation in case we had to change
// something manually at some point
#[allow(clippy::derivable_impls)]
impl Default for DebugConfig {
fn default() -> Self {
DebugConfig {
traffic: Default::default(),
cover_traffic: Default::default(),
gateway_connection: Default::default(),
acknowledgements: Default::default(),
topology: Default::default(),
reply_surbs: Default::default(),
}
}
}
pub use nym_client_core_config_types::*;
+64 -3
View File
@@ -39,6 +39,9 @@ pub enum ClientCoreError {
#[error("no gateways on network")]
NoGatewaysOnNetwork,
#[error("there are no more new gateways on the network - it seems this client has already registered with all nodes it could have")]
NoNewGatewaysAvailable,
#[error("list of nym apis is empty")]
ListOfNymApisIsEmpty,
@@ -55,8 +58,8 @@ pub enum ClientCoreError {
source: Box<dyn Error + Send + Sync>,
},
#[error("experienced a failure with our gateway details storage: {source}")]
GatewayDetailsStoreError {
#[error("experienced a failure with our gateways details storage: {source}")]
GatewaysDetailsStoreError {
source: Box<dyn Error + Send + Sync>,
},
@@ -75,6 +78,9 @@ pub enum ClientCoreError {
source: tungstenite::Error,
},
#[error("max number of retries for gateway connection has been exceeded")]
GatewayMaxRetriesExceeded,
#[cfg(target_arch = "wasm32")]
#[error("failed to establish gateway connection (wasm)")]
GatewayJsConnectionFailure,
@@ -85,6 +91,11 @@ pub enum ClientCoreError {
#[error("timed out while trying to establish gateway connection")]
GatewayConnectionTimeout,
#[error("failed to send sphinx packet to the gateway: {gateway_client_error}")]
GatewayClientSendError {
gateway_client_error: String,
},
#[error("no ping measurements for the gateway ({identity}) performed")]
NoGatewayMeasurements { identity: String },
@@ -94,13 +105,21 @@ pub enum ClientCoreError {
#[error("unexpected exit")]
UnexpectedExit,
#[error("this operation would have resulted in the gateway {gateway_id:?} key being overwritten without permission")]
ForbiddenGatewayKeyOverwrite { gateway_id: String },
#[error(
"this operation would have resulted in clients keys being overwritten without permission"
)]
ForbiddenKeyOverwrite,
#[error("gateway details are unavailable")]
#[error("the client doesn't have any gateway set as active")]
NoActiveGatewaySet,
#[error("gateway details for gateway {gateway_id:?} are unavailable")]
UnavailableGatewayDetails {
gateway_id: String,
#[source]
source: Box<dyn Error + Send + Sync>,
},
@@ -113,9 +132,15 @@ pub enum ClientCoreError {
#[error("the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys")]
MismatchedGatewayDetails { gateway_id: String },
#[error("unable to upgrade config file from `{current_version}`")]
ConfigFileUpgradeFailure { current_version: String },
#[error("unable to upgrade config file to `{new_version}`")]
UnableToUpgradeConfigFile { new_version: String },
#[error("failed to upgrade config file: {message}")]
UpgradeFailure { message: String },
#[error("the provided gateway details don't much the stored data")]
MismatchedStoredGatewayDetails,
@@ -153,6 +178,42 @@ pub enum ClientCoreError {
#[source]
source: std::io::Error,
},
#[error("the provided gateway identity {gateway_id} is malformed: {source}")]
MalformedGatewayIdentity {
gateway_id: String,
#[source]
source: Ed25519RecoveryError,
},
#[error("the account owner of gateway {gateway_id} ({raw_owner}) is malformed: {err}")]
MalformedGatewayOwnerAccountAddress {
gateway_id: String,
raw_owner: String,
// just use the string formatting as opposed to underlying type to avoid having to import cosmrs
err: String,
},
#[error(
"the listening address of gateway {gateway_id} ({raw_listener}) is malformed: {source}"
)]
MalformedListener {
gateway_id: String,
raw_listener: String,
#[source]
source: url::ParseError,
},
#[error("this client (id: '{client_id}') has already been initialised before. If you want to add additional gateway, use `add-gateway` command")]
AlreadyInitialised { client_id: String },
#[error("this client has already registered with gateway {gateway_id}")]
AlreadyRegistered { gateway_id: String },
}
/// Set of messages that the client can send to listeners via the task manager
+12 -14
View File
@@ -1,7 +1,6 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::GatewayEndpointConfig;
use crate::error::ClientCoreError;
use crate::init::types::RegistrationResult;
use futures::{SinkExt, StreamExt};
@@ -9,6 +8,7 @@ use log::{debug, info, trace, warn};
use nym_crypto::asymmetric::identity;
use nym_gateway_client::GatewayClient;
use nym_topology::{filter::VersionFilterable, gateway, mix};
use nym_validator_client::client::IdentityKeyRef;
use rand::{seq::SliceRandom, Rng};
use std::{sync::Arc, time::Duration};
use tungstenite::Message;
@@ -17,16 +17,13 @@ use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(not(target_arch = "wasm32"))]
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
use nym_validator_client::client::IdentityKeyRef;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
@@ -35,6 +32,9 @@ use wasmtimer::std::Instant;
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::sleep;
#[cfg(not(target_arch = "wasm32"))]
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
#[cfg(target_arch = "wasm32")]
type WsConn = JSWebsocket;
@@ -278,16 +278,17 @@ pub(super) fn get_specified_gateway(
}
pub(super) async fn register_with_gateway(
gateway: &GatewayEndpointConfig,
gateway_id: identity::PublicKey,
gateway_listener: Url,
our_identity: Arc<identity::KeyPair>,
) -> Result<RegistrationResult, ClientCoreError> {
let mut gateway_client =
GatewayClient::new_init(gateway.to_owned().try_into()?, our_identity.clone());
GatewayClient::new_init(gateway_listener, gateway_id, our_identity.clone());
gateway_client.establish_connection().await.map_err(|err| {
log::warn!("Failed to establish connection with gateway!");
ClientCoreError::GatewayClientError {
gateway_id: gateway.gateway_id.clone(),
gateway_id: gateway_id.to_base58_string(),
source: err,
}
})?;
@@ -295,12 +296,9 @@ pub(super) async fn register_with_gateway(
.perform_initial_authentication()
.await
.map_err(|err| {
log::warn!(
"Failed to register with the gateway {}!",
gateway.gateway_id
);
log::warn!("Failed to register with the gateway {gateway_id}: {err}");
ClientCoreError::GatewayClientError {
gateway_id: gateway.gateway_id.clone(),
gateway_id: gateway_id.to_base58_string(),
source: err,
}
})?;
+117 -124
View File
@@ -1,243 +1,236 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
//! Collection of initialization steps used by client implementations
use crate::client::base_client::storage::gateway_details::{
GatewayDetailsStore, PersistedGatewayDetails,
use crate::client::base_client::storage::helpers::{
has_gateway_details, load_active_gateway_details, load_client_keys, load_gateway_details,
store_gateway_details,
};
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ManagedKeys;
use crate::config::GatewayEndpointConfig;
use crate::client::key_manager::ClientKeys;
use crate::error::ClientCoreError;
use crate::init::helpers::{
choose_gateway_by_latency, get_specified_gateway, uniformly_random_gateway,
};
use crate::init::types::{
CustomGatewayDetails, GatewayDetails, GatewaySelectionSpecification, GatewaySetup,
InitialisationResult,
GatewaySelectionSpecification, GatewaySetup, InitialisationResult, SelectedGateway,
};
use nym_client_core_gateways_storage::GatewaysDetailsStore;
use nym_client_core_gateways_storage::{GatewayDetails, GatewayRegistration};
use nym_gateway_client::client::InitGatewayClient;
use nym_topology::gateway;
use rand::rngs::OsRng;
use serde::de::DeserializeOwned;
use rand::{CryptoRng, RngCore};
use serde::Serialize;
use std::sync::Arc;
use std::net::IpAddr;
pub mod helpers;
pub mod types;
// helpers for error wrapping
async fn _store_gateway_details<T, D>(
details_store: &D,
details: &PersistedGatewayDetails<T>,
pub async fn generate_new_client_keys<K, R>(
rng: &mut R,
key_store: &K,
) -> Result<(), ClientCoreError>
where
D: GatewayDetailsStore<T>,
D::StorageError: Send + Sync + 'static,
T: Serialize + Send + Sync,
{
details_store
.store_gateway_details(details)
.await
.map_err(|source| ClientCoreError::GatewayDetailsStoreError {
source: Box::new(source),
})
}
async fn _load_gateway_details<T, D>(
details_store: &D,
) -> Result<PersistedGatewayDetails<T>, ClientCoreError>
where
D: GatewayDetailsStore<T>,
D::StorageError: Send + Sync + 'static,
T: DeserializeOwned + Send + Sync,
{
details_store
.load_gateway_details()
.await
.map_err(|source| ClientCoreError::UnavailableGatewayDetails {
source: Box::new(source),
})
}
async fn _load_managed_keys<K>(key_store: &K) -> Result<ManagedKeys, ClientCoreError>
where
R: RngCore + CryptoRng,
K: KeyStore,
K::StorageError: Send + Sync + 'static,
{
ManagedKeys::try_load(key_store)
ClientKeys::generate_new(rng)
.persist_keys(key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})
}
fn ensure_valid_details<T>(
details: &PersistedGatewayDetails<T>,
loaded_keys: &ManagedKeys,
) -> Result<(), ClientCoreError> {
details.validate(loaded_keys.gateway_shared_key().as_deref())
}
async fn setup_new_gateway<T, K, D>(
async fn setup_new_gateway<K, D>(
key_store: &K,
details_store: &D,
overwrite_data: bool,
selection_specification: GatewaySelectionSpecification<T>,
selection_specification: GatewaySelectionSpecification,
available_gateways: Vec<gateway::Node>,
) -> Result<InitialisationResult<T>, ClientCoreError>
wg_tun_ip_address: Option<IpAddr>,
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
D: GatewayDetailsStore<T>,
D: GatewaysDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
T: DeserializeOwned + Serialize + Send + Sync,
{
log::trace!("Setting up new gateway");
// if we're setting up new gateway, failing to load existing information is fine.
// as a matter of fact, it's only potentially a problem if we DO succeed
if _load_gateway_details(details_store).await.is_ok() && !overwrite_data {
return Err(ClientCoreError::ForbiddenKeyOverwrite);
}
if _load_managed_keys(key_store).await.is_ok() && !overwrite_data {
return Err(ClientCoreError::ForbiddenKeyOverwrite);
}
// if we're setting up new gateway, we must have had generated long-term client keys before
let client_keys = load_client_keys(key_store).await?;
let mut rng = OsRng;
let mut new_keys = ManagedKeys::generate_new(&mut rng);
let gateway_details = match selection_specification {
let selected_gateway = match selection_specification {
GatewaySelectionSpecification::UniformRemote { must_use_tls } => {
let gateway = uniformly_random_gateway(&mut rng, &available_gateways, must_use_tls)?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls)?)
SelectedGateway::from_topology_node(gateway, wg_tun_ip_address, must_use_tls)?
}
GatewaySelectionSpecification::RemoteByLatency { must_use_tls } => {
let gateway =
choose_gateway_by_latency(&mut rng, &available_gateways, must_use_tls).await?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls)?)
SelectedGateway::from_topology_node(gateway, wg_tun_ip_address, must_use_tls)?
}
GatewaySelectionSpecification::Specified {
must_use_tls,
identity,
} => {
let gateway = get_specified_gateway(&identity, &available_gateways, must_use_tls)?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls)?)
SelectedGateway::from_topology_node(gateway, wg_tun_ip_address, must_use_tls)?
}
GatewaySelectionSpecification::Custom {
gateway_identity,
additional_data,
} => GatewayDetails::Custom(CustomGatewayDetails::new(gateway_identity, additional_data)),
} => SelectedGateway::custom(gateway_identity, additional_data)?,
};
let registration_result = if let GatewayDetails::Configured(gateway_cfg) = &gateway_details {
// if we're using a 'normal' gateway setup, do register
let our_identity = new_keys.identity_keypair();
Some(helpers::register_with_gateway(gateway_cfg, our_identity).await?)
} else {
None
// check if we already have details associated with this particular gateway
// and if so, see if we can overwrite it
let selected_id = selected_gateway.gateway_id().to_base58_string();
if has_gateway_details(details_store, &selected_id).await? {
return Err(ClientCoreError::AlreadyRegistered {
gateway_id: selected_id,
});
}
let (gateway_details, authenticated_ephemeral_client) = match selected_gateway {
SelectedGateway::Remote {
gateway_id,
gateway_owner_address,
gateway_listener,
wg_tun_address,
} => {
// if we're using a 'normal' gateway setup, do register
let our_identity = client_keys.identity_keypair();
// if wg address is set, use that one
let url = wg_tun_address.clone().unwrap_or(gateway_listener.clone());
let registration =
helpers::register_with_gateway(gateway_id, url, our_identity).await?;
(
GatewayDetails::new_remote(
gateway_id,
registration.shared_keys,
gateway_owner_address,
gateway_listener,
wg_tun_address,
),
Some(registration.authenticated_ephemeral_client),
)
}
SelectedGateway::Custom {
gateway_id,
additional_data,
} => (
GatewayDetails::new_custom(gateway_id, additional_data),
None,
),
};
let maybe_shared_keys = registration_result
.as_ref()
.map(|r| Arc::clone(&r.shared_keys));
let gateway_registration = gateway_details.into();
let persisted_details =
PersistedGatewayDetails::new(gateway_details, maybe_shared_keys.as_deref())?;
// persist the keys
new_keys
.deal_with_gateway_key(maybe_shared_keys, key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
// persist gateway configs
_store_gateway_details(details_store, &persisted_details).await?;
// persist gateway details
store_gateway_details(details_store, &gateway_registration).await?;
Ok(InitialisationResult {
gateway_details: persisted_details.into(),
managed_keys: new_keys,
authenticated_ephemeral_client: registration_result
.map(|r| r.authenticated_ephemeral_client),
gateway_registration,
client_keys,
authenticated_ephemeral_client,
})
}
async fn use_loaded_gateway_details<T, K, D>(
async fn use_loaded_gateway_details<K, D>(
key_store: &K,
details_store: &D,
) -> Result<InitialisationResult<T>, ClientCoreError>
gateway_id: Option<String>,
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
D: GatewayDetailsStore<T>,
D: GatewaysDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
T: DeserializeOwned + Send + Sync,
{
let loaded_details = _load_gateway_details(details_store).await?;
let loaded_keys = _load_managed_keys(key_store).await?;
let loaded_details = if let Some(gateway_id) = gateway_id {
load_gateway_details(details_store, &gateway_id).await?
} else {
load_active_gateway_details(details_store)
.await?
.registration
.ok_or(ClientCoreError::NoActiveGatewaySet)?
};
ensure_valid_details(&loaded_details, &loaded_keys)?;
let loaded_keys = load_client_keys(key_store).await?;
// no need to persist anything as we got everything from the storage
Ok(InitialisationResult::new_loaded(
loaded_details.into(),
loaded_details,
loaded_keys,
))
}
fn reuse_gateway_connection<T>(
fn reuse_gateway_connection(
authenticated_ephemeral_client: InitGatewayClient,
gateway_details: GatewayDetails<T>,
managed_keys: ManagedKeys,
) -> InitialisationResult<T> {
gateway_registration: GatewayRegistration,
client_keys: ClientKeys,
) -> InitialisationResult {
InitialisationResult {
gateway_details,
managed_keys,
gateway_registration,
client_keys,
authenticated_ephemeral_client: Some(authenticated_ephemeral_client),
}
}
pub async fn setup_gateway<T, K, D>(
setup: GatewaySetup<T>,
pub async fn setup_gateway<K, D>(
setup: GatewaySetup,
key_store: &K,
details_store: &D,
) -> Result<InitialisationResult<T>, ClientCoreError>
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
D: GatewayDetailsStore<T>,
D: GatewaysDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
T: DeserializeOwned + Serialize + Send + Sync,
{
log::debug!("Setting up gateway");
match setup {
GatewaySetup::MustLoad => use_loaded_gateway_details(key_store, details_store).await,
GatewaySetup::MustLoad { gateway_id } => {
log::debug!("GatewaySetup::MustLoad with id: {gateway_id:?}");
use_loaded_gateway_details(key_store, details_store, gateway_id).await
}
GatewaySetup::New {
specification,
available_gateways,
overwrite_data,
wg_tun_address,
} => {
log::debug!("GatewaySetup::New with spec: {specification:?}");
setup_new_gateway(
key_store,
details_store,
overwrite_data,
specification,
available_gateways,
wg_tun_address,
)
.await
}
GatewaySetup::ReuseConnection {
authenticated_ephemeral_client,
gateway_details,
managed_keys,
} => Ok(reuse_gateway_connection(
authenticated_ephemeral_client,
gateway_details,
managed_keys,
)),
client_keys: managed_keys,
} => {
log::debug!("GatewaySetup::ReuseConnection");
Ok(reuse_gateway_connection(
authenticated_ephemeral_client,
*gateway_details,
managed_keys,
))
}
}
}
+183 -157
View File
@@ -1,24 +1,132 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::base_client::storage::gateway_details::{
GatewayDetailsStore, PersistedCustomGatewayDetails, PersistedGatewayDetails,
};
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ManagedKeys;
use crate::config::{Config, GatewayEndpointConfig};
use crate::client::key_manager::ClientKeys;
use crate::config::Config;
use crate::error::ClientCoreError;
use crate::init::{_load_gateway_details, _load_managed_keys, setup_gateway};
use crate::init::{setup_gateway, use_loaded_gateway_details};
use log::info;
use nym_client_core_gateways_storage::{
GatewayRegistration, GatewaysDetailsStore, RemoteGatewayDetails,
};
use nym_crypto::asymmetric::identity;
use nym_gateway_client::client::InitGatewayClient;
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_topology::gateway;
use nym_validator_client::client::IdentityKey;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use nym_validator_client::nyxd::AccountId;
use serde::Serialize;
use std::fmt::Display;
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;
use time::OffsetDateTime;
use url::Url;
pub enum SelectedGateway {
Remote {
gateway_id: identity::PublicKey,
gateway_owner_address: AccountId,
gateway_listener: Url,
wg_tun_address: Option<Url>,
},
Custom {
gateway_id: identity::PublicKey,
additional_data: Option<Vec<u8>>,
},
}
fn wg_tun_address(
tun_ip: Option<IpAddr>,
gateway: &gateway::Node,
) -> Result<Option<Url>, ClientCoreError> {
let Some(tun_ip) = tun_ip else {
return Ok(None);
};
// log this so we'd remember about it if we ever decided to actually use that port
if gateway.clients_wss_port.is_some() {
info!(
"gateway {} exposes wss but for wireguard we're going to use ws",
gateway.identity_key
);
}
let raw_url = format!("ws://{tun_ip}:{}", gateway.clients_ws_port);
Ok(Some(raw_url.as_str().parse().map_err(|source| {
ClientCoreError::MalformedListener {
gateway_id: gateway.identity_key.to_base58_string(),
raw_listener: raw_url,
source,
}
})?))
}
impl SelectedGateway {
pub fn from_topology_node(
node: gateway::Node,
wg_tun_ip_address: Option<IpAddr>,
must_use_tls: bool,
) -> Result<Self, ClientCoreError> {
let gateway_listener = if must_use_tls {
node.clients_address_tls()
.ok_or(ClientCoreError::UnsupportedWssProtocol {
gateway: node.identity_key.to_base58_string(),
})?
} else {
node.clients_address()
};
let wg_tun_address = wg_tun_address(wg_tun_ip_address, &node)?;
let gateway_owner_address = AccountId::from_str(&node.owner).map_err(|source| {
ClientCoreError::MalformedGatewayOwnerAccountAddress {
gateway_id: node.identity_key.to_base58_string(),
raw_owner: node.owner,
err: source.to_string(),
}
})?;
let gateway_listener =
Url::parse(&gateway_listener).map_err(|source| ClientCoreError::MalformedListener {
gateway_id: node.identity_key.to_base58_string(),
raw_listener: gateway_listener,
source,
})?;
Ok(SelectedGateway::Remote {
gateway_id: node.identity_key,
gateway_owner_address,
gateway_listener,
wg_tun_address,
})
}
pub fn custom(
gateway_id: String,
additional_data: Option<Vec<u8>>,
) -> Result<Self, ClientCoreError> {
let gateway_id = identity::PublicKey::from_base58_string(&gateway_id)
.map_err(|source| ClientCoreError::MalformedGatewayIdentity { gateway_id, source })?;
Ok(SelectedGateway::Custom {
gateway_id,
additional_data,
})
}
pub fn gateway_id(&self) -> &identity::PublicKey {
match self {
SelectedGateway::Remote { gateway_id, .. } => gateway_id,
SelectedGateway::Custom { gateway_id, .. } => gateway_id,
}
}
}
/// Result of registering with a gateway:
/// - shared keys derived between ourselves and the node
@@ -33,17 +141,17 @@ pub struct RegistrationResult {
/// - all loaded (or derived) keys
/// - an optional authenticated handle of an ephemeral gateway handle created for the purposes of registration,
/// if this was the first time this client registered
pub struct InitialisationResult<T = EmptyCustomDetails> {
pub gateway_details: GatewayDetails<T>,
pub managed_keys: ManagedKeys,
pub struct InitialisationResult {
pub gateway_registration: GatewayRegistration,
pub client_keys: ClientKeys,
pub authenticated_ephemeral_client: Option<InitGatewayClient>,
}
impl<T> InitialisationResult<T> {
pub fn new_loaded(gateway_details: GatewayDetails<T>, managed_keys: ManagedKeys) -> Self {
impl InitialisationResult {
pub fn new_loaded(gateway_registration: GatewayRegistration, client_keys: ClientKeys) -> Self {
InitialisationResult {
gateway_details,
managed_keys,
gateway_registration,
client_keys,
authenticated_ephemeral_client: None,
}
}
@@ -51,135 +159,30 @@ impl<T> InitialisationResult<T> {
pub async fn try_load<K, D>(key_store: &K, details_store: &D) -> Result<Self, ClientCoreError>
where
K: KeyStore,
D: GatewayDetailsStore<T>,
D: GatewaysDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
T: DeserializeOwned + Send + Sync,
{
let loaded_details = _load_gateway_details(details_store).await?;
let loaded_keys = _load_managed_keys(key_store).await?;
match &loaded_details {
PersistedGatewayDetails::Default(loaded_default) => {
if !loaded_default.verify(&loaded_keys.must_get_gateway_shared_key()) {
return Err(ClientCoreError::MismatchedGatewayDetails {
gateway_id: loaded_default.details.gateway_id.clone(),
});
}
}
PersistedGatewayDetails::Custom(_) => {}
}
Ok(InitialisationResult {
gateway_details: loaded_details.into(),
managed_keys: loaded_keys,
authenticated_ephemeral_client: None,
})
use_loaded_gateway_details(key_store, details_store, None).await
}
pub fn client_address(&self) -> Result<Recipient, ClientCoreError> {
let client_recipient = Recipient::new(
*self.managed_keys.identity_public_key(),
*self.managed_keys.encryption_public_key(),
pub fn client_address(&self) -> Recipient {
Recipient::new(
*self.client_keys.identity_keypair().public_key(),
*self.client_keys.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(self.gateway_details.gateway_id())?,
);
Ok(client_recipient)
}
}
/// Details of particular gateway client got registered with
#[derive(Debug, Clone)]
pub enum GatewayDetails<T = EmptyCustomDetails> {
/// Standard details of a remote gateway
Configured(GatewayEndpointConfig),
/// Custom gateway setup, such as for a client embedded inside gateway itself
Custom(CustomGatewayDetails<T>),
}
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct EmptyCustomDetails {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CustomGatewayDetails<T = EmptyCustomDetails> {
// whatever custom method is used, gateway's identity must be known
pub gateway_id: String,
#[serde(flatten)]
pub additional_data: T,
}
impl<T> CustomGatewayDetails<T> {
pub fn new(gateway_id: String, additional_data: T) -> Self {
Self {
gateway_id,
additional_data,
}
}
}
impl<T> GatewayDetails<T> {
pub fn try_get_configured_endpoint(&self) -> Option<&GatewayEndpointConfig> {
if let GatewayDetails::Configured(endpoint) = &self {
Some(endpoint)
} else {
None
}
self.gateway_id(),
)
}
pub fn is_custom(&self) -> bool {
matches!(self, GatewayDetails::Custom(_))
}
pub fn gateway_id(&self) -> &str {
match self {
GatewayDetails::Configured(cfg) => &cfg.gateway_id,
GatewayDetails::Custom(custom) => &custom.gateway_id,
}
}
}
impl From<GatewayEndpointConfig> for GatewayDetails {
fn from(value: GatewayEndpointConfig) -> Self {
GatewayDetails::Configured(value)
}
}
impl<T> From<PersistedCustomGatewayDetails<T>> for CustomGatewayDetails<T> {
fn from(value: PersistedCustomGatewayDetails<T>) -> Self {
CustomGatewayDetails {
gateway_id: value.gateway_id,
additional_data: value.additional_data,
}
}
}
impl<T> From<CustomGatewayDetails<T>> for PersistedCustomGatewayDetails<T> {
fn from(value: CustomGatewayDetails<T>) -> Self {
PersistedCustomGatewayDetails {
gateway_id: value.gateway_id,
additional_data: value.additional_data,
}
}
}
impl<T> From<PersistedGatewayDetails<T>> for GatewayDetails<T> {
fn from(value: PersistedGatewayDetails<T>) -> Self {
match value {
PersistedGatewayDetails::Default(default) => {
GatewayDetails::Configured(default.details)
}
PersistedGatewayDetails::Custom(custom) => GatewayDetails::Custom(custom.into()),
}
pub fn gateway_id(&self) -> identity::PublicKey {
self.gateway_registration.details.gateway_id()
}
}
#[derive(Clone, Debug)]
pub enum GatewaySelectionSpecification<T = EmptyCustomDetails> {
pub enum GatewaySelectionSpecification {
/// Uniformly choose a random remote gateway.
UniformRemote { must_use_tls: bool },
@@ -197,11 +200,11 @@ pub enum GatewaySelectionSpecification<T = EmptyCustomDetails> {
/// This client has handled the selection by itself
Custom {
gateway_identity: String,
additional_data: T,
additional_data: Option<Vec<u8>>,
},
}
impl<T> Default for GatewaySelectionSpecification<T> {
impl Default for GatewaySelectionSpecification {
fn default() -> Self {
GatewaySelectionSpecification::UniformRemote {
must_use_tls: false,
@@ -209,7 +212,7 @@ impl<T> Default for GatewaySelectionSpecification<T> {
}
}
impl<T> GatewaySelectionSpecification<T> {
impl GatewaySelectionSpecification {
pub fn new(
gateway_identity: Option<String>,
latency_based_selection: Option<bool>,
@@ -228,19 +231,25 @@ impl<T> GatewaySelectionSpecification<T> {
}
}
pub enum GatewaySetup<T = EmptyCustomDetails> {
pub enum GatewaySetup {
/// The gateway specification (details + keys) MUST BE loaded from the underlying storage.
MustLoad,
MustLoad {
/// Optionally specify concrete gateway id. If none is selected, the current active gateway will be used.
gateway_id: Option<String>,
},
/// Specifies usage of a new gateway
New {
specification: GatewaySelectionSpecification<T>,
specification: GatewaySelectionSpecification,
// TODO: seems to be a bit inefficient to pass them by value
available_gateways: Vec<gateway::Node>,
/// Specifies whether old data should be overwritten whilst setting up new gateway client.
overwrite_data: bool,
/// Implicitly specify whether the chosen gateway must use wireguard mode by setting the tun address.
///
/// Currently this is imperfect solution as I'd imagine this address could vary from gateway to gateway
/// so perhaps it should be part of gateway::Node struct
wg_tun_address: Option<IpAddr>,
},
ReuseConnection {
@@ -248,44 +257,53 @@ pub enum GatewaySetup<T = EmptyCustomDetails> {
authenticated_ephemeral_client: InitGatewayClient,
// Details of this pre-initialised client (i.e. gateway and keys)
gateway_details: GatewayDetails<T>,
gateway_details: Box<GatewayRegistration>,
managed_keys: ManagedKeys,
client_keys: ClientKeys,
},
}
impl<T> GatewaySetup<T> {
pub fn try_reuse_connection(
init_res: InitialisationResult<T>,
) -> Result<Self, ClientCoreError> {
impl GatewaySetup {
pub fn try_reuse_connection(init_res: InitialisationResult) -> Result<Self, ClientCoreError> {
if let Some(authenticated_ephemeral_client) = init_res.authenticated_ephemeral_client {
Ok(GatewaySetup::ReuseConnection {
authenticated_ephemeral_client,
gateway_details: init_res.gateway_details,
managed_keys: init_res.managed_keys,
gateway_details: Box::new(init_res.gateway_registration),
client_keys: init_res.client_keys,
})
} else {
Err(ClientCoreError::NoInitClientPresent)
}
}
/// new gateway setup performed by each client that's inbuilt in a gateway (like NR or IPR)
pub fn new_inbuilt(identity: identity::PublicKey) -> Self {
GatewaySetup::New {
specification: GatewaySelectionSpecification::Custom {
gateway_identity: identity.to_base58_string(),
additional_data: None,
},
available_gateways: vec![],
wg_tun_address: None,
}
}
pub async fn try_setup<K, D>(
self,
key_store: &K,
details_store: &D,
) -> Result<InitialisationResult<T>, ClientCoreError>
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
D: GatewayDetailsStore<T>,
D: GatewaysDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
T: DeserializeOwned + Serialize + Send + Sync,
{
setup_gateway(self, key_store, details_store).await
}
pub fn is_must_load(&self) -> bool {
matches!(self, GatewaySetup::MustLoad)
matches!(self, GatewaySetup::MustLoad { .. })
}
pub fn has_full_details(&self) -> bool {
@@ -302,18 +320,25 @@ pub struct InitResults {
pub encryption_key: String,
pub gateway_id: String,
pub gateway_listener: String,
pub gateway_registration: OffsetDateTime,
pub address: Recipient,
}
impl InitResults {
pub fn new(config: &Config, address: Recipient, gateway: &GatewayEndpointConfig) -> Self {
pub fn new(
config: &Config,
address: Recipient,
gateway: &RemoteGatewayDetails,
registration: OffsetDateTime,
) -> Self {
Self {
version: config.client.version.clone(),
id: config.client.id.clone(),
identity_key: address.identity().to_base58_string(),
encryption_key: address.encryption_key().to_base58_string(),
gateway_id: gateway.gateway_id.clone(),
gateway_listener: gateway.gateway_listener.clone(),
gateway_id: gateway.gateway_id.to_base58_string(),
gateway_listener: gateway.gateway_listener.to_string(),
gateway_registration: registration,
address,
}
}
@@ -326,6 +351,7 @@ impl Display for InitResults {
writeln!(f, "Identity key: {}", self.identity_key)?;
writeln!(f, "Encryption: {}", self.encryption_key)?;
writeln!(f, "Gateway ID: {}", self.gateway_id)?;
write!(f, "Gateway: {}", self.gateway_listener)
writeln!(f, "Gateway: {}", self.gateway_listener)?;
write!(f, "Registered at: {}", self.gateway_registration)
}
}
+5 -1
View File
@@ -1,6 +1,10 @@
use std::future::Future;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(all(
not(target_arch = "wasm32"),
feature = "fs-surb-storage",
feature = "fs-gateways-storage"
))]
pub mod cli_helpers;
pub mod client;
pub mod config;
@@ -0,0 +1,31 @@
[package]
name = "nym-client-core-surb-storage"
version = "0.1.0"
edition = "2021"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait.workspace = true
dashmap.workspace = true
log.workspace = true
thiserror.workspace = true
time.workspace = true
nym-crypto = { path = "../../crypto", optional = true, default-features = false }
nym-sphinx = { path = "../../nymsphinx" }
nym-task = { path = "../../task" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
workspace = true
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
optional = true
[build-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
[features]
fs-surb-storage = ["sqlx", "nym-crypto", "nym-crypto/hashing"]

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