Compare commits

..

95 Commits

Author SHA1 Message Date
Simon Wicky 005b67ef0f squash 2026-02-13 10:57:42 +01:00
benedettadavico 2f98912778 fix nym-connect version 2023-06-20 13:56:38 +02:00
benedettadavico 9fbab5aaad updating versions for release v1.1.22 2023-06-20 10:24:54 +02:00
benedettadavico 2032b3bdae updating versions for release v1.1.22 2023-06-20 09:51:28 +02:00
Jon Häggblad 3748ab77a1 Statically link openssl (#3504)
* Statically link openssl

* Fix wasm client build

* Enable openssl for nyxd-client feature
2023-06-16 11:27:25 +02:00
pierre 9cbb3dfa0e copy change 2023-06-16 11:19:44 +02:00
Pierre Dommerc 4071d30f3c feat(nc-desktop): add error reporting and monitoring setting (#3553) 2023-06-16 11:09:29 +02:00
Tommy Verrall dd33052f0e Merge pull request #3520 from nymtech/bugfix/incorrect-client-versions
bugfix #3434
2023-06-14 17:23:11 +01:00
Jędrzej Stuczyński a76a51e823 fixed config version of binaries depending on socks5 core configs 2023-06-14 17:16:07 +01:00
Jon Häggblad b31d3c003f Cargo.lock 2023-06-14 15:04:37 +02:00
Jon Häggblad 21e11c9221 Merge remote-tracking branch 'origin/master' into release/v1.1.22 2023-06-14 14:55:30 +02:00
Jon Häggblad c9dcde0ee0 getrandom on workspace level 2023-06-14 13:26:09 +02:00
Jon Häggblad 3fcc16090a Update nym-outfox Cargo.toml in prep for publish to crates.io 2023-06-14 13:15:40 +02:00
farbanas 9288f71c5f Merge branch 'master' into develop 2023-06-13 11:16:17 +02:00
farbanas 79f5983c76 updating lock files 2023-06-13 11:04:36 +02:00
farbanas 335453b63c update versions and changelog for release v1.1.21 2023-06-13 10:54:01 +02:00
Jędrzej Stuczyński 4292a55614 explicitly setting up fresh gateway in native socks5 (#3547) 2023-06-12 17:01:05 +01:00
mx f97f0475e9 Merge pull request #3545 from nymtech/feature/docs-1-1-21
version bump for variables
2023-06-12 12:04:16 +00:00
mx 67a945a15f version bump for variables 2023-06-12 13:45:49 +02:00
Tommy Verrall d1a28826d5 Merge pull request #3535 from nymtech/feature/socks5-message-ordering
Feature/socks5 message ordering
2023-06-12 11:47:35 +01:00
Tommy Verrall 8f026ab6c6 Update build-and-upload-binaries-ci.yml
temp use rust version 1.69.0 for ci build uploads, will need to switch and use: cosmwasm/rust-optimizer in the near future
2023-06-12 11:07:22 +01:00
Jędrzej Stuczyński bdcdcf7f8b clippy 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 182e147a86 fixed and updated related units tests 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 687b437ea0 cargo fmt 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński f2c5dbb696 deadcode 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński a8bf690c17 tests 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 1328ba35be removed original Ordered sender 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 8b046d4139 split up functions a bit more 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński f4cd372808 simplified keeping track of message sequence 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 7228331db6 removed OrderedMessage 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 45f3f3ec01 cleanup 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński 5f9e54c83c buggy, but backwards compatible, explicit sequencing 2023-06-12 10:20:13 +01:00
Jędrzej Stuczyński f8c2f90502 initial work on putting data sequence explicitly inside socks5 request 2023-06-12 10:20:13 +01:00
Jon Häggblad 96e8bdfea4 Reduce number of surbs in nym-nr-query ping (#3540) 2023-06-12 09:53:47 +02:00
Jon Häggblad 0fa1961a04 nym-nr-query CLI tool for query network-requester (#3538)
* Initial version

* Add open_proxy request

* Start adding cli commands

* use commands

* Common response type

* json output

* Tidy

* Remove the All case

* Remove empty file

* Remove todo

* Add ping command

* Ping 4 times by default

* Logic for controlling number of loops

* Some print tweaks
2023-06-12 09:30:02 +02:00
pierre 88002969b7 ci(nyms5-android): add debug apk build 2023-06-09 22:46:04 +02:00
pierre b90dedf3ae build(android): fix repro build wip 2023-06-09 17:11:15 +02:00
pierre 582cb32223 build(android): fix repro build wip 2023-06-09 16:34:50 +02:00
Jędrzej Stuczyński 31b7921c68 bugfix #3434 2023-06-09 15:12:56 +01:00
Jędrzej Stuczyński 42a43a3709 Feature/extract gateway config (#3517)
* wip client core

* hashing shared key in persisted details

* native client using on-disk gateway details

* ibid for socks5

* ibid for NR

* nym sdk

* non-wasm fixes

* wasm

* missed cargo fmt

* fixed nym-connect build

* changed serialization of the key hash to be more human readable

* allowing some dead code

* fixed gateway details deserializtion

* removed needless borrow in wasm client

* removed deadcode

* exhaustive match on GatewaySetup after having loaded the keys
2023-06-09 15:12:29 +01:00
Jędrzej Stuczyński c5ad4006ae simplified construction of 'BaseClient' (#3513) 2023-06-09 15:11:45 +01:00
Tommy Verrall 71fb6a1ba1 Merge pull request #3516 from nymtech/feature/explorer-sp-extra-detail
Explorer - Extra detail for Service Providers
2023-06-09 10:01:59 +01:00
Jon Häggblad 68a37bc692 Merge remote-tracking branch 'origin/release/v1.1.21' into develop 2023-06-09 09:54:53 +02:00
Jon Häggblad 542fd92a46 Allow query network-requestor for open_proxy (#3531)
* wip: playing around

* WIP

* wip

* Add query request

* Create new example

* Restore control_requests example

* Remove ControlRequest::OpenProxy

* Remove Socks5RequestContent::OpenProxy

* Remove bunch of debug logging

* Remove more old leftovers

* Another few lines of leftovers to remove

* Disable logging in example

* Return error if incoming request if future version

* Serialization unit tests

* Network requester in example

* Cargo.lock
2023-06-09 09:54:21 +02:00
pierre 527c71d3df fix(nyms5-android): clean code 2023-06-08 18:45:02 +02:00
pierre 7ee47bdb4c fix(nyms5-android): fix notif channel creation on android prior to 12 2023-06-08 18:44:18 +02:00
pierre 9fa36b6393 add a log on channel creation 2023-06-08 11:06:14 +02:00
pierre 2b40b96aa1 wip 2023-06-08 11:06:14 +02:00
Jędrzej Stuczyński 9a68702d4d Feature/config refactor (#3498)
* revamping mixnode connfig

* wip

* native client config revamping

* wip

* building socks5

* using const for mixnnode config template

* compiling updated gateway

* nym-api

* nym-sdk

* everything compiling once more

but definitely not compatible with CI and older versions (yet)

* creating full directory structure on init

* renamed paths to storage_paths and fixed mixnode template

* mixnode config migration

* gateway config migration

* nym-api config migration

* native client config migration

* socks5 client config migration

* NR config migration

* removed deprecations (that will be resolved in the following PRs) + fixed clippy

* nym-connect clippy

* nym-connect config updates

* outfox fixes

* defined socks5 lib config

* clippy

* fixed wasm client build

* removed explicit packet_type argument when starting base client

it's known implicitly from the previously passed config struct

* Empty commit

* fixed re-using gateway information when client configs are re-initialised

* fixed borrowing id value in nym-connect

* post-rebase fixes

* updated 'old_config' versions

---------

Co-authored-by: Tommy Verrall <tommy@nymtech.net>
2023-06-07 17:06:35 +01:00
Drazen Urch bc5198768e Push traces to jaeger (#3522)
* Push traces to jaeger

* Remove default feature

* Update toolchain
2023-06-07 17:32:26 +02:00
Jon Häggblad 892653cd96 Remove gateway from detailed service provider type 2023-06-07 14:09:13 +02:00
Jon Häggblad a7471ef324 Replace fold with collect 2023-06-07 14:06:12 +02:00
fmtabbara 403141c1f5 add tooltip to sp routing score 2023-06-07 12:22:59 +01:00
fmtabbara d8c82bf6d0 use default sort order 2023-06-07 12:05:32 +01:00
fmtabbara dd86ba36dd use default success ping time - 120 mins 2023-06-07 11:56:44 +01:00
Tommy Verrall 42acbfe806 Update build-and-upload-binaries-ci.yml
temp use rust version 1.69.0 for ci build uploads, will need to switch and use: cosmwasm/rust-optimizer in the near future
2023-06-07 11:24:53 +02:00
farbanas 7c55483585 Merge branch 'master' into develop 2023-06-07 10:43:11 +02:00
fmtabbara 0320220219 refactor 2023-06-06 22:56:13 +01:00
fmtabbara e32ee2ccf3 add service-type and routing-scores to sps + allow sorting on routing scores 2023-06-06 22:45:09 +01:00
fmtabbara b8ca1762c2 allow sp routing score to be undefined 2023-06-06 22:45:07 +01:00
pierre d1e9fcf03a make routing score optional 2023-06-06 16:44:17 +02:00
pierre 303a774378 add some logs 2023-06-06 15:25:29 +02:00
pierre c9ca71f47b wip 2023-06-06 14:50:25 +02:00
pierre 92faf1e3d5 wip 2023-06-06 14:22:52 +02:00
fmtabbara e509989ac3 wip 2023-06-06 10:04:50 +01:00
farbanas c04cc9a4cf Merge branch 'release/v1.1.20' 2023-06-06 10:54:17 +02:00
farbanas 17258d1445 updated locks and versions bumped 2023-06-06 10:35:42 +02:00
farbanas 8f3d7606f5 bump crate versions 2023-06-06 10:07:06 +02:00
farbanas fd97f0e8ca update changelogs and version for release 2023-06-06 10:00:11 +02:00
Jędrzej Stuczyński d4ce1635a8 Fixed incorrect assertion when sending replies (#3515) 2023-06-05 14:36:44 +01:00
Jędrzej Stuczyński 2bc564ad01 updating managed keys after gateway registration (#3514) 2023-06-05 14:09:28 +01:00
Tommy Verrall 5910bcbc02 Update build-and-upload-binaries-ci.yml
add the service provider and name service contract to build output
2023-06-05 14:49:49 +02:00
Tommy Verrall 8c63fe9d0d Merge pull request #3507 from nymtech/jon/feat/reduce-shutdown-timeout-in-socks5
Reduce SHUTDOWN_TIMEOUT to 3 sec
2023-06-05 10:30:34 +01:00
Pierre Dommerc 6e5a1973da fix(wallet): fix bonding data refresh (#3499) 2023-06-05 11:07:23 +02:00
Pierre Dommerc 1aa11887aa fix(wallet): fix bonding data refresh (#3499) 2023-06-05 11:05:25 +02:00
Jon Häggblad 07740cbf08 Reduce SHUTDOWN_TIMEOUT to 3 sec
Can't think of a scenario where we don't want to close one at the same
time as the other, but let's be conservative and keep it a very low
number for now.
2023-06-05 10:42:38 +02:00
Jon Häggblad 87cb8a6b20 Sign when announcing service providers to the directory contract (#3459)
* create_payload and call from nym-cli

* Remove some commented out code

* wip

* Service announce now compiles

* Fix other compilation issues

* Move ServiceDetails into Service

* Move service_id inside Service type

* wip: start sorting out tests

* wip: sorting out testing

* wip: first announce test now works

* wip: more work on announce test

* Move nonce

* Add check for nonce

* Extract out some helpers to separate files

* reenable state::services tests

* wip: start going through the integration tests

* All integration tests reenabled

* Remove some unused stuff

* Iterate on integration tests

* More iteration on test setup

* Rename to test_setup.rs

* Add more tests specific to signing

* Tweak

* Another nonce test and reorg

* Rename to announce.rs and delete.rs

* Tidy

* Make some inner modules private

* Use IdentityKey alias

* Update nym-api contract cache

* Fix that nym-cli was asking for signing nonce from wrong contract

* Add sign comment to network-requester

* Uploaded updated service provider contract to qwerty

* Allow large enum variant

* lock files

* Remove dbg

* Move error.rs to service-provider common

* Update code for moving errors.rs to common crate

* Rename to SpContractError

* constants module not pub

* lock file

* rustfmt

* Move IdentityKey type to contract-common

* clippy
2023-06-05 10:32:58 +02:00
Jon Häggblad 2977b8f25f Fix clippy for 1.70 (#3505) 2023-06-05 08:54:18 +01:00
mx 019b3299f2 Merge pull request #3435 from Pawnflake/release/v1.1.19
mixnode documentation update
2023-06-02 09:40:50 +00:00
mx d684957423 Update documentation/docs/src/nodes/mix-node-setup.md
Co-authored-by: ️2FakTor️ <twofaktor@protonmail.com>
2023-06-02 09:20:56 +00:00
mx cb4eda4c62 Update documentation/docs/src/nodes/mix-node-setup.md
Co-authored-by: ️2FakTor️ <twofaktor@protonmail.com>
2023-06-02 09:20:42 +00:00
mx ac5f380ee2 Merge pull request #3485 from nymtech/feature/docs-1-1-20
removing hardcoded version numbers
2023-06-02 09:20:10 +00:00
mx 4e278ca07d reintroduced hardcoding for links for moment 2023-06-02 10:52:28 +02:00
mx cd6a725875 Merge pull request #3493 from twofaktor/patch-1
[BUG] network requester documentation update
2023-06-02 08:42:20 +00:00
⚡️2FakTor⚡️ d5514a060c Update network-requester-setup.md 2023-05-31 15:22:11 +02:00
mx 958b6d37ee Merge pull request #3481 from twofaktor/patch-1
[BUG] network requester documentation update
2023-05-30 14:40:33 +00:00
mx 5e36bb014c removing hardcoded versoin numbers 2023-05-30 16:10:15 +02:00
⚡️2FakTor⚡️ 8d821881ae Update network-requester-setup.md 2023-05-30 14:48:51 +02:00
mx fca9761145 Merge pull request #3141 from nymtech/chore/update-community-links-in-readme
updated readme with new developer chat links + new docs links
2023-05-30 12:05:14 +00:00
mx 11481e4d13 Merge branch 'release/v1.1.20' into chore/update-community-links-in-readme 2023-05-30 12:04:45 +00:00
Pawnflake c8b82a9553 removed comment http and https 2023-05-20 07:23:13 +08:00
mx 00c2f5359c Merge pull request #3428 from nymtech/feature/updated-sandbox-docs
added sandbox sync docs
2023-05-16 14:53:51 +00:00
mx 1a4e0f4e08 added sandbox sync docs 2023-05-16 16:44:39 +02:00
Pawnflake 69230a10cb Added ufw restart after adding ports and removed port 80 and 443 from the command 2023-05-16 10:10:41 +08:00
mx 3a0c8f3f4e updated readme with new developer chat links + new docs links 2023-03-03 13:32:44 +01:00
239 changed files with 8161 additions and 3613 deletions
@@ -107,6 +107,8 @@ jobs:
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR
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
- name: Deploy branch to CI www
continue-on-error: true
+13 -5
View File
@@ -49,7 +49,7 @@ jobs:
"build-tools;$SDK_BUILDTOOLS_VERSION"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.70.0
- name: Install rust android targets
run: |
@@ -60,6 +60,7 @@ jobs:
working-directory: sdk/lib/socks5-listener/
env:
RELEASE: true
RUSTFLAGS: "-C link-args=-Wl,--hash-style=gnu"
# build for arm64 and x86_64
run: ./build-android.sh aarch64 x86_64
@@ -68,19 +69,24 @@ jobs:
env:
ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }}
# build for arm64 and x86_64
run: ./gradlew :app:assembleArch64Release
run: |
./gradlew :app:assembleArch64Debug
./gradlew :app:assembleArch64Release
- name: Prepare APKs
run: |
mkdir apk
mv nym-connect/native/android/app/build/outputs/apk/arch64/debug/app-arch64-debug.apk \
apk/nyms5-arch64-debug.apk
mv nym-connect/native/android/app/build/outputs/apk/arch64/release/app-arch64-release-unsigned.apk \
apk/nyms5-arch64-release.apk
- name: Upload APKs
uses: actions/upload-artifact@v3
with:
name: nyms5-apk-arch64-release
name: nyms5-apk-arch64
path: |
apk/nyms5-arch64-debug.apk
apk/nyms5-arch64-release.apk
gh-release:
@@ -93,10 +99,12 @@ jobs:
- name: Download binary artifact
uses: actions/download-artifact@v3
with:
name: nyms5-apk-arch64-release
name: nyms5-apk-arch64
path: apk
- name: Release
uses: softprops/action-gh-release@v1
with:
files: apk/nyms5-arch64-release.apk
files: |
apk/nyms5-arch64-debug.apk
apk/nyms5-arch64-release.apk
+63
View File
@@ -4,6 +4,69 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.22] (2023-06-20)
- CLI tool for querying network-requesters ([#3539])
- Statically link OpenSSL ([#3510])
- NymConnect - add sentry.io reporting ([#3421])
- init command does not change version number in config.toml ([#3336])
- [Bug] Config version does not correspond to binary version ([#3434])
[#3539]: https://github.com/nymtech/nym/issues/3539
[#3510]: https://github.com/nymtech/nym/issues/3510
[#3421]: https://github.com/nymtech/nym/issues/3421
[#3336]: https://github.com/nymtech/nym/issues/3336
[#3434]: https://github.com/nymtech/nym/issues/3434
## [v1.1.21] (2023-06-13)
- mixFetch: Change socks5 `SendRequest` to include OrderedMessage index as a field rather than making it serialized inside the `data` field
([#3534])
- Explorer - add more data columns to the Service Provider section: ([#3474])
- network-requester: support report if they run an open proxy using `ControlRequest` API ([#3461])
- Refactor client configs (London discussion) ([#3444])
- Increase `DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE` to 2000 to improve reliability ([#3433])
- socks5: sender waits for lanes to clear even though the connection is closed ([#3366])
- version bump for variables ([#3545])
[#3534]: https://github.com/nymtech/nym/issues/3534
[#3474]: https://github.com/nymtech/nym/issues/3474
[#3461]: https://github.com/nymtech/nym/issues/3461
[#3444]: https://github.com/nymtech/nym/issues/3444
[#3433]: https://github.com/nymtech/nym/issues/3433
[#3366]: https://github.com/nymtech/nym/issues/3366
[#3545]: https://github.com/nymtech/nym/pull/3545
## [v1.1.20] (2023-06-06)
- Explorer - Fix SP supported apps list ([#3458])
- Investigate if we need to lower `SHUTDOWN_TIMEOUT` in socks5 to zero (or almost zero) ([#3438])
- Explorer - show all gateways in the default view regardless of the version number ([#3427])
- service-provider-directory: add signature check when announcing ([#3360])
- Support functionality for nym-name-service (nym-api, nym-cli, etc) ([#3355])
- Edit the nym-network-requester to support the enabled-credentials-mode flag ([#3101])
- [BUG] network requester documentation update ([#3493])
- removing hardcoded version numbers ([#3485])
- [BUG] network requester documentation update ([#3481])
- [BUG] network requester documentation update ([#3469])
- Sign when announcing service providers to the directory contract ([#3459])
- mixnode documentation update ([#3435])
- updated readme with new developer chat links + new docs links ([#3141])
[#3458]: https://github.com/nymtech/nym/issues/3458
[#3438]: https://github.com/nymtech/nym/issues/3438
[#3427]: https://github.com/nymtech/nym/issues/3427
[#3360]: https://github.com/nymtech/nym/issues/3360
[#3355]: https://github.com/nymtech/nym/issues/3355
[#3101]: https://github.com/nymtech/nym/issues/3101
[#3493]: https://github.com/nymtech/nym/pull/3493
[#3485]: https://github.com/nymtech/nym/pull/3485
[#3481]: https://github.com/nymtech/nym/pull/3481
[#3469]: https://github.com/nymtech/nym/pull/3469
[#3459]: https://github.com/nymtech/nym/pull/3459
[#3435]: https://github.com/nymtech/nym/pull/3435
[#3141]: https://github.com/nymtech/nym/pull/3141
## [v1.1.19] (2023-05-16)
- nym-name-service endpoint in nym-api ([#3403])
Generated
+336 -46
View File
@@ -68,7 +68,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.9",
"getrandom 0.2.10",
"once_cell",
"version_check",
]
@@ -204,6 +204,17 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "async-channel"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-file-watcher"
version = "0.1.0"
@@ -362,9 +373,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
[[package]]
name = "base64"
version = "0.21.0"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "base64ct"
@@ -378,6 +389,15 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bip32"
version = "0.3.0"
@@ -561,6 +581,12 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "castaway"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
[[package]]
name = "cc"
version = "1.0.79"
@@ -810,6 +836,15 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "concurrent-queue"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "console-api"
version = "0.5.0"
@@ -911,7 +946,7 @@ dependencies = [
"cosmos-sdk-proto",
"ecdsa",
"eyre",
"getrandom 0.2.9",
"getrandom 0.2.10",
"k256",
"prost 0.10.4",
"prost-types 0.10.1",
@@ -1229,6 +1264,36 @@ dependencies = [
"rustc_version 0.2.3",
]
[[package]]
name = "curl"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22"
dependencies = [
"curl-sys",
"libc",
"openssl-probe",
"openssl-sys",
"schannel",
"socket2",
"winapi",
]
[[package]]
name = "curl-sys"
version = "0.4.63+curl-8.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc"
dependencies = [
"cc",
"libc",
"libz-sys",
"openssl-sys",
"pkg-config",
"vcpkg",
"winapi",
]
[[package]]
name = "curve25519-dalek"
version = "3.2.0"
@@ -1243,6 +1308,20 @@ dependencies = [
"zeroize",
]
[[package]]
name = "cw-controllers"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"cw-utils",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw-storage-plus"
version = "0.13.4"
@@ -1653,7 +1732,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.19"
version = "1.1.22"
dependencies = [
"chrono",
"clap 4.2.7",
@@ -1927,6 +2006,21 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-macro"
version = "0.3.28"
@@ -2023,9 +2117,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"js-sys",
@@ -2630,6 +2724,12 @@ dependencies = [
"web-sys",
]
[[package]]
name = "integer-encoding"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02"
[[package]]
name = "inventory"
version = "0.1.11"
@@ -2699,6 +2799,31 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "isahc"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9"
dependencies = [
"async-channel",
"castaway",
"crossbeam-utils",
"curl",
"curl-sys",
"event-listener",
"futures-lite",
"http",
"log",
"once_cell",
"polling",
"slab",
"sluice",
"tracing",
"tracing-futures",
"url",
"waker-fn",
]
[[package]]
name = "isocountry"
version = "0.3.2"
@@ -3197,7 +3322,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.20"
version = "1.1.23"
dependencies = [
"anyhow",
"async-trait",
@@ -3298,18 +3423,20 @@ dependencies = [
[[package]]
name = "nym-bin-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"atty",
"clap 4.2.7",
"clap_complete",
"clap_complete_fig",
"log",
"opentelemetry",
"opentelemetry-jaeger",
"pretty_env_logger",
"semver 0.11.0",
"serde",
"serde_json",
"tracing-appender",
"tracing-opentelemetry",
"tracing-subscriber",
"tracing-tree",
"vergen",
@@ -3332,7 +3459,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.19"
version = "1.1.22"
dependencies = [
"anyhow",
"base64 0.13.1",
@@ -3381,6 +3508,7 @@ dependencies = [
"nym-name-service-common",
"nym-network-defaults",
"nym-service-provider-directory-common",
"nym-sphinx",
"nym-validator-client",
"nym-vesting-contract-common",
"rand 0.6.5",
@@ -3395,7 +3523,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.19"
version = "1.1.22"
dependencies = [
"clap 4.2.7",
"dirs 4.0.0",
@@ -3431,9 +3559,10 @@ dependencies = [
[[package]]
name = "nym-client-core"
version = "1.1.14"
version = "1.1.15"
dependencies = [
"async-trait",
"base64 0.21.2",
"dashmap 5.4.0",
"dirs 4.0.0",
"futures",
@@ -3456,6 +3585,7 @@ dependencies = [
"rand 0.7.3",
"serde",
"serde_json",
"sha2 0.10.6",
"sqlx 0.6.3",
"tap",
"tempfile",
@@ -3492,7 +3622,7 @@ dependencies = [
"digest 0.9.0",
"doc-comment",
"ff 0.11.1",
"getrandom 0.2.9",
"getrandom 0.2.10",
"group 0.11.0",
"itertools",
"nym-dkg",
@@ -3553,7 +3683,7 @@ dependencies = [
[[package]]
name = "nym-contracts-common"
version = "0.4.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -3610,7 +3740,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"aes 0.8.2",
"blake3",
@@ -3667,7 +3797,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.1.19"
version = "1.1.22"
dependencies = [
"anyhow",
"async-trait",
@@ -3720,7 +3850,7 @@ name = "nym-gateway-client"
version = "0.1.0"
dependencies = [
"futures",
"getrandom 0.2.9",
"getrandom 0.2.10",
"log",
"nym-bandwidth-controller",
"nym-coconut-interface",
@@ -3799,7 +3929,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -3818,7 +3948,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.20"
version = "1.1.23"
dependencies = [
"anyhow",
"bs58",
@@ -3928,10 +4058,12 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.19"
version = "1.1.22"
dependencies = [
"anyhow",
"async-file-watcher",
"async-trait",
"bs58",
"clap 4.2.7",
"dirs 4.0.0",
"futures",
@@ -3954,11 +4086,13 @@ dependencies = [
"nym-sphinx",
"nym-statistics-common",
"nym-task",
"nym-types",
"pretty_env_logger",
"publicsuffix",
"rand 0.7.3",
"reqwest",
"serde",
"serde_json",
"sqlx 0.6.3",
"tap",
"tempfile",
@@ -3970,7 +4104,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "1.1.19"
version = "1.1.22"
dependencies = [
"dirs 4.0.0",
"log",
@@ -4016,6 +4150,22 @@ dependencies = [
"wasm-timer",
]
[[package]]
name = "nym-nr-query"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 4.2.7",
"log",
"nym-bin-common",
"nym-network-defaults",
"nym-sdk",
"nym-service-providers-common",
"nym-socks5-requests",
"serde",
"tokio",
]
[[package]]
name = "nym-ordered-buffer"
version = "0.1.0"
@@ -4034,7 +4184,7 @@ dependencies = [
"criterion",
"curve25519-dalek",
"fastrand",
"getrandom 0.2.9",
"getrandom 0.2.10",
"log",
"rand 0.7.3",
"rayon",
@@ -4045,7 +4195,7 @@ dependencies = [
[[package]]
name = "nym-pemstore"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"pem",
]
@@ -4086,8 +4236,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -4099,6 +4253,7 @@ dependencies = [
"log",
"nym-bin-common",
"nym-sdk",
"nym-socks5-requests",
"nym-sphinx-anonymous-replies",
"serde",
"serde_json",
@@ -4108,7 +4263,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.19"
version = "1.1.22"
dependencies = [
"clap 4.2.7",
"lazy_static",
@@ -4204,10 +4359,13 @@ dependencies = [
name = "nym-socks5-requests"
version = "0.1.0"
dependencies = [
"bincode",
"log",
"nym-service-providers-common",
"nym-sphinx-addressing",
"serde",
"serde_json",
"tap",
"thiserror",
]
@@ -4379,7 +4537,7 @@ dependencies = [
"aes-gcm",
"argon2",
"generic-array 0.14.7",
"getrandom 0.2.9",
"getrandom 0.2.10",
"rand 0.8.5",
"serde",
"serde_json",
@@ -4473,6 +4631,7 @@ dependencies = [
"nym-service-provider-directory-common",
"nym-vesting-contract",
"nym-vesting-contract-common",
"openssl",
"prost 0.10.4",
"reqwest",
"serde",
@@ -4505,7 +4664,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
@@ -4635,12 +4794,55 @@ dependencies = [
"opentelemetry_sdk",
]
[[package]]
name = "opentelemetry-http"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a819b71d6530c4297b49b3cae2939ab3a8cc1b9f382826a1bc29dd0ca3864906"
dependencies = [
"async-trait",
"bytes",
"http",
"isahc",
"opentelemetry_api",
]
[[package]]
name = "opentelemetry-jaeger"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08e028dc9f4f304e9320ce38c80e7cf74067415b1ad5a8750a38bae54a4d450d"
dependencies = [
"async-trait",
"futures",
"futures-executor",
"http",
"isahc",
"once_cell",
"opentelemetry",
"opentelemetry-http",
"opentelemetry-semantic-conventions",
"thiserror",
"thrift",
"tokio",
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24e33428e6bf08c6f7fcea4ddb8e358fab0fe48ab877a87c70c6ebe20f673ce5"
dependencies = [
"opentelemetry",
]
[[package]]
name = "opentelemetry_api"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed41783a5bf567688eb38372f2b7a8530f5a607a4b49d38dd7573236c23ca7e2"
dependencies = [
"fnv",
"futures-channel",
"futures-util",
"indexmap",
@@ -4658,6 +4860,8 @@ checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1"
dependencies = [
"async-trait",
"crossbeam-channel",
"dashmap 5.4.0",
"fnv",
"futures-channel",
"futures-executor",
"futures-util",
@@ -4666,6 +4870,8 @@ dependencies = [
"percent-encoding",
"rand 0.8.5",
"thiserror",
"tokio",
"tokio-stream",
]
[[package]]
@@ -4674,6 +4880,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ordered-float"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
dependencies = [
"num-traits",
]
[[package]]
name = "os_str_bytes"
version = "6.5.0"
@@ -4704,6 +4919,12 @@ dependencies = [
"group 0.11.0",
]
[[package]]
name = "parking"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
[[package]]
name = "parking_lot"
version = "0.11.2"
@@ -4966,6 +5187,22 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "polling"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg 1.1.0",
"bitflags",
"cfg-if",
"concurrent-queue",
"libc",
"log",
"pin-project-lite",
"windows-sys 0.48.0",
]
[[package]]
name = "poly1305"
version = "0.8.0"
@@ -5280,7 +5517,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.9",
"getrandom 0.2.10",
]
[[package]]
@@ -5437,7 +5674,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom 0.2.9",
"getrandom 0.2.10",
"redox_syscall 0.2.16",
"thiserror",
]
@@ -5500,7 +5737,7 @@ version = "0.11.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
dependencies = [
"base64 0.21.0",
"base64 0.21.2",
"bytes",
"encoding_rs",
"futures-core",
@@ -5770,7 +6007,7 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
dependencies = [
"base64 0.21.0",
"base64 0.21.2",
]
[[package]]
@@ -6197,6 +6434,17 @@ dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "sluice"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5"
dependencies = [
"async-channel",
"futures-core",
"futures-io",
]
[[package]]
name = "smallvec"
version = "1.10.0"
@@ -6716,7 +6964,7 @@ dependencies = [
"bytes",
"flex-error",
"futures",
"getrandom 0.2.9",
"getrandom 0.2.10",
"http",
"hyper",
"hyper-proxy",
@@ -6784,6 +7032,28 @@ dependencies = [
"once_cell",
]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]]
name = "thrift"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09"
dependencies = [
"byteorder",
"integer-encoding",
"log",
"ordered-float",
"threadpool",
]
[[package]]
name = "time"
version = "0.1.45"
@@ -7049,7 +7319,7 @@ checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a"
dependencies = [
"async-trait",
"axum",
"base64 0.21.0",
"base64 0.21.2",
"bytes",
"futures-core",
"futures-util",
@@ -7108,22 +7378,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-appender"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
dependencies = [
"crossbeam-channel",
"time 0.3.21",
"tracing-subscriber",
]
[[package]]
name = "tracing-attributes"
version = "0.1.24"
@@ -7145,6 +7405,16 @@ dependencies = [
"valuable",
]
[[package]]
name = "tracing-futures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
dependencies = [
"pin-project",
"tracing",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
@@ -7156,6 +7426,20 @@ dependencies = [
"tracing-core",
]
[[package]]
name = "tracing-opentelemetry"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00a39dcf9bfc1742fa4d6215253b33a6e474be78275884c216fc2a06267b3600"
dependencies = [
"once_cell",
"opentelemetry",
"tracing",
"tracing-core",
"tracing-log",
"tracing-subscriber",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.17"
@@ -7450,7 +7734,7 @@ version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
dependencies = [
"getrandom 0.2.9",
"getrandom 0.2.10",
"wasm-bindgen",
]
@@ -7489,6 +7773,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "walkdir"
version = "2.3.3"
@@ -7612,7 +7902,7 @@ name = "wasm-utils"
version = "0.1.0"
dependencies = [
"futures",
"getrandom 0.2.9",
"getrandom 0.2.10",
"indexed_db_futures",
"js-sys",
"nym-store-cipher",
+5 -2
View File
@@ -87,6 +87,7 @@ members = [
"nym-api/nym-api-requests",
"nym-outfox",
"tools/nym-cli",
"tools/nym-nr-query",
"tools/ts-rs-cli"
]
@@ -112,22 +113,24 @@ edition = "2021"
license = "Apache-2.0"
[workspace.dependencies]
async-trait = "0.1.64"
anyhow = "1.0.71"
async-trait = "0.1.64"
bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0"
cosmwasm-derive = "=1.0.0"
cosmwasm-schema = "=1.0.0"
cosmwasm-std = "=1.0.0"
cosmwasm-storage = "=1.0.0"
cw-utils = "=0.13.4"
cw-controllers = "=0.13.4"
cw-storage-plus = "=0.13.4"
cw-utils = "=0.13.4"
cw2 = { version = "=0.13.4" }
cw3 = { version = "=0.13.4" }
cw3-fixed-multisig = { version = "=0.13.4" }
cw4 = { version = "=0.13.4" }
dotenvy = "0.15.6"
generic-array = "0.14.7"
getrandom = "0.2.10"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
+7 -3
View File
@@ -21,8 +21,8 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
### Building
Platform build instructions are available on [our docs site](https://nymtech.net/docs/binaries/building-nym.html).
Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/stable/nym-apps/wallet#for-developers).
Platform build instructions are available on [our docs site](https://nymtech.net/docs/binaries/build-nym.html).
Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/wallet/desktop-wallet.html).
### Developing
@@ -32,7 +32,11 @@ For Typescript components, please see [ts-packages](./ts-packages).
### Developer chat
You can chat with us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes place in the **#dev** channel. Node operators should be in the **#node-operators** channel.
> We used to use Keybase for developer chats, but we have since migrated to Matrix and Discord. We no longer check the old **nymtech.friends** Keybase team.
You can chat to us in two places:
* The #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat)
* The various developer channels on [Discord](https://discord.gg/nym)
### Rewards
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.19"
version = "1.1.22"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+16 -15
View File
@@ -20,7 +20,8 @@ pub use nym_client_core::config::Config as BaseClientConfig;
pub use nym_client_core::config::{DebugConfig, GatewayEndpointConfig};
pub mod old_config_v1_1_13;
pub mod old_config_v1_1_19;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
mod persistence;
mod template;
@@ -52,19 +53,6 @@ pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
.join(DEFAULT_DATA_DIR)
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub enum SocketType {
WebSocket,
None,
}
impl SocketType {
pub fn is_websocket(&self) -> bool {
matches!(self, SocketType::WebSocket)
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct Config {
#[serde(flatten)]
@@ -87,7 +75,7 @@ impl NymConfigTemplate for Config {
impl Config {
pub fn new<S: AsRef<str>>(id: S) -> Self {
Config {
base: BaseClientConfig::new(id.as_ref()),
base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")),
storage_paths: ClientPaths::new_default(default_data_directory(id.as_ref())),
logging: Default::default(),
socket: Default::default(),
@@ -187,6 +175,19 @@ impl Config {
// define_optional_set_inner!(Config, base, BaseClientConfig);
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub enum SocketType {
WebSocket,
None,
}
impl SocketType {
pub fn is_websocket(&self) -> bool {
matches!(self, SocketType::WebSocket)
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Socket {
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::old_config_v1_1_19::{ConfigV1_1_19, SocketV1_1_19};
use crate::client::config::old_config_v1_1_20::{ConfigV1_1_20, SocketV1_1_20};
use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use serde::{Deserialize, Serialize};
@@ -13,7 +13,7 @@ pub struct OldConfigV1_1_13 {
#[serde(flatten)]
pub base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
pub socket: SocketV1_1_19,
pub socket: SocketV1_1_20,
}
impl MigrationNymConfig for OldConfigV1_1_13 {
@@ -25,9 +25,9 @@ impl MigrationNymConfig for OldConfigV1_1_13 {
}
}
impl From<OldConfigV1_1_13> for ConfigV1_1_19 {
impl From<OldConfigV1_1_13> for ConfigV1_1_20 {
fn from(value: OldConfigV1_1_13) -> Self {
ConfigV1_1_19 {
ConfigV1_1_20 {
base: value.base.into(),
socket: value.socket,
}
@@ -1,13 +1,16 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::persistence::ClientPaths;
use crate::client::config::{Config, Socket, SocketType};
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::CommonClientPaths;
use nym_client_core::config::old_config_v1_1_19::ConfigV1_1_19 as BaseConfigV1_1_19;
use nym_client_core::config::{Client, Config as BaseConfig};
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::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,
};
use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use serde::{Deserialize, Serialize};
@@ -17,34 +20,34 @@ use std::path::PathBuf;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub enum SocketTypeV1_1_19 {
pub enum SocketTypeV1_1_20 {
WebSocket,
None,
}
impl From<SocketTypeV1_1_19> for SocketType {
fn from(value: SocketTypeV1_1_19) -> Self {
impl From<SocketTypeV1_1_20> for SocketTypeV1_1_20_2 {
fn from(value: SocketTypeV1_1_20) -> Self {
match value {
SocketTypeV1_1_19::WebSocket => SocketType::WebSocket,
SocketTypeV1_1_19::None => SocketType::None,
SocketTypeV1_1_20::WebSocket => SocketTypeV1_1_20_2::WebSocket,
SocketTypeV1_1_20::None => SocketTypeV1_1_20_2::None,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_19 {
pub struct ConfigV1_1_20 {
#[serde(flatten)]
pub base: BaseConfigV1_1_19<ConfigV1_1_19>,
pub base: BaseConfigV1_1_20<ConfigV1_1_20>,
pub socket: SocketV1_1_19,
pub socket: SocketV1_1_20,
}
impl From<ConfigV1_1_19> for Config {
fn from(value: ConfigV1_1_19) -> Self {
Config {
base: BaseConfig {
client: Client {
impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
fn from(value: ConfigV1_1_20) -> Self {
ConfigV1_1_20_2 {
base: BaseConfigV1_1_20_2 {
client: ClientV1_1_20_2 {
version: value.base.client.version,
id: value.base.client.id,
disabled_credentials_mode: value.base.client.disabled_credentials_mode,
@@ -55,8 +58,8 @@ impl From<ConfigV1_1_19> for Config {
debug: value.base.debug.into(),
},
socket: value.socket.into(),
storage_paths: ClientPaths {
common_paths: CommonClientPaths {
storage_paths: ClientPathsV1_1_20_2 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPaths {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
@@ -74,7 +77,7 @@ impl From<ConfigV1_1_19> for Config {
}
}
impl MigrationNymConfig for ConfigV1_1_19 {
impl MigrationNymConfig for ConfigV1_1_20 {
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
@@ -85,15 +88,15 @@ impl MigrationNymConfig for ConfigV1_1_19 {
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct SocketV1_1_19 {
socket_type: SocketTypeV1_1_19,
pub struct SocketV1_1_20 {
socket_type: SocketTypeV1_1_20,
host: IpAddr,
listening_port: u16,
}
impl From<SocketV1_1_19> for Socket {
fn from(value: SocketV1_1_19) -> Self {
Socket {
impl From<SocketV1_1_20> for SocketV1_1_20_2 {
fn from(value: SocketV1_1_20) -> Self {
SocketV1_1_20_2 {
socket_type: value.socket_type.into(),
host: value.host,
listening_port: value.listening_port,
@@ -101,10 +104,10 @@ impl From<SocketV1_1_19> for Socket {
}
}
impl Default for SocketV1_1_19 {
impl Default for SocketV1_1_20 {
fn default() -> Self {
SocketV1_1_19 {
socket_type: SocketTypeV1_1_19::WebSocket,
SocketV1_1_20 {
socket_type: SocketTypeV1_1_20::WebSocket,
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
}
@@ -0,0 +1,103 @@
// Copyright 2023 - 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 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::GatewayEndpointConfig;
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_20_2 {
#[serde(flatten)]
pub common_paths: CommonClientPathsV1_1_20_2,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct ConfigV1_1_20_2 {
#[serde(flatten)]
pub base: BaseConfigV1_1_20_2,
pub socket: SocketV1_1_20_2,
pub storage_paths: ClientPathsV1_1_20_2,
pub logging: LoggingSettings,
}
impl ConfigV1_1_20_2 {
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))
}
// in this upgrade, gateway endpoint configuration was moved out of the config file,
// so its returned to be stored elsewhere.
pub fn upgrade(self) -> (Config, GatewayEndpointConfig) {
let gateway_details = self.base.client.gateway_endpoint.clone().into();
let config = Config {
base: self.base.into(),
socket: self.socket.into(),
storage_paths: ClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default(),
},
logging: self.logging,
};
(config, gateway_details)
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub enum SocketTypeV1_1_20_2 {
WebSocket,
None,
}
impl From<SocketTypeV1_1_20_2> for SocketType {
fn from(value: SocketTypeV1_1_20_2) -> Self {
match value {
SocketTypeV1_1_20_2::WebSocket => SocketType::WebSocket,
SocketTypeV1_1_20_2::None => SocketType::None,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct SocketV1_1_20_2 {
pub socket_type: SocketTypeV1_1_20_2,
pub host: IpAddr,
pub listening_port: u16,
}
impl From<SocketV1_1_20_2> for Socket {
fn from(value: SocketV1_1_20_2) -> Self {
Socket {
socket_type: value.socket_type.into(),
host: value.host,
listening_port: value.listening_port,
}
}
}
impl Default for SocketV1_1_20_2 {
fn default() -> Self {
SocketV1_1_20_2 {
socket_type: SocketTypeV1_1_20_2::WebSocket,
host: IpAddr::V4(Ipv4Addr::LOCALHOST),
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
}
}
}
+3 -12
View File
@@ -64,18 +64,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 }}'
# DEPRECATED
[client.gateway_endpoint]
# ID of the gateway from which the client should be fetching messages.
gateway_id = '{{ client.gateway_endpoint.gateway_id }}'
# Address of the gateway owner to which the client should send messages.
gateway_owner = '{{ client.gateway_endpoint.gateway_owner }}'
# Address of the gateway listener to which all client requests should be sent.
gateway_listener = '{{ client.gateway_endpoint.gateway_listener }}'
# 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 }}'
##### socket config options #####
+14 -31
View File
@@ -6,18 +6,15 @@ use crate::error::ClientError;
use crate::websocket;
use futures::channel::mpsc;
use log::*;
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::base_client::non_wasm_helpers::create_bandwidth_controller;
use nym_client_core::client::base_client::non_wasm_helpers::default_query_dkg_client_from_config;
use nym_client_core::client::base_client::storage::OnDiskPersistent;
use nym_client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
BaseClientBuilder, ClientInput, ClientOutput, ClientState,
};
use nym_client_core::client::inbound_messages::InputMessage;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::client::received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver,
};
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
@@ -45,17 +42,6 @@ impl SocketClient {
SocketClient { config }
}
async fn create_bandwidth_controller(
config: &Config,
) -> BandwidthController<Client<QueryNyxdClient>, PersistentStorage> {
let storage = nym_credential_storage::initialise_persistent_storage(
&config.storage_paths.common_paths.credentials_database,
)
.await;
create_bandwidth_controller(&config.base, storage)
}
fn start_websocket_listener(
config: &Config,
client_input: ClientInput,
@@ -105,29 +91,26 @@ impl SocketClient {
res
}
fn key_store(&self) -> OnDiskKeys {
OnDiskKeys::new(self.config.storage_paths.common_paths.keys.clone())
async fn initialise_storage(&self) -> Result<OnDiskPersistent, ClientError> {
Ok(OnDiskPersistent::from_paths(
self.config.storage_paths.common_paths.clone(),
&self.config.base.debug,
)
.await?)
}
// TODO: see if this could also be shared with socks5 client / nym-sdk maybe
async fn create_base_client_builder(&self) -> Result<NativeClientBuilder, ClientError> {
// don't create bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.base.client.disabled_credentials_mode {
// don't create dkg client for the bandwidth controller if credentials are disabled
let dkg_query_client = if self.config.base.client.disabled_credentials_mode {
None
} else {
Some(Self::create_bandwidth_controller(&self.config).await)
Some(default_query_dkg_client_from_config(&self.config.base))
};
let base_client = BaseClientBuilder::new_from_base_config(
&self.config.base,
self.key_store(),
bandwidth_controller,
non_wasm_helpers::setup_fs_reply_surb_backend(
&self.config.storage_paths.common_paths.reply_surb_database,
&self.config.base.debug.reply_surbs,
)
.await?,
);
let storage = self.initialise_storage().await?;
let base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client);
Ok(base_client)
}
+18 -14
View File
@@ -12,7 +12,10 @@ use crate::{
};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use serde::Serialize;
@@ -105,9 +108,9 @@ pub struct InitResults {
}
impl InitResults {
fn new(config: &Config, address: &Recipient) -> Self {
fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
Self {
client_core: nym_client_core::init::InitResults::new(&config.base, address),
client_core: nym_client_core::init::InitResults::new(&config.base, address, gateway),
client_listening_port: config.socket.listening_port,
client_address: address.to_string(),
}
@@ -157,25 +160,29 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
// Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = args.gateway;
let gateway_setup = GatewaySetup::new_fresh(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(args.latency_based_selection),
);
// Load and potentially override config
let mut config = override_config(Config::new(id), OverrideConfig::from(args.clone()));
let config = override_config(Config::new(id), OverrideConfig::from(args.clone()));
// 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(config.storage_paths.common_paths.keys.clone());
let gateway = nym_client_core::init::setup_gateway_from_config::<_>(
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let init_details = nym_client_core::init::setup_gateway(
&gateway_setup,
&key_store,
&details_store,
register_gateway,
user_chosen_gateway_id,
&config.base,
args.latency_based_selection,
Some(&config.base.client.nym_api_urls),
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
config.base.set_gateway_endpoint(gateway);
let config_save_location = config.default_location();
config.save_to_default_location().tap_err(|_| {
log::error!("Failed to save the config file");
@@ -185,14 +192,11 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
config_save_location.display()
);
let address = nym_client_core::init::get_client_address_from_stored_ondisk_keys(
&config.storage_paths.common_paths.keys,
&config.base.client.gateway_endpoint,
)?;
let address = init_details.client_address()?;
eprintln!("Client configuration completed.\n");
let init_results = InitResults::new(&config, &address);
let init_results = InitResults::new(&config, &address, &init_details.gateway_details);
println!("{}", args.output.format(&init_results));
Ok(())
+77 -11
View File
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::client::config::old_config_v1_1_19::ConfigV1_1_19;
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::{BaseClientConfig, Config};
use crate::error::ClientError;
use clap::CommandFactory;
@@ -11,6 +12,12 @@ use lazy_static::lazy_static;
use log::{error, info};
use nym_bin_common::build_information::BinaryBuildInformation;
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_config::OptionalSet;
use std::error::Error;
use std::net::IpAddr;
@@ -109,6 +116,28 @@ 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, &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> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
@@ -121,40 +150,77 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
info!("It seems the client is using <= v1.1.13 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_19 = old_config.into();
let updated: Config = updated_step1.into();
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)?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_19_config(id: &str) -> Result<bool, ClientError> {
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.19 (which is incompatible with the current one, i.e. +1.1.20)
let Ok(old_config) = ConfigV1_1_19::load_from_file(id) else {
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20::load_from_file(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.19 config template.");
info!("It seems the client is using <= v1.1.20 config template.");
info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into();
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated, gateway_config) = updated_step1.upgrade();
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
Ok(true)
}
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
// 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.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)?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
let upgraded = try_upgrade_v1_1_13_config(id)?;
if !upgraded {
try_upgrade_v1_1_19_config(id)?;
if try_upgrade_v1_1_13_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_20_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_20_2_config(id)? {
return Ok(());
}
Ok(())
}
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() {
Err(ClientError::ConfigValidationFailure)
} else {
Ok(cfg)
};
}
// we couldn't load it - try upgrading it from older revisions
try_upgrade_config(id)?;
let config = match Config::read_from_default_path(id) {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.19"
version = "1.1.22"
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"
+23 -14
View File
@@ -11,7 +11,10 @@ use crate::{
};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::clients::Recipient;
use serde::Serialize;
@@ -107,9 +110,13 @@ pub struct InitResults {
}
impl InitResults {
fn new(config: &Config, address: &Recipient) -> Self {
fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> Self {
Self {
client_core: nym_client_core::init::InitResults::new(&config.core.base, address),
client_core: nym_client_core::init::InitResults::new(
&config.core.base,
address,
gateway,
),
socks5_listening_port: config.core.socks5.listening_port,
client_address: address.to_string(),
}
@@ -160,9 +167,13 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
// Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = args.gateway;
let gateway_setup = GatewaySetup::new_fresh(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(args.latency_based_selection),
);
// Load and potentially override config
let mut config = override_config(
let config = override_config(
Config::new(id, &provider_address.to_string()),
OverrideConfig::from(args.clone()),
);
@@ -170,18 +181,18 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
// 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(config.storage_paths.common_paths.keys.clone());
let gateway = nym_client_core::init::setup_gateway_from_config::<_>(
let details_store =
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
let init_details = nym_client_core::init::setup_gateway(
&gateway_setup,
&key_store,
&details_store,
register_gateway,
user_chosen_gateway_id,
&config.core.base,
args.latency_based_selection,
Some(&config.core.base.client.nym_api_urls),
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
config.core.base.set_gateway_endpoint(gateway);
// TODO: ask the service provider we specified for its interface version and set it in the config
let config_save_location = config.default_location();
@@ -193,11 +204,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
config_save_location.display()
);
let address = nym_client_core::init::get_client_address_from_stored_ondisk_keys(
&config.storage_paths.common_paths.keys,
&config.core.base.client.gateway_endpoint,
)?;
let init_results = InitResults::new(&config, &address);
let address = init_details.client_address()?;
let init_results = InitResults::new(&config, &address, &init_details.gateway_details);
println!("{}", args.output.format(&init_results));
Ok(())
+77 -11
View File
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::config::old_config_v1_1_19::ConfigV1_1_19;
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::{BaseClientConfig, Config};
use crate::error::Socks5ClientError;
use clap::CommandFactory;
@@ -11,6 +12,12 @@ use lazy_static::lazy_static;
use log::{error, info};
use nym_bin_common::build_information::BinaryBuildInformation;
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_config::OptionalSet;
use nym_sphinx::params::PacketType;
use std::error::Error;
@@ -116,6 +123,28 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
)
}
fn persist_gateway_details(
config: &Config,
details: GatewayEndpointConfig,
) -> Result<(), Socks5ClientError> {
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| {
Socks5ClientError::ClientCoreError(ClientCoreError::KeyStoreError {
source: Box::new(source),
})
})?;
let persisted_details = PersistedGatewayDetails::new(details, &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> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
@@ -128,40 +157,77 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
info!("It seems the client is using <= v1.1.13 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_19 = old_config.into();
let updated: Config = updated_step1.into();
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)?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_v1_1_19_config(id: &str) -> Result<bool, Socks5ClientError> {
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.19 (which is incompatible with the current one, i.e. +1.1.20)
let Ok(old_config) = ConfigV1_1_19::load_from_file(id) else {
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
let Ok(old_config) = ConfigV1_1_20::load_from_file(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.19 config template.");
info!("It seems the client is using <= v1.1.20 config template.");
info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into();
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated, gateway_config) = updated_step1.upgrade();
persist_gateway_details(&updated, gateway_config)?;
updated.save_to_default_location()?;
Ok(true)
}
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
// 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.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)?;
updated.save_to_default_location()?;
Ok(true)
}
fn try_upgrade_config(id: &str) -> Result<(), Socks5ClientError> {
let upgraded = try_upgrade_v1_1_13_config(id)?;
if !upgraded {
try_upgrade_v1_1_19_config(id)?;
if try_upgrade_v1_1_13_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_20_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_20_2_config(id)? {
return Ok(());
}
Ok(())
}
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() {
Err(Socks5ClientError::ConfigValidationFailure)
} else {
Ok(cfg)
};
}
// we couldn't load it - try upgrading it from older revisions
try_upgrade_config(id)?;
let config = match Config::read_from_default_path(id) {
+7 -2
View File
@@ -18,7 +18,8 @@ pub use nym_client_core::config::Config as BaseClientConfig;
pub use nym_socks5_client_core::config::Config as CoreConfig;
pub mod old_config_v1_1_13;
pub mod old_config_v1_1_19;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
mod persistence;
mod template;
@@ -69,7 +70,11 @@ impl NymConfigTemplate for Config {
impl Config {
pub fn new<S: AsRef<str>>(id: S, provider_mix_address: S) -> Self {
Config {
core: CoreConfig::new(id.as_ref(), provider_mix_address.as_ref()),
core: CoreConfig::new(
id.as_ref(),
env!("CARGO_PKG_VERSION"),
provider_mix_address.as_ref(),
),
storage_paths: SocksClientPaths::new_default(default_data_directory(id.as_ref())),
logging: Default::default(),
}
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_19::{ConfigV1_1_19, Socks5V1_1_19};
use crate::config::old_config_v1_1_20::{ConfigV1_1_20, Socks5V1_1_20};
use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use nym_config::must_get_home;
@@ -14,7 +14,7 @@ pub struct OldConfigV1_1_13 {
#[serde(flatten)]
pub base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
pub socks5: Socks5V1_1_19,
pub socks5: Socks5V1_1_20,
}
impl MigrationNymConfig for OldConfigV1_1_13 {
@@ -28,9 +28,9 @@ impl MigrationNymConfig for OldConfigV1_1_13 {
}
}
impl From<OldConfigV1_1_13> for ConfigV1_1_19 {
impl From<OldConfigV1_1_13> for ConfigV1_1_20 {
fn from(value: OldConfigV1_1_13) -> Self {
ConfigV1_1_19 {
ConfigV1_1_20 {
base: value.base.into(),
socks5: value.socks5,
}
@@ -1,18 +1,20 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::SocksClientPaths;
use crate::config::{BaseClientConfig, Config, CoreConfig};
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::CommonClientPaths;
use nym_client_core::config::old_config_v1_1_19::ConfigV1_1_19 as BaseConfigV1_1_19;
use nym_client_core::config::Client;
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::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;
use nym_config::must_get_home;
use nym_socks5_client_core::config::{
ProviderInterfaceVersion, Socks5, Socks5Debug, Socks5ProtocolVersion,
use nym_socks5_client_core::config::old_config_v1_1_20_2::{
BaseClientConfigV1_1_20_2, Socks5DebugV1_1_20_2, Socks5V1_1_20_2,
};
use nym_socks5_client_core::config::{ProviderInterfaceVersion, Socks5ProtocolVersion};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::path::PathBuf;
@@ -22,19 +24,19 @@ const DEFAULT_PER_REQUEST_SURBS: u32 = 3;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_19 {
pub struct ConfigV1_1_20 {
#[serde(flatten)]
pub base: BaseConfigV1_1_19<ConfigV1_1_19>,
pub base: BaseConfigV1_1_20<ConfigV1_1_20>,
pub socks5: Socks5V1_1_19,
pub socks5: Socks5V1_1_20,
}
impl From<ConfigV1_1_19> for Config {
fn from(value: ConfigV1_1_19) -> Self {
Config {
core: CoreConfig {
base: BaseClientConfig {
client: Client {
impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
fn from(value: ConfigV1_1_20) -> Self {
ConfigV1_1_20_2 {
core: CoreConfigV1_1_20_2 {
base: BaseClientConfigV1_1_20_2 {
client: ClientV1_1_20_2 {
version: value.base.client.version,
id: value.base.client.id,
disabled_credentials_mode: value.base.client.disabled_credentials_mode,
@@ -46,8 +48,8 @@ impl From<ConfigV1_1_19> for Config {
},
socks5: value.socks5.into(),
},
storage_paths: SocksClientPaths {
common_paths: CommonClientPaths {
storage_paths: SocksClientPathsV1_1_20_2 {
common_paths: CommonClientPathsV1_1_20_2 {
keys: ClientKeysPaths {
private_identity_key_file: value.base.client.private_identity_key_file,
public_identity_key_file: value.base.client.public_identity_key_file,
@@ -65,7 +67,7 @@ impl From<ConfigV1_1_19> for Config {
}
}
impl MigrationNymConfig for ConfigV1_1_19 {
impl MigrationNymConfig for ConfigV1_1_20 {
fn default_root_directory() -> PathBuf {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let base_dir = must_get_home();
@@ -78,7 +80,7 @@ impl MigrationNymConfig for ConfigV1_1_19 {
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5V1_1_19 {
pub struct Socks5V1_1_20 {
pub listening_port: u16,
pub provider_mix_address: String,
@@ -93,12 +95,12 @@ pub struct Socks5V1_1_19 {
pub send_anonymously: bool,
#[serde(default)]
pub socks5_debug: Socks5DebugV1_1_19,
pub socks5_debug: Socks5DebugV1_1_20,
}
impl From<Socks5V1_1_19> for Socks5 {
fn from(value: Socks5V1_1_19) -> Self {
Socks5 {
impl From<Socks5V1_1_20> for Socks5V1_1_20_2 {
fn from(value: Socks5V1_1_20) -> Self {
Socks5V1_1_20_2 {
listening_port: value.listening_port,
provider_mix_address: value.provider_mix_address,
provider_interface_version: value.provider_interface_version,
@@ -111,23 +113,23 @@ impl From<Socks5V1_1_19> for Socks5 {
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5DebugV1_1_19 {
pub struct Socks5DebugV1_1_20 {
connection_start_surbs: u32,
per_request_surbs: u32,
}
impl From<Socks5DebugV1_1_19> for Socks5Debug {
fn from(value: Socks5DebugV1_1_19) -> Self {
Socks5Debug {
impl From<Socks5DebugV1_1_20> for Socks5DebugV1_1_20_2 {
fn from(value: Socks5DebugV1_1_20) -> Self {
Socks5DebugV1_1_20_2 {
connection_start_surbs: value.connection_start_surbs,
per_request_surbs: value.per_request_surbs,
}
}
}
impl Default for Socks5DebugV1_1_19 {
impl Default for Socks5DebugV1_1_20 {
fn default() -> Self {
Socks5DebugV1_1_19 {
Socks5DebugV1_1_20 {
connection_start_surbs: DEFAULT_CONNECTION_START_SURBS,
per_request_surbs: DEFAULT_PER_REQUEST_SURBS,
}
@@ -0,0 +1,54 @@
// 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 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_config::read_config_from_toml_file;
pub use nym_socks5_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as CoreConfigV1_1_20_2;
use serde::{Deserialize, Serialize};
use std::io;
use std::path::Path;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct SocksClientPathsV1_1_20_2 {
#[serde(flatten)]
pub common_paths: CommonClientPathsV1_1_20_2,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20_2 {
pub core: CoreConfigV1_1_20_2,
pub storage_paths: SocksClientPathsV1_1_20_2,
pub logging: LoggingSettings,
}
impl ConfigV1_1_20_2 {
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))
}
// in this upgrade, gateway endpoint configuration was moved out of the config file,
// so its returned to be stored elsewhere.
pub fn upgrade(self) -> (Config, GatewayEndpointConfig) {
let gateway_details = self.core.base.client.gateway_endpoint.clone().into();
let config = Config {
core: self.core.into(),
storage_paths: SocksClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default(),
},
logging: self.logging,
};
(config, gateway_details)
}
}
+3 -11
View File
@@ -64,17 +64,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 }}'
# DEPRECATED
[core.client.gateway_endpoint]
# ID of the gateway from which the client should be fetching messages.
gateway_id = '{{ core.client.gateway_endpoint.gateway_id }}'
# Address of the gateway owner to which the client should send messages.
gateway_owner = '{{ core.client.gateway_endpoint.gateway_owner }}'
# Address of the gateway listener to which all client requests should be sent.
gateway_listener = '{{ core.client.gateway_endpoint.gateway_listener }}'
# 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 }}'
##### socket config options #####
+42 -22
View File
@@ -62,7 +62,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.9",
"getrandom 0.2.10",
"once_cell",
"version_check",
]
@@ -225,9 +225,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.21.0"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "base64ct"
@@ -622,7 +622,7 @@ dependencies = [
"cosmos-sdk-proto",
"ecdsa",
"eyre",
"getrandom 0.2.9",
"getrandom 0.2.10",
"k256",
"prost",
"prost-types",
@@ -865,6 +865,20 @@ dependencies = [
"zeroize",
]
[[package]]
name = "cw-controllers"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"cw-utils",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw-storage-plus"
version = "0.13.4"
@@ -1428,9 +1442,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@@ -2244,6 +2258,7 @@ name = "nym-client-core"
version = "1.1.14"
dependencies = [
"async-trait",
"base64 0.21.2",
"dashmap",
"dirs 4.0.0",
"futures",
@@ -2266,6 +2281,7 @@ dependencies = [
"rand 0.7.3",
"serde",
"serde_json",
"sha2 0.10.6",
"sqlx 0.6.3",
"tap",
"thiserror",
@@ -2329,7 +2345,7 @@ dependencies = [
"bs58",
"digest 0.9.0",
"ff 0.11.1",
"getrandom 0.2.9",
"getrandom 0.2.10",
"group 0.11.0",
"itertools",
"nym-dkg",
@@ -2389,7 +2405,7 @@ dependencies = [
[[package]]
name = "nym-contracts-common"
version = "0.4.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -2424,7 +2440,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"aes 0.8.2",
"blake3",
@@ -2473,7 +2489,7 @@ name = "nym-gateway-client"
version = "0.1.0"
dependencies = [
"futures",
"getrandom 0.2.9",
"getrandom 0.2.10",
"log",
"nym-bandwidth-controller",
"nym-coconut-interface",
@@ -2531,7 +2547,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -2620,7 +2636,7 @@ dependencies = [
"chacha20",
"chacha20poly1305",
"curve25519-dalek",
"getrandom 0.2.9",
"getrandom 0.2.10",
"log",
"rand 0.7.3",
"rayon",
@@ -2631,7 +2647,7 @@ dependencies = [
[[package]]
name = "nym-pemstore"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"pem",
]
@@ -2641,8 +2657,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -2796,7 +2816,7 @@ dependencies = [
"aes-gcm",
"argon2",
"generic-array 0.14.7",
"getrandom 0.2.9",
"getrandom 0.2.10",
"rand 0.8.5",
"serde",
"serde_json",
@@ -2895,7 +2915,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
@@ -3382,7 +3402,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.9",
"getrandom 0.2.10",
]
[[package]]
@@ -3450,7 +3470,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom 0.2.9",
"getrandom 0.2.10",
"redox_syscall 0.2.16",
"thiserror",
]
@@ -3478,7 +3498,7 @@ version = "0.11.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
dependencies = [
"base64 0.21.0",
"base64 0.21.2",
"bytes",
"encoding_rs",
"futures-core",
@@ -3612,7 +3632,7 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
dependencies = [
"base64 0.21.0",
"base64 0.21.2",
]
[[package]]
@@ -4368,7 +4388,7 @@ dependencies = [
"bytes",
"flex-error",
"futures",
"getrandom 0.2.9",
"getrandom 0.2.10",
"http",
"hyper",
"hyper-proxy",
@@ -4768,7 +4788,7 @@ version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
dependencies = [
"getrandom 0.2.9",
"getrandom 0.2.10",
"wasm-bindgen",
]
@@ -4944,7 +4964,7 @@ name = "wasm-utils"
version = "0.1.0"
dependencies = [
"futures",
"getrandom 0.2.9",
"getrandom 0.2.10",
"indexed_db_futures",
"js-sys",
"nym-store-cipher",
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.1.0"
version = "1.1.1"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
+2 -2
View File
@@ -29,7 +29,7 @@ impl Config {
#[wasm_bindgen(constructor)]
pub fn new(id: String, validator_server: String, debug: Option<DebugWasm>) -> Self {
Config {
base: BaseClientConfig::new(id)
base: BaseClientConfig::new(id, env!("CARGO_PKG_VERSION").to_string())
.with_custom_nyxd(vec![validator_server
.parse()
.expect("provided url was malformed")])
@@ -39,7 +39,7 @@ impl Config {
pub(crate) fn new_tester_config<S: Into<String>>(id: S) -> Self {
Config {
base: BaseClientConfig::new(id)
base: BaseClientConfig::new(id.into(), env!("CARGO_PKG_VERSION").to_string())
.with_disabled_credentials(true)
.with_disabled_cover_traffic(true)
.with_disabled_topology_refresh(true),
+26 -53
View File
@@ -7,7 +7,7 @@ use crate::client::response_pusher::ResponsePusher;
use crate::constants::NODE_TESTER_CLIENT_ID;
use crate::error::WasmClientError;
use crate::helpers::{
choose_gateway, gateway_from_topology, parse_recipient, parse_sender_tag,
parse_recipient, parse_sender_tag, setup_from_topology, setup_gateway_from_api,
setup_reply_surb_storage_backend,
};
use crate::storage::traits::FullWasmClientStorage;
@@ -15,13 +15,11 @@ use crate::storage::ClientStorage;
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::base_client::{
BaseClientBuilder, ClientInput, ClientOutput, ClientState, CredentialsToggle,
BaseClientBuilder, ClientInput, ClientOutput, ClientState,
};
use nym_client_core::client::inbound_messages::InputMessage;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
@@ -29,7 +27,7 @@ use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider};
use nym_topology::NymTopology;
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use rand::{thread_rng, RngCore};
use rand::RngCore;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
@@ -63,14 +61,7 @@ pub struct NymClientBuilder {
preferred_gateway: Option<IdentityKey>,
storage_passphrase: Option<String>,
reply_surb_storage_backend: browser_backend::Backend,
on_message: js_sys::Function,
// unimplemented:
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
disabled_credentials: bool,
}
#[wasm_bindgen]
@@ -83,15 +74,10 @@ impl NymClientBuilder {
storage_passphrase: Option<String>,
) -> Self {
NymClientBuilder {
reply_surb_storage_backend: setup_reply_surb_storage_backend(
config.base.debug.reply_surbs,
),
config,
custom_topology: None,
storage_passphrase,
on_message,
bandwidth_controller: None,
disabled_credentials: true,
preferred_gateway,
}
}
@@ -114,14 +100,9 @@ impl NymClientBuilder {
let full_config = Config::new_tester_config(NODE_TESTER_CLIENT_ID);
NymClientBuilder {
reply_surb_storage_backend: setup_reply_surb_storage_backend(
full_config.base.debug.reply_surbs,
),
config: full_config,
custom_topology: Some(topology.into()),
on_message,
bandwidth_controller: None,
disabled_credentials: true,
storage_passphrase: None,
preferred_gateway: gateway,
}
@@ -139,15 +120,17 @@ impl NymClientBuilder {
}
}
fn initialise_storage(config: &Config, base_storage: ClientStorage) -> FullWasmClientStorage {
FullWasmClientStorage {
keys_and_gateway_store: base_storage,
reply_storage: setup_reply_surb_storage_backend(config.base.debug.reply_surbs),
credential_storage: EphemeralCredentialStorage::default(),
}
}
async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> {
console_log!("Starting the wasm client");
let disabled_credentials = if self.disabled_credentials {
CredentialsToggle::Disabled
} else {
CredentialsToggle::Enabled
};
let nym_api_endpoints = self.config.base.client.nym_api_urls.clone();
// TODO: this will have to be re-used for surbs. but this is a problem for another PR.
@@ -155,35 +138,25 @@ impl NymClientBuilder {
ClientStorage::new_async(&self.config.base.client.id, self.storage_passphrase.take())
.await?;
let user_chosen = self.preferred_gateway.clone();
// if we provided hardcoded topology, get gateway from it, otherwise get it the 'standard' way
let gateway_endpoint = if let Some(topology) = &self.custom_topology {
gateway_from_topology(
&mut thread_rng(),
self.preferred_gateway.as_deref(),
topology,
&client_store,
)
.await?
if let Some(topology) = &self.custom_topology {
setup_from_topology(user_chosen, topology, &client_store).await?
} else {
choose_gateway(
&client_store,
self.preferred_gateway.clone(),
&nym_api_endpoints,
)
.await?
setup_gateway_from_api(&client_store, user_chosen, &nym_api_endpoints).await?
};
let packet_type = self.config.base.debug.traffic.packet_type;
let storage = Self::initialise_storage(&self.config, client_store);
let maybe_topology_provider = self.topology_provider();
let mut base_builder: BaseClientBuilder<_, FullWasmClientStorage> = BaseClientBuilder::new(
&gateway_endpoint,
&self.config.base.debug,
client_store,
self.bandwidth_controller,
self.reply_surb_storage_backend,
disabled_credentials,
nym_api_endpoints,
);
let mut base_builder: BaseClientBuilder<_, FullWasmClientStorage> =
BaseClientBuilder::<FakeClient<DirectSigningNyxdClient>, _>::new(
&self.config.base,
storage,
None,
);
if let Some(topology_provider) = maybe_topology_provider {
base_builder = base_builder.with_topology_provider(topology_provider);
}
@@ -202,7 +175,7 @@ impl NymClientBuilder {
client_state: Arc::new(started_client.client_state),
_full_topology: None,
_task_manager: started_client.task_manager,
packet_type: self.config.base.debug.traffic.packet_type,
packet_type,
})
}
+32 -83
View File
@@ -7,19 +7,18 @@ use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::GatewaySetup;
use nym_crypto::asymmetric::identity;
use nym_client_core::init::helpers::current_gateways;
use nym_client_core::init::{setup_gateway_from, GatewaySetup, InitialisationDetails};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::NymTopology;
use nym_validator_client::client::{IdentityKey, IdentityKeyRef};
use nym_topology::{gateway, NymTopology};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::NymApiClient;
use rand::{CryptoRng, Rng};
use rand::thread_rng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, PromisableResult};
use wasm_utils::PromisableResult;
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no disk_persistence
@@ -79,87 +78,37 @@ pub fn current_network_topology(nym_api_url: String) -> Promise {
})
}
pub(crate) async fn choose_gateway(
async fn setup_gateway(
client_store: &ClientStorage,
chosen_gateway: Option<IdentityKey>,
gateways: &[gateway::Node],
) -> Result<InitialisationDetails, WasmClientError> {
let setup = if client_store.has_full_gateway_info().await? {
GatewaySetup::MustLoad
} else {
GatewaySetup::new_fresh(chosen_gateway.clone(), None)
};
setup_gateway_from(&setup, client_store, client_store, false, Some(gateways))
.await
.map_err(Into::into)
}
pub(crate) async fn setup_gateway_from_api(
client_store: &ClientStorage,
chosen_gateway: Option<IdentityKey>,
nym_apis: &[Url],
) -> Result<GatewayEndpointConfig, WasmClientError> {
let existing_gateway_config = client_store.read_gateway_config().await?;
console_log!("loaded: {:?}", existing_gateway_config);
if let Some(existing) = existing_gateway_config {
if let Some(provided) = &chosen_gateway {
if provided != &existing.gateway_id {
return Err(WasmClientError::AlreadyRegistered {
gateway_config: existing,
});
}
}
return Ok(existing);
};
// if NOTHING is specified nor available, choose gateway randomly.
let setup = GatewaySetup::new(None, chosen_gateway, None);
let config = setup.try_get_gateway_details(nym_apis).await?;
// perform registration + persist the new gateway info
// TODO: this is actually quite bad. we shouldn't be persisting gateway info here since we did not have persisted
// the shared key yet. this will only happen when we start the base client itself.
// but unfortunately, we can't do much more until we do a bit more refactoring.
client_store.store_gateway_config(&config).await?;
console_log!("stored: {:?}", config);
Ok(config)
) -> Result<InitialisationDetails, WasmClientError> {
let mut rng = thread_rng();
let gateways = current_gateways(&mut rng, nym_apis).await?;
setup_gateway(client_store, chosen_gateway, &gateways).await
}
pub(crate) async fn gateway_from_topology<R: Rng + CryptoRng>(
rng: &mut R,
explicit_gateway: Option<IdentityKeyRef<'_>>,
pub(crate) async fn setup_from_topology(
explicit_gateway: Option<IdentityKey>,
topology: &NymTopology,
client_store: &ClientStorage,
) -> Result<GatewayEndpointConfig, WasmClientError> {
let existing_gateway_config = client_store.read_gateway_config().await?;
console_log!("loaded: {:?}", existing_gateway_config);
let new_gateway: GatewayEndpointConfig = if let Some(provided) = explicit_gateway {
if let Some(existing) = existing_gateway_config {
// we have stored gateway info and explicitly provided identity key
//
// check if they match, otherwise return an error
return if provided != existing.gateway_id {
Err(WasmClientError::AlreadyRegistered {
gateway_config: existing,
})
} else {
Ok(existing)
};
} else {
// we have explicitly provided identity key and didn't have any prior stored data
//
// try to grab details from the topology
let gateway_identity = identity::PublicKey::from_base58_string(provided)
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
if let Some(gateway) = topology.get_gateway(&gateway_identity) {
gateway.clone().into()
} else {
return Err(WasmClientError::NonExistentGateway {
gateway_identity: gateway_identity.to_base58_string(),
});
}
}
} else if let Some(existing) = existing_gateway_config {
// we have stored data and didn't provide anything separately - use what's stored!
return Ok(existing);
} else {
// we don't have anything stored nor we have provided anything
//
// just grab random gateway from our topology
topology.random_gateway(rng)?.clone().into()
};
console_log!("storing: {:?}", new_gateway);
client_store.store_gateway_config(&new_gateway).await?;
Ok(new_gateway)
) -> Result<InitialisationDetails, WasmClientError> {
let gateways = topology.gateways();
setup_gateway(client_store, explicit_gateway, gateways).await
}
@@ -16,6 +16,9 @@ pub enum ClientStorageError {
#[error("{typ} cryptographic key is not available in storage")]
CryptoKeyNotInStorage { typ: String },
#[error("the prior gateway details are not available in the storage")]
GatewayDetailsNotInStorage,
}
impl From<ClientStorageError> for JsValue {
+44 -10
View File
@@ -1,9 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::storage::errors::ClientStorageError;
use js_sys::Promise;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::client::base_client::storage::gateway_details::PersistedGatewayDetails;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_client::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
@@ -27,8 +28,8 @@ mod v1 {
pub const CORE_STORE: &str = "core";
// keys
// TODO: to replace with FULL config
pub const GATEWAY_CONFIG: &str = "gateway_config";
pub const CONFIG: &str = "config";
pub const GATEWAY_DETAILS: &str = "gateway_details";
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
@@ -110,15 +111,32 @@ impl ClientStorage {
})
}
pub(crate) async fn read_gateway_config(
&self,
) -> Result<Option<GatewayEndpointConfig>, ClientStorageError> {
// TODO: persist client's config
#[allow(dead_code)]
pub(crate) async fn read_config(&self) -> Result<Option<Config>, ClientStorageError> {
self.inner
.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_CONFIG))
.read_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG))
.await
.map_err(Into::into)
}
pub(crate) async fn may_read_gateway_details(
&self,
) -> Result<Option<PersistedGatewayDetails>, ClientStorageError> {
self.inner
.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_DETAILS))
.await
.map_err(Into::into)
}
pub(crate) async fn must_read_gateway_details(
&self,
) -> Result<PersistedGatewayDetails, ClientStorageError> {
self.may_read_gateway_details()
.await?
.ok_or(ClientStorageError::GatewayDetailsNotInStorage)
}
async fn may_read_identity_keypair(
&self,
) -> Result<Option<identity::KeyPair>, ClientStorageError> {
@@ -244,17 +262,33 @@ impl ClientStorage {
.map_err(Into::into)
}
pub(crate) async fn store_gateway_config(
pub(crate) async fn store_gateway_details(
&self,
gateway_endpoint: &GatewayEndpointConfig,
gateway_endpoint: &PersistedGatewayDetails,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::CORE_STORE,
JsValue::from_str(v1::GATEWAY_CONFIG),
JsValue::from_str(v1::GATEWAY_DETAILS),
gateway_endpoint,
)
.await
.map_err(Into::into)
}
// TODO: persist client's config
#[allow(dead_code)]
pub(crate) async fn store_config(&self, config: &Config) -> Result<(), ClientStorageError> {
self.inner
.store_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG), config)
.await
.map_err(Into::into)
}
pub(crate) async fn has_full_gateway_info(&self) -> Result<bool, ClientStorageError> {
let has_keys = self.may_read_gateway_shared_key().await?.is_some();
let has_details = self.may_read_gateway_details().await?.is_some();
Ok(has_keys && has_details)
}
}
+31 -6
View File
@@ -4,6 +4,9 @@
use crate::storage::errors::ClientStorageError;
use crate::storage::ClientStorage;
use async_trait::async_trait;
use nym_client_core::client::base_client::storage::gateway_details::{
GatewayDetailsStore, PersistedGatewayDetails,
};
use nym_client_core::client::base_client::storage::MixnetClientStorage;
use nym_client_core::client::key_manager::persistence::KeyStore;
use nym_client_core::client::key_manager::KeyManager;
@@ -14,9 +17,9 @@ use wasm_utils::console_log;
// temporary until other variants are properly implemented (probably it should get changed into `ClientStorage`
// implementing all traits and everything getting combined
pub struct FullWasmClientStorage {
key_store: ClientStorage,
reply_storage: browser_backend::Backend,
credential_storage: EphemeralCredentialStorage,
pub(crate) keys_and_gateway_store: ClientStorage,
pub(crate) reply_storage: browser_backend::Backend,
pub(crate) credential_storage: EphemeralCredentialStorage,
}
impl MixnetClientStorage for FullWasmClientStorage {
@@ -24,12 +27,14 @@ impl MixnetClientStorage for FullWasmClientStorage {
type ReplyStore = browser_backend::Backend;
type CredentialStore = EphemeralCredentialStorage;
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
(self.key_store, self.reply_storage, self.credential_storage)
type GatewayDetailsStore = ClientStorage;
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
(self.reply_storage, self.credential_storage)
}
fn key_store(&self) -> &Self::KeyStore {
&self.key_store
&self.keys_and_gateway_store
}
fn reply_store(&self) -> &Self::ReplyStore {
@@ -39,6 +44,10 @@ impl MixnetClientStorage for FullWasmClientStorage {
fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_storage
}
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore {
&self.keys_and_gateway_store
}
}
#[async_trait(?Send)]
@@ -74,3 +83,19 @@ impl KeyStore for ClientStorage {
.await
}
}
#[async_trait(?Send)]
impl GatewayDetailsStore for ClientStorage {
type StorageError = ClientStorageError;
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError> {
self.must_read_gateway_details().await
}
async fn store_gateway_details(
&self,
details: &PersistedGatewayDetails,
) -> Result<(), Self::StorageError> {
self.store_gateway_details(details).await
}
}
+15 -22
View File
@@ -3,7 +3,7 @@
use crate::constants::NODE_TESTER_ID;
use crate::error::WasmClientError;
use crate::helpers::{current_network_topology_async, gateway_from_topology};
use crate::helpers::{current_network_topology_async, setup_from_topology};
use crate::storage::ClientStorage;
use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
use crate::tester::helpers::{
@@ -15,7 +15,7 @@ use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::key_manager::ManagedKeys;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::init::InitialisationDetails;
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_gateway_client::GatewayClient;
use nym_node_tester_utils::receiver::SimpleMessageReceiver;
@@ -28,7 +28,6 @@ use nym_task::TaskManager;
use nym_topology::NymTopology;
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use rand::{CryptoRng, Rng};
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex as SyncMutex};
@@ -120,29 +119,26 @@ impl NymNodeTesterBuilder {
})
}
async fn gateway_info<R: Rng + CryptoRng>(
async fn gateway_info(
&self,
rng: &mut R,
client_store: &ClientStorage,
) -> Result<GatewayEndpointConfig, WasmClientError> {
gateway_from_topology(
rng,
self.gateway.as_deref(),
&self.base_topology,
client_store,
)
.await
) -> Result<InitialisationDetails, WasmClientError> {
if let Ok(loaded) = InitialisationDetails::try_load(client_store, client_store).await {
Ok(loaded)
} else {
setup_from_topology(self.gateway.clone(), &self.base_topology, client_store).await
}
}
async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> {
let mut rng = OsRng;
let task_manager = TaskManager::default();
let client_store = ClientStorage::new_async(NODE_TESTER_ID, None).await?;
let gateway_endpoint = self.gateway_info(&mut rng, &client_store).await?;
let init_details = self.gateway_info(&client_store).await?;
let gateway_endpoint = init_details.gateway_details;
let gateway_identity = gateway_endpoint.try_get_gateway_identity_key()?;
let mut managed_keys = ManagedKeys::load_or_generate(&mut rng, &client_store).await;
let managed_keys = init_details.managed_keys;
let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded();
@@ -151,7 +147,7 @@ impl NymNodeTesterBuilder {
gateway_endpoint.gateway_listener,
managed_keys.identity_keypair(),
gateway_identity,
managed_keys.gateway_shared_key(),
Some(managed_keys.must_get_gateway_shared_key()),
mixnet_message_sender,
ack_sender,
Duration::from_secs(10),
@@ -160,14 +156,11 @@ impl NymNodeTesterBuilder {
);
gateway_client.set_disabled_credentials_mode(true);
let shared_keys = gateway_client.authenticate_and_start().await?;
managed_keys
.deal_with_gateway_key(shared_keys, &client_store)
.await?;
gateway_client.authenticate_and_start().await?;
// TODO: make those values configurable later
let tester = NodeTester::new(
rng,
OsRng,
self.base_topology,
Some(address(&managed_keys, gateway_identity)),
PacketSize::default(),
+15 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-bin-common"
version = "0.5.0"
version = "0.6.0"
description = "Common code for nym binaries"
edition = { workspace = true }
authors = { workspace = true }
@@ -19,11 +19,17 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }
## tracing
tracing-appender = { version = "0.2.2", optional = true }
tracing-subscriber = { version = "0.3.16", features = [
"env-filter",
], optional = true }
tracing-tree = { version = "0.2.2", optional = true }
opentelemetry-jaeger = { version = "0.18.0", optional = true, features = [
"rt-tokio",
"collector_client",
"isahc_collector_client",
] }
tracing-opentelemetry = { version = "0.19.0", optional = true }
opentelemetry = { version = "0.19.0", optional = true, features = ["rt-tokio"] }
[build-dependencies]
@@ -37,4 +43,10 @@ vergen = { version = "=7.4.3", default-features = false, features = [
[features]
default = []
output_format = ["serde_json"]
tracing = ["tracing-appender", "tracing-subscriber", "tracing-tree"]
tracing = [
"tracing-subscriber",
"tracing-tree",
"opentelemetry-jaeger",
"tracing-opentelemetry",
"opentelemetry",
]
@@ -101,7 +101,7 @@ impl BinaryBuildInformation {
}
}
#[derive(Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BinaryBuildInformationOwned {
// VERGEN_BUILD_TIMESTAMP
/// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
+25 -13
View File
@@ -4,7 +4,11 @@
use serde::{Deserialize, Serialize};
#[cfg(feature = "tracing")]
pub use tracing_appender;
pub use opentelemetry;
#[cfg(feature = "tracing")]
pub use opentelemetry_jaeger;
#[cfg(feature = "tracing")]
pub use tracing_opentelemetry;
#[cfg(feature = "tracing")]
pub use tracing_subscriber;
#[cfg(feature = "tracing")]
@@ -43,27 +47,35 @@ pub fn setup_logging() {
#[cfg(feature = "tracing")]
#[macro_export]
macro_rules! setup_tracing {
($file_name: expr) => {
($service_name: expr) => {
use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt;
use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt;
let file_appender =
nym_bin_common::logging::tracing_appender::rolling::hourly($file_name, "log");
let (non_blocking, _guard) =
nym_bin_common::logging::tracing_appender::non_blocking(file_appender);
let appender_layer = nym_bin_common::logging::tracing_subscriber::fmt::Layer::new()
.with_ansi(false)
.with_writer(non_blocking);
nym_bin_common::logging::tracing_subscriber::Registry::default()
let registry = nym_bin_common::logging::tracing_subscriber::Registry::default()
.with(nym_bin_common::logging::tracing_subscriber::EnvFilter::from_default_env())
.with(appender_layer)
.with(
nym_bin_common::logging::tracing_tree::HierarchicalLayer::new(4)
.with_targets(true)
.with_bracketed_fields(true),
);
let tracer = nym_bin_common::logging::opentelemetry_jaeger::new_collector_pipeline()
.with_endpoint("http://44.199.230.10:14268/api/traces")
.with_service_name($service_name)
.with_isahc()
.with_trace_config(
nym_bin_common::logging::opentelemetry::sdk::trace::config().with_sampler(
nym_bin_common::logging::opentelemetry::sdk::trace::Sampler::TraceIdRatioBased(
0.1,
),
),
)
.init();
.install_batch(nym_bin_common::logging::opentelemetry::runtime::Tokio)
.expect("Could not init tracer");
let telemetry = nym_bin_common::logging::tracing_opentelemetry::layer().with_tracer(tracer);
registry.with(telemetry).init();
};
}
+3 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client-core"
version = "1.1.14"
version = "1.1.15"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.66"
@@ -9,6 +9,7 @@ rust-version = "1.66"
[dependencies]
async-trait = { workspace = true }
base64 = "0.21.2"
dirs = "4.0"
dashmap = "5.4.0"
futures = "0.3"
@@ -17,6 +18,7 @@ log = { workspace = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sha2 = "0.10.6"
tap = "1.0.1"
thiserror = "1.0.34"
url = { version ="2.2", features = ["serde"] }
@@ -42,7 +42,6 @@ use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use rand::rngs::OsRng;
use std::sync::Arc;
use tap::TapFallible;
use url::Url;
@@ -50,6 +49,8 @@ use url::Url;
#[cfg(target_arch = "wasm32")]
use nym_bandwidth_controller::wasm_mockups::DkgQueryClient;
use crate::client::base_client::storage::gateway_details::GatewayDetailsStore;
use crate::init::{setup_gateway, GatewaySetup, InitialisationDetails};
#[cfg(not(target_arch = "wasm32"))]
use nym_validator_client::nyxd::traits::DkgQueryClient;
@@ -157,17 +158,11 @@ impl From<bool> for CredentialsToggle {
}
pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
// due to wasm limitations I had to split it like this : (
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
disabled_credentials: bool,
nym_api_endpoints: Vec<Url>,
reply_storage_backend: S::ReplyStore,
key_store: S::KeyStore,
config: &'a Config,
client_store: S,
dkg_query_client: Option<C>,
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
managed_keys: ManagedKeys,
setup_method: GatewaySetup,
}
impl<'a, C, S> BaseClientBuilder<'a, C, S>
@@ -175,47 +170,23 @@ where
S: MixnetClientStorage + 'static,
C: DkgQueryClient + Send + Sync + 'static,
{
// TODO: combine all storages
pub fn new_from_base_config(
pub fn new(
base_config: &'a Config,
key_store: S::KeyStore,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
reply_storage_backend: S::ReplyStore,
client_store: S,
dkg_query_client: Option<C>,
) -> BaseClientBuilder<'a, C, S> {
BaseClientBuilder {
gateway_config: base_config.get_gateway_endpoint_config(),
debug_config: &base_config.debug,
disabled_credentials: base_config.get_disabled_credentials_mode(),
nym_api_endpoints: base_config.get_nym_api_endpoints(),
bandwidth_controller,
reply_storage_backend,
key_store,
managed_keys: ManagedKeys::Invalidated,
config: base_config,
client_store,
dkg_query_client,
custom_topology_provider: None,
setup_method: GatewaySetup::MustLoad,
}
}
// TODO: combine all storages
pub fn new(
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
key_store: S::KeyStore,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
reply_storage_backend: S::ReplyStore,
credentials_toggle: CredentialsToggle,
nym_api_endpoints: Vec<Url>,
) -> BaseClientBuilder<'a, C, S> {
BaseClientBuilder {
gateway_config,
debug_config,
disabled_credentials: credentials_toggle.is_disabled(),
nym_api_endpoints,
reply_storage_backend,
custom_topology_provider: None,
bandwidth_controller,
key_store,
managed_keys: ManagedKeys::Invalidated,
}
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
self.setup_method = setup;
self
}
pub fn with_topology_provider(
@@ -228,13 +199,16 @@ 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 as_mix_recipient(&self) -> Recipient {
fn mix_address(
managed_keys: &ManagedKeys,
gateway_config: &GatewayEndpointConfig,
) -> Recipient {
Recipient::new(
*self.managed_keys.identity_public_key(),
*self.managed_keys.encryption_public_key(),
*managed_keys.identity_public_key(),
*managed_keys.encryption_public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(&self.gateway_config.gateway_id).unwrap(),
NodeIdentity::from_base58_string(&gateway_config.gateway_id).unwrap(),
)
}
@@ -318,7 +292,10 @@ where
}
async fn start_gateway_client(
&mut self,
config: &Config,
gateway_config: GatewayEndpointConfig,
managed_keys: &ManagedKeys,
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: TaskClient,
@@ -327,33 +304,26 @@ where
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
{
let gateway_id = self.gateway_config.gateway_id.clone();
if gateway_id.is_empty() {
return Err(ClientCoreError::GatewayIdUnknown);
}
let gateway_address = self.gateway_config.gateway_listener.clone();
if gateway_address.is_empty() {
return Err(ClientCoreError::GatewayAddressUnknown);
}
let gateway_address = gateway_config.gateway_listener.clone();
let gateway_id = gateway_config.gateway_id;
// TODO: in theory, at this point, this should be infallible
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
let mut gateway_client = GatewayClient::new(
gateway_address,
self.managed_keys.identity_keypair(),
managed_keys.identity_keypair(),
gateway_identity,
self.managed_keys.gateway_shared_key(),
Some(managed_keys.must_get_gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.debug_config
.gateway_connection
.gateway_response_timeout,
self.bandwidth_controller.take(),
config.debug.gateway_connection.gateway_response_timeout,
bandwidth_controller,
shutdown,
);
gateway_client.set_disabled_credentials_mode(self.disabled_credentials);
gateway_client.set_disabled_credentials_mode(config.client.disabled_credentials_mode);
let shared_key = gateway_client
.authenticate_and_start()
@@ -362,12 +332,7 @@ where
log::error!("Could not authenticate and start up the gateway connection - {err}")
})?;
self.managed_keys
.deal_with_gateway_key(shared_key, &self.key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
managed_keys.ensure_gateway_key(shared_key);
Ok(gateway_client)
}
@@ -473,21 +438,41 @@ where
Ok(mem_store)
}
async fn initial_key_setup(&mut self) {
assert!(!self.managed_keys.is_valid());
let mut rng = OsRng;
self.managed_keys = ManagedKeys::load_or_generate(&mut rng, &self.key_store).await;
async fn initialise_keys_and_gateway(&self) -> Result<InitialisationDetails, ClientCoreError>
where
<S::KeyStore as KeyStore>::StorageError: Sync + Send,
<S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
{
setup_gateway(
&self.setup_method,
self.client_store.key_store(),
self.client_store.gateway_details_store(),
false,
Some(&self.config.client.nym_api_urls),
)
.await
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
where
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
S::ReplyStore: Send + Sync,
<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,
{
info!("Starting nym client");
self.initial_key_setup().await;
// derive (or load) client keys and gateway configuration
let details = self.initialise_keys_and_gateway().await?;
let gateway_config = details.gateway_details;
let managed_keys = details.managed_keys;
let (reply_storage_backend, credential_store) = self.client_store.into_runtime_stores();
let bandwidth_controller = self
.dkg_query_client
.map(|client| BandwidthController::new(credential_store, client));
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -515,34 +500,39 @@ where
let (reply_controller_sender, reply_controller_receiver) =
reply_controller::requests::new_control_channels();
let self_address = self.as_mix_recipient();
let self_address = Self::mix_address(&managed_keys, &gateway_config);
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender, task_manager.subscribe())
.await?;
let reply_storage = Self::setup_persistent_reply_storage(
self.reply_storage_backend,
let gateway_client = Self::start_gateway_client(
self.config,
gateway_config,
&managed_keys,
bandwidth_controller,
mixnet_messages_sender,
ack_sender,
task_manager.subscribe(),
)
.await?;
let reply_storage =
Self::setup_persistent_reply_storage(reply_storage_backend, task_manager.subscribe())
.await?;
let topology_provider = Self::setup_topology_provider(
self.custom_topology_provider.take(),
self.nym_api_endpoints,
self.config.get_nym_api_endpoints(),
);
Self::start_topology_refresher(
topology_provider,
self.debug_config.topology,
self.config.debug.topology,
shared_topology_accessor.clone(),
task_manager.subscribe(),
)
.await?;
Self::start_received_messages_buffer_controller(
self.managed_keys.encryption_keypair(),
managed_keys.encryption_keypair(),
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_storage.key_storage(),
@@ -566,8 +556,8 @@ where
let shared_lane_queue_lengths = LaneQueueLengths::new();
let controller_config = real_messages_control::Config::new(
self.debug_config,
self.managed_keys.ack_key(),
&self.config.debug,
managed_keys.ack_key(),
self_address,
);
@@ -583,17 +573,18 @@ where
shared_lane_queue_lengths.clone(),
client_connection_rx,
task_manager.subscribe(),
self.debug_config.traffic.packet_type,
self.config.debug.traffic.packet_type,
);
if !self
.debug_config
.config
.debug
.cover_traffic
.disable_loop_cover_traffic_stream
{
Self::start_cover_traffic_stream(
self.debug_config,
self.managed_keys.ack_key(),
&self.config.debug,
managed_keys.ack_key(),
self_address,
shared_topology_accessor.clone(),
message_sender,
@@ -122,13 +122,30 @@ pub fn create_bandwidth_controller_with_urls<St: CredentialStorage>(
nym_api_url: Url,
storage: St,
) -> BandwidthController<Client<QueryNyxdClient>, St> {
let client = default_query_dkg_client(nyxd_url, nym_api_url);
BandwidthController::new(storage, client)
}
pub fn default_query_dkg_client_from_config(config: &Config) -> Client<QueryNyxdClient> {
let nyxd_url = config
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
let api_url = config
.get_nym_api_endpoints()
.pop()
.expect("No validator api endpoint provided");
default_query_dkg_client(nyxd_url, api_url)
}
pub fn default_query_dkg_client(nyxd_url: Url, nym_api_url: Url) -> Client<QueryNyxdClient> {
let details = nym_network_defaults::NymNetworkDetails::new_from_env();
let mut client_config = nym_validator_client::Config::try_from_nym_network_details(&details)
.expect("failed to construct validator client config");
// overwrite env configuration with config URLs
client_config = client_config.with_urls(nyxd_url, nym_api_url);
let client = nym_validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
BandwidthController::new(storage, client)
nym_validator_client::Client::new_query(client_config)
.expect("Could not construct query client")
}
@@ -0,0 +1,201 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::GatewayEndpointConfig;
use async_trait::async_trait;
use nym_gateway_requests::registration::handshake::SharedKeys;
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 {
type StorageError: Error;
async fn load_gateway_details(&self) -> Result<PersistedGatewayDetails, Self::StorageError>;
async fn store_gateway_details(
&self,
details: &PersistedGatewayDetails,
) -> Result<(), Self::StorageError>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistedGatewayDetails {
// 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(crate) details: GatewayEndpointConfig,
}
impl From<PersistedGatewayDetails> for GatewayEndpointConfig {
fn from(value: PersistedGatewayDetails) -> Self {
value.details
}
}
impl PersistedGatewayDetails {
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();
PersistedGatewayDetails { 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()
}
}
// 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(&self) -> Result<PersistedGatewayDetails, OnDiskGatewayDetailsError> {
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(
&self,
details: &PersistedGatewayDetails,
) -> Result<(), OnDiskGatewayDetailsError> {
// 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 {
details: Mutex<Option<PersistedGatewayDetails>>,
}
#[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(())
}
}
@@ -4,17 +4,20 @@
// 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, EphemeralStorage,
};
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;
@@ -25,24 +28,33 @@ use crate::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;
// TODO: ideally this should be changed into
// `MixnetClientStorage: KeyStore + ReplyStorageBackend + CredentialStorage + GatewayDetailsStore`
pub trait MixnetClientStorage {
type KeyStore: KeyStore;
type ReplyStore: ReplyStorageBackend;
type CredentialStore: CredentialStorage;
type GatewayDetailsStore: GatewayDetailsStore;
// this is a TERRIBLE name...
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore);
// fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore, Self::GatewayDetailsStore);
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;
}
#[derive(Default)]
pub struct Ephemeral {
key_store: InMemEphemeralKeys,
reply_store: reply_storage::Empty,
credential_store: EphemeralStorage,
credential_store: EphemeralCredentialStorage,
gateway_details_store: InMemGatewayDetails,
}
impl Ephemeral {
@@ -55,9 +67,10 @@ impl MixnetClientStorage for Ephemeral {
type KeyStore = InMemEphemeralKeys;
type ReplyStore = reply_storage::Empty;
type CredentialStore = EphemeralCredentialStorage;
type GatewayDetailsStore = InMemGatewayDetails;
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
(self.key_store, self.reply_store, self.credential_store)
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
(self.reply_store, self.credential_store)
}
fn key_store(&self) -> &Self::KeyStore {
@@ -71,6 +84,10 @@ impl MixnetClientStorage for Ephemeral {
fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_store
}
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore {
&self.gateway_details_store
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
@@ -78,6 +95,7 @@ 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,
}
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
@@ -86,11 +104,13 @@ impl OnDiskPersistent {
key_store: OnDiskKeys,
reply_store: fs_backend::Backend,
credential_store: PersistentCredentialStorage,
gateway_details_store: OnDiskGatewayDetails,
) -> Self {
Self {
key_store,
reply_store,
credential_store,
gateway_details_store,
}
}
@@ -109,10 +129,13 @@ impl OnDiskPersistent {
let credential_store =
nym_credential_storage::initialise_persistent_storage(paths.credentials_database).await;
let gateway_details_store = OnDiskGatewayDetails::new(paths.gateway_details);
Ok(OnDiskPersistent {
key_store,
reply_store,
credential_store,
gateway_details_store,
})
}
}
@@ -122,9 +145,10 @@ impl MixnetClientStorage for OnDiskPersistent {
type KeyStore = OnDiskKeys;
type ReplyStore = fs_backend::Backend;
type CredentialStore = PersistentCredentialStorage;
type GatewayDetailsStore = OnDiskGatewayDetails;
fn into_split(self) -> (Self::KeyStore, Self::ReplyStore, Self::CredentialStore) {
(self.key_store, self.reply_store, self.credential_store)
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
(self.reply_store, self.credential_store)
}
fn key_store(&self) -> &Self::KeyStore {
@@ -138,4 +162,8 @@ impl MixnetClientStorage for OnDiskPersistent {
fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_store
}
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore {
&self.gateway_details_store
}
}
@@ -6,6 +6,7 @@ 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;
@@ -20,6 +21,16 @@ pub enum ManagedKeys {
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)
@@ -84,6 +95,11 @@ impl ManagedKeys {
}
}
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,
@@ -108,6 +124,17 @@ impl ManagedKeys {
}
}
pub fn ensure_gateway_key(&self, gateway_shared_key: Arc<SharedKeys>) {
if let ManagedKeys::FullyDerived(key_manager) = &self {
if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key)
|| key_manager.gateway_shared_key != gateway_shared_key
{
// 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: Arc<SharedKeys>,
@@ -120,12 +147,7 @@ impl ManagedKeys {
key_manager
}
ManagedKeys::FullyDerived(key_manager) => {
if !Arc::ptr_eq(&key_manager.gateway_shared_key, &gateway_shared_key)
|| key_manager.gateway_shared_key != gateway_shared_key
{
// this should NEVER happen thus panic here
panic!("derived fresh gateway shared key whilst already holding one!")
}
self.ensure_gateway_key(gateway_shared_key);
key_manager
}
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
@@ -4,6 +4,7 @@
use crate::client::key_manager::KeyManager;
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;
@@ -36,6 +37,7 @@ pub enum OnDiskKeysError {
KeyPairLoadFailure {
keys: String,
paths: nym_pemstore::KeyPairPath,
#[source]
err: std::io::Error,
},
@@ -43,6 +45,7 @@ pub enum OnDiskKeysError {
KeyPairStoreFailure {
keys: String,
paths: nym_pemstore::KeyPairPath,
#[source]
err: std::io::Error,
},
@@ -50,6 +53,7 @@ pub enum OnDiskKeysError {
KeyLoadFailure {
key: String,
path: String,
#[source]
err: std::io::Error,
},
@@ -57,6 +61,7 @@ pub enum OnDiskKeysError {
KeyStoreFailure {
key: String,
path: String,
#[source]
err: std::io::Error,
},
}
@@ -79,6 +84,26 @@ 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 keys")
.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();
self.load_keypair(encryption_paths, "encryption keys")
}
#[doc(hidden)]
pub fn load_identity_keypair(&self) -> Result<identity::KeyPair, OnDiskKeysError> {
let identity_paths = self.paths.identity_key_pair_path();
self.load_keypair(identity_paths, "identity keys")
}
fn load_key<T: PemStorableKey>(
&self,
path: &std::path::Path,
@@ -132,13 +157,8 @@ impl OnDiskKeys {
}
fn load_keys(&self) -> Result<KeyManager, OnDiskKeysError> {
let identity_paths = self.paths.identity_key_pair_path();
let encryption_paths = self.paths.encryption_key_pair_path();
let identity_keypair: identity::KeyPair =
self.load_keypair(identity_paths, "identity keys")?;
let encryption_keypair: encryption::KeyPair =
self.load_keypair(encryption_paths, "encryption keys")?;
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: SharedKeys =
@@ -193,10 +213,12 @@ impl KeyStore for OnDiskKeys {
}
#[derive(Default)]
pub struct InMemEphemeralKeys;
pub struct InMemEphemeralKeys {
keys: Mutex<Option<KeyManager>>,
}
#[derive(Debug, thiserror::Error)]
#[error("ephemeral keys can't be loaded from storage")]
#[error("old ephemeral keys can't be loaded from storage")]
pub struct EphemeralKeysError;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -205,10 +227,11 @@ impl KeyStore for InMemEphemeralKeys {
type StorageError = EphemeralKeysError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
Err(EphemeralKeysError)
self.keys.lock().await.clone().ok_or(EphemeralKeysError)
}
async fn store_keys(&self, _keys: &KeyManager) -> Result<(), Self::StorageError> {
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
*self.keys.lock().await = Some(keys.clone());
Ok(())
}
}
@@ -565,7 +565,7 @@ where
fragments: Vec<Fragment>,
reply_surbs: Vec<ReplySurb>,
) -> Result<Vec<PreparedFragment>, SurbWrappedPreparationError> {
debug_assert_ne!(
debug_assert_eq!(
fragments.len(),
reply_surbs.len(),
"attempted to send {} fragments with {} reply surbs",
@@ -6,16 +6,21 @@ 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,
// TODO:
// pub gateway_config_pathfinder: (),
/// 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,
@@ -30,6 +35,7 @@ impl CommonClientPaths {
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_default(base_data_directory),
}
}
@@ -0,0 +1,30 @@
// 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 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) -> CommonClientPaths {
let data_dir = self
.reply_surb_database
.parent()
.expect("client paths upgrade failure");
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,
}
}
}
+27 -45
View File
@@ -14,7 +14,8 @@ use wasm_bindgen::prelude::*;
pub mod disk_persistence;
pub mod old_config_v1_1_13;
pub mod old_config_v1_1_19;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
@@ -65,15 +66,19 @@ pub struct Config {
}
impl Config {
pub fn new<S: Into<String>>(id: S) -> Self {
pub fn new<S: Into<String>>(id: S, version: S) -> Self {
Config {
client: Client::new_default(id),
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.client.validate() && self.debug.validate()
self.debug.validate()
}
pub fn with_debug_config(mut self, debug: DebugConfig) -> Self {
@@ -86,19 +91,6 @@ impl Config {
self
}
pub fn set_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) {
self.client.gateway_endpoint = gateway_endpoint;
}
pub fn with_gateway_endpoint(mut self, gateway_endpoint: GatewayEndpointConfig) -> Self {
self.client.gateway_endpoint = gateway_endpoint;
self
}
pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) {
self.client.gateway_endpoint.gateway_id = id.into();
}
pub fn with_custom_nyxd(mut self, urls: Vec<Url>) -> Self {
self.client.nyxd_urls = urls;
self
@@ -174,22 +166,6 @@ impl Config {
pub fn get_nym_api_endpoints(&self) -> Vec<Url> {
self.client.nym_api_urls.clone()
}
pub fn get_gateway_id(&self) -> String {
self.client.gateway_endpoint.gateway_id.clone()
}
pub fn get_gateway_owner(&self) -> String {
self.client.gateway_endpoint.gateway_owner.clone()
}
pub fn get_gateway_listener(&self) -> String {
self.client.gateway_endpoint.gateway_listener.clone()
}
pub fn get_gateway_endpoint_config(&self) -> &GatewayEndpointConfig {
&self.client.gateway_endpoint
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
@@ -242,6 +218,8 @@ impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
}
#[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,
@@ -251,6 +229,7 @@ pub struct Client {
/// 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,
@@ -261,15 +240,10 @@ pub struct Client {
/// 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>,
/// Information regarding how the client should send data to gateway.
// #[deprecated(note = "this shall be moved to separate file because it doesn't belong here...")]
// TODO: this should be removed from config files and be moved to separate file instead
pub gateway_endpoint: GatewayEndpointConfig,
}
impl Client {
pub fn new_default<S: Into<String>>(id: S) -> Self {
pub fn new_default<S: Into<String>>(id: S, version: S) -> Self {
let network = NymNetworkDetails::new_mainnet();
let nyxd_urls = network
.endpoints
@@ -283,19 +257,27 @@ impl Client {
.collect::<Vec<_>>();
Client {
version: env!("CARGO_PKG_VERSION").to_string(),
version: version.into(),
id: id.into(),
disabled_credentials_mode: true,
nyxd_urls,
nym_api_urls,
gateway_endpoint: Default::default(),
}
}
pub fn validate(&self) -> bool {
!self.gateway_endpoint.gateway_id.is_empty()
&& !self.gateway_endpoint.gateway_owner.is_empty()
&& !self.gateway_endpoint.gateway_owner.is_empty()
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,
}
}
}
@@ -1,9 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_19::{
AcknowledgementsV1_1_19, ClientV1_1_19, ConfigV1_1_19, CoverTrafficV1_1_19, DebugConfigV1_1_19,
GatewayConnectionV1_1_19, LoggingV1_1_19, ReplySurbsV1_1_19, TopologyV1_1_19, TrafficV1_1_19,
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,
@@ -39,7 +39,7 @@ 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_19<T>,
pub client: ClientV1_1_20<T>,
#[serde(default)]
pub logging: OldLoggingV1_1_13,
@@ -51,9 +51,9 @@ pub struct OldConfigV1_1_13<T> {
#[serde(deny_unknown_fields)]
pub struct OldLoggingV1_1_13 {}
impl From<OldLoggingV1_1_13> for LoggingV1_1_19 {
impl From<OldLoggingV1_1_13> for LoggingV1_1_20 {
fn from(_value: OldLoggingV1_1_13) -> Self {
LoggingV1_1_19 {}
LoggingV1_1_20 {}
}
}
@@ -114,10 +114,10 @@ pub struct OldDebugConfigV1_1_13 {
pub maximum_reply_key_age: Duration,
}
impl From<OldDebugConfigV1_1_13> for DebugConfigV1_1_19 {
impl From<OldDebugConfigV1_1_13> for DebugConfigV1_1_20 {
fn from(value: OldDebugConfigV1_1_13) -> Self {
DebugConfigV1_1_19 {
traffic: TrafficV1_1_19 {
DebugConfigV1_1_20 {
traffic: TrafficV1_1_20 {
average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
@@ -125,25 +125,25 @@ impl From<OldDebugConfigV1_1_13> for DebugConfigV1_1_19 {
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: value.use_extended_packet_size.map(Into::into),
},
cover_traffic: CoverTrafficV1_1_19 {
cover_traffic: CoverTrafficV1_1_20 {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream,
..CoverTrafficV1_1_19::default()
..CoverTrafficV1_1_20::default()
},
gateway_connection: GatewayConnectionV1_1_19 {
gateway_connection: GatewayConnectionV1_1_20 {
gateway_response_timeout: value.gateway_response_timeout,
},
acknowledgements: AcknowledgementsV1_1_19 {
acknowledgements: AcknowledgementsV1_1_20 {
average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition,
},
topology: TopologyV1_1_19 {
topology: TopologyV1_1_20 {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: false,
},
reply_surbs: ReplySurbsV1_1_19 {
reply_surbs: ReplySurbsV1_1_20 {
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,
@@ -190,10 +190,10 @@ impl Default for OldDebugConfigV1_1_13 {
}
}
impl<T, U> From<OldConfigV1_1_13<T>> for ConfigV1_1_19<U> {
impl<T, U> From<OldConfigV1_1_13<T>> for ConfigV1_1_20<U> {
fn from(value: OldConfigV1_1_13<T>) -> Self {
ConfigV1_1_19 {
client: ClientV1_1_19 {
ConfigV1_1_20 {
client: ClientV1_1_20 {
version: value.client.version,
id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode,
@@ -1,9 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{
Acknowledgements, CoverTraffic, DebugConfig, GatewayConnection, GatewayEndpointConfig,
ReplySurbs, Topology, Traffic,
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 nym_sphinx::params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
@@ -55,25 +56,25 @@ pub(crate) const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(2
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_19<T> {
pub client: ClientV1_1_19<T>,
pub struct ConfigV1_1_20<T> {
pub client: ClientV1_1_20<T>,
#[serde(default)]
pub logging: LoggingV1_1_19,
pub logging: LoggingV1_1_20,
#[serde(default)]
pub debug: DebugConfigV1_1_19,
pub debug: DebugConfigV1_1_20,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayEndpointConfigV1_1_19 {
pub struct GatewayEndpointConfigV1_1_20 {
pub gateway_id: String,
pub gateway_owner: String,
pub gateway_listener: String,
}
impl From<GatewayEndpointConfigV1_1_19> for GatewayEndpointConfig {
fn from(value: GatewayEndpointConfigV1_1_19) -> Self {
GatewayEndpointConfig {
impl From<GatewayEndpointConfigV1_1_20> for GatewayEndpointConfigV1_1_20_2 {
fn from(value: GatewayEndpointConfigV1_1_20) -> Self {
GatewayEndpointConfigV1_1_20_2 {
gateway_id: value.gateway_id,
gateway_owner: value.gateway_owner,
gateway_listener: value.gateway_listener,
@@ -82,7 +83,7 @@ impl From<GatewayEndpointConfigV1_1_19> for GatewayEndpointConfig {
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct ClientV1_1_19<T> {
pub struct ClientV1_1_20<T> {
pub version: String,
pub id: String,
#[serde(default)]
@@ -97,7 +98,7 @@ pub struct ClientV1_1_19<T> {
pub public_encryption_key_file: PathBuf,
pub gateway_shared_key_file: PathBuf,
pub ack_key_file: PathBuf,
pub gateway_endpoint: GatewayEndpointConfigV1_1_19,
pub gateway_endpoint: GatewayEndpointConfigV1_1_20,
pub database_path: PathBuf,
#[serde(default)]
pub reply_surb_database_path: PathBuf,
@@ -109,11 +110,11 @@ pub struct ClientV1_1_19<T> {
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LoggingV1_1_19 {}
pub struct LoggingV1_1_20 {}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct TrafficV1_1_19 {
pub struct TrafficV1_1_20 {
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
#[serde(with = "humantime_serde")]
@@ -123,9 +124,9 @@ pub struct TrafficV1_1_19 {
pub secondary_packet_size: Option<PacketSize>,
}
impl From<TrafficV1_1_19> for Traffic {
fn from(value: TrafficV1_1_19) -> Self {
Traffic {
impl From<TrafficV1_1_20> for TrafficV1_1_20_2 {
fn from(value: TrafficV1_1_20) -> Self {
TrafficV1_1_20_2 {
average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
@@ -137,9 +138,9 @@ impl From<TrafficV1_1_19> for Traffic {
}
}
impl Default for TrafficV1_1_19 {
impl Default for TrafficV1_1_20 {
fn default() -> Self {
TrafficV1_1_19 {
TrafficV1_1_20 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
@@ -151,16 +152,16 @@ impl Default for TrafficV1_1_19 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTrafficV1_1_19 {
pub struct CoverTrafficV1_1_20 {
#[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_19> for CoverTraffic {
fn from(value: CoverTrafficV1_1_19) -> Self {
CoverTraffic {
impl From<CoverTrafficV1_1_20> for CoverTrafficV1_1_20_2 {
fn from(value: CoverTrafficV1_1_20) -> Self {
CoverTrafficV1_1_20_2 {
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,
@@ -168,9 +169,9 @@ impl From<CoverTrafficV1_1_19> for CoverTraffic {
}
}
impl Default for CoverTrafficV1_1_19 {
impl Default for CoverTrafficV1_1_20 {
fn default() -> Self {
CoverTrafficV1_1_19 {
CoverTrafficV1_1_20 {
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,
@@ -180,22 +181,22 @@ impl Default for CoverTrafficV1_1_19 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnectionV1_1_19 {
pub struct GatewayConnectionV1_1_20 {
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl From<GatewayConnectionV1_1_19> for GatewayConnection {
fn from(value: GatewayConnectionV1_1_19) -> Self {
GatewayConnection {
impl From<GatewayConnectionV1_1_20> for GatewayConnectionV1_1_20_2 {
fn from(value: GatewayConnectionV1_1_20) -> Self {
GatewayConnectionV1_1_20_2 {
gateway_response_timeout: value.gateway_response_timeout,
}
}
}
impl Default for GatewayConnectionV1_1_19 {
impl Default for GatewayConnectionV1_1_20 {
fn default() -> Self {
GatewayConnectionV1_1_19 {
GatewayConnectionV1_1_20 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
@@ -203,7 +204,7 @@ impl Default for GatewayConnectionV1_1_19 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV1_1_19 {
pub struct AcknowledgementsV1_1_20 {
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
pub ack_wait_multiplier: f64,
@@ -211,9 +212,9 @@ pub struct AcknowledgementsV1_1_19 {
pub ack_wait_addition: Duration,
}
impl From<AcknowledgementsV1_1_19> for Acknowledgements {
fn from(value: AcknowledgementsV1_1_19) -> Self {
Acknowledgements {
impl From<AcknowledgementsV1_1_20> for AcknowledgementsV1_1_20_2 {
fn from(value: AcknowledgementsV1_1_20) -> Self {
AcknowledgementsV1_1_20_2 {
average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition,
@@ -221,9 +222,9 @@ impl From<AcknowledgementsV1_1_19> for Acknowledgements {
}
}
impl Default for AcknowledgementsV1_1_19 {
impl Default for AcknowledgementsV1_1_20 {
fn default() -> Self {
AcknowledgementsV1_1_19 {
AcknowledgementsV1_1_20 {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
@@ -233,7 +234,7 @@ impl Default for AcknowledgementsV1_1_19 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TopologyV1_1_19 {
pub struct TopologyV1_1_20 {
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
#[serde(with = "humantime_serde")]
@@ -241,9 +242,9 @@ pub struct TopologyV1_1_19 {
pub disable_refreshing: bool,
}
impl From<TopologyV1_1_19> for Topology {
fn from(value: TopologyV1_1_19) -> Self {
Topology {
impl From<TopologyV1_1_20> for TopologyV1_1_20_2 {
fn from(value: TopologyV1_1_20) -> Self {
TopologyV1_1_20_2 {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: value.disable_refreshing,
@@ -251,9 +252,9 @@ impl From<TopologyV1_1_19> for Topology {
}
}
impl Default for TopologyV1_1_19 {
impl Default for TopologyV1_1_20 {
fn default() -> Self {
TopologyV1_1_19 {
TopologyV1_1_20 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
@@ -263,7 +264,7 @@ impl Default for TopologyV1_1_19 {
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV1_1_19 {
pub struct ReplySurbsV1_1_20 {
pub minimum_reply_surb_storage_threshold: usize,
pub maximum_reply_surb_storage_threshold: usize,
pub minimum_reply_surb_request_size: u32,
@@ -279,9 +280,9 @@ pub struct ReplySurbsV1_1_19 {
pub maximum_reply_key_age: Duration,
}
impl From<ReplySurbsV1_1_19> for ReplySurbs {
fn from(value: ReplySurbsV1_1_19) -> Self {
ReplySurbs {
impl From<ReplySurbsV1_1_20> for ReplySurbsV1_1_20_2 {
fn from(value: ReplySurbsV1_1_20) -> Self {
ReplySurbsV1_1_20_2 {
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,
@@ -296,9 +297,9 @@ impl From<ReplySurbsV1_1_19> for ReplySurbs {
}
}
impl Default for ReplySurbsV1_1_19 {
impl Default for ReplySurbsV1_1_20 {
fn default() -> Self {
ReplySurbsV1_1_19 {
ReplySurbsV1_1_20 {
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,
@@ -313,20 +314,20 @@ impl Default for ReplySurbsV1_1_19 {
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfigV1_1_19 {
pub traffic: TrafficV1_1_19,
pub cover_traffic: CoverTrafficV1_1_19,
pub gateway_connection: GatewayConnectionV1_1_19,
pub acknowledgements: AcknowledgementsV1_1_19,
pub topology: TopologyV1_1_19,
pub reply_surbs: ReplySurbsV1_1_19,
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,
}
impl From<DebugConfigV1_1_19> for DebugConfig {
fn from(value: DebugConfigV1_1_19) -> Self {
DebugConfig {
impl From<DebugConfigV1_1_20> for DebugConfigV1_1_20_2 {
fn from(value: DebugConfigV1_1_20) -> Self {
DebugConfigV1_1_20_2 {
traffic: value.traffic.into(),
cover_traffic: value.cover_traffic.into(),
gateway_connection: value.gateway_connection.into(),
@@ -336,19 +337,3 @@ impl From<DebugConfigV1_1_19> for DebugConfig {
}
}
}
// 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 DebugConfigV1_1_19 {
fn default() -> Self {
DebugConfigV1_1_19 {
traffic: Default::default(),
cover_traffic: Default::default(),
gateway_connection: Default::default(),
acknowledgements: Default::default(),
topology: Default::default(),
reply_surbs: Default::default(),
}
}
}
@@ -0,0 +1,348 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection,
GatewayEndpointConfig, ReplySurbs, Topology, Traffic,
};
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);
// 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 ConfigV1_1_20_2 {
pub client: ClientV1_1_20_2,
#[serde(default)]
pub debug: DebugConfigV1_1_20_2,
}
impl From<ConfigV1_1_20_2> for Config {
fn from(value: ConfigV1_1_20_2) -> Self {
Config {
client: value.client.into(),
debug: value.debug.into(),
}
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayEndpointConfigV1_1_20_2 {
/// 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 From<GatewayEndpointConfigV1_1_20_2> for GatewayEndpointConfig {
fn from(value: GatewayEndpointConfigV1_1_20_2) -> Self {
GatewayEndpointConfig {
gateway_id: value.gateway_id,
gateway_owner: value.gateway_owner,
gateway_listener: value.gateway_listener,
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct ClientV1_1_20_2 {
pub version: String,
pub id: String,
#[serde(default)]
pub disabled_credentials_mode: bool,
#[serde(alias = "validator_urls")]
pub nyxd_urls: Vec<Url>,
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
pub gateway_endpoint: GatewayEndpointConfigV1_1_20_2,
}
impl From<ClientV1_1_20_2> for Client {
fn from(value: ClientV1_1_20_2) -> Self {
Client {
version: value.version,
id: value.id,
disabled_credentials_mode: value.disabled_credentials_mode,
nyxd_urls: value.nyxd_urls,
nym_api_urls: value.nym_api_urls,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct TrafficV1_1_20_2 {
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
pub disable_main_poisson_packet_distribution: bool,
pub primary_packet_size: PacketSize,
pub secondary_packet_size: Option<PacketSize>,
pub packet_type: PacketType,
}
impl From<TrafficV1_1_20_2> for Traffic {
fn from(value: TrafficV1_1_20_2) -> Self {
Traffic {
average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
.disable_main_poisson_packet_distribution,
primary_packet_size: value.primary_packet_size,
secondary_packet_size: value.secondary_packet_size,
packet_type: PacketType::Mix,
}
}
}
impl Default for TrafficV1_1_20_2 {
fn default() -> Self {
TrafficV1_1_20_2 {
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 CoverTrafficV1_1_20_2 {
#[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 CoverTraffic {
fn from(value: CoverTrafficV1_1_20_2) -> Self {
CoverTraffic {
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,
}
}
}
impl Default for CoverTrafficV1_1_20_2 {
fn default() -> Self {
CoverTrafficV1_1_20_2 {
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 GatewayConnectionV1_1_20_2 {
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl From<GatewayConnectionV1_1_20_2> for GatewayConnection {
fn from(value: GatewayConnectionV1_1_20_2) -> Self {
GatewayConnection {
gateway_response_timeout: value.gateway_response_timeout,
}
}
}
impl Default for GatewayConnectionV1_1_20_2 {
fn default() -> Self {
GatewayConnectionV1_1_20_2 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV1_1_20_2 {
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
pub ack_wait_multiplier: f64,
#[serde(with = "humantime_serde")]
pub ack_wait_addition: Duration,
}
impl From<AcknowledgementsV1_1_20_2> for Acknowledgements {
fn from(value: AcknowledgementsV1_1_20_2) -> Self {
Acknowledgements {
average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition,
}
}
}
impl Default for AcknowledgementsV1_1_20_2 {
fn default() -> Self {
AcknowledgementsV1_1_20_2 {
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 TopologyV1_1_20_2 {
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
pub disable_refreshing: bool,
}
impl Default for TopologyV1_1_20_2 {
fn default() -> Self {
TopologyV1_1_20_2 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
}
}
}
impl From<TopologyV1_1_20_2> for Topology {
fn from(value: TopologyV1_1_20_2) -> Self {
Topology {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: value.disable_refreshing,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV1_1_20_2 {
pub minimum_reply_surb_storage_threshold: usize,
pub maximum_reply_surb_storage_threshold: usize,
pub minimum_reply_surb_request_size: u32,
pub maximum_reply_surb_request_size: u32,
pub maximum_allowed_reply_surb_request_size: u32,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_rerequest_waiting_period: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_drop_waiting_period: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_age: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
}
impl Default for ReplySurbsV1_1_20_2 {
fn default() -> Self {
ReplySurbsV1_1_20_2 {
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,
}
}
}
impl From<ReplySurbsV1_1_20_2> for ReplySurbs {
fn from(value: ReplySurbsV1_1_20_2) -> Self {
ReplySurbs {
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,
maximum_reply_surb_request_size: value.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: value.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: value
.maximum_reply_surb_rerequest_waiting_period,
maximum_reply_surb_drop_waiting_period: value.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: value.maximum_reply_surb_age,
maximum_reply_key_age: value.maximum_reply_key_age,
}
}
}
#[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,
}
impl From<DebugConfigV1_1_20_2> for DebugConfig {
fn from(value: DebugConfigV1_1_20_2) -> Self {
DebugConfig {
traffic: value.traffic.into(),
cover_traffic: value.cover_traffic.into(),
gateway_connection: value.gateway_connection.into(),
acknowledgements: value.acknowledgements.into(),
topology: value.topology.into(),
reply_surbs: value.reply_surbs.into(),
}
}
}
+19
View File
@@ -50,6 +50,11 @@ pub enum ClientCoreError {
source: Box<dyn Error + Send + Sync>,
},
#[error("experienced a failure with our gateway details storage: {source}")]
GatewayDetailsStoreError {
source: Box<dyn Error + Send + Sync>,
},
#[error("The gateway id is invalid - {0}")]
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
@@ -97,6 +102,20 @@ pub enum ClientCoreError {
"This operation would have resulted in clients keys being overwritten without permission"
)]
ForbiddenKeyOverwrite,
#[error("gateway details are unavailable")]
UnavailableGatewayDetails {
source: Box<dyn Error + Send + Sync>,
},
#[error("gateway shared key is unavailable whilst we have full node information")]
UnavailableSharedKey,
#[error("attempted to obtain fresh gateway details whilst already knowing about one")]
UnexpectedGatewayDetails,
#[error("the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys")]
MismatchedGatewayDetails { gateway_id: String },
}
/// Set of messages that the client can send to listeners via the task manager
+9 -9
View File
@@ -44,18 +44,18 @@ const MEASUREMENTS: usize = 3;
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
const PING_TIMEOUT: Duration = Duration::from_millis(1000);
struct GatewayWithLatency {
gateway: gateway::Node,
struct GatewayWithLatency<'a> {
gateway: &'a gateway::Node,
latency: Duration,
}
impl GatewayWithLatency {
fn new(gateway: gateway::Node, latency: Duration) -> Self {
impl<'a> GatewayWithLatency<'a> {
fn new(gateway: &'a gateway::Node, latency: Duration) -> Self {
GatewayWithLatency { gateway, latency }
}
}
pub(super) async fn current_gateways<R: Rng>(
pub async fn current_gateways<R: Rng>(
rng: &mut R,
nym_apis: &[Url],
) -> Result<Vec<gateway::Node>, ClientCoreError> {
@@ -64,7 +64,7 @@ pub(super) async fn current_gateways<R: Rng>(
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let client = nym_validator_client::client::NymApiClient::new(nym_api.clone());
log::trace!("Fetching list of gateways from: {}", nym_api);
log::trace!("Fetching list of gateways from: {nym_api}");
let gateways = client.get_cached_gateways().await?;
let valid_gateways = gateways
@@ -91,7 +91,7 @@ async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure)
}
async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, ClientCoreError> {
async fn measure_latency(gateway: &gateway::Node) -> Result<GatewayWithLatency, ClientCoreError> {
let addr = gateway.clients_address();
trace!(
"establishing connection to {} ({addr})...",
@@ -156,7 +156,7 @@ async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, C
pub(super) async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
gateways: &[gateway::Node],
) -> Result<gateway::Node, ClientCoreError> {
info!("choosing gateway by latency...");
@@ -189,7 +189,7 @@ pub(super) async fn choose_gateway_by_latency<R: Rng>(
pub(super) fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
gateways: &[gateway::Node],
) -> Result<gateway::Node, ClientCoreError> {
gateways
.choose(rng)
+273 -145
View File
@@ -3,26 +3,82 @@
//! Collection of initialization steps used by client implementations
use crate::client::base_client::storage::MixnetClientStorage;
use crate::client::base_client::storage::gateway_details::{
GatewayDetailsStore, PersistedGatewayDetails,
};
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::{KeyManager, ManagedKeys};
use crate::client::key_manager::ManagedKeys;
use crate::init::helpers::{choose_gateway_by_latency, current_gateways, uniformly_random_gateway};
use crate::{
config::{disk_persistence::keys_paths::ClientKeysPaths, Config, GatewayEndpointConfig},
config::{Config, GatewayEndpointConfig},
error::ClientCoreError,
};
use nym_crypto::asymmetric::{encryption, identity};
use nym_crypto::asymmetric::identity;
use nym_sphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
use nym_topology::gateway;
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use serde::Serialize;
use std::fmt::{Debug, Display};
use url::Url;
mod helpers;
pub mod helpers;
#[derive(Clone)]
// TODO: rename to something better...
#[derive(Debug)]
pub struct InitialisationDetails {
pub gateway_details: GatewayEndpointConfig,
pub managed_keys: ManagedKeys,
}
impl InitialisationDetails {
pub fn new(gateway_details: GatewayEndpointConfig, managed_keys: ManagedKeys) -> Self {
InitialisationDetails {
gateway_details,
managed_keys,
}
}
pub async fn try_load<K, D>(key_store: &K, details_store: &D) -> Result<Self, ClientCoreError>
where
K: KeyStore,
D: GatewayDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
{
let loaded_details = _load_gateway_details(details_store).await?;
let loaded_keys = _load_managed_keys(key_store).await?;
if !loaded_details.verify(&loaded_keys.must_get_gateway_shared_key()) {
return Err(ClientCoreError::MismatchedGatewayDetails {
gateway_id: loaded_details.details.gateway_id,
});
}
Ok(InitialisationDetails {
gateway_details: loaded_details.into(),
managed_keys: loaded_keys,
})
}
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(),
// 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)
}
}
#[derive(Debug, Clone)]
pub enum GatewaySetup {
/// The gateway specification MUST BE loaded from the underlying storage.
MustLoad,
/// Specifies usage of a new, random, gateway.
New {
/// Should the new gateway be selected based on latency.
@@ -34,13 +90,13 @@ pub enum GatewaySetup {
},
Predefined {
/// Full gateway configuration
config: GatewayEndpointConfig,
details: PersistedGatewayDetails,
},
}
impl From<GatewayEndpointConfig> for GatewaySetup {
fn from(config: GatewayEndpointConfig) -> Self {
GatewaySetup::Predefined { config }
impl From<PersistedGatewayDetails> for GatewaySetup {
fn from(details: PersistedGatewayDetails) -> Self {
GatewaySetup::Predefined { details }
}
}
@@ -57,14 +113,11 @@ impl Default for GatewaySetup {
}
impl GatewaySetup {
pub fn new(
full_config: Option<GatewayEndpointConfig>,
gateway_identity: Option<IdentityKey>,
pub fn new_fresh(
gateway_identity: Option<String>,
latency_based_selection: Option<bool>,
) -> Self {
if let Some(config) = full_config {
GatewaySetup::Predefined { config }
} else if let Some(gateway_identity) = gateway_identity {
if let Some(gateway_identity) = gateway_identity {
GatewaySetup::Specified { gateway_identity }
} else {
GatewaySetup::New {
@@ -73,15 +126,22 @@ impl GatewaySetup {
}
}
pub async fn try_get_gateway_details(
self,
validator_servers: &[Url],
pub fn is_must_load(&self) -> bool {
matches!(self, GatewaySetup::MustLoad)
}
pub fn has_full_details(&self) -> bool {
matches!(self, GatewaySetup::Predefined { .. }) || self.is_must_load()
}
pub async fn choose_gateway(
&self,
gateways: &[gateway::Node],
) -> Result<GatewayEndpointConfig, ClientCoreError> {
match self {
GatewaySetup::New { by_latency } => {
let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers).await?;
if by_latency {
if *by_latency {
choose_gateway_by_latency(&mut rng, gateways).await
} else {
uniformly_random_gateway(&mut rng, gateways)
@@ -89,20 +149,28 @@ impl GatewaySetup {
}
.map(Into::into),
GatewaySetup::Specified { gateway_identity } => {
let user_gateway = identity::PublicKey::from_base58_string(&gateway_identity)
let user_gateway = identity::PublicKey::from_base58_string(gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers).await?;
gateways
.into_iter()
.iter()
.find(|gateway| gateway.identity_key == user_gateway)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))
.cloned()
}
.map(Into::into),
GatewaySetup::Predefined { config } => Ok(config),
_ => Err(ClientCoreError::UnexpectedGatewayDetails),
}
}
pub async fn try_get_new_gateway_details(
&self,
validator_servers: &[Url],
) -> Result<GatewayEndpointConfig, ClientCoreError> {
let mut rng = OsRng;
let gateways = current_gateways(&mut rng, validator_servers).await?;
self.choose_gateway(&gateways).await
}
}
/// Struct describing the results of the client initialization procedure.
@@ -117,14 +185,14 @@ pub struct InitResults {
}
impl InitResults {
pub fn new(config: &Config, address: &Recipient) -> Self {
pub fn new(config: &Config, address: &Recipient, gateway: &GatewayEndpointConfig) -> 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: config.get_gateway_id(),
gateway_listener: config.get_gateway_listener(),
gateway_id: gateway.gateway_id.clone(),
gateway_listener: gateway.gateway_listener.clone(),
}
}
}
@@ -140,38 +208,164 @@ impl Display for InitResults {
}
}
/// Recovers the already present gateway information or attempts to register with new gateway
/// and stores the newly obtained key
pub async fn get_registered_gateway<S>(
validator_servers: Vec<Url>,
key_store: &S::KeyStore,
setup: GatewaySetup,
overwrite_keys: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError>
// helpers for error wrapping
async fn _store_gateway_details<D>(
details_store: &D,
details: &PersistedGatewayDetails,
) -> Result<(), ClientCoreError>
where
S: MixnetClientStorage,
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
D: GatewayDetailsStore,
D::StorageError: Send + Sync + 'static,
{
details_store
.store_gateway_details(details)
.await
.map_err(|source| ClientCoreError::GatewayDetailsStoreError {
source: Box::new(source),
})
}
async fn _load_gateway_details<D>(
details_store: &D,
) -> Result<PersistedGatewayDetails, ClientCoreError>
where
D: GatewayDetailsStore,
D::StorageError: Send + Sync + 'static,
{
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
K: KeyStore,
K::StorageError: Send + Sync + 'static,
{
ManagedKeys::try_load(key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})
}
fn ensure_valid_details(
details: &PersistedGatewayDetails,
loaded_keys: &ManagedKeys,
) -> Result<(), ClientCoreError> {
if !details.verify(&loaded_keys.must_get_gateway_shared_key()) {
Err(ClientCoreError::MismatchedGatewayDetails {
gateway_id: details.details.gateway_id.clone(),
})
} else {
Ok(())
}
}
pub async fn setup_gateway_from<K, D>(
setup: &GatewaySetup,
key_store: &K,
details_store: &D,
overwrite_data: bool,
gateways: Option<&[gateway::Node]>,
) -> Result<InitialisationDetails, ClientCoreError>
where
K: KeyStore,
D: GatewayDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
{
let mut rng = OsRng;
// try load keys
// try load gateway details
let loaded_details = _load_gateway_details(details_store).await;
// try load keys and decide what to do based on the GatewaySetup
let mut managed_keys = match ManagedKeys::try_load(key_store).await {
Ok(_) => {
// if we loaded something and we don't have full gateway details, check if we can overwrite the data
if let GatewaySetup::Predefined { config } = setup {
// we already have defined gateway details AND a shared key, so nothing more for us to do
return Ok(config);
} else if overwrite_keys {
ManagedKeys::generate_new(&mut rng)
} else {
return Err(ClientCoreError::ForbiddenKeyOverwrite);
Ok(loaded_keys) => {
match setup {
GatewaySetup::MustLoad => {
// get EVERYTHING from the storage
let details = loaded_details?;
ensure_valid_details(&details, &loaded_keys)?;
// no need to persist anything as we got everything from the storage
return Ok(InitialisationDetails::new(details.into(), loaded_keys));
}
GatewaySetup::Predefined { details } => {
// we already have defined gateway details AND a shared key
ensure_valid_details(details, &loaded_keys)?;
// if nothing was stored or we're allowed to overwrite what's there, just persist the passed data
if overwrite_data || loaded_details.is_err() {
_store_gateway_details(details_store, details).await?;
}
return Ok(InitialisationDetails::new(
details.clone().into(),
loaded_keys,
));
}
GatewaySetup::Specified { gateway_identity } => {
// if that data was already stored...
if let Ok(existing_gateway) = loaded_details {
ensure_valid_details(&existing_gateway, &loaded_keys)?;
if &existing_gateway.details.gateway_id != gateway_identity
&& !overwrite_data
{
// if our loaded details don't match requested value and we CANT overwrite it...
return Err(ClientCoreError::UnexpectedGatewayDetails);
} else if &existing_gateway.details.gateway_id == gateway_identity {
// if they do match up, just return it
return Ok(InitialisationDetails::new(
existing_gateway.into(),
loaded_keys,
));
}
}
// we didn't get full details from the store and we have loaded some keys
// so we can only continue if we're allowed to overwrite keys
if overwrite_data {
ManagedKeys::generate_new(&mut rng)
} else {
return Err(ClientCoreError::ForbiddenKeyOverwrite);
}
}
GatewaySetup::New { .. } => {
if let Ok(existing_gateway) = loaded_details {
ensure_valid_details(&existing_gateway, &loaded_keys)?;
return Ok(InitialisationDetails::new(
existing_gateway.into(),
loaded_keys,
));
}
// we didn't get full details from the store and we have loaded some keys
// so we can only continue if we're allowed to overwrite keys
if overwrite_data {
ManagedKeys::generate_new(&mut rng)
} else {
return Err(ClientCoreError::ForbiddenKeyOverwrite);
}
}
}
}
Err(_) => ManagedKeys::generate_new(&mut rng),
Err(_) => {
// if we failed to load the keys, ensure we didn't provide gateway details in some form
// (in that case we CAN'T generate new keys
if setup.has_full_details() {
return Err(ClientCoreError::UnavailableSharedKey);
}
ManagedKeys::generate_new(&mut rng)
}
};
// choose gateway
let gateway_details = setup.try_get_gateway_details(&validator_servers).await?;
let gateway_details = setup.choose_gateway(gateways.unwrap_or_default()).await?;
// get our identity key
let our_identity = managed_keys.identity_keypair();
@@ -179,6 +373,9 @@ where
// Establish connection, authenticate and generate keys for talking with the gateway
let shared_keys = helpers::register_with_gateway(&gateway_details, our_identity).await?;
let persisted_details = PersistedGatewayDetails::new(gateway_details, &shared_keys);
// persist gateway keys
managed_keys
.deal_with_gateway_key(shared_keys, key_store)
.await
@@ -186,108 +383,39 @@ where
source: Box::new(source),
})?;
// TODO: here we should be probably persisting gateway details as opposed to returning them
// persist gateway config
_store_gateway_details(details_store, &persisted_details).await?;
Ok(gateway_details)
Ok(InitialisationDetails::new(
persisted_details.into(),
managed_keys,
))
}
/// Convenience function for setting up the gateway for a client given a `Config`. Depending on the
/// arguments given it will do the sensible thing. Either it will
///
/// a. Reuse existing gateway configuration from storage.
/// b. Create a new gateway configuration but keep existing keys. This assumes that the caller
/// knows what they are doing and that the keys match the requested gateway.
/// c. Create a new gateway configuration with a newly registered gateway and keys.
pub async fn setup_gateway_from_config<KSt>(
key_store: &KSt,
register_gateway: bool,
user_chosen_gateway_id: Option<identity::PublicKey>,
config: &Config,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError>
pub async fn setup_gateway<K, D>(
setup: &GatewaySetup,
key_store: &K,
details_store: &D,
overwrite_data: bool,
validator_servers: Option<&[Url]>,
) -> Result<InitialisationDetails, ClientCoreError>
where
KSt: KeyStore,
<KSt as KeyStore>::StorageError: Send + Sync + 'static,
K: KeyStore,
D: GatewayDetailsStore,
K::StorageError: Send + Sync + 'static,
D::StorageError: Send + Sync + 'static,
{
// If we are not going to register gateway, and an explicitly chosen gateway is not passed in,
// load the existing configuration file
if !register_gateway && user_chosen_gateway_id.is_none() {
eprintln!("Not registering gateway, will reuse existing config and keys");
return Ok(config.client.gateway_endpoint.clone());
}
let gateway_setup = GatewaySetup::new(
None,
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(by_latency),
);
// Else, we proceed by querying the nym-api
let gateway = gateway_setup
.try_get_gateway_details(&config.get_nym_api_endpoints())
.await?;
log::debug!("Querying gateway gives: {:?}", gateway);
// If we are not registering, just return this and assume the caller has the keys already and
// wants to keep the,
if !register_gateway && user_chosen_gateway_id.is_some() {
eprintln!("Using gateway provided by user, keeping existing keys");
return Ok(gateway);
}
let mut rng = OsRng;
let mut managed_keys =
crate::client::key_manager::ManagedKeys::load_or_generate(&mut rng, key_store).await;
let gateways = current_gateways(&mut rng, validator_servers.unwrap_or_default()).await?;
// Create new keys and derive our identity
let our_identity = managed_keys.identity_keypair();
// Establish connection, authenticate and generate keys for talking with the gateway
eprintln!("Registering with new gateway");
let shared_keys = helpers::register_with_gateway(&gateway, our_identity).await?;
managed_keys
.deal_with_gateway_key(shared_keys, key_store)
.await
.map_err(|source| ClientCoreError::KeyStoreError {
source: Box::new(source),
})?;
Ok(gateway)
}
/// Get the full client address from the client keys and the gateway identity
pub fn get_client_address(
key_manager: &KeyManager,
gateway_config: &GatewayEndpointConfig,
) -> Recipient {
Recipient::new(
*key_manager.identity_keypair().public_key(),
*key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(&gateway_config.gateway_id).unwrap(),
setup_gateway_from(
setup,
key_store,
details_store,
overwrite_data,
Some(&gateways),
)
}
/// Get the client address by loading the keys from stored files.
// TODO: rethink that sucker
pub fn get_client_address_from_stored_ondisk_keys(
keys_paths: &ClientKeysPaths,
gateway_config: &GatewayEndpointConfig,
) -> Result<Recipient, ClientCoreError> {
let public_identity: identity::PublicKey =
nym_pemstore::load_key(&keys_paths.public_identity_key_file)?;
let public_encryption: encryption::PublicKey =
nym_pemstore::load_key(&keys_paths.public_encryption_key_file)?;
let client_recipient = Recipient::new(
public_identity,
public_encryption,
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(&gateway_config.gateway_id)?,
);
Ok(client_recipient)
.await
}
pub fn output_to_json<T: Serialize>(init_results: &T, output_file: &str) {
+90 -122
View File
@@ -15,9 +15,10 @@ use std::net::SocketAddr;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::net::{TcpStream, UdpSocket};
use tokio::time::sleep;
use tokio_util::codec::Framed;
use tokio_util::udp::UdpFramed;
pub struct Config {
initial_reconnection_backoff: Duration,
@@ -57,136 +58,98 @@ pub trait SendWithoutResponse {
}
pub struct Client {
conn_new: HashMap<NymNodeRoutingAddress, ConnectionSender>,
conn_new: Option<mpsc::Sender<(FramedNymPacket, SocketAddr)>>,
config: Config,
}
struct ConnectionSender {
channel: mpsc::Sender<FramedNymPacket>,
current_reconnection_attempt: Arc<AtomicU32>,
}
impl ConnectionSender {
fn new(channel: mpsc::Sender<FramedNymPacket>) -> Self {
ConnectionSender {
channel,
current_reconnection_attempt: Arc::new(AtomicU32::new(0)),
}
}
}
impl Client {
pub fn new(config: Config) -> Client {
Client {
conn_new: HashMap::new(),
conn_new: None,
config,
}
}
async fn manage_connection(
address: SocketAddr,
receiver: mpsc::Receiver<FramedNymPacket>,
connection_timeout: Duration,
current_reconnection: &AtomicU32,
receiver: mpsc::Receiver<(FramedNymPacket, SocketAddr)>,
) {
let connection_fut = TcpStream::connect(address);
let conn = match tokio::time::timeout(connection_timeout, connection_fut).await {
Ok(stream_res) => match stream_res {
Ok(stream) => {
debug!("Managed to establish connection to {}", address);
// if we managed to connect, reset the reconnection count (whatever it might have been)
current_reconnection.store(0, Ordering::Release);
Framed::new(stream, NymCodec)
}
Err(err) => {
debug!(
"failed to establish connection to {} (err: {})",
address, err
);
return;
}
},
Err(_) => {
debug!(
"failed to connect to {} within {:?}",
address, connection_timeout
);
// we failed to connect - increase reconnection attempt
current_reconnection.fetch_add(1, Ordering::SeqCst);
let socket = match UdpSocket::bind("0.0.0.0:0").await {
Ok(socket) => socket,
Err(err) => {
error!("Failed to bind to - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?");
return;
}
};
let framed_conn = UdpFramed::new(socket, NymCodec);
// Take whatever the receiver channel produces and put it on the connection.
// We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care
// about neither receiver nor the connection, it doesn't matter which one gets consumed
if let Err(err) = receiver.map(Ok).forward(conn).await {
if let Err(err) = receiver.map(Ok).forward(framed_conn).await {
warn!("Failed to forward packets to {} - {err}", address);
}
debug!(
"connection manager to {} is finished. Either the connection failed or mixnet client got dropped",
address
"connection manager is finished. Either the connection failed or mixnet client got dropped"
);
}
/// If we're trying to reconnect, determine how long we should wait.
fn determine_backoff(&self, current_attempt: u32) -> Option<Duration> {
if current_attempt == 0 {
None
} else {
let exp = 2_u32.checked_pow(current_attempt);
let backoff = exp
.and_then(|exp| self.config.initial_reconnection_backoff.checked_mul(exp))
.unwrap_or(self.config.maximum_reconnection_backoff);
// fn determine_backoff(&self, current_attempt: u32) -> Option<Duration> {
// if current_attempt == 0 {
// None
// } else {
// let exp = 2_u32.checked_pow(current_attempt);
// let backoff = exp
// .and_then(|exp| self.config.initial_reconnection_backoff.checked_mul(exp))
// .unwrap_or(self.config.maximum_reconnection_backoff);
Some(std::cmp::min(
backoff,
self.config.maximum_reconnection_backoff,
))
}
}
// Some(std::cmp::min(
// backoff,
// self.config.maximum_reconnection_backoff,
// ))
// }
// }
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
// this CAN'T fail because we just created the channel which has a non-zero capacity
if self.config.maximum_connection_buffer_size > 0 {
sender.try_send(pending_packet).unwrap();
sender.try_send((pending_packet, address.into())).unwrap();
}
self.conn_new = Some(sender);
// if we already tried to connect to `address` before, grab the current attempt count
let current_reconnection_attempt = if let Some(existing) = self.conn_new.get_mut(&address) {
existing.channel = sender;
Arc::clone(&existing.current_reconnection_attempt)
} else {
let new_entry = ConnectionSender::new(sender);
let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt);
self.conn_new.insert(address, new_entry);
current_attempt
};
// let current_reconnection_attempt = if let Some(existing) = self.conn_new.get_mut(&address) {
// existing.channel = sender;
// Arc::clone(&existing.current_reconnection_attempt)
// } else {
// let new_entry = ConnectionSender::new(sender);
// let current_attempt = Arc::clone(&new_entry.current_reconnection_attempt);
// self.conn_new.insert(address, new_entry);
// current_attempt
// };
// load the actual value.
let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire);
let backoff = self.determine_backoff(reconnection_attempt);
// let reconnection_attempt = current_reconnection_attempt.load(Ordering::Acquire);
// let backoff = self.determine_backoff(reconnection_attempt);
// copy the value before moving into another task
let initial_connection_timeout = self.config.initial_connection_timeout;
// let initial_connection_timeout = self.config.initial_connection_timeout;
tokio::spawn(async move {
// before executing the manager, wait for what was specified, if anything
if let Some(backoff) = backoff {
trace!("waiting for {:?} before attempting connection", backoff);
sleep(backoff).await;
}
// if let Some(backoff) = backoff {
// trace!("waiting for {:?} before attempting connection", backoff);
// sleep(backoff).await;
// }
Self::manage_connection(
address.into(),
receiver,
initial_connection_timeout,
&current_reconnection_attempt,
//initial_connection_timeout,
//&current_reconnection_attempt,
)
.await
});
@@ -204,48 +167,53 @@ impl SendWithoutResponse for Client {
let framed_packet =
FramedNymPacket::new(packet, packet_type, self.config.use_legacy_version);
if let Some(sender) = self.conn_new.get_mut(&address) {
if let Err(err) = sender.channel.try_send(framed_packet) {
if err.is_full() {
debug!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address);
// it's not a 'big' error, but we did not manage to send the packet
// if the queue is full, we can't really do anything but to drop the packet
Err(io::Error::new(
io::ErrorKind::WouldBlock,
"connection queue is full",
))
} else if err.is_disconnected() {
debug!(
"Connection to {} seems to be dead. attempting to re-establish it...",
address
);
// it's not a 'big' error, but we did not manage to send the packet, but queue
// it up to send it as soon as the connection is re-established
self.make_connection(address, err.into_inner());
Err(io::Error::new(
io::ErrorKind::ConnectionAborted,
"reconnection attempt is in progress",
))
match &self.conn_new {
Some(sender) => {
if let Err(err) = sender.clone().try_send((framed_packet, address.into())) {
if err.is_full() {
debug!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address);
// it's not a 'big' error, but we did not manage to send the packet
// if the queue is full, we can't really do anything but to drop the packet
Err(io::Error::new(
io::ErrorKind::WouldBlock,
"connection queue is full",
))
} else if err.is_disconnected() {
debug!(
"Connection to {} seems to be dead. attempting to re-establish it...",
address
);
// it's not a 'big' error, but we did not manage to send the packet, but queue
// it up to send it as soon as the connection is re-established
self.make_connection(address, err.into_inner().0);
Err(io::Error::new(
io::ErrorKind::ConnectionAborted,
"reconnection attempt is in progress",
))
} else {
// this can't really happen, but let's safe-guard against it in case something changes in futures library
Err(io::Error::new(
io::ErrorKind::Other,
"unknown connection buffer error",
))
}
} else {
// this can't really happen, but let's safe-guard against it in case something changes in futures library
Err(io::Error::new(
io::ErrorKind::Other,
"unknown connection buffer error",
))
debug!("Sending packet to {:?}", address);
Ok(())
}
} else {
Ok(())
}
} else {
// there was never a connection to begin with
debug!("establishing initial connection to {}", address);
// it's not a 'big' error, but we did not manage to send the packet, but queue the packet
// for sending for as soon as the connection is created
self.make_connection(address, framed_packet);
Err(io::Error::new(
io::ErrorKind::NotConnected,
"connection is in progress",
))
None => {
// there was never a connection to begin with
debug!("establishing initial connection");
// it's not a 'big' error, but we did not manage to send the packet, but queue the packet
// for sending for as soon as the connection is created
self.make_connection(address, framed_packet);
Err(io::Error::new(
io::ErrorKind::NotConnected,
"connection is in progress",
))
}
}
}
}
@@ -29,6 +29,7 @@ log = { workspace = true }
url = { version = "2.2", features = ["serde"] }
tokio = { version = "1.24.1", features = ["sync", "time"] }
futures = "0.3"
openssl = { version = "0.10", features = ["vendored"], optional = true }
nym-coconut-interface = { path = "../../coconut-interface" }
nym-network-defaults = { path = "../../network-defaults" }
@@ -77,13 +78,14 @@ required-features = ["nyxd-client"]
nyxd-client = [
"async-trait",
"cosmrs",
"cosmwasm-std",
"cw3",
"cw4",
"prost",
"flate2",
"sha2",
"itertools",
"cosmwasm-std",
"openssl",
"prost",
"sha2",
"signing"
]
signing = [
@@ -1,12 +1,12 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::ContractBuildInformation;
use nym_contracts_common::{signing::Nonce, ContractBuildInformation};
use nym_service_provider_directory_common::{
msg::QueryMsg as SpQueryMsg,
response::{
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
},
NymAddress, ServiceId, ServiceInfo,
NymAddress, Service, ServiceId,
};
use serde::Deserialize;
@@ -63,17 +63,14 @@ pub trait SpDirectoryQueryClient {
.await
}
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, NyxdError> {
async fn get_all_services(&self) -> Result<Vec<Service>, NyxdError> {
let mut services = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.get_services_paged(start_after.take(), None).await?;
let last_id = paged_response.services.last().map(|serv| serv.service_id);
services.append(&mut paged_response.services);
if let Some(start_after_res) = last_id {
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
@@ -82,6 +79,13 @@ pub trait SpDirectoryQueryClient {
Ok(services)
}
async fn get_service_signing_nonce(&self, address: &AccountId) -> Result<Nonce, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::SigningNonce {
address: address.to_string(),
})
.await
}
}
#[async_trait]
@@ -2,8 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_contracts_common::signing::MessageSignature;
use nym_service_provider_directory_common::{
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceDetails, ServiceId,
};
use crate::nyxd::{
@@ -22,16 +23,16 @@ pub trait SpDirectorySigningClient {
async fn announce_service_provider(
&self,
nym_address: NymAddress,
service_type: ServiceType,
service: ServiceDetails,
owner_signature: MessageSignature,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_service_provider_directory_contract(
fee,
SpExecuteMsg::Announce {
nym_address,
service_type,
service,
owner_signature,
},
vec![deposit],
)
+1
View File
@@ -40,3 +40,4 @@ nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
nym-name-service-common = { path = "../cosmwasm-smart-contracts/name-service" }
nym-sphinx = { path = "../../common/nymsphinx" }
@@ -14,6 +14,7 @@ pub struct Mixnet {
pub command: MixnetCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetCommands {
/// Query the mixnet directory
@@ -15,6 +15,7 @@ pub struct MixnetOperators {
pub command: MixnetOperatorsCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsCommands {
/// Manage your mixnode
@@ -1,6 +1,7 @@
use clap::Parser;
use log::info;
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceType};
use nym_contracts_common::signing::MessageSignature;
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceDetails, ServiceType};
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
use crate::context::SigningClient;
@@ -10,9 +11,15 @@ pub struct Args {
#[clap(long)]
pub nym_address: String,
#[clap(long)]
pub signature: MessageSignature,
/// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym')
#[clap(long)]
pub deposit: u128,
#[clap(long)]
pub identity_key: String,
}
pub async fn announce(args: Args, client: SigningClient) {
@@ -20,12 +27,17 @@ pub async fn announce(args: Args, client: SigningClient) {
let nym_address = NymAddress::Address(args.nym_address);
let service_type = ServiceType::NetworkRequester;
let service = ServiceDetails {
nym_address,
service_type,
identity_key: args.identity_key,
};
let denom = client.current_chain_details().mix_denom.base.as_str();
let deposit = Coin::new(args.deposit, denom);
let res = client
.announce_service_provider(nym_address, service_type, deposit.into(), None)
.announce_service_provider(service, args.signature, deposit.into(), None)
.await
.expect("Failed to announce service provider");
@@ -0,0 +1,61 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
context::SigningClient,
utils::{account_id_to_cw_addr, DataWrapper},
};
use clap::Parser;
use cosmwasm_std::Coin;
use nym_bin_common::output_format::OutputFormat;
use nym_service_provider_directory_common::{
signing_types::construct_service_provider_announce_sign_payload, NymAddress,
ServiceType::NetworkRequester,
};
use nym_sphinx::addressing::clients::Recipient;
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub nym_address: Recipient,
#[clap(long)]
pub amount: u128,
#[clap(long)]
pub identity_key: String,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
pub async fn create_payload(args: Args, client: SigningClient) {
let service = nym_service_provider_directory_common::ServiceDetails {
nym_address: NymAddress::new(&args.nym_address.to_string()),
service_type: NetworkRequester,
identity_key: args.identity_key,
};
let denom = client.current_chain_details().mix_denom.base.as_str();
let deposit = Coin::new(args.amount, denom);
let nonce = match client.get_service_signing_nonce(client.address()).await {
Ok(nonce) => nonce,
Err(err) => {
eprint!(
"failed to query for the signing nonce of {}: {err}",
client.address()
);
return;
}
};
let address = account_id_to_cw_addr(client.address());
let payload =
construct_service_provider_announce_sign_payload(nonce, address, deposit, service);
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -1,6 +1,7 @@
use clap::{Args, Subcommand};
pub mod announce;
pub mod announce_sign_payload;
pub mod delete;
#[derive(Debug, Args)]
@@ -10,10 +11,13 @@ pub struct MixnetOperatorsService {
pub command: MixnetOperatorsServiceCommands,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsServiceCommands {
/// Announce service provider to the world
Announce(announce::Args),
/// Delete entry for service provider from the directory
Delete(delete::Args),
/// Create base58-encoded payload required for producing valid announce signature.
CreateServiceAnnounceSignPayload(announce_sign_payload::Args),
}
@@ -37,7 +37,7 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
for service in res.services {
table.add_row(vec![
service.service_id.to_string(),
service.service.announcer.to_string(),
service.announcer.to_string(),
service.service.service_type.to_string(),
service.service.nym_address.to_string(),
]);
@@ -1,6 +1,6 @@
[package]
name = "nym-contracts-common"
version = "0.4.0"
version = "0.5.0"
description = "Common library for Nym cosmwasm contracts"
edition = { workspace = true }
authors = { workspace = true }
@@ -11,6 +11,9 @@ use std::ops::Mul;
use std::str::FromStr;
use thiserror::Error;
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub fn truncate_decimal(amount: Decimal) -> Uint128 {
amount * Uint128::new(1)
}
@@ -1,6 +1,6 @@
[package]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.6.0"
description = "Common library for the Nym mixnet contract"
rust-version = "1.62"
edition = { workspace = true }
@@ -15,7 +15,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
schemars = "0.8"
thiserror = "1.0"
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
# use 0.4.1 as that's the version used by cosmwasm-std 1.0.0
# (and ideally we don't want to pull the same dependency twice)
serde-json-wasm = "=0.4.1"
@@ -4,6 +4,7 @@
use crate::error::MixnetContractError;
use crate::families::{Family, FamilyHead};
use crate::{Layer, RewardedSetNodeStatus};
use contracts_common::IdentityKey;
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
use schemars::JsonSchema;
@@ -11,8 +12,6 @@ use serde::{Deserialize, Serialize};
use std::ops::Index;
// type aliases for better reasoning about available data
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
pub type SphinxKey = String;
pub type SphinxKeyRef<'a> = &'a str;
pub type EpochId = u32;
@@ -7,5 +7,9 @@ edition = "2021"
[dependencies]
cosmwasm-std = { workspace = true }
nym-contracts-common = { path = "../contracts-common", version = "0.5.0" }
schemars = "0.8"
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
cw-utils = { workspace = true }
cw-controllers = { workspace = true }
@@ -1,10 +1,12 @@
use cosmwasm_std::{Addr, StdError};
use cw_controllers::AdminError;
use nym_service_provider_directory_common::{NymAddress, ServiceId};
use nym_contracts_common::signing::verifier::ApiVerifierError;
use thiserror::Error;
use crate::{NymAddress, ServiceId};
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
pub enum SpContractError {
#[error("{0}")]
Std(#[from] StdError),
@@ -46,6 +48,21 @@ pub enum ContractError {
value: String,
error_message: String,
},
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
MalformedEd25519IdentityKey(String),
#[error("Failed to recover ed25519 signature from its base58 representation - {0}")]
MalformedEd25519Signature(String),
#[error("Provided ed25519 signature did not verify correctly")]
InvalidEd25519Signature,
#[error("failed to verify message signature: {source}")]
SignatureVerificationFailure {
#[from]
source: ApiVerifierError,
},
}
pub(crate) type Result<T, E = ContractError> = std::result::Result<T, E>;
pub type Result<T, E = SpContractError> = std::result::Result<T, E>;
@@ -39,16 +39,16 @@ pub fn new_announce_event(service_id: ServiceId, service: Service) -> Event {
Event::new(ServiceProviderEventType::Announce)
.add_attribute(ACTION, ServiceProviderEventType::Announce)
.add_attribute(SERVICE_ID, service_id.to_string())
.add_attribute(SERVICE_TYPE, service.service_type.to_string())
.add_attribute(NYM_ADDRESS, service.nym_address.to_string())
.add_attribute(SERVICE_TYPE, service.service.service_type.to_string())
.add_attribute(NYM_ADDRESS, service.service.nym_address.to_string())
.add_attribute(OWNER, service.announcer.to_string())
}
pub fn new_delete_id_event(service_id: ServiceId, service: Service) -> Event {
pub fn new_delete_id_event(service: Service) -> Event {
Event::new(ServiceProviderEventType::DeleteId)
.add_attribute(ACTION, ServiceProviderEventType::DeleteId)
.add_attribute(SERVICE_ID, service_id.to_string())
.add_attribute(NYM_ADDRESS, service.nym_address.to_string())
.add_attribute(SERVICE_ID, service.service_id.to_string())
.add_attribute(NYM_ADDRESS, service.service.nym_address.to_string())
}
pub fn new_update_deposit_required_event(deposit_required: Coin) -> Event {
@@ -1,6 +1,8 @@
pub mod error;
pub mod events;
pub mod msg;
pub mod response;
pub mod signing_types;
pub mod types;
// Re-export all types at the top-level
@@ -1,5 +1,6 @@
use crate::{NymAddress, ServiceId, ServiceType};
use crate::{NymAddress, ServiceDetails, ServiceId};
use cosmwasm_std::Coin;
use nym_contracts_common::signing::MessageSignature;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -22,8 +23,8 @@ pub struct MigrateMsg {}
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Announce {
nym_address: NymAddress,
service_type: ServiceType,
service: ServiceDetails,
owner_signature: MessageSignature,
},
DeleteId {
service_id: ServiceId,
@@ -44,9 +45,12 @@ impl ExecuteMsg {
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::Announce {
nym_address,
service_type,
} => format!("announcing {nym_address} as type {service_type}"),
service,
owner_signature: _,
} => format!(
"announcing {} as type {}",
service.nym_address, service.service_type
),
ExecuteMsg::DeleteId { service_id } => {
format!("deleting service with service id {service_id}")
}
@@ -76,6 +80,9 @@ pub enum QueryMsg {
limit: Option<u32>,
start_after: Option<ServiceId>,
},
SigningNonce {
address: String,
},
Config {},
GetContractVersion {},
#[serde(rename = "get_cw2_contract_version")]
@@ -1,4 +1,4 @@
use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo};
use crate::{Service, ServiceId};
use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -13,22 +13,17 @@ pub struct ServiceInfoResponse {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServicesListResponse {
pub services: Vec<ServiceInfo>,
pub services: Vec<Service>,
}
impl ServicesListResponse {
pub fn new(services: Vec<(ServiceId, Service)>) -> ServicesListResponse {
ServicesListResponse {
services: services
.into_iter()
.map(|(service_id, service)| ServiceInfo::new(service_id, service))
.collect(),
}
pub fn new(services: Vec<Service>) -> ServicesListResponse {
ServicesListResponse { services }
}
}
impl From<&[ServiceInfo]> for ServicesListResponse {
fn from(services: &[ServiceInfo]) -> Self {
impl From<&[Service]> for ServicesListResponse {
fn from(services: &[Service]) -> Self {
Self {
services: services.to_vec(),
}
@@ -38,21 +33,17 @@ impl From<&[ServiceInfo]> for ServicesListResponse {
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct PagedServicesListResponse {
pub services: Vec<ServiceInfo>,
pub services: Vec<Service>,
pub per_page: usize,
pub start_next_after: Option<ServiceId>,
}
impl PagedServicesListResponse {
pub fn new(
services: Vec<(ServiceId, Service)>,
services: Vec<Service>,
per_page: usize,
start_next_after: Option<ServiceId>,
) -> PagedServicesListResponse {
let services = services
.into_iter()
.map(|(service_id, service)| ServiceInfo::new(service_id, service))
.collect();
PagedServicesListResponse {
services,
per_page,
@@ -66,12 +57,3 @@ impl PagedServicesListResponse {
pub struct ConfigResponse {
pub deposit_required: Coin,
}
impl From<Service> for ExecuteMsg {
fn from(service: Service) -> Self {
ExecuteMsg::Announce {
nym_address: service.nym_address,
service_type: service.service_type,
}
}
}
@@ -0,0 +1,33 @@
use cosmwasm_std::{Addr, Coin};
use nym_contracts_common::signing::{
ContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose,
};
use serde::Serialize;
use crate::ServiceDetails;
pub type SignableServiceProviderAnnounceMsg =
SignableMessage<ContractMessageContent<ServiceProviderAnnounce>>;
#[derive(Serialize)]
pub struct ServiceProviderAnnounce {
service: ServiceDetails,
}
impl SigningPurpose for ServiceProviderAnnounce {
fn message_type() -> MessageType {
MessageType::new("service-provider-announce")
}
}
pub fn construct_service_provider_announce_sign_payload(
nonce: Nonce,
sender: Addr,
deposit: Coin,
service: ServiceDetails,
) -> SignableServiceProviderAnnounceMsg {
let payload = ServiceProviderAnnounce { service };
let proxy = None;
let content = ContractMessageContent::new(sender, proxy, vec![deposit], payload);
SignableMessage::new(nonce, content)
}
@@ -1,6 +1,7 @@
use std::fmt::{Display, Formatter};
use cosmwasm_std::{Addr, Coin};
use nym_contracts_common::IdentityKey;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -9,11 +10,11 @@ pub type ServiceId = u32;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
pub struct Service {
/// The address of the service.
pub nym_address: NymAddress,
/// The service type.
pub service_type: ServiceType,
/// Service owner.
/// Unique id assigned to the anounced service.
pub service_id: ServiceId,
/// The announced service.
pub service: ServiceDetails,
/// Address of the service owner.
pub announcer: Addr,
/// Block height at which the service was added.
pub block_height: u64,
@@ -21,6 +22,16 @@ pub struct Service {
pub deposit: Coin,
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
pub struct ServiceDetails {
/// The address of the service.
pub nym_address: NymAddress,
/// The service type.
pub service_type: ServiceType,
/// The identity key of the service.
pub identity_key: IdentityKey,
}
/// The types of addresses supported.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
@@ -28,7 +39,7 @@ pub enum NymAddress {
/// String representation of a nym address, which is of the form
/// client_id.client_enc@gateway_id.
Address(String),
// For the future when we have a nym-dns contract
// String name that can looked up in the nym-name-service contract (once it exists)
//Name(String),
}
@@ -41,6 +52,7 @@ impl NymAddress {
pub fn as_str(&self) -> &str {
match self {
NymAddress::Address(address) => address,
//NymAddress::Name(name) => name,
}
}
}
@@ -66,19 +78,3 @@ impl std::fmt::Display for ServiceType {
write!(f, "{service_type}")
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServiceInfo {
pub service_id: ServiceId,
pub service: Service,
}
impl ServiceInfo {
pub fn new(service_id: ServiceId, service: Service) -> Self {
Self {
service_id,
service,
}
}
}
@@ -1,6 +1,6 @@
[package]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.7.0"
description = "Common library for the Nym vesting contract"
edition = { workspace = true }
authors = { workspace = true }
@@ -9,8 +9,8 @@ repository = { workspace = true }
[dependencies]
cosmwasm-std = { workspace = true }
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.5.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.0" }
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
ts-rs = {version = "6.1.2", optional = true}
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-crypto"
version = "0.3.0"
version = "0.4.0"
description = "Crypto library for the nym mixnet"
edition = { workspace = true }
authors = { workspace = true }
@@ -28,7 +28,7 @@ zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
# internal
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" }
nym-pemstore = { path = "../../common/pemstore", version = "0.2.0" }
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
[dev-dependencies]
rand_chacha = "0.2"
+14 -14
View File
@@ -19,20 +19,20 @@ macro_rules! measure {
cfg_if::cfg_if! {
if #[cfg(feature = "cpucycles")] {
let start_cycles = $crate::cpu_cycles();
// if the block needs to return something, we can return it
let r = $x;
let end_cycles = $crate::cpu_cycles();
let name = if let Some(meta) = tracing::Span::current().metadata() {
meta.name()
} else {
"measure"
};
match (start_cycles, end_cycles) {
(Ok(start), Ok(end)) => info!("{} cpucycles: {}", name, end - start),
(Err(e), _) => error!("{e}"),
(_, Err(e)) => error!("{e}"),
}
r
// if the block needs to return something, we can return it
let r = $x;
let end_cycles = $crate::cpu_cycles();
let name = if let Some(meta) = tracing::Span::current().metadata() {
meta.name()
} else {
"measure"
};
match (start_cycles, end_cycles) {
(Ok(start), Ok(end)) => log::trace!("{} cpucycles: {}", name, end - start),
(Err(e), _) => error!("{e}"),
(_, Err(e)) => error!("{e}"),
}
r
} else {
$x
}
+2 -2
View File
@@ -25,12 +25,12 @@ nym-sphinx-types = { path = "types" }
# those dependencies are due to intriducing preparer and receiver. Perpaphs that indicates they should be moved
# to separate crate?
nym-crypto = { path = "../crypto", version = "0.3.0" }
nym-crypto = { path = "../crypto", version = "0.4.0" }
nym-topology = { path = "../topology" }
[dev-dependencies]
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
nym-crypto = { path = "../crypto", version = "0.3.0", features = ["asymmetric"] }
nym-crypto = { path = "../crypto", version = "0.4.0", features = ["asymmetric"] }
# do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require
# net2 via tokio-util -> tokio -> mio -> net2
+1 -1
View File
@@ -26,7 +26,7 @@ const ACK_IV_SIZE: usize = 16;
const ACK_PACKET_SIZE: usize = ACK_IV_SIZE + FRAG_ID_LEN + SPHINX_PACKET_OVERHEAD;
const REGULAR_PACKET_SIZE: usize = 2 * 1024 + SPHINX_PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_8: usize = 8 * 1024 + SPHINX_PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_8: usize = 1 * 512 + SPHINX_PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_16: usize = 16 * 1024 + SPHINX_PACKET_OVERHEAD;
const EXTENDED_PACKET_SIZE_32: usize = 32 * 1024 + SPHINX_PACKET_OVERHEAD;
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-pemstore"
description = "Store private-public keypairs in PEM format"
version = "0.2.0"
version = "0.3.0"
edition = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
+4 -2
View File
@@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::str::FromStr;
pub mod old_config_v1_1_20_2;
pub use nym_service_providers_common::interface::ProviderInterfaceVersion;
pub use nym_socks5_requests::Socks5ProtocolVersion;
@@ -25,9 +27,9 @@ pub struct Config {
}
impl Config {
pub fn new<S: Into<String>>(id: S, provider_mix_address: S) -> Self {
pub fn new<S: Into<String>>(id: S, version: S, provider_mix_address: S) -> Self {
Config {
base: BaseClientConfig::new(id),
base: BaseClientConfig::new(id, version),
socks5: Socks5::new(provider_mix_address),
}
}
@@ -0,0 +1,87 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseClientConfigV1_1_20_2;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use crate::config::{Config, Socks5, Socks5Debug};
pub use nym_service_providers_common::interface::ProviderInterfaceVersion;
pub use nym_socks5_requests::Socks5ProtocolVersion;
const DEFAULT_CONNECTION_START_SURBS: u32 = 20;
const DEFAULT_PER_REQUEST_SURBS: u32 = 3;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_20_2 {
#[serde(flatten)]
pub base: BaseClientConfigV1_1_20_2,
pub socks5: Socks5V1_1_20_2,
}
impl From<ConfigV1_1_20_2> for Config {
fn from(value: ConfigV1_1_20_2) -> Self {
Config {
base: value.base.into(),
socks5: value.socks5.into(),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5V1_1_20_2 {
pub listening_port: u16,
pub provider_mix_address: String,
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
pub provider_interface_version: ProviderInterfaceVersion,
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
pub socks5_protocol_version: Socks5ProtocolVersion,
#[serde(default)]
pub send_anonymously: bool,
#[serde(default)]
pub socks5_debug: Socks5DebugV1_1_20_2,
}
impl From<Socks5V1_1_20_2> for Socks5 {
fn from(value: Socks5V1_1_20_2) -> Self {
Socks5 {
listening_port: value.listening_port,
provider_mix_address: value.provider_mix_address,
provider_interface_version: value.provider_interface_version,
socks5_protocol_version: value.socks5_protocol_version,
send_anonymously: value.send_anonymously,
socks5_debug: value.socks5_debug.into(),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5DebugV1_1_20_2 {
/// Number of reply SURBs attached to each `Request::Connect` message.
pub connection_start_surbs: u32,
/// Number of reply SURBs attached to each `Request::Send` message.
pub per_request_surbs: u32,
}
impl From<Socks5DebugV1_1_20_2> for Socks5Debug {
fn from(value: Socks5DebugV1_1_20_2) -> Self {
Socks5Debug {
connection_start_surbs: value.connection_start_surbs,
per_request_surbs: value.per_request_surbs,
}
}
}
impl Default for Socks5DebugV1_1_20_2 {
fn default() -> Self {
Socks5DebugV1_1_20_2 {
connection_start_surbs: DEFAULT_CONNECTION_START_SURBS,
per_request_surbs: DEFAULT_PER_REQUEST_SURBS,
}
}
}
+23 -16
View File
@@ -10,13 +10,16 @@ use crate::socks::{
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_client_core::client::base_client::non_wasm_helpers::default_query_dkg_client_from_config;
use nym_client_core::client::base_client::storage::gateway_details::GatewayDetailsStore;
use nym_client_core::client::base_client::storage::MixnetClientStorage;
use nym_client_core::client::base_client::{
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput, ClientState,
BaseClientBuilder, ClientInput, ClientOutput, ClientState,
};
use nym_client_core::client::key_manager::persistence::KeyStore;
use nym_client_core::client::replies::reply_storage::ReplyStorageBackend;
use nym_client_core::config::DebugConfig;
use nym_client_core::init::GatewaySetup;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType;
@@ -51,6 +54,8 @@ pub struct NymClient<S> {
config: Config,
storage: S,
setup_method: GatewaySetup,
}
impl<S> NymClient<S>
@@ -59,10 +64,20 @@ where
S::ReplyStore: Send + Sync,
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync,
<S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
{
pub fn new(config: Config, storage: S) -> Self {
NymClient { config, storage }
NymClient {
config,
storage,
setup_method: GatewaySetup::MustLoad,
}
}
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
self.setup_method = setup;
self
}
#[allow(clippy::too_many_arguments)]
@@ -187,24 +202,16 @@ where
}
pub async fn start(self) -> Result<StartedSocks5Client, Socks5ClientCoreError> {
let (key_store, reply_storage_backend, credential_store) = self.storage.into_split();
// don't create bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.base.client.disabled_credentials_mode {
// don't create dkg client for the bandwidth controller if credentials are disabled
let dkg_query_client = if self.config.base.client.disabled_credentials_mode {
None
} else {
Some(non_wasm_helpers::create_bandwidth_controller(
&self.config.base,
credential_store,
))
Some(default_query_dkg_client_from_config(&self.config.base))
};
let base_builder = BaseClientBuilder::<_, S>::new_from_base_config(
&self.config.base,
key_store,
bandwidth_controller,
reply_storage_backend,
);
let base_builder =
BaseClientBuilder::new(&self.config.base, self.storage, dkg_query_client)
.with_gateway_setup(self.setup_method);
let packet_type = self.config.base.debug.traffic.packet_type;
let mut started_client = base_builder.start_base().await?;
@@ -428,18 +428,14 @@ impl SocksClient {
Some(self.lane_queue_lengths.clone()),
self.shutdown_listener.clone(),
)
.run(move |conn_id, read_data, socket_closed| {
let provider_request = Socks5Request::new_send(
request_version.provider_protocol,
conn_id,
read_data,
socket_closed,
);
.run(move |socket_data| {
let lane = TransmissionLane::ConnectionId(socket_data.header.connection_id);
let provider_request =
Socks5Request::new_send(request_version.provider_protocol, socket_data);
let provider_message = Socks5ProviderRequest::new_provider_data(
request_version.provider_interface,
provider_request,
);
let lane = TransmissionLane::ConnectionId(conn_id);
if anonymous {
InputMessage::new_anonymous(
recipient,
@@ -1,19 +1,20 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Socks5ClientCoreError;
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_client_core::client::received_buffer::ReconstructedMessagesReceiver;
use nym_client_core::client::received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestSender,
};
use nym_service_providers_common::interface::{ControlResponse, ResponseContent};
use nym_socks5_proxy_helpers::connection_controller::ControllerSender;
use nym_socks5_proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
use nym_socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::TaskClient;
use crate::error::Socks5ClientCoreError;
pub(crate) struct MixnetResponseListener {
buffer_requester: ReceivedBufferRequestSender,
mix_response_receiver: ReconstructedMessagesReceiver,
@@ -79,12 +80,20 @@ impl MixnetResponseListener {
);
Err(err_response.into())
}
Socks5ResponseContent::NetworkData(response) => {
Socks5ResponseContent::NetworkData { content } => {
self.controller_sender
.unbounded_send(response.into())
.unbounded_send(ControllerCommand::new_send(content))
.unwrap();
Ok(())
}
Socks5ResponseContent::Query(response) => {
error!("received a query response which we don't know how to handle yet!");
error!("got: {:?}", response);
// I guess we'd need another channel here to forward those to where they need to go
Ok(())
}
}
}
+172 -142
View File
@@ -1,6 +1,21 @@
use crate::message::OrderedMessage;
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use log::*;
use std::collections::HashMap;
use std::collections::BTreeMap;
use thiserror::Error;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum OrderedMessageError {
#[error("received message with sequence number {received}, which is way higher than our current {current}")]
MessageSequenceTooLarge { current: u64, received: u64 },
#[error("received message with sequence number {received}, while we're already at {current}!")]
MessageAlreadyReconstructed { current: u64, received: u64 },
#[error("attempted to overwrite message at sequence {received}")]
AttemptedToOverwriteSequence { received: u64 },
}
/// Stores messages and emits them in order.
///
@@ -9,36 +24,58 @@ use std::collections::HashMap;
/// to fill up with the full sequence.
#[derive(Debug)]
pub struct OrderedMessageBuffer {
next_index: u64,
messages: HashMap<u64, OrderedMessage>,
next_sequence: u64,
messages: BTreeMap<u64, Vec<u8>>,
}
/// Data returned from `OrderedMessageBuffer` on a successful read of gapless ordered data.
#[derive(Debug, PartialEq, Eq)]
pub struct ReadContiguousData {
pub data: Vec<u8>,
pub last_index: u64,
pub last_sequence: u64,
}
const MAX_REASONABLE_OFFSET: u64 = 1000;
impl OrderedMessageBuffer {
pub fn new() -> OrderedMessageBuffer {
OrderedMessageBuffer {
next_index: 0,
messages: HashMap::new(),
next_sequence: 0,
messages: BTreeMap::new(),
}
}
/// Writes a message to the buffer. messages are sort on insertion, so
/// that later on multiple reads for incomplete sequences don't result in
/// useless sort work.
pub fn write(&mut self, message: OrderedMessage) {
pub fn write(&mut self, sequence: u64, data: Vec<u8>) -> Result<(), OrderedMessageError> {
// reject messages that have clearly malformed sequence
if sequence > self.next_sequence + MAX_REASONABLE_OFFSET {
return Err(OrderedMessageError::MessageSequenceTooLarge {
current: self.next_sequence,
received: sequence,
});
}
if self.messages.contains_key(&sequence) {
return Err(OrderedMessageError::AttemptedToOverwriteSequence { received: sequence });
}
if sequence < self.next_sequence {
return Err(OrderedMessageError::MessageAlreadyReconstructed {
current: self.next_sequence,
received: sequence,
});
}
trace!(
"Writing message index: {} length {:?} to OrderedMessageBuffer.",
message.index,
message.data.len()
"Writing message index: {} length {} to OrderedMessageBuffer.",
sequence,
data.len()
);
self.messages.insert(message.index, message);
self.messages.insert(sequence, data);
Ok(())
}
/// Returns `Option<Vec<u8>>` where it's `Some(bytes)` if there is gapless
@@ -49,33 +86,31 @@ impl OrderedMessageBuffer {
/// a read will return the bytes of messages 0, 1, 2. Subsequent reads will
/// return `None` until message 3 comes in, at which point 3, 4, and any
/// further contiguous messages which have arrived will be returned.
#[must_use]
pub fn read(&mut self) -> Option<ReadContiguousData> {
if !self.messages.contains_key(&self.next_index) {
if !self.messages.contains_key(&self.next_sequence) {
return None;
}
let mut contiguous_messages = Vec::new();
let mut index = self.next_index;
let mut seq = self.next_sequence;
while let Some(ordered_message) = self.messages.remove(&index) {
contiguous_messages.push(ordered_message);
index += 1;
while let Some(mut data) = self.messages.remove(&seq) {
contiguous_messages.append(&mut data);
seq += 1;
}
let high_water = index;
self.next_index = high_water;
trace!("Next high water mark is: {}", high_water);
let high_water = seq;
self.next_sequence = high_water;
trace!("Next high water mark is: {high_water}");
// dig out the bytes from inside the struct
let data: Vec<u8> = contiguous_messages
.into_iter()
.flat_map(|message| message.data)
.collect();
trace!("Returning {} bytes from ordered message buffer", data.len());
trace!(
"Returning {} bytes from ordered message buffer",
contiguous_messages.len()
);
Some(ReadContiguousData {
data,
last_index: index,
data: contiguous_messages,
last_sequence: seq,
})
}
}
@@ -90,6 +125,64 @@ impl Default for OrderedMessageBuffer {
mod test_chunking_and_reassembling {
use super::*;
#[test]
fn trying_to_write_unreasonable_high_sequence() {
let mut buffer = OrderedMessageBuffer::new();
let first_message = vec![1, 2, 3, 4];
let second_message = vec![5, 6, 7, 8];
buffer.write(0, first_message).unwrap();
buffer.write(1, second_message).unwrap();
assert_eq!(
Err(OrderedMessageError::MessageSequenceTooLarge {
current: 0,
received: 12345678
}),
buffer.write(12345678, b"foomp".to_vec())
)
}
#[test]
fn trying_to_overwrite_sequence() {
let mut buffer = OrderedMessageBuffer::new();
let message = vec![1, 2, 3, 4];
buffer.write(0, message.clone()).unwrap();
buffer.write(1, message.clone()).unwrap();
buffer.write(2, message.clone()).unwrap();
buffer.write(3, message.clone()).unwrap();
for seq in 0..=3 {
assert_eq!(
Err(OrderedMessageError::AttemptedToOverwriteSequence { received: seq }),
buffer.write(seq, message.clone())
)
}
}
#[test]
fn writing_past_data() {
let mut buffer = OrderedMessageBuffer::new();
let message = vec![1, 2, 3, 4];
buffer.write(0, message.clone()).unwrap();
buffer.write(1, message.clone()).unwrap();
buffer.write(2, message.clone()).unwrap();
buffer.write(3, message.clone()).unwrap();
let _ = buffer.read().unwrap();
for seq in 0..=3 {
assert_eq!(
Err(OrderedMessageError::MessageAlreadyReconstructed {
current: 4,
received: seq
}),
buffer.write(seq, message.clone())
)
}
}
#[cfg(test)]
mod reading_from_and_writing_to_the_buffer {
use super::*;
@@ -102,20 +195,14 @@ mod test_chunking_and_reassembling {
fn read_returns_ordered_bytes_and_resets_buffer() {
let mut buffer = OrderedMessageBuffer::new();
let first_message = OrderedMessage {
data: vec![1, 2, 3, 4],
index: 0,
};
let second_message = OrderedMessage {
data: vec![5, 6, 7, 8],
index: 1,
};
let first_message = vec![1, 2, 3, 4];
let second_message = vec![5, 6, 7, 8];
buffer.write(first_message);
buffer.write(0, first_message).unwrap();
let first_read = buffer.read().unwrap().data;
assert_eq!(vec![1, 2, 3, 4], first_read);
buffer.write(second_message);
buffer.write(1, second_message).unwrap();
let second_read = buffer.read().unwrap().data;
assert_eq!(vec![5, 6, 7, 8], second_read);
@@ -126,17 +213,11 @@ mod test_chunking_and_reassembling {
fn test_multiple_adds_stacks_up_bytes_in_the_buffer() {
let mut buffer = OrderedMessageBuffer::new();
let first_message = OrderedMessage {
data: vec![1, 2, 3, 4],
index: 0,
};
let second_message = OrderedMessage {
data: vec![5, 6, 7, 8],
index: 1,
};
let first_message = vec![1, 2, 3, 4];
let second_message = vec![5, 6, 7, 8];
buffer.write(first_message);
buffer.write(second_message);
buffer.write(0, first_message).unwrap();
buffer.write(1, second_message).unwrap();
let second_read = buffer.read();
assert_eq!(vec![1, 2, 3, 4, 5, 6, 7, 8], second_read.unwrap().data);
assert_eq!(None, buffer.read()); // second read on fully ordered result set is empty
@@ -146,17 +227,11 @@ mod test_chunking_and_reassembling {
fn out_of_order_adds_results_in_ordered_byte_vector() {
let mut buffer = OrderedMessageBuffer::new();
let first_message = OrderedMessage {
data: vec![1, 2, 3, 4],
index: 0,
};
let second_message = OrderedMessage {
data: vec![5, 6, 7, 8],
index: 1,
};
let first_message = vec![1, 2, 3, 4];
let second_message = vec![5, 6, 7, 8];
buffer.write(second_message);
buffer.write(first_message);
buffer.write(1, second_message).unwrap();
buffer.write(0, first_message).unwrap();
let read = buffer.read().unwrap().data;
assert_eq!(vec![1, 2, 3, 4, 5, 6, 7, 8], read);
assert_eq!(None, buffer.read()); // second read on fully ordered result set is empty
@@ -170,23 +245,13 @@ mod test_chunking_and_reassembling {
fn setup() -> OrderedMessageBuffer {
let mut buffer = OrderedMessageBuffer::new();
let zero_message = OrderedMessage {
data: vec![0, 0, 0, 0],
index: 0,
};
let one_message = OrderedMessage {
data: vec![1, 1, 1, 1],
index: 1,
};
let zero_message = vec![0, 0, 0, 0];
let one_message = vec![1, 1, 1, 1];
let three_message = vec![3, 3, 3, 3];
let three_message = OrderedMessage {
data: vec![3, 3, 3, 3],
index: 3,
};
buffer.write(zero_message);
buffer.write(one_message);
buffer.write(three_message);
buffer.write(0, zero_message).unwrap();
buffer.write(1, one_message).unwrap();
buffer.write(3, three_message).unwrap();
buffer
}
#[test]
@@ -199,43 +264,31 @@ mod test_chunking_and_reassembling {
assert_eq!(None, buffer.read());
// let's add another message, leaving a gap in place at index 2
let five_message = OrderedMessage {
data: vec![5, 5, 5, 5],
index: 5,
};
buffer.write(five_message);
let five_message = vec![5, 5, 5, 5];
buffer.write(5, five_message).unwrap();
assert_eq!(None, buffer.read());
}
#[test]
fn filling_the_gap_allows_us_to_get_everything() {
let mut buffer = setup();
buffer.read(); // that burns the first two. We still have a gap before the 3s.
let _ = buffer.read(); // that burns the first two. We still have a gap before the 3s.
let two_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 2,
};
buffer.write(two_message);
let two_message = vec![2, 2, 2, 2];
buffer.write(2, two_message).unwrap();
let more_ordered_bytes = buffer.read().unwrap().data;
assert_eq!([2, 2, 2, 2, 3, 3, 3, 3].to_vec(), more_ordered_bytes);
// let's add another message
let five_message = OrderedMessage {
data: vec![5, 5, 5, 5],
index: 5,
};
buffer.write(five_message);
let five_message = vec![5, 5, 5, 5];
buffer.write(5, five_message).unwrap();
assert_eq!(None, buffer.read());
// let's fill in the gap of 4s now and read again
let four_message = OrderedMessage {
data: vec![4, 4, 4, 4],
index: 4,
};
buffer.write(four_message);
let four_message = vec![4, 4, 4, 4];
buffer.write(4, four_message).unwrap();
assert_eq!(
[4, 4, 4, 4, 5, 5, 5, 5].to_vec(),
@@ -249,70 +302,47 @@ mod test_chunking_and_reassembling {
#[test]
fn filling_the_gap_allows_us_to_get_everything_when_last_element_is_empty() {
let mut buffer = OrderedMessageBuffer::new();
let zero_message = OrderedMessage {
data: vec![0, 0, 0, 0],
index: 0,
};
let one_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 1,
};
let two_message = OrderedMessage {
data: vec![],
index: 2,
};
let zero_message = vec![0, 0, 0, 0];
let one_message = vec![2, 2, 2, 2];
let two_message = vec![];
buffer.write(zero_message);
buffer.write(0, zero_message).unwrap();
assert!(buffer.read().is_some()); // burn the buffer
buffer.write(two_message);
buffer.write(one_message);
buffer.write(2, two_message).unwrap();
buffer.write(1, one_message).unwrap();
assert!(buffer.read().is_some());
assert_eq!(buffer.next_index, 3);
assert_eq!(buffer.next_sequence, 3);
}
#[test]
fn works_with_gaps_bigger_than_one() {
let mut buffer = OrderedMessageBuffer::new();
let zero_message = OrderedMessage {
data: vec![0, 0, 0, 0],
index: 0,
};
let one_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 1,
};
let two_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 2,
};
let three_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 3,
};
let four_message = OrderedMessage {
data: vec![2, 2, 2, 2],
index: 4,
};
buffer.write(zero_message);
let zero_message = vec![0, 0, 0, 0];
let one_message = vec![2, 2, 2, 2];
let two_message = vec![2, 2, 2, 2];
let three_message = vec![2, 2, 2, 2];
let four_message = vec![2, 2, 2, 2];
buffer.write(0, zero_message).unwrap();
assert!(buffer.read().is_some());
assert_eq!(buffer.next_index, 1);
assert_eq!(buffer.next_sequence, 1);
buffer.write(four_message);
buffer.write(4, four_message).unwrap();
assert!(buffer.read().is_none());
assert_eq!(buffer.next_index, 1);
assert_eq!(buffer.next_sequence, 1);
buffer.write(three_message);
buffer.write(3, three_message).unwrap();
assert!(buffer.read().is_none());
assert_eq!(buffer.next_index, 1);
assert_eq!(buffer.next_sequence, 1);
buffer.write(two_message);
buffer.write(2, two_message).unwrap();
assert!(buffer.read().is_none());
assert_eq!(buffer.next_index, 1);
assert_eq!(buffer.next_sequence, 1);
buffer.write(one_message);
buffer.write(1, one_message).unwrap();
assert!(buffer.read().is_some());
assert_eq!(buffer.next_index, 5)
assert_eq!(buffer.next_sequence, 5)
}
}
}
+1 -6
View File
@@ -1,8 +1,3 @@
mod buffer;
mod message;
mod sender;
pub use buffer::{OrderedMessageBuffer, ReadContiguousData};
pub use message::MessageError;
pub use message::OrderedMessage;
pub use sender::OrderedMessageSender;
pub use buffer::{OrderedMessageBuffer, OrderedMessageError, ReadContiguousData};
-143
View File
@@ -1,143 +0,0 @@
// Copyright 2020-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::cmp::Ordering;
use thiserror::Error;
#[derive(Error, Debug, PartialEq, Eq)]
pub enum MessageError {
#[error("the received message was empty")]
NoData,
#[error("could not extract message index. Received {received} bytes, but expected {expected}")]
IndexTooShort { received: usize, expected: usize },
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OrderedMessage {
pub data: Vec<u8>,
pub index: u64,
}
impl OrderedMessage {
/// Serializes an `OrderedMessage` into bytes.
/// The output format is:
/// | 8 bytes index | data... |
pub fn into_bytes(self) -> Vec<u8> {
self.index
.to_be_bytes()
.iter()
.cloned()
.chain(self.data.into_iter())
.collect()
}
/// Attempts to deserialize an `OrderedMessage` from bytes.
pub fn try_from_bytes(data: Vec<u8>) -> Result<OrderedMessage, MessageError> {
if data.is_empty() {
return Err(MessageError::NoData);
}
if data.len() < 8 {
return Err(MessageError::IndexTooShort {
received: data.len(),
expected: 8,
});
}
let index = u64::from_be_bytes([
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
]);
Ok(OrderedMessage {
data: data[8..].to_vec(),
index,
})
}
}
/// Order messages by their index only, ignoring their data
impl PartialOrd for OrderedMessage {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some((self.index).cmp(&(other.index)))
}
}
#[cfg(test)]
mod ordered_message_to_bytes {
use super::*;
#[test]
fn works() {
let message = OrderedMessage {
data: vec![123],
index: 1,
};
let bytes = message.into_bytes();
let expected = vec![0, 0, 0, 0, 0, 0, 0, 1, 123];
assert_eq!(expected, bytes);
}
}
#[cfg(test)]
mod ordered_message_from_bytes {
use super::*;
#[test]
fn fails_when_there_is_no_data() {
let result = OrderedMessage::try_from_bytes(Vec::new());
assert_eq!(Err(MessageError::NoData), result);
}
#[test]
fn fails_when_data_is_too_short() {
let result = OrderedMessage::try_from_bytes(vec![1, 2, 3]);
assert_eq!(
Err(MessageError::IndexTooShort {
received: 3,
expected: 8
}),
result
);
}
#[test]
fn works_when_there_is_enough_to_make_a_sequence_number_but_no_message_data() {
let expected = OrderedMessage {
data: Vec::new(),
index: 1,
};
let result = OrderedMessage::try_from_bytes(vec![0, 0, 0, 0, 0, 0, 0, 1]).unwrap();
assert_eq!(expected, result);
}
#[test]
fn works_when_there_is_seq_number_and_data() {
let expected = OrderedMessage {
data: vec![255, 255, 255],
index: 1,
};
let result =
OrderedMessage::try_from_bytes(vec![0, 0, 0, 0, 0, 0, 0, 1, 255, 255, 255]).unwrap();
assert_eq!(expected, result);
}
}
#[test]
fn empty_message_does_not_affect_ordering() {
let mut msg1 = OrderedMessage {
data: vec![255, 255, 255],
index: 1,
};
let mut msg2 = OrderedMessage {
data: vec![],
index: 2,
};
assert!(msg1 < msg2);
msg1.index = 2;
msg2.index = 1;
assert!(msg1 > msg2);
}
@@ -1,57 +0,0 @@
use crate::message::OrderedMessage;
/// Assigns sequence numbers to outbound byte vectors. These messages can then
/// be reassembled into an ordered sequence by the `OrderedMessageSender`.
#[derive(Debug)]
pub struct OrderedMessageSender {
next_index: u64,
}
impl OrderedMessageSender {
pub fn new() -> OrderedMessageSender {
OrderedMessageSender { next_index: 0 }
}
/// Turns raw bytes into an OrderedMessage containing the original bytes
/// and a sequence number;
pub fn wrap_message(&mut self, input: Vec<u8>) -> OrderedMessage {
let message = OrderedMessage {
data: input.to_vec(),
index: self.next_index,
};
self.next_index += 1;
message
}
}
impl Default for OrderedMessageSender {
fn default() -> Self {
OrderedMessageSender::new()
}
}
#[cfg(test)]
mod ordered_message_sender {
use super::*;
mod when_input_bytes_are_empty {}
#[cfg(test)]
mod sequence_index_numbers {
use super::*;
#[test]
fn increase_as_messages_are_sent() {
let mut sender = OrderedMessageSender::new();
let first_bytes = vec![1, 2, 3, 4];
let second_bytes = vec![5, 6, 7, 8];
let first_message = sender.wrap_message(first_bytes);
assert_eq!(first_message.index, 0);
let second_message = sender.wrap_message(second_bytes);
assert_eq!(second_message.index, 1);
}
}
}
@@ -1,11 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_ordered_buffer::{OrderedMessage, OrderedMessageBuffer, ReadContiguousData};
use nym_socks5_requests::{ConnectionId, NetworkData, SendRequest};
use nym_ordered_buffer::{OrderedMessageBuffer, ReadContiguousData};
use nym_socks5_requests::{ConnectionId, SocketData};
use nym_task::connections::{ConnectionCommand, ConnectionCommandSender};
use nym_task::TaskClient;
use std::collections::{HashMap, HashSet};
@@ -40,29 +40,13 @@ pub enum ControllerCommand {
connection_id: ConnectionId,
},
Send {
connection_id: ConnectionId,
data: Vec<u8>,
is_closed: bool,
data: SocketData,
},
}
impl From<NetworkData> for ControllerCommand {
fn from(value: NetworkData) -> Self {
ControllerCommand::Send {
connection_id: value.connection_id,
data: value.data,
is_closed: value.is_closed,
}
}
}
impl From<SendRequest> for ControllerCommand {
fn from(value: SendRequest) -> Self {
ControllerCommand::Send {
connection_id: value.conn_id,
data: value.data,
is_closed: value.local_closed,
}
impl ControllerCommand {
pub fn new_send(data: SocketData) -> Self {
ControllerCommand::Send { data }
}
}
@@ -74,18 +58,13 @@ struct ActiveConnection {
}
impl ActiveConnection {
fn write_to_buf(&mut self, payload: Vec<u8>, is_closed: bool) {
let ordered_message = match OrderedMessage::try_from_bytes(payload) {
Ok(msg) => msg,
Err(err) => {
error!("Malformed ordered message - {err}");
return;
}
};
fn write_to_buf(&mut self, seq: u64, payload: Vec<u8>, is_closed: bool) {
if is_closed {
self.closed_at_index = Some(ordered_message.index);
self.closed_at_index = Some(seq);
}
if let Err(err) = self.ordered_buffer.write(seq, payload) {
error!("failed to write to the buffer: {err}")
}
self.ordered_buffer.write(ordered_message);
}
fn read_from_buf(&mut self) -> Option<ReadContiguousData> {
@@ -117,7 +96,7 @@ pub struct Controller {
// buffer for messages received before connection was established due to mixnet being able to
// un-order messages. Note we don't ever expect to have more than 1-2 messages per connection here
pending_messages: HashMap<ConnectionId, Vec<(Vec<u8>, bool)>>,
pending_messages: HashMap<ConnectionId, Vec<SocketData>>,
shutdown: TaskClient,
}
@@ -154,8 +133,8 @@ impl Controller {
// check if there were any pending messages
if let Some(pending) = self.pending_messages.remove(&conn_id) {
debug!("There were some pending messages for {}", conn_id);
for (payload, is_closed) in pending {
self.send_to_connection(conn_id, payload, is_closed)
for data in pending {
self.send_to_connection(data)
}
}
}
@@ -184,20 +163,25 @@ impl Controller {
}
}
fn send_to_connection(&mut self, conn_id: ConnectionId, payload: Vec<u8>, is_closed: bool) {
if let Some(active_connection) = self.active_connections.get_mut(&conn_id) {
if !payload.is_empty() {
active_connection.write_to_buf(payload, is_closed);
} else if !is_closed {
error!("Tried to write an empty message to a not-closing connection. Please let us know if you see this message");
}
fn send_to_connection(&mut self, message: SocketData) {
let hdr = message.header;
if let Some(active_connection) = self.active_connections.get_mut(&hdr.connection_id) {
// always write to the buffer even if payload is empty (because it could have been the keep-alive message)
active_connection.write_to_buf(hdr.seq, message.data, hdr.local_socket_closed);
if let Some(payload) = active_connection.read_from_buf() {
if let Some(closed_at_index) = active_connection.closed_at_index {
if payload.last_index > closed_at_index {
if payload.last_sequence > closed_at_index {
active_connection.is_closed = true;
}
}
// however, don't send empty payload to the actual connection if it's not a close message
// TODO: or should we?
if payload.data.is_empty() && !active_connection.is_closed {
return;
}
if let Err(err) = active_connection
.connection_sender
.as_mut()
@@ -207,34 +191,26 @@ impl Controller {
socket_closed: active_connection.is_closed,
})
{
error!("WTF IS THIS: {err}");
error!("failed to send on the active connection channel: {err}");
}
// TODO: ABOVE UNWRAP CAUSED A CRASH IN A NORMAL USE!!!!
// TODO:
// TODO: surprisingly it only happened on socks client, never on nSP
// TODO:
// TODO:
// TODO:
// TODO:
}
} else if !self.recently_closed.contains(&conn_id) {
} else if !self.recently_closed.contains(&hdr.connection_id) {
debug!("Received a 'Send' before 'Connect' - going to buffer the data");
let pending = self
.pending_messages
.entry(conn_id)
.entry(hdr.connection_id)
.or_insert_with(Vec::new);
pending.push((payload, is_closed));
} else if !is_closed {
pending.push(message);
} else if !hdr.local_socket_closed {
error!(
"Tried to write to closed connection {} ({} bytes were 'lost)",
conn_id,
payload.len()
hdr.connection_id,
message.data.len()
);
} else {
debug!(
"Tried to write to closed connection {}, but remote is already closed",
conn_id
hdr.connection_id
)
}
}
@@ -243,8 +219,8 @@ impl Controller {
loop {
tokio::select! {
command = self.receiver.next() => match command {
Some(ControllerCommand::Send{connection_id, data, is_closed}) => {
self.send_to_connection(connection_id, data, is_closed)
Some(ControllerCommand::Send{data}) => {
self.send_to_connection(data)
}
Some(ControllerCommand::Insert{connection_id, connection_sender}) => {
self.insert_connection(connection_id, connection_sender)
+1
View File
@@ -3,4 +3,5 @@
pub mod available_reader;
pub mod connection_controller;
pub mod ordered_sender;
pub mod proxy_runner;
@@ -0,0 +1,116 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::proxy_runner::MixProxySender;
use bytes::Bytes;
use log::{debug, error};
use nym_socks5_requests::{ConnectionId, SocketData};
use std::io;
pub(crate) struct OrderedMessageSender<F, S> {
connection_id: ConnectionId,
// addresses are provided for better logging
local_destination_address: String,
remote_source_address: String,
mixnet_sender: MixProxySender<S>,
next_message_seq: u64,
mix_message_adapter: F,
}
impl<F, S> OrderedMessageSender<F, S>
where
F: Fn(SocketData) -> S,
{
pub(crate) fn new(
local_destination_address: String,
remote_source_address: String,
connection_id: ConnectionId,
mixnet_sender: MixProxySender<S>,
mix_message_adapter: F,
) -> Self {
OrderedMessageSender {
local_destination_address,
remote_source_address,
connection_id,
mixnet_sender,
next_message_seq: 0,
mix_message_adapter,
}
}
fn sequence(&mut self) -> u64 {
let next = self.next_message_seq;
self.next_message_seq += 1;
next
}
fn construct_message(&mut self, data: Vec<u8>, local_socket_closed: bool) -> S {
let data = SocketData::new(
self.sequence(),
self.connection_id,
local_socket_closed,
data,
);
(self.mix_message_adapter)(data)
}
async fn send_message(&self, message: S) {
if self.mixnet_sender.send(message).await.is_err() {
panic!("BatchRealMessageReceiver has stopped receiving!")
}
}
pub(crate) async fn send_empty_close(&mut self) {
let message = self.construct_message(Vec::new(), true);
self.send_message(message).await
}
pub(crate) async fn send_empty_keepalive(&mut self) {
log::trace!("Sending keepalive for connection: {}", self.connection_id);
let message = self.construct_message(Vec::new(), false);
self.send_message(message).await
}
pub(crate) fn process_data(&self, read_data: Option<io::Result<Bytes>>) -> ProcessedData {
let (read_data, is_finished) = match read_data {
Some(data) => match data {
Ok(data) => (data, false),
Err(err) => {
error!(target: &*format!("({}) socks5 inbound", self.connection_id), "failed to read request from the socket - {err}");
(Default::default(), true)
}
},
None => (Default::default(), true),
};
ProcessedData {
data: read_data,
is_done: is_finished,
}
}
fn log_sent_message(&self, data: &ProcessedData) {
debug!(
target: &*format!("({}) socks5 inbound", self.connection_id),
"[{} bytes]\t{} → local → mixnet → remote → {}. Local closed: {}",
data.data.len(),
self.local_destination_address,
self.remote_source_address,
data.is_done
);
}
/// Send data read from local socket into the mixnet
pub(crate) async fn send_data(&mut self, data: ProcessedData) {
self.log_sent_message(&data);
let message = self.construct_message(data.data.into(), data.is_done);
self.send_message(message).await;
}
}
// helper wrapper to keep track of field meanings
pub(crate) struct ProcessedData {
data: Bytes,
pub(crate) is_done: bool,
}
@@ -1,106 +1,22 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::MixProxySender;
use super::SHUTDOWN_TIMEOUT;
use crate::available_reader::AvailableReader;
use crate::ordered_sender::OrderedMessageSender;
use crate::proxy_runner::KEEPALIVE_INTERVAL;
use bytes::Bytes;
use futures::FutureExt;
use futures::StreamExt;
use log::*;
use nym_ordered_buffer::OrderedMessageSender;
use nym_socks5_requests::ConnectionId;
use nym_socks5_requests::{ConnectionId, SocketData};
use nym_task::connections::LaneQueueLengths;
use nym_task::connections::TransmissionLane;
use nym_task::TaskClient;
use std::fmt::Debug;
use std::sync::Arc;
use std::time::Duration;
use std::{io, sync::Arc};
use tokio::select;
use tokio::{net::tcp::OwnedReadHalf, sync::Notify, time::sleep};
async fn send_empty_close<F, S>(
connection_id: ConnectionId,
message_sender: &mut OrderedMessageSender,
mix_sender: &MixProxySender<S>,
adapter_fn: F,
) where
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
S: Debug,
{
let ordered_msg = message_sender.wrap_message(Vec::new()).into_bytes();
mix_sender
.send(adapter_fn(connection_id, ordered_msg, true))
.await
.expect("BatchRealMessageReceiver has stopped receiving!");
}
async fn send_empty_keepalive<F, S>(
connection_id: ConnectionId,
message_sender: &mut OrderedMessageSender,
mix_sender: &MixProxySender<S>,
adapter_fn: F,
) where
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
S: Debug,
{
log::trace!("Sending keepalive for connection: {connection_id}");
let ordered_msg = message_sender.wrap_message(Vec::new()).into_bytes();
mix_sender
.send(adapter_fn(connection_id, ordered_msg, false))
.await
.expect("BatchRealMessageReceiver has stopped receiving!");
}
async fn deal_with_data<F, S>(
read_data: Option<io::Result<Bytes>>,
local_destination_address: &str,
remote_source_address: &str,
connection_id: ConnectionId,
message_sender: &mut OrderedMessageSender,
mix_sender: &MixProxySender<S>,
adapter_fn: F,
) -> bool
where
F: Fn(ConnectionId, Vec<u8>, bool) -> S,
S: Debug,
{
let (read_data, is_finished) = match read_data {
Some(data) => match data {
Ok(data) => (data, false),
Err(err) => {
error!(target: &*format!("({connection_id}) socks5 inbound"), "failed to read request from the socket - {err}");
(Default::default(), true)
}
},
None => (Default::default(), true),
};
debug!(
target: &*format!("({connection_id}) socks5 inbound"),
"[{} bytes]\t{} → local → mixnet → remote → {}. Local closed: {}",
read_data.len(),
local_destination_address,
remote_source_address,
is_finished
);
// if we're sending through the mixnet increase the sequence number...
let ordered_msg = message_sender.wrap_message(read_data.to_vec()).into_bytes();
log::trace!(
"pushing data down the input sender: size: {}",
ordered_msg.len()
);
mix_sender
.send(adapter_fn(connection_id, ordered_msg, is_finished))
.await
.expect("InputMessageReceiver has stopped receiving!");
is_finished
}
async fn wait_until_lane_empty(lane_queue_lengths: &Option<LaneQueueLengths>, connection_id: u64) {
if let Some(lane_queue_lengths) = lane_queue_lengths {
if tokio::time::timeout(
@@ -158,27 +74,21 @@ async fn wait_for_lane(
}
}
#[allow(clippy::too_many_arguments)]
pub(super) async fn run_inbound<F, S>(
mut reader: OwnedReadHalf,
local_destination_address: String, // addresses are provided for better logging
remote_source_address: String,
mut message_sender: OrderedMessageSender<F, S>,
connection_id: ConnectionId,
mix_sender: MixProxySender<S>,
available_plaintext_per_mix_packet: usize,
adapter_fn: F,
shutdown_notify: Arc<Notify>,
lane_queue_lengths: Option<LaneQueueLengths>,
mut shutdown_listener: TaskClient,
) -> OwnedReadHalf
where
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + 'static,
S: Debug,
F: Fn(SocketData) -> S + Send + 'static,
{
// TODO: this multiplication by 4 is completely arbitrary here
let mut available_reader =
AvailableReader::new(&mut reader, Some(available_plaintext_per_mix_packet * 4));
let mut message_sender = OrderedMessageSender::new();
// Shutdown if outbound signled to shutdown
let shutdown_future = shutdown_notify.notified().then(|_| sleep(SHUTDOWN_TIMEOUT));
@@ -217,7 +127,7 @@ where
);
// inform remote just in case it was closed because of lack of heartbeat.
// worst case the remote will just have couple of false negatives
send_empty_close(connection_id, &mut message_sender, &mix_sender, &adapter_fn).await;
message_sender.send_empty_close().await;
break;
}
_ = shutdown_listener.recv() => {
@@ -233,7 +143,7 @@ where
break;
}
_ = keepalive_timer.tick() => {
send_empty_keepalive(connection_id, &mut message_sender, &mix_sender, &adapter_fn).await;
message_sender.send_empty_keepalive().await;
}
// Read the next data when there is space in the lane.
// The purpose of chaining the wait here is that it makes sure we can cancel the
@@ -241,15 +151,12 @@ where
read_data = wait_until_lane_almost_empty(&lane_queue_lengths, connection_id)
.then(|_| available_reader.next()), if !we_are_closed =>
{
if deal_with_data(
read_data,
&local_destination_address,
&remote_source_address,
connection_id,
&mut message_sender,
&mix_sender,
&adapter_fn,
).await {
let processed = message_sender.process_data(read_data);
let is_done = processed.is_done;
message_sender.send_data(processed).await;
if is_done {
// After reading the last data, notify the closing_future to wait until the
// lane is clear before exiting.
// We don't wait here since we want to be able to cancel the wait on close or
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::connection_controller::ConnectionReceiver;
use nym_socks5_requests::ConnectionId;
use crate::ordered_sender::OrderedMessageSender;
use nym_socks5_requests::{ConnectionId, SocketData};
use nym_task::connections::LaneQueueLengths;
use nym_task::TaskClient;
use std::fmt::Debug;
@@ -13,7 +14,7 @@ mod inbound;
mod outbound;
// TODO: make this configurable
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(30);
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(3);
// Send empty keepalive messages regurarly to keep the connection alive. This should be smaller
// than [`MIX_TTL`].
@@ -92,20 +93,24 @@ where
// request/response as required by entity running particular side of the proxy.
pub async fn run<F>(mut self, adapter_fn: F) -> Self
where
F: Fn(ConnectionId, Vec<u8>, bool) -> S + Send + Sync + 'static,
F: Fn(SocketData) -> S + Send + Sync + 'static,
{
let (read_half, write_half) = self.socket.take().unwrap().into_split();
let shutdown_notify = Arc::new(Notify::new());
// should run until either inbound closes or is notified from outbound
let inbound_future = inbound::run_inbound(
read_half,
let ordered_sender = OrderedMessageSender::new(
self.local_destination_address.clone(),
self.remote_source_address.clone(),
self.connection_id,
self.mix_sender.clone(),
self.available_plaintext_per_mix_packet,
adapter_fn,
);
let inbound_future = inbound::run_inbound(
read_half,
ordered_sender,
self.connection_id,
self.available_plaintext_per_mix_packet,
Arc::clone(&shutdown_notify),
self.lane_queue_lengths.clone(),
self.shutdown_listener.clone(),
+6 -4
View File
@@ -7,9 +7,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = { workspace = true }
bincode = "1.3.3"
log = { workspace = true }
nym-service-providers-common = { path = "../../../service-providers/common" }
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" }
nym-service-providers-common = { path = "../../../service-providers/common" }
tap = { workspace = true }
thiserror = { workspace = true }
+293 -3
View File
@@ -3,6 +3,7 @@
use nym_service_providers_common::interface;
use nym_service_providers_common::interface::ServiceProviderMessagingError;
use std::mem;
use thiserror::Error;
pub use request::*;
@@ -16,6 +17,185 @@ pub mod version;
pub type Socks5ProviderRequest = interface::Request<Socks5Request>;
pub type Socks5ProviderResponse = interface::Response<Socks5Request>;
#[derive(Debug, Error, PartialEq, Eq)]
#[error(
"didn't receive enough data to recover socket data. got {received}, but expected at least {expected}"
)]
pub struct InsufficientSocketDataError {
received: usize,
expected: usize,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct SocketDataHeader {
pub seq: u64,
pub connection_id: ConnectionId,
pub local_socket_closed: bool,
}
impl SocketDataHeader {
const SERIALIZED_LEN: usize = mem::size_of::<ConnectionId>() + 1 + mem::size_of::<u64>();
// we need to have two serialization methods for backwards compatibility,
// since we serialized those fields differently depending on whether it was ingress vs egress...
pub fn try_from_request_bytes(
b: &[u8],
) -> Result<SocketDataHeader, InsufficientSocketDataError> {
if b.len() != Self::SERIALIZED_LEN {
return Err(InsufficientSocketDataError {
received: b.len(),
expected: Self::SERIALIZED_LEN,
});
}
// the unwraps here are fine as we just ensured we have the exact amount of bytes we need
let connection_id = ConnectionId::from_be_bytes(b[0..8].try_into().unwrap());
let local_socket_closed = b[8] != 0;
let seq = u64::from_be_bytes(b[9..].try_into().unwrap());
Ok(SocketDataHeader {
seq,
connection_id,
local_socket_closed,
})
}
// the serialization of the header looks as follows:
// (it's vital it's not modified as we need this exact structure for backwards compatibility)
// CONNECTION_ID (8B) || SOCKET_CLOSED (1B) || SEQUENCE (8B)
pub fn into_request_bytes(self) -> Vec<u8> {
self.into_request_bytes_iter().collect()
}
pub fn into_request_bytes_iter(self) -> impl Iterator<Item = u8> {
self.connection_id
.to_be_bytes()
.into_iter()
.chain(std::iter::once(self.local_socket_closed as u8))
.chain(self.seq.to_be_bytes().into_iter())
}
pub fn try_from_response_bytes(
b: &[u8],
) -> Result<SocketDataHeader, InsufficientSocketDataError> {
if b.len() != Self::SERIALIZED_LEN {
return Err(InsufficientSocketDataError {
received: b.len(),
expected: Self::SERIALIZED_LEN,
});
}
// the unwraps here are fine as we just ensured we have the exact amount of bytes we need
let local_socket_closed = b[0] != 0;
let connection_id = ConnectionId::from_be_bytes(b[1..9].try_into().unwrap());
let seq = u64::from_be_bytes(b[9..].try_into().unwrap());
Ok(SocketDataHeader {
seq,
connection_id,
local_socket_closed,
})
}
// the serialization of the header looks as follows:
// (it's vital it's not modified as we need this exact structure for backwards compatibility)
// SOCKET_CLOSED (1B) || CONNECTION_ID (8B) || SEQUENCE (8B)
pub fn into_response_bytes(self) -> Vec<u8> {
self.into_response_bytes_iter().collect()
}
pub fn into_response_bytes_iter(self) -> impl Iterator<Item = u8> {
std::iter::once(self.local_socket_closed as u8)
.chain(self.connection_id.to_be_bytes().into_iter())
.chain(self.seq.to_be_bytes().into_iter())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SocketData {
pub header: SocketDataHeader,
pub data: Vec<u8>,
}
impl SocketData {
pub fn new(
seq: u64,
connection_id: ConnectionId,
local_socket_closed: bool,
data: Vec<u8>,
) -> Self {
SocketData {
header: SocketDataHeader {
seq,
connection_id,
local_socket_closed,
},
data,
}
}
fn verify_deserialization_len(b: &[u8]) -> Result<(), InsufficientSocketDataError> {
if b.is_empty() {
return Err(InsufficientSocketDataError {
received: 0,
expected: SocketDataHeader::SERIALIZED_LEN,
});
}
if b.len() < SocketDataHeader::SERIALIZED_LEN {
return Err(InsufficientSocketDataError {
received: b.len(),
expected: SocketDataHeader::SERIALIZED_LEN,
});
}
Ok(())
}
// we need to have two serialization methods for backwards compatibility,
// since we serialized those fields differently depending on whether it was ingress vs egress...
pub fn try_from_request_bytes(b: &[u8]) -> Result<SocketData, InsufficientSocketDataError> {
Self::verify_deserialization_len(b)?;
let header =
SocketDataHeader::try_from_request_bytes(&b[..SocketDataHeader::SERIALIZED_LEN])?;
let data = b[SocketDataHeader::SERIALIZED_LEN..].to_vec();
Ok(SocketData { header, data })
}
// the serialization of the socket data looks as follows:
// HEADER || DATA
pub fn into_request_bytes(self) -> Vec<u8> {
self.into_request_bytes_iter().collect()
}
pub fn into_request_bytes_iter(self) -> impl Iterator<Item = u8> {
self.header
.into_request_bytes_iter()
.chain(self.data.into_iter())
}
pub fn try_from_response_bytes(b: &[u8]) -> Result<SocketData, InsufficientSocketDataError> {
Self::verify_deserialization_len(b)?;
let header =
SocketDataHeader::try_from_response_bytes(&b[..SocketDataHeader::SERIALIZED_LEN])?;
let data = b[SocketDataHeader::SERIALIZED_LEN..].to_vec();
Ok(SocketData { header, data })
}
pub fn into_response_bytes(self) -> Vec<u8> {
self.into_response_bytes_iter().collect()
}
pub fn into_response_bytes_iter(self) -> impl Iterator<Item = u8> {
self.header
.into_response_bytes_iter()
.chain(self.data.into_iter())
}
}
#[derive(Debug, Error)]
pub enum Socks5RequestError {
#[error("failed to deserialize received request: {source}")]
@@ -32,6 +212,18 @@ pub enum Socks5RequestError {
#[error(transparent)]
ProviderInterfaceError(#[from] ServiceProviderMessagingError),
#[error("received unsupported request protocol version: {protocol_version}")]
UnsupportedProtocolVersion {
protocol_version: <Socks5Request as interface::ServiceProviderRequest>::ProtocolVersion,
},
}
fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options;
bincode::DefaultOptions::new()
.with_big_endian()
.with_varint_encoding()
}
#[cfg(test)]
@@ -39,6 +231,103 @@ mod tests {
use super::*;
use nym_service_providers_common::interface::RequestContent;
#[cfg(test)]
mod socket_data_serialization {
use super::*;
#[test]
fn for_requests() {
assert_eq!(
InsufficientSocketDataError {
received: 0,
expected: SocketDataHeader::SERIALIZED_LEN
},
SocketData::try_from_request_bytes(&[]).unwrap_err()
);
assert_eq!(
InsufficientSocketDataError {
received: 10,
expected: SocketDataHeader::SERIALIZED_LEN
},
SocketData::try_from_request_bytes(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap_err()
);
let good_data = SocketData::new(42, 12345, false, vec![2, 3]);
let serialized = good_data.clone().into_request_bytes();
assert_eq!(
good_data,
SocketData::try_from_request_bytes(&serialized).unwrap()
);
assert_ne!(
good_data,
SocketData::try_from_response_bytes(&serialized).unwrap()
);
let raw_bytes = [
6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255,
];
assert_eq!(
SocketData {
header: SocketDataHeader {
seq: u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
connection_id: ConnectionId::from_be_bytes([6, 6, 6, 6, 6, 6, 6, 6]),
local_socket_closed: false,
},
data: vec![255, 255, 255],
},
SocketData::try_from_request_bytes(&raw_bytes).unwrap()
)
}
#[test]
fn for_responses() {
assert_eq!(
InsufficientSocketDataError {
received: 0,
expected: SocketDataHeader::SERIALIZED_LEN
},
SocketData::try_from_response_bytes(&[]).unwrap_err()
);
assert_eq!(
InsufficientSocketDataError {
received: 10,
expected: SocketDataHeader::SERIALIZED_LEN
},
SocketData::try_from_response_bytes(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap_err()
);
let good_data = SocketData::new(42, 12345, false, vec![2, 3]);
let serialized = good_data.clone().into_response_bytes();
assert_eq!(
good_data,
SocketData::try_from_response_bytes(&serialized).unwrap()
);
assert_ne!(
good_data,
SocketData::try_from_request_bytes(&serialized).unwrap()
);
let raw_bytes = [
0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255,
];
assert_eq!(
SocketData {
header: SocketDataHeader {
seq: u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
connection_id: ConnectionId::from_be_bytes([6, 6, 6, 6, 6, 6, 6, 6]),
local_socket_closed: false,
},
data: vec![255, 255, 255],
},
SocketData::try_from_response_bytes(&raw_bytes).unwrap()
)
}
}
#[cfg(test)]
mod interface_backwards_compatibility {
use super::*;
@@ -87,9 +376,10 @@ mod tests {
match new_deserialized.content {
RequestContent::ProviderData(req) => match req.content {
Socks5RequestContent::Send(send_req) => {
assert_eq!(send_req.conn_id, 7810961472501196273);
assert_eq!(send_req.data.len(), 111);
assert!(!send_req.local_closed);
assert_eq!(send_req.data.header.connection_id, 7810961472501196273);
assert_eq!(send_req.data.header.seq, 0);
assert_eq!(send_req.data.data.len(), 103);
assert!(!send_req.data.header.local_socket_closed);
}
_ => panic!("unexpected request"),
},
+160 -66
View File
@@ -1,10 +1,15 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{Socks5ProtocolVersion, Socks5RequestError, Socks5Response};
use crate::{
make_bincode_serializer, InsufficientSocketDataError, SocketData, Socks5ProtocolVersion,
Socks5RequestError, Socks5Response,
};
use nym_service_providers_common::interface::{Serializable, ServiceProviderRequest};
use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use tap::TapFallible;
use thiserror::Error;
pub type ConnectionId = u64;
@@ -15,6 +20,7 @@ pub type RemoteAddress = String;
pub enum RequestFlag {
Connect = 0,
Send = 1,
Query = 2,
}
impl TryFrom<u8> for RequestFlag {
@@ -24,6 +30,7 @@ impl TryFrom<u8> for RequestFlag {
match value {
_ if value == (RequestFlag::Connect as u8) => Ok(Self::Connect),
_ if value == (RequestFlag::Send as u8) => Ok(Self::Send),
_ if value == (RequestFlag::Query as u8) => Ok(Self::Query),
value => Err(RequestDeserializationError::UnknownRequestFlag { value }),
}
}
@@ -51,6 +58,15 @@ pub enum RequestDeserializationError {
#[error("malformed return address - {0}")]
MalformedReturnAddress(RecipientFormattingError),
#[error("failed to deserialize query request: {source}")]
QueryDeserializationError {
#[from]
source: bincode::Error,
},
#[error(transparent)]
InvalidSocketData(#[from] InsufficientSocketDataError),
}
impl RequestDeserializationError {
@@ -59,7 +75,7 @@ impl RequestDeserializationError {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConnectRequest {
// TODO: is connection_id redundant now?
pub conn_id: ConnectionId,
@@ -67,11 +83,15 @@ pub struct ConnectRequest {
pub return_address: Option<Recipient>,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SendRequest {
pub conn_id: ConnectionId,
pub data: Vec<u8>,
pub local_closed: bool,
pub data: SocketData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum QueryRequest {
OpenProxy,
Description,
}
#[derive(Debug, Clone)]
@@ -108,6 +128,13 @@ impl Serializable for Socks5Request {
}
let protocol_version = Socks5ProtocolVersion::from(b[0]);
if protocol_version > Self::max_supported_version() {
return Err(Socks5RequestError::UnsupportedProtocolVersion { protocol_version });
}
// TODO: handle the case then protocol version if less then the current one. Then we should
// make sure to only respond with the same version
Ok(Socks5Request {
protocol_version,
content: Socks5RequestContent::try_from_bytes(&b[1..])?,
@@ -155,22 +182,27 @@ impl Socks5Request {
}
}
pub fn new_send(
pub fn new_send(protocol_version: Socks5ProtocolVersion, data: SocketData) -> Socks5Request {
Socks5Request {
protocol_version,
content: Socks5RequestContent::new_send(data),
}
}
pub fn new_query(
protocol_version: Socks5ProtocolVersion,
conn_id: ConnectionId,
data: Vec<u8>,
local_closed: bool,
query: QueryRequest,
) -> Socks5Request {
Socks5Request {
protocol_version,
content: Socks5RequestContent::new_send(conn_id, data, local_closed),
content: Socks5RequestContent::Query(query),
}
}
}
/// A request from a SOCKS5 client that a Nym Socks5 service provider should
/// take an action for an application using a (probably local) Nym Socks5 proxy.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Socks5RequestContent {
/// Start a new TCP connection to the specified `RemoteAddress` and send
/// the request data up the connection.
@@ -179,6 +211,8 @@ pub enum Socks5RequestContent {
/// Re-use an existing TCP connection, sending more request data up it.
Send(SendRequest),
Query(QueryRequest),
}
impl Socks5RequestContent {
@@ -196,16 +230,8 @@ impl Socks5RequestContent {
}
/// Construct a new Request::Send instance
pub fn new_send(
conn_id: ConnectionId,
data: Vec<u8>,
local_closed: bool,
) -> Socks5RequestContent {
Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
})
pub fn new_send(data: SocketData) -> Socks5RequestContent {
Socks5RequestContent::Send(SendRequest { data })
}
/// Deserialize the request type, connection id, destination address and port,
@@ -222,18 +248,27 @@ impl Socks5RequestContent {
/// The request_flag tells us whether this is a new connection request (`new_connect`),
/// an already-established connection we should send up (`new_send`), or
/// a request to close an established connection (`new_close`).
// connect:
// RequestFlag::Connect || CONN_ID || ADDR_LEN || ADDR || <RETURN_ADDR>
//
// send:
// RequestFlag::Send || CONN_ID || LOCAL_CLOSED || DATA
// where DATA: SEQ || TRUE_DATA
pub fn try_from_bytes(b: &[u8]) -> Result<Socks5RequestContent, RequestDeserializationError> {
// each request needs to at least contain flag and ConnectionId
if b.is_empty() {
return Err(RequestDeserializationError::NoData);
}
if b.len() < 9 {
return Err(RequestDeserializationError::ConnectionIdTooShort);
}
let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
match RequestFlag::try_from(b[0])? {
RequestFlag::Connect => {
if b.len() < 9 {
return Err(RequestDeserializationError::ConnectionIdTooShort);
}
let conn_id = u64::from_be_bytes([b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]]);
let connect_request_bytes = &b[9..];
// we need to be able to read at least 2 bytes that specify address length
@@ -278,15 +313,13 @@ impl Socks5RequestContent {
return_address,
))
}
RequestFlag::Send => {
let local_closed = b[9] != 0;
let data = b[10..].to_vec();
Ok(Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
}))
RequestFlag::Send => Ok(Socks5RequestContent::Send(SendRequest {
data: SocketData::try_from_request_bytes(&b[1..])?,
})),
RequestFlag::Query => {
use bincode::Options;
let query = make_bincode_serializer().deserialize(&b[1..])?;
Ok(Socks5RequestContent::Query(query))
}
}
}
@@ -313,10 +346,21 @@ impl Socks5RequestContent {
}
}
Socks5RequestContent::Send(req) => std::iter::once(RequestFlag::Send as u8)
.chain(req.conn_id.to_be_bytes().into_iter())
.chain(std::iter::once(req.local_closed as u8))
.chain(req.data.into_iter())
.chain(req.data.into_request_bytes_iter())
.collect(),
Socks5RequestContent::Query(query) => {
use bincode::Options;
let query_bytes: Vec<u8> = make_bincode_serializer()
.serialize(&query)
.tap_err(|err| {
log::error!("Failed to serialize query request: {:?}: {err}", query);
})
.unwrap_or_default();
std::iter::once(RequestFlag::Query as u8)
.chain(query_bytes.into_iter())
.collect()
}
}
}
}
@@ -559,26 +603,7 @@ mod request_deserialization_tests {
#[test]
fn works_when_request_is_sized_properly_even_without_data() {
// correct 8 bytes of connection_id, 1 byte of local_closed and 0 bytes request data
let request_bytes = [RequestFlag::Send as u8, 1, 2, 3, 4, 5, 6, 7, 8, 0].to_vec();
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request {
Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
}) => {
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
assert_eq!(Vec::<u8>::new(), data);
assert!(!local_closed)
}
_ => unreachable!(),
}
}
#[test]
fn works_when_request_is_sized_properly_and_has_data() {
// correct 8 bytes of connection_id, 1 byte of local_closed and 3 bytes request data (all 255)
// correct 8 bytes of connection_id, 1 byte of local_closed, 8 bytes of sequence and 0 bytes request data
let request_bytes = [
RequestFlag::Send as u8,
1,
@@ -590,6 +615,53 @@ mod request_deserialization_tests {
7,
8,
0,
0,
0,
0,
0,
0,
0,
0,
1,
]
.to_vec();
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request {
Socks5RequestContent::Send(SendRequest { data }) => {
assert_eq!(
u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
data.header.connection_id
);
assert!(!data.header.local_socket_closed);
assert_eq!(1, data.header.seq);
assert!(data.data.is_empty());
}
_ => unreachable!(),
}
}
#[test]
fn works_when_request_is_sized_properly_and_has_data() {
// correct 8 bytes of connection_id, 1 byte of local_closed, 8 bytes of sequence and 3 bytes request data (all 255)
let request_bytes = [
RequestFlag::Send as u8,
1,
2,
3,
4,
5,
6,
7,
8,
1,
0,
0,
0,
0,
0,
0,
0,
1,
255,
255,
255,
@@ -598,17 +670,39 @@ mod request_deserialization_tests {
let request = Socks5RequestContent::try_from_bytes(&request_bytes).unwrap();
match request {
Socks5RequestContent::Send(SendRequest {
conn_id,
data,
local_closed,
}) => {
assert_eq!(u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), conn_id);
assert_eq!(vec![255, 255, 255], data);
assert!(!local_closed)
Socks5RequestContent::Send(SendRequest { data }) => {
assert_eq!(
u64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]),
data.header.connection_id
);
assert!(data.header.local_socket_closed);
assert_eq!(1, data.header.seq);
assert_eq!(vec![255, 255, 255], data.data);
}
_ => unreachable!(),
}
}
}
#[cfg(test)]
mod serialize_query_request {
use super::*;
#[test]
fn serialize_there_and_back() {
let open_proxy = Socks5RequestContent::Query(QueryRequest::OpenProxy);
let bytes_open_proxy = open_proxy.clone().into_bytes();
assert_eq!(bytes_open_proxy, vec![2, 0]);
let description = Socks5RequestContent::Query(QueryRequest::Description);
let bytes_description = description.clone().into_bytes();
assert_eq!(bytes_description, vec![2, 1]);
let open_proxy2 = Socks5RequestContent::try_from_bytes(&bytes_open_proxy).unwrap();
let description2 = Socks5RequestContent::try_from_bytes(&bytes_description).unwrap();
assert_eq!(open_proxy, open_proxy2);
assert_eq!(description, description2);
}
}
}
+104 -147
View File
@@ -1,8 +1,13 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{ConnectionId, Socks5ProtocolVersion, Socks5RequestError};
use crate::{
make_bincode_serializer, ConnectionId, InsufficientSocketDataError, SocketData,
Socks5ProtocolVersion, Socks5RequestError,
};
use nym_service_providers_common::interface::{Serializable, ServiceProviderResponse};
use serde::{Deserialize, Serialize};
use tap::TapFallible;
use thiserror::Error;
// don't start tags from 0 for easier backwards compatibility since `NetworkData`
@@ -13,6 +18,7 @@ use thiserror::Error;
pub enum ResponseFlag {
NetworkData = 1,
ConnectionError = 2,
Query = 3,
}
impl TryFrom<u8> for ResponseFlag {
@@ -22,13 +28,20 @@ impl TryFrom<u8> for ResponseFlag {
match value {
_ if value == (ResponseFlag::NetworkData as u8) => Ok(Self::NetworkData),
_ if value == (ResponseFlag::ConnectionError as u8) => Ok(Self::ConnectionError),
_ if value == (ResponseFlag::Query as u8) => Ok(Self::Query),
value => Err(ResponseDeserializationError::UnknownResponseFlag { value }),
}
}
}
#[derive(Debug, Error, PartialEq, Eq)]
#[derive(Debug, Error)]
pub enum ResponseDeserializationError {
#[error("the network data was malformed: {source}")]
MalformedNetworkData {
#[from]
source: InsufficientSocketDataError,
},
#[error("not enough bytes to recover the connection id")]
ConnectionIdTooShort,
@@ -43,6 +56,12 @@ pub enum ResponseDeserializationError {
#[from]
source: std::string::FromUtf8Error,
},
#[error("failed to deserialize query response: {source}")]
QueryDeserializationError {
#[from]
source: bincode::Error,
},
}
#[derive(Debug)]
@@ -105,23 +124,14 @@ impl Socks5Response {
pub fn new_network_data(
protocol_version: Socks5ProtocolVersion,
seq: u64,
connection_id: ConnectionId,
data: Vec<u8>,
is_closed: bool,
) -> Socks5Response {
Socks5Response {
protocol_version,
content: Socks5ResponseContent::new_network_data(connection_id, data, is_closed),
}
}
pub fn new_closed_empty(
protocol_version: Socks5ProtocolVersion,
connection_id: ConnectionId,
) -> Socks5Response {
Socks5Response {
protocol_version,
content: Socks5ResponseContent::new_closed_empty(connection_id),
content: Socks5ResponseContent::new_network_data(seq, connection_id, data, is_closed),
}
}
@@ -135,25 +145,35 @@ impl Socks5Response {
content: Socks5ResponseContent::new_connection_error(connection_id, error_message),
}
}
pub fn new_query(
protocol_version: Socks5ProtocolVersion,
query_response: QueryResponse,
) -> Socks5Response {
Socks5Response {
protocol_version,
content: Socks5ResponseContent::Query(query_response),
}
}
}
#[derive(Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Socks5ResponseContent {
NetworkData(NetworkData),
NetworkData { content: SocketData },
ConnectionError(ConnectionError),
Query(QueryResponse),
}
impl Socks5ResponseContent {
pub fn new_network_data(
seq: u64,
connection_id: ConnectionId,
data: Vec<u8>,
is_closed: bool,
) -> Socks5ResponseContent {
Socks5ResponseContent::NetworkData(NetworkData::new(connection_id, data, is_closed))
}
pub fn new_closed_empty(connection_id: ConnectionId) -> Socks5ResponseContent {
Socks5ResponseContent::NetworkData(NetworkData::new_closed_empty(connection_id))
Socks5ResponseContent::NetworkData {
content: SocketData::new(seq, connection_id, is_closed, data),
}
}
pub fn new_connection_error(
@@ -165,9 +185,9 @@ impl Socks5ResponseContent {
pub fn into_bytes(self) -> Vec<u8> {
match self {
Socks5ResponseContent::NetworkData(res) => {
Socks5ResponseContent::NetworkData { content } => {
std::iter::once(ResponseFlag::NetworkData as u8)
.chain(res.into_bytes().into_iter())
.chain(content.into_response_bytes_iter())
.collect()
}
Socks5ResponseContent::ConnectionError(res) => {
@@ -175,6 +195,18 @@ impl Socks5ResponseContent {
.chain(res.into_bytes().into_iter())
.collect()
}
Socks5ResponseContent::Query(query) => {
use bincode::Options;
let query_bytes: Vec<u8> = make_bincode_serializer()
.serialize(&query)
.tap_err(|err| {
log::error!("Failed to serialize query response: {:?}: {err}", query);
})
.unwrap_or_default();
std::iter::once(ResponseFlag::Query as u8)
.chain(query_bytes.into_iter())
.collect()
}
}
}
@@ -187,84 +219,29 @@ impl Socks5ResponseContent {
let response_flag = ResponseFlag::try_from(b[0])?;
match response_flag {
ResponseFlag::NetworkData => Ok(Socks5ResponseContent::NetworkData(
NetworkData::try_from_bytes(&b[1..])?,
)),
ResponseFlag::NetworkData => Ok(Socks5ResponseContent::NetworkData {
content: SocketData::try_from_response_bytes(&b[1..])?,
}),
ResponseFlag::ConnectionError => Ok(Socks5ResponseContent::ConnectionError(
ConnectionError::try_from_bytes(&b[1..])?,
)),
ResponseFlag::Query => {
use bincode::Options;
let query = make_bincode_serializer().deserialize(&b[1..])?;
Ok(Socks5ResponseContent::Query(query))
}
}
}
pub fn as_query(&self) -> Option<&QueryResponse> {
match self {
Socks5ResponseContent::Query(query) => Some(query),
_ => None,
}
}
}
/// A remote network network data response retrieved by the Socks5 service provider. This
/// can be serialized and sent back through the mixnet to the requesting
/// application.
#[derive(Debug)]
pub struct NetworkData {
pub data: Vec<u8>,
pub connection_id: ConnectionId,
pub is_closed: bool,
}
impl NetworkData {
/// Constructor for responses
pub fn new(connection_id: ConnectionId, data: Vec<u8>, is_closed: bool) -> Self {
NetworkData {
data,
connection_id,
is_closed,
}
}
pub fn new_closed_empty(connection_id: ConnectionId) -> Self {
NetworkData {
data: vec![],
connection_id,
is_closed: false,
}
}
pub fn try_from_bytes(b: &[u8]) -> Result<NetworkData, ResponseDeserializationError> {
if b.is_empty() {
return Err(ResponseDeserializationError::NoData);
}
let is_closed = b[0] != 0;
if b.len() < 9 {
return Err(ResponseDeserializationError::ConnectionIdTooShort);
}
let mut connection_id_bytes = b.to_vec();
let data = connection_id_bytes.split_off(9);
let connection_id = u64::from_be_bytes([
connection_id_bytes[1],
connection_id_bytes[2],
connection_id_bytes[3],
connection_id_bytes[4],
connection_id_bytes[5],
connection_id_bytes[6],
connection_id_bytes[7],
connection_id_bytes[8],
]);
let response = NetworkData::new(connection_id, data, is_closed);
Ok(response)
}
/// Serializes the response into bytes so that it can be sent back through
/// the mixnet to the requesting application.
pub fn into_bytes(self) -> Vec<u8> {
std::iter::once(self.is_closed as u8)
.chain(self.connection_id.to_be_bytes().iter().cloned())
.chain(self.data.into_iter())
.collect()
}
}
#[derive(Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConnectionError {
pub connection_id: ConnectionId,
pub network_requester_error: String,
@@ -318,62 +295,16 @@ impl ConnectionError {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum QueryResponse {
OpenProxy(bool),
Description(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(test)]
mod constructing_socks5_data_responses_from_bytes {
use super::*;
#[test]
fn fails_when_zero_bytes_are_supplied() {
let response_bytes = Vec::new();
assert_eq!(
ResponseDeserializationError::NoData,
NetworkData::try_from_bytes(&response_bytes).unwrap_err()
);
}
#[test]
fn fails_when_connection_id_bytes_are_too_short() {
let response_bytes = vec![0, 1, 2, 3, 4, 5, 6];
assert_eq!(
ResponseDeserializationError::ConnectionIdTooShort,
NetworkData::try_from_bytes(&response_bytes).unwrap_err()
);
}
#[test]
fn works_when_there_is_no_data() {
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7];
let expected = NetworkData::new(
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
Vec::new(),
false,
);
let actual = NetworkData::try_from_bytes(&response_bytes).unwrap();
assert_eq!(expected.connection_id, actual.connection_id);
assert_eq!(expected.data, actual.data);
assert_eq!(expected.is_closed, actual.is_closed);
}
#[test]
fn works_when_there_is_data() {
let response_bytes = vec![0, 0, 1, 2, 3, 4, 5, 6, 7, 255, 255, 255];
let expected = NetworkData::new(
u64::from_be_bytes([0, 1, 2, 3, 4, 5, 6, 7]),
vec![255, 255, 255],
false,
);
let actual = NetworkData::try_from_bytes(&response_bytes).unwrap();
assert_eq!(expected.connection_id, actual.connection_id);
assert_eq!(expected.data, actual.data);
assert_eq!(expected.is_closed, actual.is_closed);
}
}
#[cfg(test)]
mod connection_error_response_serde_tests {
use super::*;
@@ -396,11 +327,14 @@ mod tests {
#[test]
fn deserialization_errors() {
let err = ConnectionError::try_from_bytes(&[]).err().unwrap();
assert_eq!(err, ResponseDeserializationError::NoData);
assert!(matches!(err, ResponseDeserializationError::NoData));
let bytes: [u8; 5] = [1, 2, 3, 4, 5];
let err = ConnectionError::try_from_bytes(&bytes).err().unwrap();
assert_eq!(err, ResponseDeserializationError::ConnectionIdTooShort);
assert!(matches!(
err,
ResponseDeserializationError::ConnectionIdTooShort
));
let bytes: Vec<u8> = 42u64
.to_be_bytes()
@@ -414,4 +348,27 @@ mod tests {
));
}
}
#[cfg(test)]
mod serialize_query_response {
use super::*;
#[test]
fn serialize_there_and_back() {
let open_proxy = Socks5ResponseContent::Query(QueryResponse::OpenProxy(true));
let bytes_open_proxy = open_proxy.clone().into_bytes();
assert_eq!(bytes_open_proxy, vec![3, 0, 1]);
let description =
Socks5ResponseContent::Query(QueryResponse::Description("foo".to_string()));
let bytes_description = description.clone().into_bytes();
assert_eq!(bytes_description, vec![3, 1, 3, 102, 111, 111]);
let open_proxy2 = Socks5ResponseContent::try_from_bytes(&bytes_open_proxy).unwrap();
let description2 = Socks5ResponseContent::try_from_bytes(&bytes_description).unwrap();
assert_eq!(open_proxy, open_proxy2);
assert_eq!(description, description2);
}
}
}
+20 -12
View File
@@ -31,7 +31,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.8",
"getrandom 0.2.10",
"once_cell",
"version_check",
]
@@ -936,9 +936,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.8"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"js-sys",
@@ -1148,9 +1148,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.142"
version = "0.2.146"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
[[package]]
name = "libgit2-sys"
@@ -1319,7 +1319,7 @@ dependencies = [
[[package]]
name = "nym-contracts-common"
version = "0.4.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -1330,7 +1330,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"bs58",
"ed25519-dalek",
@@ -1378,7 +1378,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -1444,7 +1444,7 @@ dependencies = [
"chacha20",
"chacha20poly1305",
"curve25519-dalek",
"getrandom 0.2.8",
"getrandom 0.2.10",
"log",
"rand 0.7.3",
"rayon",
@@ -1455,7 +1455,7 @@ dependencies = [
[[package]]
name = "nym-pemstore"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"pem",
]
@@ -1465,6 +1465,7 @@ name = "nym-service-provider-directory"
version = "0.1.0"
dependencies = [
"anyhow",
"bs58",
"cosmwasm-std",
"cw-controllers",
"cw-multi-test",
@@ -1472,7 +1473,10 @@ dependencies = [
"cw-utils",
"cw2",
"nym-contracts-common",
"nym-crypto",
"nym-service-provider-directory-common",
"rand_chacha 0.2.2",
"rstest",
"semver",
"serde",
"thiserror",
@@ -1484,8 +1488,12 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-controllers",
"cw-utils",
"nym-contracts-common",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -1522,7 +1530,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.6.0"
version = "0.7.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
@@ -1742,7 +1750,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.8",
"getrandom 0.2.10",
]
[[package]]

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