Compare commits

..

274 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
pierre 9ae4fd04ac ci(ns5-android): fix workflow 2023-06-02 15:44:39 +02:00
pierre 4470969bec ci(ns5-android): update workflow to create GH release 2023-06-02 14:49:14 +02:00
pierre 1a4c3a7709 chore(ns5-android): add app metadata for listing 2023-06-02 14:19:17 +02:00
Pierre Dommerc 99b31920d5 fix(ns5-android): make lib calling callbacks (#3496) 2023-06-02 13:59:10 +02: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
pierre 62ccb6b4cd build(ns5-android): add product flavors config 2023-06-01 19:20:32 +02:00
pierre 365e0134b4 build(nc-native-android): clean build script 2023-05-31 17:31:24 +02:00
⚡️2FakTor⚡️ d5514a060c Update network-requester-setup.md 2023-05-31 15:22:11 +02:00
pierre 8432c30f6c build(nc-native-android): add gradle build universal apk 2023-05-31 14:20:49 +02:00
pierre c2764f90b3 ci(nc-native-android): update github workflow to build unsigned apks 2023-05-31 13:14:44 +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
pierre a6a39d1234 chore(nc-native-android): remove outdated todo 2023-05-30 13:58:30 +02:00
pierre 5f35d54fcb build(nc-native-android): fix script build paths 2023-05-30 13:40:52 +02:00
Jędrzej Stuczyński 096a599673 Socks5lib - FFI endpoint for getting 'ClientState' (#3464)
* method for getting current socks5 connection state

* prevent shutting down disconnected client (and starting connected client)

* fixed ios build

* setup additional logging
2023-05-30 10:38:44 +01:00
dependabot[bot] 41da67ad6f Bump yaml from 2.1.1 to 2.2.2 in /nym-api/tests (#3352)
Bumps [yaml](https://github.com/eemeli/yaml) from 2.1.1 to 2.2.2.
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v2.1.1...v2.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 12:08:31 +01:00
Jędrzej Stuczyński 273a741cdf Fix typo in wasm-client x25519 keypair storage key (#3463) 2023-05-29 11:05:05 +01:00
Drazen Urch b5c8b69547 Outfox integration (#3331)
* Experiment with serde

* Framed encoding serde POC

* Outfox framing

* Outfox rest compat (#3333)

* Outfox forwarding compat

* Tidy up interface

* PacketSize compat

* Address PR comments

* Rebase on develop

commit 342883fcbe
Author: durch <durch@users.noreply.github.com>
Date:   Thu Apr 27 09:17:18 2023 +0200

    Put back PacketType 1

commit 61a0ee5a19
Author: Tommy Verrall <tommyvez@protonmail.com>
Date:   Wed Apr 26 16:37:29 2023 +0100

    change output for cpu-cycle management logs

commit 3956109c7e
Author: Tommy Verrall <tommy@nymtech.net>
Date:   Wed Apr 26 12:13:22 2023 +0100

    change the workflow file to build with cpucycles

commit 8d725b13c5
Author: durch <durch@users.noreply.github.com>
Date:   Mon Apr 24 13:14:58 2023 +0200

    Outfox client compat

commit 4d166c389b
Author: durch <durch@users.noreply.github.com>
Date:   Fri Apr 21 00:30:46 2023 +0200

    Address PR comments

commit 145c3c1223
Author: durch <durch@users.noreply.github.com>
Date:   Fri Apr 21 00:12:35 2023 +0200

    Rename PacketMode

commit cbd654d6fd
Author: Drazen Urch <drazen@urch.eu>
Date:   Thu Apr 20 23:59:40 2023 +0200

    Outfox rest compat (#3333)

    * Outfox forwarding compat

    * Tidy up interface

    * PacketSize compat

commit e7be91a94c
Author: durch <durch@users.noreply.github.com>
Date:   Wed Apr 19 16:36:48 2023 +0200

    Remove serde cruft

commit 582e7d566a
Author: durch <durch@users.noreply.github.com>
Date:   Wed Apr 19 16:24:09 2023 +0200

    Outfox framing

commit 6464da5f01
Author: durch <durch@users.noreply.github.com>
Date:   Tue Apr 18 22:23:02 2023 +0200

    Framing compat

commit d5e77e499b
Author: durch <durch@users.noreply.github.com>
Date:   Tue Apr 18 18:18:54 2023 +0200

    Framed encoding serde POC

commit f086f9c35a
Author: durch <durch@users.noreply.github.com>
Date:   Tue Apr 18 16:54:21 2023 +0200

    Experiment with serde

* Client tweaks

* Speed up from_plaintext

* SurbAcks

* More work on the reciever end, and outfox format

* Cleanup and fmt

* Wrap up rebase

* Happy clippy

* Fix lock files

* Final cleanup
2023-05-29 10:05:11 +02:00
pierre 5bd87bdaa8 feat(nc-native-android): add notification tap action
on notification tap, bring app to foreground
use a shield icon for the top bar app icon
clean code
add notes on a pending bug
2023-05-27 00:00:24 +02:00
omahs 7d64618701 Fix: typos (#3143)
* Fix: typos

* Fix: typos

* Fix: typo
2023-05-26 14:46:08 +01:00
Jon Häggblad 4151c65251 Add nym-socks5-listener to main workspace (#3455)
* Add nym-socks5-listener to main workspace

* add socks5-listener to CI build path trigger

* Using repr(u8) instead of repr(C) for ClientState enum

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2023-05-26 11:01:17 +02:00
Fouad ccb48f92cd fix sp drop down list (#3454) 2023-05-26 09:16:28 +01:00
pierre 8fce478a1f doc(nc-native-android): add readme 2023-05-26 00:34:40 +02:00
pierre 4f712ad4ba fix(android-native): crash on android 11 2023-05-25 23:14:11 +02:00
pierre 562fd44a30 fix(android-native): crash on android 11 2023-05-25 23:00:36 +02:00
pierre 603e897e2d build(android-native): add split abi config
refactor android build script
2023-05-25 18:03:36 +02:00
Pierre Dommerc 6c122aad10 add nym ic launcher (#3456) 2023-05-25 18:00:22 +02:00
Jon Häggblad b4ac601a82 Merge remote-tracking branch 'origin/release/v1.1.20' into develop 2023-05-25 15:58:14 +02:00
Jon Häggblad ce380a6b0d Native nym-connect clients for iOS and Android (initial version) (#3452)
* initial crate

* foomp

* Make it work for x86_64-linux-android

* remove unused stuff

* Add header

* another layer of hacks

* additional target os locking

* cleanup

* bootstrap android app

* android jni function

* instructions + xcode project

* update jni name

* add native socks5 class

* typo

* gitkeep android native lib path

* add native socks5 class

* add socks5 native lib in java

* add build script

* fix jni dependency declaration

* wip

* Update build.sh

* Move build.sh to new subdir

* rename to build-android.sh

* fix typo in FFI function name

* use a good SP

* wip not crashing state

* add android network permissions

* android_logging

* starting client on button in swift + safer ffi

* set tag for libnyms5 logs

* testing callbacks

* android: start socks5 process in a separated thread

* non-blocking client with callbacks

* Remove the old non-working logger

* Restore commented out functionality in socks5 client

* basic file write/load + possible android fix

* Fully working state (minus task manager)

* Remove unused function

* data persistence + cb with address

* Remove stray old MyClass file from the merge

* Make storage_dir and Option

* Fix char_p for android

* Android now works with the new branch

* Tidy up a little in the jni code

* Move android mod to seperate file

* jni wrap start/stop

* Add android build to Makefile

* android: add basic UI and start/stop actions

* typo

* add nym word

* dirty persistence restored

* dirty android fixes

* even dirtier workaround

* Move rust crate to sdk/lib

* Update cargo.toml

* Strip release binary

* Update lib name in android project

* Move ios project to nym-connect directory

* remove old gitignore file

* Move ios client one step deeper

* fixed xcode lib paths

* removed old tracked file

* move android app under new path

* a bit of cleanup

* hopefully fixing the CI issues (🤞)

* Update Makefile

* android: add better support for persistent state

* updating ios UI on ffi callbacks

* missing dead code

* Added toggle button (wip)

* swapped connect and disconnect  methods around

* icon

* fixed android build

* reset button + reuse service provider

* disabling reset button

* android: run proxy in a worker as foreground service

* todo user cancel action

* android build script: add aarch64

* add stop action from notification

* add simple callbacks to the socks5 bridge

* pick a sp randomly

* pass stop cb to lib call

* add loading state support

* refactor(android): base connection state on callback calls

* android: add optimistic ui

* android: unique instance of libnym

* removing deadcode

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: pierre <dommerc.pierre@gmail.com>
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2023-05-25 15:45:40 +02:00
Jon Häggblad 5b02801c04 nym-cli: support name-service contract (#3404)
* nym-cli: support name-service contract

* rustfmt

* Fix querying names and service-providers

* Tidy some logging

* Fix table headers

* Remove expect from register/delete names
2023-05-25 13:54:30 +02:00
Jędrzej Stuczyński 525372d7ac Tmp hack/wasm client persist gateway cfg (#3443)
* wip

* semi-hacky way of persisting wasm-client gateway config

a better way shall be introduced after config refactoring

* cargo fmt

* wasm client clippy

* removed artifacts from other branches
2023-05-25 09:02:27 +01:00
Jon Häggblad e473a05250 Tweak fern logging format to be more similar to pretty_env_logger (#3451) 2023-05-25 09:29:59 +02:00
Simon Wicky a55d604bf5 increase connection buffer size to 2000 (#3439) 2023-05-23 16:08:55 +02:00
Jon Häggblad 5075894ff5 socks5: abort sending data if the connection is closed (#3365)
* socks5 inbound: stop reading when closing connection

* Wait for lane at select top-level

* Allow closing connection while waiting for lanes to clear

* Some tidy in inbound.rs

* Put chained future back inline in the select

* Remove commented out line

* Disable the read data branch on is_finished
2023-05-23 15:01:13 +02:00
Pawnflake c8b82a9553 removed comment http and https 2023-05-20 07:23:13 +08:00
fmtabbara c392266a4c Explorer: Fix - Load service provider data before trying to display 2023-05-17 12:44:24 +01:00
Tommy Verrall 9b540936db Merge pull request #3429 from nymtech/feature/explorer-show-all-gateway-versions
Add 'show all versions' options to gateways list
2023-05-17 10:50:06 +01:00
Tommy Verrall 7bf1036b68 Merge pull request #3430 from nymtech/service-providers-fix
small typo correction and service providers fix
2023-05-17 10:48:20 +01:00
Gala 1d82ec56d8 change where the change is applied 2023-05-17 11:41:45 +02:00
Gala 3ae5b59141 small typo correction and service providers fix 2023-05-17 11:27:49 +02:00
fmtabbara 929b401f95 default to 'all version' 2023-05-17 10:13:14 +01:00
fmtabbara d158deba77 add 'show all versions' options to gateways list 2023-05-17 10:07:25 +01: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
Tommy Verrall 5c77b48708 Merge pull request #3424 from nymtech/feature/nr_coconut
Link coconut flag with the functionality in the sdk
2023-05-16 15:45:16 +01:00
mx 1a4e0f4e08 added sandbox sync docs 2023-05-16 16:44:39 +02:00
Bogdan-Ștefan Neacșu 73d53653db Link coconut flag with the functionality in the sdk 2023-05-16 15:15:35 +02:00
Tommy Verrall 74bedead20 Merge pull request #3418 from nymtech/jstuczyn-patch-1
Fix feature-locking for fs surb storage
2023-05-16 12:56:32 +01:00
farbanas 1bec95d2a1 Merge branch 'master' into develop 2023-05-16 13:24:21 +02:00
Jędrzej Stuczyński 30137e285d Fix feature-locking for fs surb storage 2023-05-16 11:29:08 +01:00
Mark Sinclair 5253d5ec51 Merge pull request #3413 from nymtech/sdk-fixes
TypeScript SDK fixes
2023-05-16 11:17:38 +01:00
farbanas 7ed7329917 Merge branch 'release/v1.1.19' 2023-05-16 11:33:23 +02:00
Tommy Verrall 4abc0ae0ba Merge remote-tracking branch 'origin/release/v1.1.19' into release/v1.1.19 2023-05-16 11:22:59 +02:00
Tommy Verrall 47d8fcb21a remove package.json 2023-05-16 11:22:46 +02:00
Tommy Verrall acef5a5652 rename package.json to prevent ci moaning 2023-05-16 11:22:03 +02:00
Nadim Kobeissi cb919a3af9 TypeScript SDK fixes 2023-05-16 10:55:15 +02:00
farbanas 8e021a4419 update Cargo.lock 2023-05-16 10:33:04 +02:00
farbanas e5db7cb915 update changelog for release v1.1.19 2023-05-16 10:30:06 +02:00
mx 9bd03af3e9 version bump 2023-05-16 10:14:27 +02:00
mx e8a026ef0b updated sandbox peer 2023-05-16 10:02:39 +02:00
mx 86755aa6ba updated admonishments for uniformity 2023-05-16 10:00:01 +02:00
mx 77e0c6425e * updated validator go version + added link to precompiled binaries
* general version bump
2023-05-16 09:54:40 +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
Pierre Dommerc 0373e2b02a feat(wallet): select validator (#3375) 2023-05-15 10:29:47 +02:00
Jon Häggblad e8dd347186 Update Cargo.lock (#3410) 2023-05-14 22:00:17 +02:00
Tommy Verrall 5681920092 Merge pull request #3386 from nymtech/feature/wasm-key-persistance
Feature/wasm key persistance
2023-05-12 09:39:10 +01:00
Pierre Dommerc edbf35cb34 feat(wallet): select validator (#3375) 2023-05-12 10:37:08 +02:00
Jon Häggblad c46d04d3c4 Fix non-wasm build of wasm-client (#3399) 2023-05-10 18:45:46 +02:00
farbanas 4ebd1dd7f5 Merge branch 'master' into develop 2023-05-10 09:51:33 +01:00
Jon Häggblad 62ab760656 Add name-service endpoint to nym-api (#3394) 2023-05-10 10:42:49 +02:00
Nadim Kobeissi 32de7efc32 Fix #3371 2023-05-09 19:43:57 +02:00
farbanas deae210b82 update changelog in for release v1.1.18 2023-05-09 12:21:30 +01:00
farbanas 5b2b45a6eb updated versions for release v1.1.18 2023-05-09 12:20:07 +01:00
Jędrzej Stuczyński bc0f0cfc55 renamed some methods 2023-05-09 11:33:05 +01:00
Jędrzej Stuczyński 2661e4539e fixed bandwidth doc-test 2023-05-09 10:46:52 +01:00
Jędrzej Stuczyński 2fe5a5249c fixed unit test in crypto 2023-05-09 10:46:52 +01:00
Jędrzej Stuczyński c5b678d4e9 cleanup 2023-05-09 10:46:52 +01:00
Jędrzej Stuczyński 487f07ef35 cargo lock 2023-05-09 10:46:52 +01:00
Jędrzej Stuczyński 6a63be63e9 removing dead code + extra docs 2023-05-09 10:46:52 +01:00
Jędrzej Stuczyński 552c99389e fixed nym-sdk examples 2023-05-09 10:46:51 +01:00
Jędrzej Stuczyński 55502d210c Added associated error type to credential storage trait
so that one wishing to implement that trait wouldnt be forced to use predefined error enum, especially since it relies on sqlx
2023-05-09 10:46:41 +01:00
Jędrzej Stuczyński 66967ca88e improved error messages for on disk key storage 2023-05-09 10:46:41 +01:00
Jędrzej Stuczyński ba6d02b1db fixed network requester build 2023-05-09 10:46:41 +01:00
Jędrzej Stuczyński ebc957c04d nym-sdk compiling once more 2023-05-09 10:46:41 +01:00
Jędrzej Stuczyński 81c3ad74ca combining storage traits 2023-05-09 10:46:41 +01:00
Jędrzej Stuczyński 88f57e6d87 clients compiling 2023-05-09 10:46:41 +01:00
Jędrzej Stuczyński 28a965e698 persistent wasm client keys 2023-05-09 10:46:40 +01:00
Jędrzej Stuczyński 029c445805 removed redundant error wrapper 2023-05-09 10:46:26 +01:00
Jędrzej Stuczyński 358c95d541 Moved basic wasm-storage support to the common crate 2023-05-09 10:46:25 +01:00
Jędrzej Stuczyński 0f7793a881 persisting cipher info upon creation 2023-05-09 10:46:10 +01:00
Jędrzej Stuczyński 5478c23563 experimenting with storing concrete crypto types 2023-05-09 10:45:48 +01:00
Jędrzej Stuczyński 9f39e93e7b dummy indexeddb storage 2023-05-09 10:44:43 +01:00
mx 896a3e1be6 temporarily removed compatibility table: will reintroduce once fixed 2023-05-09 11:40:07 +02:00
Jon Häggblad 800390db85 Fix warning about default-features being ignored (#3398) 2023-05-09 11:32:21 +02:00
mx 1eaa13155c Merge pull request #3392 from nymtech/feature/release-1-1-18-docs
version bump to 1.1.18
2023-05-09 09:17:02 +00:00
Tommy Verrall fad3346096 Merge pull request #3391 from nymtech/bugfix/wallet-signin-ui
Bug fix: resolve dead-lock when switching signin to main app window in the Nym Wallet
2023-05-09 09:19:07 +01:00
Tommy Verrall 150f832f8e Merge pull request #3388 from nymtech/feature/wallet_enforce_semver
feat(wallet-bonding): enforce semver for node version
2023-05-09 09:18:23 +01:00
Nadim Kobeissi 202336b8a1 Fix Typescript SDK compilation errors 2023-05-05 16:23:46 +02:00
Jon Häggblad f0e94f8e5e Add name-service support to validator-client (#3384)
* Add name-service support to validator-client

* Add default_memo

* contract address for wallet

* rustfmt

* lock file

* Tidy up nym-wallet-types network config

* Typo

* Remove some unused contract constants
2023-05-05 15:39:38 +02:00
Fouad 6cd00b8d10 estimated fees for sending tokens (#3389)
* estimated fees for sending tokens
2023-05-05 14:08:16 +01:00
mx 534187cc8f Merge pull request #3368 from nymtech/add-docs-template
Update issue templates
2023-05-05 12:21:17 +00:00
mx 25b4934f69 added ntv blog to community guides section 2023-05-05 14:12:20 +02:00
mx 5ef7e24893 removed additional whitespace 2023-05-05 13:56:35 +02:00
mx 87ef46bc05 version bump to 1.1.18 2023-05-05 13:49:52 +02:00
Mark Sinclair f7bc5be8e4 Bug fix: resolve dead-lock when switching signin to main app window in the Nym Wallet
- change operations to async
- open the new window first and then try to close the old window, to prevent the process from exiting
2023-05-05 12:12:08 +01:00
Nadim Kobeissi b309583886 Run wasm-opt manually (Apple Silicon issue)
wasm-opt has a known issue on Apple Silicon:
https://github.com/rustwasm/wasm-pack/issues/913

The workaround currently seems to be running wasm-opt locally instead of
defining it as part of the Rust package's build pipeline in Cargo.toml.

I hope this is okay!
2023-05-05 12:09:13 +02:00
pierre 245185710a strip off v in node version 2023-05-05 11:03:44 +02:00
Jon Häggblad b7cfe31d72 Initial version of nym-name-service contract (only) (#3380)
* Initial version of nym-name-service, based on nym-service-provider-directory

* rustfmt

* Rename to NameEntry

* Restrict address format

* Remove deprecated random test

* Fix clippy

* Add to top-level Makefile

* Restore wasm-opt Makefile rule

* Restore NymAddress as enum

* rustfmt

* Add contract address to qa-qwerty.env

* Rename NymAddress to Address

* Tweak event output

* rustfmt

* add event_tag()

* qwerty contract address
2023-05-04 15:57:41 +02:00
Pierre Dommerc 68ca41a6be refactor(wallet-bonding): fetch node data concurrently (#3362) 2023-05-04 10:10:50 +02:00
Pierre Dommerc 5621e7d22e refactor(wallet-bonding): fetch node data concurrently (#3362) 2023-05-04 10:02:29 +02:00
Fouad a1a5c7772d Use Loading Modal component when loading Delegations data (#3377)
* allow loading modal to display custom text

* use loading modal

* dont repeatedly reset delegation state

* show loading modal when loading + no other modal is open

* fix lint errors

* log any delegations errors

* fix typo

* refresh interval in delegations page
2023-05-03 17:41:24 +02:00
Tommy Verrall b47deafc14 Merge pull request #3381 from nymtech/feature/add-nyxd-builds-ci
Feature/add nyxd builds ci
2023-05-03 16:30:46 +01:00
benedettadavico cc6a6d8db2 tweaking file 2023-05-03 17:19:28 +02:00
benedettadavico 5b28e24c17 workflow to add nyxd to builds ci 2023-05-03 17:15:47 +02:00
farbanas f8d68d8ef0 fix: merge resolve 2023-05-02 15:34:15 +02:00
farbanas a9d86508b5 Merge branch 'master' into develop 2023-05-02 14:38:11 +02:00
Tommy Verrall bb7fa587de formatting 2023-05-02 13:19:17 +02:00
Tommy Verrall 6585732dfc fix broken network address - set to none 2023-05-02 13:14:20 +02:00
farbanas 2065e0fc17 Merge branch 'master' into develop 2023-05-02 12:09:52 +02:00
farbanas 3f7bdad59c update lock files 2023-05-02 10:54:54 +02:00
farbanas 6209e78c1e bump crates 2023-05-02 10:53:42 +02:00
farbanas 8e5062af96 bump versions and update changelogs for release v1.1.17 2023-05-02 10:39:24 +02:00
mx 496e642d7f Merge pull request #3370 from nymtech/feature/1-1-17-docs
Feature/1 1 17 docs
2023-05-02 08:10:32 +00:00
mx 07e18ec198 added tokio dependency note 2023-04-28 15:51:20 +02:00
mx d5953c28c1 added note on running example code 2023-04-28 15:45:38 +02:00
mx 3aa4b66588 added info re buying NYM from wallet with BTC 2023-04-28 15:43:57 +02:00
mx 005f0ce340 * added correct version variable to sign command output
* added info that you can buy NYM from wallet with BTC
2023-04-28 15:41:55 +02:00
mx 8d1d025fa2 bumped point version 2023-04-28 15:41:44 +02:00
mx 1d53a2f954 updated readme with more details re: each directory having a readme and running them 2023-04-28 15:41:18 +02:00
mx 966d123608 Update issue templates 2023-04-27 16:15:10 +02:00
Tommy Verrall 963d55273f Merge pull request #3367 from nymtech/feature/adding-sp-api-tests
adding a test for SP endpoint
2023-04-27 14:47:26 +01:00
benedettadavico 6872d7bf77 adding a test for SP endpoint 2023-04-27 15:37:59 +02:00
Jon Häggblad 6fe93bcda0 Merge pull request #3332 from nymtech/jon/feat/sp-integrations
Service provider directory support in nym-api, nym-cli, validator-client
2023-04-27 11:51:00 +02:00
Jędrzej Stuczyński 78d568e04e Feature/store cipher (#3350)
* initial nym-store-cipher

* cleanup
2023-04-27 10:24:36 +01:00
Jon Häggblad 8880bdd857 Fix build target in top-level Makefile 2023-04-26 16:57:01 +02:00
Jon Häggblad cc83ecf7e4 socks5: send empty keepalive msg to avoid triggering MIX_TTL during long downloads (#3364)
* socks5: send empty keepalive msg to avoid triggering MIX_TTL during long downloads

* rustfmt

* reset timer after each normal send
2023-04-26 16:44:27 +02:00
Pierre Dommerc 796f5a678a feat(wallet): update bond amount (#3338) 2023-04-26 15:35:47 +02:00
Pierre Dommerc 00b60f5493 feat(wallet): update bond amount (#3338) 2023-04-26 15:29:39 +02:00
Jon Häggblad 0dfd1cca44 Review comments 2023-04-26 10:58:12 +02:00
Jon Häggblad eacefd3697 nym-cli: add announce and delete service provider 2023-04-26 10:58:12 +02:00
Jon Häggblad 424c25768c Add schemars to lock file 2023-04-26 10:58:12 +02:00
Jon Häggblad e460c1700e Add support for listing services in nym-cli 2023-04-26 10:58:12 +02:00
Jon Häggblad 9935c99d41 nym-api: add service provider endpoint and caching 2023-04-26 10:58:12 +02:00
Jon Häggblad 2c4aee63bf Make the contract optional 2023-04-26 10:58:12 +02:00
Jon Häggblad 0ebc395df9 rustfmt 2023-04-26 10:58:12 +02:00
Jon Häggblad 1de3317e75 Add sp directory contract traits and methods to nyxd client 2023-04-26 10:58:12 +02:00
Tommy Verrall 91c20af893 Merge pull request #3328 from nymtech/feature/shared-network-monitor
Feature/shared network monitor
2023-04-26 09:49:54 +01:00
pierre 1365e2f246 chore(wallet): add v prefix in wallet version 2023-04-26 10:04:58 +02:00
pierre cfa1ce46f2 chore(wallet): add v prefix in wallet version 2023-04-26 09:30:46 +02:00
Mark Sinclair 3f4f76859b Merge pull request #3188 from nymtech/feature/wallet-login
Split wallet sign in and main into two entry points
2023-04-25 11:07:00 +01:00
Raphaël Walther 3f7f4b82de Move workflows to custom runner 2023-04-25 11:10:12 +02:00
farbanas 934ba2b027 Merge branch 'master' into develop 2023-04-25 10:53:43 +02:00
Jędrzej Stuczyński 221e1870e5 removed redundant trait 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński 9b36bccf0c wasm tester fixes 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński 80d7285497 further improvements + reduced log noise 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński b94f2a483d nym-api compiling again 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński f64cfb4cd1 wip 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński eda223ed3d Resolved beta clippy complaints (#3351) 2023-04-25 09:53:11 +01:00
farbanas c98d4305fa update cargo locks 2023-04-25 10:09:49 +02:00
farbanas 2eecbca6eb bump versions and update changelogs for release v1.1.16 2023-04-25 10:06:03 +02:00
farbanas a58c80ef08 update versions of mixnet and vesting contract crates 2023-04-25 10:06:03 +02:00
farbanas ac9d0db8be update versions of mixnet and vesting contract crates 2023-04-25 10:06:03 +02:00
farbanas 7521d98963 update versions of mixnet and vesting contract common crates 2023-04-25 10:06:03 +02:00
mx 1e98131090 Merge pull request #3349 from nymtech/feature/general-docs-updates
version bump for next release
2023-04-25 07:39:10 +00:00
mx 46bf65462c Merge pull request #3325 from esomore/mixnode/description
update mix-node setup docs with node description
2023-04-25 07:38:22 +00:00
mx e3df4c2d68 reintroduce minimum rust version variable 2023-04-24 17:13:03 +02:00
mx 45c013350f version bump for next release 2023-04-24 17:10:41 +02:00
Mark Sinclair 30e2f27c64 Fix linting error 2023-04-24 15:51:50 +01:00
mx 6fecc53975 Merge pull request #3339 from nymtech/feature/coconut-rust-sdk-docs
added coconut credential generation example
2023-04-24 14:49:24 +00:00
Tommy Verrall e4dbfb1904 Merge pull request #3222 from nymtech/feature/available_reader_changes
Feature/available reader changes
2023-04-24 15:48:08 +01:00
Mark Sinclair 3113c1e9a7 Fix build issues 2023-04-24 15:41:13 +01:00
Tommy Verrall f822d3db7b cargo fmt 2023-04-24 15:29:51 +01:00
Jędrzej Stuczyński 9d23766288 updated used packet size 2023-04-24 15:29:51 +01:00
Jędrzej Stuczyński fd4930b198 removed old leftover log statement 2023-04-24 15:29:51 +01:00
Jędrzej Stuczyński 5d7be89edb replaced inner implementation with tokio's 'ReaderStream' 2023-04-24 15:29:51 +01:00
Jędrzej Stuczyński 47f5b4ceac limit the maximum buffer size of AvailableReader by PacketSize of our mix packets 2023-04-24 15:29:51 +01:00
Jędrzej Stuczyński 790220039b added read deadline to AvailableReader 2023-04-24 15:29:51 +01:00
Tommy Verrall 16fdfa4583 Update mainnet.env 2023-04-24 13:54:54 +02:00
Mark Sinclair 1d8a931e0c Do not keep mnemonic or password (and variations) in logs 2023-04-24 12:24:19 +01:00
Mark Sinclair 48d0883b31 Clear stashed state completely on logout 2023-04-24 12:24:19 +01:00
Mark Sinclair e271370326 Split wallet sign in and main into two entry points
Stash some of the state in the Rust process and load it when the React app mounts
Fix connect_with_mnemonic logging
2023-04-24 12:24:19 +01:00
Jędrzej Stuczyński cbbeb66b5b Feature/wasm client topology injection (#3311)
* added cargo config file to explicitly specify build target

* wip

* Config option to disable topology refreshing

* extracted common parsing code

* helper trait for working on wasm topology

* wasm topology parsing

* restored (slightly modified) old js-example

* wip

* Moved message preparation into a trait

* wip

* long-winded way of sending test packet

* standalone NymNodeTester

* finishing the test upon receiving all packets even if timeout wasnt reached

* initial round of cleanup

* sending multiple test packets in normal NymClient

* javascript-side cleanup

* starting mixnode test on btn click

* Improved NymNodeTester constructors

* improved error handling and constructors

* tester utils error handling

* further cleanup + using BTreeMap for NymTopology mixnodes

* handling missed errors

* splitting up 'test_node'

* split up and cleaned up generation of test result

* clippy + fixed example

* post rebase fixes

* another broken test

* prevent running multiple parallel tests

* cargo fmt

* Added nym- prefix to node tester utils
2023-04-24 09:56:26 +01:00
mx de020f46a6 added coconut credential generation example 2023-04-21 16:32:20 +02:00
Jędrzej Stuczyński f24bb5c038 reduced noise in CODEOWNERS (#3313)
* reduced noise in CODEOWNERS

* Add @octol to codeowners

* added @mfahampshire as owner of /documentation

---------

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2023-04-20 15:25:23 +01:00
Jon Häggblad 79dfe7eeda Add clippy target in top-level Makefile 2023-04-20 14:13:20 +02:00
Jon Häggblad 0108c6ed19 Merge remote-tracking branch 'origin/release/v1.1.16' into develop 2023-04-20 09:55:19 +02:00
Itamar Perez 0e8f60d501 update mix-node setup docs with node description 2023-04-19 12:01:36 -07:00
Jon Häggblad 6e30e6178b Update Cargo.lock files after bumping internal versions during release 2023-04-19 09:37:38 +02:00
mx 3a0c8f3f4e updated readme with new developer chat links + new docs links 2023-03-03 13:32:44 +01:00
678 changed files with 50292 additions and 15817 deletions
+17 -22
View File
@@ -11,30 +11,25 @@
# In each subsection folders are ordered first by depth, then alphabetically.
# This should make it easy to add new rules without breaking existing ones.
# Something weird not covered by anything else
* @futurechimp @mmsinclair
# contracts
/contracts/mixnet @durch @jstuczyn
/contracts/vesting @durch @jstuczyn
/contracts/service-provider-directory @octol
# Rust rules:
*.rs @durch @futurechimp @jstuczyn @neacsu @octol
Cargo.* @durch @futurechimp @jstuczyn @neacsu @octol
# crypto code
/common/crypto/ @jstuczyn
/common/nymcoconut/ @jstuczyn
/common/dkg/ @jstuczyn
/common/nymsphinx/ @jstuczyn
# JS rules:
*.js @mmsinclair @fmtabbara
*.ts @mmsinclair @fmtabbara
*.tsx @mmsinclair @fmtabbara
*.jsx @mmsinclair @fmtabbara
# rust sdk
/sdk/rust/ @octol
# Something looking like possible documentation rules:
*.md @mfahampshire
# nym-connect (rust)
/nym-connect/desktop/src-tauri/ @octol
# our docker scripts
/docker/ @neacsu
# nym-wallet (rust)
/nym-wallet/src-tauri/ @octol
# if there are any changes in the core crypto, I feel like Ania should take a look:
/common/crypto/ @aniampio
/common/nymsphinx/ @aniampio
# Explorer and wallet should probably get looked by the product team
/explorer/ @nymtech/product
/nym-wallet/ @nymtech/product
/wallet-web/ @nymtech/product
# documentation
/documentation @mfahampshire
+14
View File
@@ -0,0 +1,14 @@
---
name: 'Documentation'
about: Suggest a fix or enhancement to the documentation or developer portal content
title: "[DOCS]"
labels: documentation
assignees: mfahampshire
---
Is your issue either:
- [ ] a fix to existing documentation (e.g. fixing a broken link or incorrect command)
- [ ] an enhancement (e.g. adding a description for an undocumented feature)
Please briefly describe your issue:
@@ -63,7 +63,7 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: 1.69.0
- name: Build all binaries
uses: actions-rs/cargo@v1
@@ -74,7 +74,7 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: 1.69.0
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
@@ -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
+4
View File
@@ -9,12 +9,14 @@ on:
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/lib/socks5-listener/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
- 'Cargo.toml'
pull_request:
paths:
- 'clients/**'
@@ -23,12 +25,14 @@ on:
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/lib/socks5-listener/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
- 'Cargo.toml'
jobs:
build:
-138
View File
@@ -1,138 +0,0 @@
name: Nym Connect - Android APK Build
on:
workflow_dispatch:
push:
branches:
- "release/nc-android-v[0-9].[0-9].[0-9]*"
jobs:
build:
name: Build APK
runs-on: custom-runner-linux
env:
ANDROID_HOME: ${{ github.workspace }}/android-sdk
NDK_VERSION: 25.1.8937393
NDK_HOME: ${{ github.workspace }}/android-sdk/ndk/25.1.8937393
SDK_PLATFORM_VERSION: android-33
SDK_BUILDTOOLS_VERSION: 33.0.1
steps:
- name: Install Dependencies (Linux)
# https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/linux/#1-system-dependencies
run: |
sudo apt-get update
sudo apt-get -y install \
build-essential \
unzip \
curl \
wget \
libssl-dev \
squashfs-tools \
librsvg2-dev
- name: Checkout
uses: actions/checkout@v3
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "17"
- name: Install Android SDK manager
# https://developer.android.com/studio/command-line/sdkmanager
run: |
curl -sS https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -o cmdline-tools.zip
unzip cmdline-tools.zip
mkdir -p $ANDROID_HOME/cmdline-tools/latest
mv cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest
rm -rf cmdline-tools
- name: Install Android S/NDK
run: |
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
"platforms;$SDK_PLATFORM_VERSION" \
"platform-tools" \
"ndk;$NDK_VERSION" \
"build-tools;$SDK_BUILDTOOLS_VERSION"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
# TODO this step takes a considerable amount of time
# We could avoid to compile from source tauri-cli and use instead
# pre-compiled binary provided by the node package `@tauri-apps/cli`
# But when using the later the build fails for some reason
# so keep installing and using tauri-cli
- name: Install tauri cli
run: cargo install tauri-cli --version "^2.0.0-alpha.2"
- name: Install rust android targets
run: |
rustup target add aarch64-linux-android \
armv7-linux-androideabi \
i686-linux-android \
x86_64-linux-android
- name: Setup Nodejs
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install yarn
run: |
npm i -g yarn
yarn --version
- name: Build frontend code
run: |
yarn install --frozen-lockfile
yarn build
yarn workspace @nym/nym-connect-mobile webpack:prod
- name: Build APK
working-directory: nym-connect/mobile
env:
# NODE_TAURI_CLI=${{ github.workspace }}/nym-connect/mobile/node_modules/.bin/tauri
ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }}
WRY_ANDROID_PACKAGE: net.nymtech.nym_connect
WRY_ANDROID_LIBRARY: nym_connect
# TODO build with release profile (--release), it will requires
# to sign the APK. For now build with debug profile to avoid that
# TODO build using `yarn tauri`, provide NODE_TAURI_CLI, see TODO notes above
run: cargo tauri android build --debug --apk --split-per-abi -t aarch64
# TODO add the version number to APK name
- name: Rename APK artifact
run: |
mkdir apk/
mv nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build/outputs/apk/arm64/debug/app-arm64-debug.apk \
apk/nym-connect-arm64-debug.apk
mv nym-connect/mobile/src-tauri/gen/android/nym_connect/app/build/outputs/apk/x86_64/debug/app-x86_64-debug.apk \
apk/nym-connect-x86_64-debug.apk
- name: Upload APK artifact
uses: actions/upload-artifact@v3
with:
name: nc-apk-debug
path: |
apk/nym-connect-arm64-debug.apk
apk/nym-connect-x86_64-debug.apk
# publish:
# name: Publish APK
# needs: build
# runs-on: ubuntu-latest
# steps:
# - name: Checkout
# uses: actions/checkout@v3
# - name: Download binary artifact
# uses: actions/download-artifact@v3
# with:
# name: nc-apk-debug
# path: apk
# # TODO add a step to upload the APK somewhere
# - name: Publish
# uses: ???
@@ -6,7 +6,7 @@
},
{
"os":"windows-latest",
"os":"windows10",
"rust":"stable",
"runOnEvent":"schedule"
},
@@ -22,7 +22,7 @@
"runOnEvent":"schedule"
},
{
"os":"windows-latest",
"os":"windows10",
"rust":"beta",
"runOnEvent":"schedule"
},
@@ -38,7 +38,7 @@
"runOnEvent":"schedule"
},
{
"os":"windows-latest",
"os":"windows10",
"rust":"nightly",
"runOnEvent":"schedule"
},
+110
View File
@@ -0,0 +1,110 @@
name: Nyms5 Android
# unsigned APKs only, supported archs:
# - arm64-v8a (arm64)
# - x86_64
on:
workflow_dispatch:
push:
tags:
- nyms5-android-v*
jobs:
build:
name: Build APK
runs-on: custom-runner-linux
env:
ANDROID_HOME: ${{ github.workspace }}/android-sdk
NDK_VERSION: 25.2.9519653
NDK_HOME: ${{ github.workspace }}/android-sdk/ndk/25.2.9519653
SDK_PLATFORM_VERSION: android-33
SDK_BUILDTOOLS_VERSION: 33.0.2
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "17"
- name: Install Android SDK manager
# https://developer.android.com/studio/command-line/sdkmanager
run: |
curl -sS https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip -o cmdline-tools.zip
unzip cmdline-tools.zip
mkdir -p $ANDROID_HOME/cmdline-tools/latest
mv cmdline-tools/* $ANDROID_HOME/cmdline-tools/latest
rm -rf cmdline-tools
- name: Install Android S/NDK
run: |
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
"platforms;$SDK_PLATFORM_VERSION" \
"platform-tools" \
"ndk;$NDK_VERSION" \
"build-tools;$SDK_BUILDTOOLS_VERSION"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.70.0
- name: Install rust android targets
run: |
rustup target add aarch64-linux-android \
x86_64-linux-android
- name: Build lib nym-socks5-listener
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
- name: Build APKs (unsigned)
working-directory: nym-connect/native/android
env:
ANDROID_SDK_ROOT: ${{ env.ANDROID_HOME }}
# build for arm64 and x86_64
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
path: |
apk/nyms5-arch64-debug.apk
apk/nyms5-arch64-release.apk
gh-release:
name: Publish APK (GH release)
needs: build
runs-on: custom-runner-linux
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Download binary artifact
uses: actions/download-artifact@v3
with:
name: nyms5-apk-arch64
path: apk
- name: Release
uses: softprops/action-gh-release@v1
with:
files: |
apk/nyms5-arch64-debug.apk
apk/nyms5-arch64-release.apk
+79
View File
@@ -0,0 +1,79 @@
name: Upload nyxd to CI
on:
workflow_dispatch:
jobs:
publish-nyxd:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Prepare build output directory
shell: bash
env:
OUTPUT_DIR: ci-builds/nyxd
run: |
rm -rf ci-builds || true
mkdir -p $OUTPUT_DIR
echo $OUTPUT_DIR
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools git
continue-on-error: true
- name: Update env variables to include go
run: |
sudo rm -rf /usr/local/go
curl https://dl.google.com/go/go1.19.2.linux-amd64.tar.gz | sudo tar -C/usr/local -zxvf -
cat <<'EOF' >>$HOME/.profile
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export GO111MODULE=on
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
EOF
source $HOME/.profile
- name: Verify Go is installed
run: go version
- name: Clone nyxd repo
run: |
git clone https://github.com/tommyv1987/nyxd
cd nyxd
git checkout release/v0.30.2
- name: Run nyxd
run: |
pwd
cd nyxd && make build
sleep 10
ls /home/runner/work/nym/nym/nyxd/build
- name: Prepare build output
shell: bash
env:
OUTPUT_DIR: ci-builds/nyxd
run: |
cp /home/runner/work/nym/nym/nyxd/build/nyxd $OUTPUT_DIR
WASMVM_SO=$(ldd /home/runner/work/nym/nym/nyxd/build/nyxd | grep "libwasm*" | awk '{ print $3 }')
ls $WASMVM_SO
sleep 3
cp $(echo $WASMVM_SO) $OUTPUT_DIR
- name: Deploy nyxd to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-avzr"
SOURCE: "ci-builds/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/
EXCLUDE: "/dist/, /node_modules/"
+2
View File
@@ -8,6 +8,7 @@
.idea
target
.env
.env.dev
/.vscode/settings.json
validator/.vscode
sample-configs/validator-config.toml
@@ -42,3 +43,4 @@ envs/qwerty.env
.parcel-cache
**/.DS_Store
cpu-cycles/libcpucycles/build
foxyfox.env
+125 -7
View File
@@ -4,14 +4,132 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
- nym-network-statistics properly handles signals ([#3209])
- add socks5 support for Rust SDK ([#3226], [#3255])
- add coconut bandwidth credential support for Rust SDK ([#3273])
## [v1.1.22] (2023-06-20)
[#3209]: https://github.com/nymtech/nym/issues/3209
[#3226]: https://github.com/nymtech/nym/pull/3226
[#3255]: https://github.com/nymtech/nym/pull/3255
[#3273]: https://github.com/nymtech/nym/pull/3273
- 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])
- Implement key storage for WASM client using IndexedDB (for browser) ([#3329])
- Initial version of nym-name-service contract providing name aliases for nym-addresses ([#3274])
- Update Cargo.lock ([#3410])
[#3403]: https://github.com/nymtech/nym/issues/3403
[#3329]: https://github.com/nymtech/nym/issues/3329
[#3274]: https://github.com/nymtech/nym/issues/3274
[#3410]: https://github.com/nymtech/nym/pull/3410
## [v1.1.18] (2023-05-09)
- Implement heartbeat messages between socks5 proxy and network requester ([#3215])
[#3215]: https://github.com/nymtech/nym/issues/3215
## [v1.1.17] (2023-05-02)
- Add service-provider-directory-contract support to nym-cli ([#3334])
- Start using the node-testing-utils (implemented in #3270) in nym-api Network monitor to simplify the logic there ([#3312])
- Add service-provider-directory support to validator-client ([#3296])
- Allow topology injection in our WASM client ('test my node' feature) ([#3270])
- Expose service-provider-directory contract data in nym-api endpoints ([#3242])
- Cache service provider contract in nym-api ([#3241])
- Feature/1 1 17 docs ([#3370])
- adding a test for SP endpoint ([#3367])
- Feature/store cipher ([#3350])
[#3334]: https://github.com/nymtech/nym/issues/3334
[#3312]: https://github.com/nymtech/nym/issues/3312
[#3296]: https://github.com/nymtech/nym/issues/3296
[#3270]: https://github.com/nymtech/nym/issues/3270
[#3242]: https://github.com/nymtech/nym/issues/3242
[#3241]: https://github.com/nymtech/nym/issues/3241
[#3370]: https://github.com/nymtech/nym/pull/3370
[#3367]: https://github.com/nymtech/nym/pull/3367
[#3350]: https://github.com/nymtech/nym/pull/3350
## [v1.1.16] (2023-04-25)
- Explorer - Fix sorting function on Stake Saturation. It is currently working per page and not globally ([#3320])
- Poisson process gets stuck at too slow rate. Rework to more aggressively up-regulate ([#3309])
- decrease the logging level of warnings associated with clients dropping packets due to gateway being overloaded (I'd say reduce it to debug/trace) - there are few sources of those, e.g. in real and cover traffic streams ([#3299])
- Make the buffer size in `AvailableReader` depend on packet sizes the client is using + introduce read timeouts ([#3213])
- Rust SDK - Support coconut, credential storage etc ([#2755])
- version bump for next release ([#3349])
- added coconut credential generation example ([#3339])
- update mix-node setup docs with node description ([#3325])
- exposed missing gateway commands in nym-cli ([#3324])
- make sure to clear inner 'ack_map' in 'GatewaysReader' ([#3300])
[#3320]: https://github.com/nymtech/nym/issues/3320
[#3309]: https://github.com/nymtech/nym/issues/3309
[#3299]: https://github.com/nymtech/nym/issues/3299
[#3213]: https://github.com/nymtech/nym/issues/3213
[#2755]: https://github.com/nymtech/nym/issues/2755
[#3349]: https://github.com/nymtech/nym/pull/3349
[#3339]: https://github.com/nymtech/nym/pull/3339
[#3325]: https://github.com/nymtech/nym/pull/3325
[#3324]: https://github.com/nymtech/nym/pull/3324
[#3300]: https://github.com/nymtech/nym/pull/3300
## [v1.1.15] (2023-04-18)
Generated
+1342 -576
View File
File diff suppressed because it is too large Load Diff
+12 -1
View File
@@ -37,6 +37,7 @@ members = [
"common/cosmwasm-smart-contracts/group-contract",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/name-service",
"common/cosmwasm-smart-contracts/service-provider-directory",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/credential-storage",
@@ -48,6 +49,7 @@ members = [
"common/ledger",
"common/mixnode-common",
"common/network-defaults",
"common/node-tester-utils",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nymsphinx",
@@ -59,12 +61,14 @@ members = [
"common/nymsphinx/forwarding",
"common/nymsphinx/framing",
"common/nymsphinx/params",
"common/nymsphinx/routing",
"common/nymsphinx/types",
"common/pemstore",
"common/socks5-client-core",
"common/socks5/proxy-helpers",
"common/socks5/requests",
"common/statistics",
"common/store-cipher",
"common/task",
"common/topology",
"common/types",
@@ -74,6 +78,7 @@ members = [
"gateway/gateway-requests",
"integrations/bity",
"mixnode",
"sdk/lib/socks5-listener",
"sdk/rust/nym-sdk",
"service-providers/common",
"service-providers/network-requester",
@@ -82,6 +87,7 @@ members = [
"nym-api/nym-api-requests",
"nym-outfox",
"tools/nym-cli",
"tools/nym-nr-query",
"tools/ts-rs-cli"
]
@@ -107,6 +113,7 @@ edition = "2021"
license = "Apache-2.0"
[workspace.dependencies]
anyhow = "1.0.71"
async-trait = "0.1.64"
bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0"
@@ -114,13 +121,16 @@ 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"
@@ -131,3 +141,4 @@ tap = "1.0.1"
thiserror = "1.0.38"
tokio = "1.24.1"
url = "2.2"
zeroize = "1.6.0"
+8 -2
View File
@@ -13,6 +13,10 @@ happy: fmt clippy-happy test
# on all workspaces.
build-release: build-release-main wasm
# Deprecated
# For backwards compatibility
clippy-all: clippy
# -----------------------------------------------------------------------------
# Define targets for a given workspace
# $(1): name
@@ -52,11 +56,11 @@ fmt-$(1):
cargo fmt --manifest-path $(2)/Cargo.toml --all
clippy-happy: clippy-happy-$(1)
clippy-all: clippy-$(1) clippy-examples-$(1)
clippy: clippy-$(1) clippy-examples-$(1)
check: check-$(1)
cargo-test: test-$(1)
cargo-test-expensive: test-expensive-$(1)
build: build-$(1) build-$(1)-examples
build: build-$(1) build-examples-$(1)
build-release-all: build-release-$(1)
fmt: fmt-$(1)
@@ -95,6 +99,7 @@ CONTRACTS_OUT_DIR=contracts/target/wasm32-unknown-unknown/release
VESTING_CONTRACT=$(CONTRACTS_OUT_DIR)/vesting_contract.wasm
MIXNET_CONTRACT=$(CONTRACTS_OUT_DIR)/mixnet_contract.wasm
SERVICE_PROVIDER_DIRECTORY_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_service_provider_directory.wasm
NAME_SERVICE_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_name_service.wasm
wasm: wasm-build wasm-opt
@@ -105,6 +110,7 @@ wasm-opt:
wasm-opt --disable-sign-ext -Os $(VESTING_CONTRACT) -o $(VESTING_CONTRACT)
wasm-opt --disable-sign-ext -Os $(MIXNET_CONTRACT) -o $(MIXNET_CONTRACT)
wasm-opt --disable-sign-ext -Os $(SERVICE_PROVIDER_DIRECTORY_CONTRACT) -o $(SERVICE_PROVIDER_DIRECTORY_CONTRACT)
wasm-opt --disable-sign-ext -Os $(NAME_SERVICE_CONTRACT) -o $(NAME_SERVICE_CONTRACT)
# -----------------------------------------------------------------------------
# Misc
+9 -5
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 to 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 places 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
@@ -46,7 +50,7 @@ Node, node operator and delegator rewards are determined according to the princi
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda_{i}#gh-dark-mode-only">|ratio of stake operator has pledged to their node to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\omega_{i}#gh-dark-mode-only">|fraction of total effort undertaken by node `i`, set to `1/k`.
|<img src="https://render.githubusercontent.com/render/math?math=k#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}k#gh-dark-mode-only">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitiveness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10% in.
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PF_{i}#gh-dark-mode-only">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMT.
@@ -70,7 +74,7 @@ Operator of node `i` is credited with the following amount:
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
Delegate with stake `s` recieves:
Delegate with stake `s` receives:
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
+2
View File
@@ -12,7 +12,9 @@ serde = { workspace = true, features = ["derive"] }
thiserror = "1.0"
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-client-core = { path = "../../common/client-core" }
nym-config = { path = "../../common/config" }
nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
+8 -6
View File
@@ -1,4 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod commands;
@@ -9,13 +9,14 @@ use commands::*;
use error::Result;
use log::*;
use nym_bin_common::completions::fig_generate;
use nym_config::{CRED_DB_FILE_NAME, DATA_DIR};
use nym_config::DEFAULT_DATA_DIR;
use nym_network_defaults::{setup_env, NymNetworkDetails};
use std::process::exit;
use std::time::{Duration, SystemTime};
use clap::{CommandFactory, Parser};
use nym_bin_common::logging::setup_logging;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_validator_client::nyxd::traits::DkgQueryClient;
use nym_validator_client::nyxd::{Coin, CosmWasmClient};
use nym_validator_client::Config;
@@ -71,10 +72,11 @@ async fn main() -> Result<()> {
match args.command {
Command::Run(r) => {
let db_path = r
.client_home_directory
.join(DATA_DIR)
.join(CRED_DB_FILE_NAME);
// we assume the structure of <home-dir>/data
let data_dir = r.client_home_directory.join(DEFAULT_DATA_DIR);
let paths = CommonClientPaths::new_default(data_dir);
let db_path = paths.credentials_database;
let shared_storage =
nym_credential_storage::initialise_persistent_storage(db_path).await;
let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?;
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.15"
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"
+92 -101
View File
@@ -1,99 +1,104 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::template::config_template;
use nym_client_core::config::ClientCoreConfigTrait;
use crate::client::config::persistence::ClientPaths;
use crate::client::config::template::CONFIG_TEMPLATE;
use nym_bin_common::logging::LoggingSettings;
use nym_config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
use nym_config::{NymConfig, OptionalSet};
use nym_config::{
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
OptionalSet, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::io;
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::str::FromStr;
pub use nym_client_core::config::Config as BaseConfig;
pub use nym_client_core::config::MISSING_VALUE;
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_20;
pub mod old_config_v1_1_20_2;
mod persistence;
mod template;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub enum SocketType {
WebSocket,
None,
const DEFAULT_CLIENTS_DIR: &str = "clients";
/// Derive default path to clients's config directory.
/// It should get resolved to `$HOME/.nym/mixnodes/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_CLIENTS_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
}
impl SocketType {
pub fn from_string<S: Into<String>>(val: S) -> Self {
let mut upper = val.into();
upper.make_ascii_uppercase();
match upper.as_ref() {
"WEBSOCKET" | "WS" => SocketType::WebSocket,
_ => SocketType::None,
}
}
pub fn is_websocket(&self) -> bool {
matches!(self, SocketType::WebSocket)
}
/// Derive default path to client's config file.
/// It should get resolved to `$HOME/.nym/clients/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
/// Derive default path to client's data directory where files, such as keys, are stored.
/// It should get resolved to `$HOME/.nym/clients/<id>/data`
pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_CLIENTS_DIR)
.join(id)
.join(DEFAULT_DATA_DIR)
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct Config {
#[serde(flatten)]
base: BaseConfig<Config>,
pub base: BaseClientConfig,
socket: Socket,
pub socket: Socket,
// pub paths: CommonClientPathfinder,
pub storage_paths: ClientPaths,
pub logging: LoggingSettings,
}
impl NymConfig for Config {
impl NymConfigTemplate for Config {
fn template() -> &'static str {
config_template()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("clients")
}
fn try_default_root_directory() -> Option<PathBuf> {
dirs::home_dir().map(|path| path.join(".nym").join("clients"))
}
fn root_directory(&self) -> PathBuf {
self.base.get_nym_root_directory()
}
fn config_directory(&self) -> PathBuf {
self.root_directory()
.join(self.base.get_id())
.join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory().join(self.base.get_id()).join("data")
}
}
impl ClientCoreConfigTrait for Config {
fn get_gateway_endpoint(&self) -> &nym_client_core::config::GatewayEndpointConfig {
self.base.get_gateway_endpoint()
CONFIG_TEMPLATE
}
}
impl Config {
pub fn new<S: Into<String>>(id: S) -> Self {
pub fn new<S: AsRef<str>>(id: S) -> Self {
Config {
base: BaseConfig::new(id),
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(),
}
}
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn default_location(&self) -> PathBuf {
default_config_filepath(&self.base.client.id)
}
pub fn save_to_default_location(&self) -> io::Result<()> {
let config_save_location: PathBuf = self.default_location();
save_formatted_config_to_file(self, config_save_location)
}
pub fn validate(&self) -> bool {
// no other sections have explicit requirements (yet)
self.base.validate()
@@ -123,39 +128,10 @@ impl Config {
self
}
// getters
pub fn get_config_file_save_location(&self) -> PathBuf {
self.config_directory().join(Self::config_file_name())
}
pub fn get_base(&self) -> &BaseConfig<Self> {
&self.base
}
pub fn get_base_mut(&mut self) -> &mut BaseConfig<Self> {
&mut self.base
}
pub fn get_debug_settings(&self) -> &DebugConfig {
self.get_base().get_debug_config()
}
pub fn get_socket_type(&self) -> SocketType {
self.socket.socket_type
}
pub fn get_listening_ip(&self) -> IpAddr {
self.socket.host
}
pub fn get_listening_port(&self) -> u16 {
self.socket.listening_port
}
// poor man's 'builder' method
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{
self.base = f(self.base, val);
self
@@ -165,7 +141,7 @@ impl Config {
// (plz, lets refactor it)
pub fn with_optional_ext<F, T>(mut self, f: F, val: Option<T>) -> Self
where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{
self.base = self.base.with_optional(f, val);
self
@@ -173,7 +149,7 @@ impl Config {
pub fn with_optional_env_ext<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
T: FromStr,
<T as FromStr>::Err: Debug,
{
@@ -189,7 +165,7 @@ impl Config {
parser: G,
) -> Self
where
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
G: Fn(&str) -> T,
{
self.base = self.base.with_optional_custom_env(f, val, env_var, parser);
@@ -197,19 +173,34 @@ 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 {
socket_type: SocketType,
host: IpAddr,
listening_port: u16,
pub socket_type: SocketType,
pub host: IpAddr,
pub listening_port: u16,
}
impl Default for Socket {
fn default() -> Self {
Socket {
socket_type: SocketType::WebSocket,
host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
host: IpAddr::V4(Ipv4Addr::LOCALHOST),
listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT,
}
}
@@ -1,58 +1,33 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, Socket};
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::NymConfig;
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13 {
#[serde(flatten)]
base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
pub base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
socket: Socket,
pub socket: SocketV1_1_20,
}
impl NymConfig for OldConfigV1_1_13 {
fn template() -> &'static str {
// not intended to be used
unimplemented!()
}
impl MigrationNymConfig for OldConfigV1_1_13 {
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("clients")
}
fn try_default_root_directory() -> Option<PathBuf> {
dirs::home_dir().map(|path| path.join(".nym").join("clients"))
}
fn root_directory(&self) -> PathBuf {
self.base.client.nym_root_directory.clone()
}
fn config_directory(&self) -> PathBuf {
self.root_directory()
.join(&self.base.client.id)
.join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory()
.join(&self.base.client.id)
.join("data")
}
}
impl From<OldConfigV1_1_13> for Config {
impl From<OldConfigV1_1_13> for ConfigV1_1_20 {
fn from(value: OldConfigV1_1_13) -> Self {
Config {
ConfigV1_1_20 {
base: value.base.into(),
socket: value.socket,
}
@@ -0,0 +1,115 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::old_config_v1_1_20_2::{
ClientPathsV1_1_20_2, ConfigV1_1_20_2, SocketTypeV1_1_20_2, SocketV1_1_20_2,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::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};
use std::fmt::Debug;
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub enum SocketTypeV1_1_20 {
WebSocket,
None,
}
impl From<SocketTypeV1_1_20> for SocketTypeV1_1_20_2 {
fn from(value: SocketTypeV1_1_20) -> Self {
match value {
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_20 {
#[serde(flatten)]
pub base: BaseConfigV1_1_20<ConfigV1_1_20>,
pub socket: SocketV1_1_20,
}
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,
nyxd_urls: value.base.client.nyxd_urls,
nym_api_urls: value.base.client.nym_api_urls,
gateway_endpoint: value.base.client.gateway_endpoint.into(),
},
debug: value.base.debug.into(),
},
socket: value.socket.into(),
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,
private_encryption_key_file: value.base.client.private_encryption_key_file,
public_encryption_key_file: value.base.client.public_encryption_key_file,
gateway_shared_key_file: value.base.client.gateway_shared_key_file,
ack_key_file: value.base.client.ack_key_file,
},
credentials_database: value.base.client.database_path,
reply_surb_database: value.base.client.reply_surb_database_path,
},
},
logging: LoggingSettings::default(),
}
}
}
impl MigrationNymConfig for ConfigV1_1_20 {
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("clients")
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct SocketV1_1_20 {
socket_type: SocketTypeV1_1_20,
host: IpAddr,
listening_port: u16,
}
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,
}
}
}
impl Default for SocketV1_1_20 {
fn default() -> Self {
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,
}
}
}
@@ -0,0 +1,20 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::disk_persistence::CommonClientPaths;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct ClientPaths {
#[serde(flatten)]
pub common_paths: CommonClientPaths,
}
impl ClientPaths {
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
ClientPaths {
common_paths: CommonClientPaths::new_default(base_data_directory),
}
}
}
+22 -36
View File
@@ -1,12 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) fn config_template() -> &'static str {
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs.
r#"
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs.
pub(crate) const CONFIG_TEMPLATE: &str = r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
@@ -37,50 +36,37 @@ nym_api_urls = [
{{/each}}
]
[storage_paths]
# Path to file containing private identity key.
private_identity_key_file = '{{ client.private_identity_key_file }}'
keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}'
# Path to file containing public identity key.
public_identity_key_file = '{{ client.public_identity_key_file }}'
keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}'
# Path to file containing private encryption key.
private_encryption_key_file = '{{ client.private_encryption_key_file }}'
keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}'
# Path to file containing public encryption key.
public_encryption_key_file = '{{ client.public_encryption_key_file }}'
# Path to the database containing bandwidth credentials
database_path = '{{ client.database_path }}'
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
reply_surb_database_path = '{{ client.reply_surb_database_path }}'
##### additional client config options #####
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
# A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway.
gateway_shared_key_file = '{{ client.gateway_shared_key_file }}'
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
# Path to file containing key used for encrypting and decrypting the content of an
# acknowledgement so that nobody besides the client knows which packet it refers to.
ack_key_file = '{{ client.ack_key_file }}'
keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}'
##### advanced configuration options #####
# Absolute path to the home Nym Clients directory.
nym_root_directory = '{{ client.nym_root_directory }}'
[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 database containing bandwidth credentials
credentials_database = '{{ storage_paths.credentials_database }}'
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
reply_surb_database = '{{ storage_paths.reply_surb_database }}'
# Path to the file containing information about gateway used by this client,
# i.e. details such as its public key, owner address or the network information.
gateway_details = '{{ storage_paths.gateway_details }}'
##### socket config options #####
@@ -120,5 +106,5 @@ average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
[debug.cover_traffic]
loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}'
"#
}
"#;
+59 -101
View File
@@ -1,4 +1,4 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
@@ -6,85 +6,40 @@ 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::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::received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestSender, ReconstructedMessagesReceiver,
};
use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_validator_client::nyxd::QueryNyxdClient;
use nym_validator_client::Client;
use std::error::Error;
use tokio::sync::watch::error::SendError;
pub use nym_client_core::client::key_manager::KeyManager;
use nym_credential_storage::persistent_storage::PersistentStorage;
pub use nym_sphinx::addressing::clients::Recipient;
pub use nym_sphinx::receiver::ReconstructedMessage;
use nym_validator_client::Client;
pub mod config;
type NativeClientBuilder<'a> = BaseClientBuilder<'a, Client<QueryNyxdClient>, OnDiskPersistent>;
pub struct SocketClient {
/// Client configuration options, including, among other things, packet sending rates,
/// key filepaths, etc.
config: Config,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
}
impl SocketClient {
pub fn new(config: Config) -> Self {
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
SocketClient {
config,
key_manager,
}
}
pub fn new_with_keys(config: Config, key_manager: KeyManager) -> Self {
SocketClient {
config,
key_manager,
}
}
async fn create_bandwidth_controller(
config: &Config,
) -> BandwidthController<Client<QueryNyxdClient>, PersistentStorage> {
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");
let nyxd_url = config
.get_base()
.get_validator_endpoints()
.pop()
.expect("No nyxd validator endpoint provided");
let api_url = config
.get_base()
.get_nym_api_endpoints()
.pop()
.expect("No validator api endpoint provided");
// overwrite env configuration with config URLs
client_config = client_config.with_urls(nyxd_url, api_url);
let client = nym_validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
BandwidthController::new(
nym_credential_storage::initialise_persistent_storage(
config.get_base().get_database_path(),
)
.await,
client,
)
SocketClient { config }
}
fn start_websocket_listener(
@@ -94,6 +49,7 @@ impl SocketClient {
client_state: ClientState,
self_address: &Recipient,
shutdown: nym_task::TaskClient,
packet_type: PacketType,
) {
info!("Starting websocket listener...");
@@ -119,9 +75,10 @@ impl SocketClient {
self_address,
shared_lane_queue_lengths,
reply_controller_sender,
Some(packet_type),
);
websocket::Listener::new(config.get_listening_ip(), config.get_listening_port())
websocket::Listener::new(config.socket.host, config.socket.listening_port)
.start(websocket_handler, shutdown);
}
@@ -134,31 +91,39 @@ impl SocketClient {
res
}
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 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(default_query_dkg_client_from_config(&self.config.base))
};
let storage = self.initialise_storage().await?;
let base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client);
Ok(base_client)
}
pub async fn start_socket(self) -> Result<TaskManager, ClientError> {
if !self.config.get_socket_type().is_websocket() {
if !self.config.socket.socket_type.is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
// don't create bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
None
} else {
Some(Self::create_bandwidth_controller(&self.config).await)
};
let base_builder = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
bandwidth_controller,
non_wasm_helpers::setup_fs_reply_surb_backend(
Some(self.config.get_base().get_reply_surb_database_path()),
self.config.get_debug_settings(),
)
.await?,
);
let self_address = base_builder.as_mix_recipient();
let base_builder = self.create_base_client_builder().await?;
let packet_type = self.config.base.debug.traffic.packet_type;
let mut started_client = base_builder.start_base().await?;
let self_address = started_client.address;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
let client_state = started_client.client_state;
@@ -170,40 +135,24 @@ impl SocketClient {
client_state,
&self_address,
started_client.task_manager.subscribe(),
packet_type,
);
info!("Client startup finished!");
info!("The address of this client is: {}", self_address);
info!("The address of this client is: {self_address}");
Ok(started_client.task_manager)
}
pub async fn start_direct(self) -> Result<DirectClient, ClientError> {
if self.config.get_socket_type().is_websocket() {
if self.config.socket.socket_type.is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
// don't create bandwidth controller if credentials are disabled
let bandwidth_controller = if self.config.get_base().get_disabled_credentials_mode() {
None
} else {
Some(Self::create_bandwidth_controller(&self.config).await)
};
let base_client = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
bandwidth_controller,
non_wasm_helpers::setup_fs_reply_surb_backend(
Some(self.config.get_base().get_reply_surb_database_path()),
self.config.get_debug_settings(),
)
.await?,
);
let address = base_client.as_mix_recipient();
let mut started_client = base_client.start_base().await?;
let base_builder = self.create_base_client_builder().await?;
let packet_type = self.config.base.debug.traffic.packet_type;
let mut started_client = base_builder.start_base().await?;
let address = started_client.address;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
@@ -224,6 +173,7 @@ impl SocketClient {
reconstructed_receiver,
address,
shutdown_notifier: started_client.task_manager,
packet_type,
})
}
}
@@ -237,6 +187,7 @@ pub struct DirectClient {
// we need to keep reference to this guy otherwise things will start dropping
shutdown_notifier: TaskManager,
packet_type: PacketType,
}
impl DirectClient {
@@ -257,7 +208,7 @@ impl DirectClient {
/// well enough in local tests)
pub async fn send_regular_message(&mut self, recipient: Recipient, message: Vec<u8>) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane);
let input_msg = InputMessage::new_regular(recipient, message, lane, Some(self.packet_type));
self.client_input
.input_sender
@@ -276,7 +227,13 @@ impl DirectClient {
reply_surbs: u32,
) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
let input_msg = InputMessage::new_anonymous(
recipient,
message,
reply_surbs,
lane,
Some(self.packet_type),
);
self.client_input
.input_sender
@@ -290,7 +247,8 @@ impl DirectClient {
/// well enough in local tests)
pub async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: Vec<u8>) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
let input_msg =
InputMessage::new_reply(recipient_tag, message, lane, Some(self.packet_type));
self.client_input
.input_sender
+49 -36
View File
@@ -1,7 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::try_upgrade_v1_1_13_config;
use crate::client::config::{
default_config_directory, default_config_filepath, default_data_directory,
};
use crate::commands::try_upgrade_config;
use crate::{
client::config::Config,
commands::{override_config, OverrideConfig},
@@ -9,13 +12,16 @@ use crate::{
};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_config::NymConfig;
use nym_credential_storage::persistent_storage::PersistentStorage;
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;
use std::fmt::Display;
use std::net::IpAddr;
use std::{fs, io};
use tap::TapFallible;
#[derive(Args, Clone)]
@@ -97,15 +103,15 @@ impl From<Init> for OverrideConfig {
pub struct InitResults {
#[serde(flatten)]
client_core: nym_client_core::init::InitResults,
client_listening_port: String,
client_listening_port: u16,
client_address: String,
}
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.get_base(), address),
client_listening_port: config.get_listening_port().to_string(),
client_core: nym_client_core::init::InitResults::new(&config.base, address, gateway),
client_listening_port: config.socket.listening_port,
client_address: address.to_string(),
}
}
@@ -119,18 +125,26 @@ impl Display for InitResults {
}
}
fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_data_directory(id))?;
fs::create_dir_all(default_config_directory(id))
}
pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
eprintln!("Initialising client...");
let id = &args.id;
let already_init = Config::default_config_file_path(id).exists();
if already_init {
let already_init = if default_config_filepath(id).exists() {
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
try_upgrade_v1_1_13_config(id)?;
try_upgrade_config(id)?;
eprintln!("Client \"{id}\" was already initialised before");
}
true
} else {
init_paths(id)?;
false
};
// Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted.
@@ -146,45 +160,44 @@ 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 gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, PersistentStorage>(
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
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.get_base(),
args.latency_based_selection,
Some(&config.base.client.nym_api_urls),
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
config.get_base_mut().set_gateway_endpoint(gateway);
config.save_to_file(None).tap_err(|_| {
let config_save_location = config.default_location();
config.save_to_default_location().tap_err(|_| {
log::error!("Failed to save the config file");
})?;
eprintln!(
"Saved configuration file to {}",
config_save_location.display()
);
print_saved_config(&config);
let address = init_details.client_address()?;
let address = nym_client_core::init::get_client_address_from_stored_keys(config.get_base())?;
let init_results = InitResults::new(&config, &address);
eprintln!("Client configuration completed.\n");
let init_results = InitResults::new(&config, &address, &init_details.gateway_details);
println!("{}", args.output.format(&init_results));
Ok(())
}
fn print_saved_config(config: &Config) {
let config_save_location = config.get_config_file_save_location();
eprintln!("Saved configuration file to {config_save_location:?}");
eprintln!("Using gateway: {}", config.get_base().get_gateway_id());
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner());
log::debug!(
"Gateway listener: {}",
config.get_base().get_gateway_listener()
);
eprintln!("Client configuration completed.\n");
}
+133 -13
View File
@@ -2,14 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::client::config::{BaseConfig, Config};
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;
use clap::{Parser, Subcommand};
use lazy_static::lazy_static;
use log::info;
use log::{error, info};
use nym_bin_common::build_information::BinaryBuildInformation;
use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_config::{NymConfig, OptionalSet};
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;
@@ -82,40 +91,151 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Syn
pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
config
.with_optional(Config::with_disabled_socket, args.disable_socket)
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
.with_base(
BaseClientConfig::with_high_default_traffic_volume,
args.fastmode,
)
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
.with_optional(Config::with_port, args.port)
.with_optional(Config::with_host, args.host)
.with_optional_custom_env_ext(
BaseConfig::with_custom_nym_apis,
BaseClientConfig::with_custom_nym_apis,
args.nym_apis,
nym_network_defaults::var_names::NYM_API,
nym_config::parse_urls,
)
.with_optional_custom_env_ext(
BaseConfig::with_custom_nyxd,
BaseClientConfig::with_custom_nyxd,
args.nyxd_urls,
nym_network_defaults::var_names::NYXD,
nym_config::parse_urls,
)
.with_optional_ext(
BaseConfig::with_disabled_credentials,
BaseClientConfig::with_disabled_credentials,
args.enabled_credentials_mode.map(|b| !b),
)
}
fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> {
// explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+)
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;
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
let Ok(old_config) = OldConfigV1_1_13::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(());
return Ok(false);
};
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: Config = old_config.into();
updated.save_to_file(None)
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_20_config(id: &str) -> Result<bool, ClientError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
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.20 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated, gateway_config) = updated_step1.upgrade();
persist_gateway_details(&updated, gateway_config)?;
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> {
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) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})");
return Err(ClientError::FailedToLoadConfig(id.to_string()));
}
};
if !config.validate() {
return Err(ClientError::ConfigValidationFailure);
}
Ok(config)
}
#[cfg(test)]
+7 -29
View File
@@ -1,10 +1,7 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::error::Error;
use std::net::IpAddr;
use crate::commands::try_upgrade_v1_1_13_config;
use crate::commands::try_load_current_config;
use crate::{
client::{config::Config, SocketClient},
commands::{override_config, OverrideConfig},
@@ -13,8 +10,9 @@ use crate::{
use clap::Args;
use log::*;
use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity;
use std::error::Error;
use std::net::IpAddr;
#[derive(Args, Clone)]
pub(crate) struct Run {
@@ -82,7 +80,7 @@ impl From<Run> for OverrideConfig {
// network version. It might do so in the future.
fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_base().get_version();
let config_version = &cfg.base.client.version;
if binary_version == config_version {
true
} else {
@@ -98,30 +96,10 @@ fn version_check(cfg: &Config) -> bool {
}
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
let id = &args.id;
eprintln!("Starting client {}...", args.id);
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
try_upgrade_v1_1_13_config(id)?;
let mut config = match Config::load_from_file(id) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
return Err(Box::new(ClientError::FailedToLoadConfig(id.to_string())));
}
};
if !config.validate() {
return Err(Box::new(ClientError::ConfigValidationFailure));
}
let override_config_fields = OverrideConfig::from(args.clone());
config = override_config(config, override_config_fields);
if config.get_base_mut().set_empty_fields_to_defaults() {
warn!("some of the core config options were left unset. the default values are going to get used instead.");
}
let mut config = try_load_current_config(&args.id)?;
config = override_config(config, OverrideConfig::from(args.clone()));
if !version_check(&config) {
error!("failed the local version check");
+16 -84
View File
@@ -1,42 +1,14 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, MISSING_VALUE};
use nym_bin_common::version_checker::Version;
use nym_config::NymConfig;
use crate::client::config::Config;
use crate::commands::try_load_current_config;
use clap::Args;
use std::fmt::Display;
use nym_bin_common::version_checker::Version;
use std::process;
#[allow(dead_code)]
fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! {
print_failed_upgrade(from_version, to_version);
process::exit(1)
}
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!("\n==================\nTrying to upgrade client from {from} to {to} ...");
}
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
eprintln!("Upgrade from {from} to {to} failed!\n==================\n");
}
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!("Upgrade from {from} to {to} was successful!\n==================\n");
}
fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! {
eprintln!(
"Cannot perform upgrade from {config_version} to {package_version}. Your version is too old to perform the upgrade.!"
);
process::exit(1)
}
fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! {
eprintln!("Cannot perform upgrade from {config_version} to {current_version}. Please let the developers know about this issue if you expected it to work!");
fn unimplemented_upgrade(current_version: &Version, config_version: &Version) -> ! {
eprintln!("Cannot perform upgrade from {config_version} to {current_version} as it hasn't been implemented yet");
process::exit(1)
}
@@ -48,7 +20,7 @@ pub(crate) struct Upgrade {
}
fn parse_config_version(config: &Config) -> Version {
let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| {
let version = Version::parse(&config.base.client.version).unwrap_or_else(|err| {
eprintln!("failed to parse client version! - {err}");
process::exit(1)
});
@@ -77,53 +49,14 @@ fn parse_package_version() -> Version {
version
}
fn minor_0_12_upgrade(
mut config: Config,
_matches: &Upgrade,
config_version: &Version,
package_version: &Version,
) -> Config {
let to_version = if package_version.major == 0 && package_version.minor == 12 {
package_version.clone()
} else {
Version::new(0, 12, 0)
};
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {err}");
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
print_successful_upgrade(config_version, to_version);
config
}
fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) {
loop {
let config_version = parse_config_version(&config);
if &config_version == package_version {
println!("You're using the most recent version!");
return;
}
config = match config_version.major {
0 => match config_version.minor {
9 | 10 => outdated_upgrade(&config_version, package_version),
11 => minor_0_12_upgrade(config, args, &config_version, package_version),
_ => unsupported_upgrade(&config_version, package_version),
},
_ => unsupported_upgrade(&config_version, package_version),
}
fn do_upgrade(config: Config, _args: &Upgrade, package_version: &Version) {
let config_version = parse_config_version(&config);
if &config_version == package_version {
println!("You're using the most recent version!");
return;
}
unimplemented_upgrade(package_version, &config_version)
}
pub(crate) fn execute(args: &Upgrade) {
@@ -131,16 +64,15 @@ pub(crate) fn execute(args: &Upgrade) {
let id = &args.id;
let existing_config = Config::load_from_file(id).unwrap_or_else(|err| {
let existing_config = try_load_current_config(id).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {err}");
process::exit(1)
});
if existing_config.get_base().get_version() == MISSING_VALUE {
if existing_config.base.client.version.is_empty() {
eprintln!("the existing configuration file does not seem to contain version number.");
process::exit(1);
}
// here be upgrade path to 0.9.X and beyond based on version number from config
do_upgrade(existing_config, args, &package_version)
}
+10 -3
View File
@@ -14,6 +14,7 @@ use nym_client_core::client::{
use nym_client_websocket_requests::{requests::ClientRequest, responses::ServerResponse};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::connections::{
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
@@ -41,6 +42,7 @@ pub(crate) struct HandlerBuilder {
self_full_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
}
impl HandlerBuilder {
@@ -51,6 +53,7 @@ impl HandlerBuilder {
self_full_address: &Recipient,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
) -> Self {
Self {
msg_input,
@@ -59,6 +62,7 @@ impl HandlerBuilder {
self_full_address: *self_full_address,
lane_queue_lengths,
reply_controller_sender,
packet_type,
}
}
@@ -73,6 +77,7 @@ impl HandlerBuilder {
received_response_type: Default::default(),
lane_queue_lengths: self.lane_queue_lengths.clone(),
reply_controller_sender: self.reply_controller_sender.clone(),
packet_type: self.packet_type,
}
}
}
@@ -86,6 +91,7 @@ pub(crate) struct Handler {
received_response_type: ReceivedResponseType,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
}
impl Drop for Handler {
@@ -160,7 +166,7 @@ impl Handler {
});
// the ack control is now responsible for chunking, etc.
let input_msg = InputMessage::new_regular(recipient, message, lane);
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
self.msg_input
.send(input_msg)
.await
@@ -191,7 +197,8 @@ impl Handler {
TransmissionLane::ConnectionId(id)
});
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
let input_msg =
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
self.msg_input
.send(input_msg)
.await
@@ -218,7 +225,7 @@ impl Handler {
TransmissionLane::ConnectionId(id)
});
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
self.msg_input
.send(input_msg)
.await
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.15"
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"
+51 -36
View File
@@ -1,20 +1,25 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::try_upgrade_v1_1_13_config;
use crate::commands::try_upgrade_config;
use crate::config::{
default_config_directory, default_config_filepath, default_data_directory, Config,
};
use crate::{
commands::{override_config, OverrideConfig},
error::Socks5ClientError,
};
use clap::Args;
use nym_bin_common::output_format::OutputFormat;
use nym_config::NymConfig;
use nym_credential_storage::persistent_storage::PersistentStorage;
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_socks5_client_core::config::Config;
use nym_sphinx::addressing::clients::Recipient;
use serde::Serialize;
use std::fmt::Display;
use std::{fs, io};
use tap::TapFallible;
#[derive(Args, Clone)]
@@ -91,6 +96,7 @@ impl From<Init> for OverrideConfig {
no_cover: init_config.no_cover,
nyxd_urls: init_config.nyxd_urls,
enabled_credentials_mode: init_config.enabled_credentials_mode,
outfox: false,
}
}
}
@@ -99,15 +105,19 @@ impl From<Init> for OverrideConfig {
pub struct InitResults {
#[serde(flatten)]
client_core: nym_client_core::init::InitResults,
socks5_listening_port: String,
socks5_listening_port: u16,
client_address: String,
}
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.get_base(), address),
socks5_listening_port: config.get_socks5().get_listening_port().to_string(),
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(),
}
}
@@ -121,19 +131,27 @@ impl Display for InitResults {
}
}
fn init_paths(id: &str) -> io::Result<()> {
fs::create_dir_all(default_data_directory(id))?;
fs::create_dir_all(default_config_directory(id))
}
pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
eprintln!("Initialising client...");
let id = &args.id;
let provider_address = &args.provider;
let already_init = Config::default_config_file_path(id).exists();
if already_init {
let already_init = if default_config_filepath(id).exists() {
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
try_upgrade_v1_1_13_config(id)?;
try_upgrade_config(id)?;
eprintln!("SOCKS5 client \"{id}\" was already initialised before");
}
true
} else {
init_paths(id)?;
false
};
// Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted.
@@ -149,50 +167,47 @@ 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()),
);
// 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 gateway = nym_client_core::init::setup_gateway_from_config::<Config, _, PersistentStorage>(
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
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.get_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.get_base_mut().set_gateway_endpoint(gateway);
// TODO: ask the service provider we specified for its interface version and set it in the config
config.save_to_file(None).tap_err(|_| {
let config_save_location = config.default_location();
config.save_to_default_location().tap_err(|_| {
log::error!("Failed to save the config file");
})?;
eprintln!(
"Saved configuration file to {}",
config_save_location.display()
);
print_saved_config(&config);
let address = init_details.client_address()?;
let address = nym_client_core::init::get_client_address_from_stored_keys(config.get_base())?;
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(())
}
fn print_saved_config(config: &Config) {
let config_save_location = config.get_config_file_save_location();
eprintln!("Saved configuration file to {:?}", config_save_location);
eprintln!("Using gateway: {}", config.get_base().get_gateway_id());
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner());
log::debug!(
"Gateway listener: {}",
config.get_base().get_gateway_listener()
);
eprintln!("Client configuration completed.\n");
}
+145 -17
View File
@@ -1,15 +1,25 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::config::{BaseClientConfig, Config};
use crate::error::Socks5ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use lazy_static::lazy_static;
use log::info;
use log::{error, info};
use nym_bin_common::build_information::BinaryBuildInformation;
use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_config::{NymConfig, OptionalSet};
use nym_socks5_client_core::config::old_config_v1_1_13::OldConfigV1_1_13;
use nym_socks5_client_core::config::{BaseConfig, Config};
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;
pub mod init;
@@ -64,6 +74,7 @@ pub(crate) struct OverrideConfig {
no_cover: bool,
nyxd_urls: Option<Vec<url::Url>>,
enabled_credentials_mode: Option<bool>,
outfox: bool,
}
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
@@ -80,41 +91,158 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Syn
}
pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
let packet_type = if args.outfox {
PacketType::Outfox
} else {
PacketType::Mix
};
config
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
.with_base(
BaseClientConfig::with_high_default_traffic_volume,
args.fastmode,
)
.with_base(BaseClientConfig::with_disabled_cover_traffic, args.no_cover)
.with_base(BaseClientConfig::with_packet_type, packet_type)
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
.with_optional(Config::with_port, args.port)
.with_optional_custom_env_ext(
BaseConfig::with_custom_nym_apis,
.with_optional_base_custom_env(
BaseClientConfig::with_custom_nym_apis,
args.nym_apis,
nym_network_defaults::var_names::NYM_API,
nym_config::parse_urls,
)
.with_optional_custom_env_ext(
BaseConfig::with_custom_nyxd,
.with_optional_base_custom_env(
BaseClientConfig::with_custom_nyxd,
args.nyxd_urls,
nym_network_defaults::var_names::NYXD,
nym_config::parse_urls,
)
.with_optional_ext(
BaseConfig::with_disabled_credentials,
.with_optional_base(
BaseClientConfig::with_disabled_credentials,
args.enabled_credentials_mode.map(|b| !b),
)
}
fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> {
// explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+)
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;
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
let Ok(old_config) = OldConfigV1_1_13::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(());
return Ok(false);
};
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: Config = old_config.into();
updated.save_to_file(None)
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_20_config(id: &str) -> Result<bool, Socks5ClientError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
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.20 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated, gateway_config) = updated_step1.upgrade();
persist_gateway_details(&updated, gateway_config)?;
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> {
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) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})");
return Err(Socks5ClientError::FailedToLoadConfig(id.to_string()));
}
};
if !config.validate() {
return Err(Socks5ClientError::ConfigValidationFailure);
}
Ok(config)
}
#[cfg(test)]
+18 -37
View File
@@ -1,7 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::try_upgrade_v1_1_13_config;
use crate::commands::try_load_current_config;
use crate::config::Config;
use crate::{
commands::{override_config, OverrideConfig},
error::Socks5ClientError,
@@ -9,9 +10,9 @@ use crate::{
use clap::Args;
use log::*;
use nym_bin_common::version_checker::is_minor_version_compatible;
use nym_config::NymConfig;
use nym_client_core::client::base_client::storage::OnDiskPersistent;
use nym_crypto::asymmetric::identity;
use nym_socks5_client_core::{config::Config, NymClient};
use nym_socks5_client_core::NymClient;
use nym_sphinx::addressing::clients::Recipient;
#[derive(Args, Clone)]
@@ -20,10 +21,6 @@ pub(crate) struct Run {
#[clap(long)]
id: String,
/// Custom path to the nym-mixnet-client configuration file
#[clap(long)]
config: Option<String>,
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
/// While this is going to hide its actual address information, it will make the actual communication
/// slower and consume nearly double the bandwidth as it will require sending reply SURBs.
@@ -67,6 +64,9 @@ pub(crate) struct Run {
/// with bandwidth credential requirement.
#[clap(long, hide = true)]
enabled_credentials_mode: Option<bool>,
#[clap(long, hide = true, action)]
outfox: bool,
}
impl From<Run> for OverrideConfig {
@@ -79,6 +79,7 @@ impl From<Run> for OverrideConfig {
no_cover: run_config.no_cover,
nyxd_urls: run_config.nyxd_urls,
enabled_credentials_mode: run_config.enabled_credentials_mode,
outfox: run_config.outfox,
}
}
}
@@ -87,13 +88,12 @@ impl From<Run> for OverrideConfig {
// network version. It might do so in the future.
fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_base().get_version();
let config_version = &cfg.core.base.client.version;
if binary_version == config_version {
true
} else {
warn!(
"The mixnode binary has different version than what is specified in config file! {} and {}",
binary_version, config_version
"The socks5-client binary has different version than what is specified in config file! {binary_version} and {config_version}",
);
if is_minor_version_compatible(binary_version, config_version) {
info!("but they are still semver compatible. However, consider running the `upgrade` command");
@@ -106,37 +106,18 @@ fn version_check(cfg: &Config) -> bool {
}
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let id = &args.id;
eprintln!("Starting client {}...", args.id);
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
try_upgrade_v1_1_13_config(id)?;
let mut config = match Config::load_from_file(id) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
return Err(Box::new(Socks5ClientError::FailedToLoadConfig(
id.to_string(),
)));
}
};
if !config.validate() {
return Err(Box::new(Socks5ClientError::ConfigValidationFailure));
}
let override_config_fields = OverrideConfig::from(args.clone());
config = override_config(config, override_config_fields);
if config.get_base_mut().set_empty_fields_to_defaults() {
warn!("some of the core config options were left unset. the default values are going to get used instead.");
}
let mut config = try_load_current_config(&args.id)?;
config = override_config(config, OverrideConfig::from(args.clone()));
if !version_check(&config) {
error!("failed the local version check");
return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck));
}
NymClient::new(config).run_forever().await
let storage =
OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug)
.await?;
NymClient::new(config.core, storage).run_forever().await
}
+19 -99
View File
@@ -1,50 +1,14 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_bin_common::version_checker::Version;
use nym_config::NymConfig;
use nym_socks5_client_core::config::{Config, MISSING_VALUE};
use crate::commands::try_load_current_config;
use crate::config::Config;
use clap::Args;
use std::{fmt::Display, process};
use nym_bin_common::version_checker::Version;
use std::process;
#[allow(dead_code)]
fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! {
print_failed_upgrade(from_version, to_version);
process::exit(1)
}
fn print_start_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"\n==================\nTrying to upgrade client from {} to {} ...",
from, to
);
}
fn print_failed_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
eprintln!(
"Upgrade from {} to {} failed!\n==================\n",
from, to
);
}
fn print_successful_upgrade<D1: Display, D2: Display>(from: D1, to: D2) {
println!(
"Upgrade from {} to {} was successful!\n==================\n",
from, to
);
}
fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! {
eprintln!(
"Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!",
config_version, package_version
);
process::exit(1)
}
fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! {
eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, current_version);
fn unimplemented_upgrade(current_version: &Version, config_version: &Version) -> ! {
eprintln!("Cannot perform upgrade from {config_version} to {current_version} as it hasn't been implemented yet");
process::exit(1)
}
@@ -56,15 +20,14 @@ pub(crate) struct Upgrade {
}
fn parse_config_version(config: &Config) -> Version {
let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| {
let version = Version::parse(&config.core.base.client.version).unwrap_or_else(|err| {
eprintln!("failed to parse client version! - {err}");
process::exit(1)
});
if version.is_prerelease() || !version.build.is_empty() {
eprintln!(
"Trying to upgrade from a non-released version {}. This is not supported!",
version
"Trying to upgrade from a non-released version {version}. This is not supported!"
);
process::exit(1)
}
@@ -79,63 +42,21 @@ fn parse_package_version() -> Version {
// however, we are not using them ourselves at the moment and hence it should be fine.
// if we change our mind, we could easily tweak this code
if version.is_prerelease() || !version.build.is_empty() {
eprintln!(
"Trying to upgrade to a non-released version {}. This is not supported!",
version
);
eprintln!("Trying to upgrade to a non-released version {version}. This is not supported!");
process::exit(1)
}
version
}
fn minor_0_12_upgrade(
mut config: Config,
_args: &Upgrade,
config_version: &Version,
package_version: &Version,
) -> Config {
let to_version = if package_version.major == 0 && package_version.minor == 12 {
package_version.clone()
} else {
Version::new(0, 12, 0)
};
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
.set_custom_version(to_version.to_string().as_ref());
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {err}");
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
print_successful_upgrade(config_version, to_version);
config
}
fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) {
loop {
let config_version = parse_config_version(&config);
if &config_version == package_version {
println!("You're using the most recent version!");
return;
}
config = match config_version.major {
0 => match config_version.minor {
9 | 10 => outdated_upgrade(&config_version, package_version),
11 => minor_0_12_upgrade(config, args, &config_version, package_version),
_ => unsupported_upgrade(&config_version, package_version),
},
_ => unsupported_upgrade(&config_version, package_version),
}
fn do_upgrade(config: Config, _args: &Upgrade, package_version: &Version) {
let config_version = parse_config_version(&config);
if &config_version == package_version {
println!("You're using the most recent version!");
return;
}
unimplemented_upgrade(package_version, &config_version)
}
pub(crate) fn execute(args: &Upgrade) {
@@ -143,16 +64,15 @@ pub(crate) fn execute(args: &Upgrade) {
let id = &args.id;
let existing_config = Config::load_from_file(id).unwrap_or_else(|err| {
let existing_config = try_load_current_config(id).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {err}");
process::exit(1)
});
if existing_config.get_base().get_version() == MISSING_VALUE {
if existing_config.core.base.client.version.is_empty() {
eprintln!("the existing configuration file does not seem to contain version number.");
process::exit(1);
}
// here be upgrade path to 0.9.X and beyond based on version number from config
do_upgrade(existing_config, args, &package_version)
}
+160
View File
@@ -0,0 +1,160 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::SocksClientPaths;
use crate::config::template::CONFIG_TEMPLATE;
use nym_bin_common::logging::LoggingSettings;
use nym_config::{
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
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_20;
pub mod old_config_v1_1_20_2;
mod persistence;
mod template;
const DEFAULT_SOCKS5_CLIENTS_DIR: &str = "socks5-clients";
/// Derive default path to clients's config directory.
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_SOCKS5_CLIENTS_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
}
/// Derive default path to client's config file.
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
}
/// Derive default path to client's data directory where files, such as keys, are stored.
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/data`
pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_SOCKS5_CLIENTS_DIR)
.join(id)
.join(DEFAULT_DATA_DIR)
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
pub core: CoreConfig,
pub storage_paths: SocksClientPaths,
pub logging: LoggingSettings,
}
impl NymConfigTemplate for Config {
fn template() -> &'static str {
CONFIG_TEMPLATE
}
}
impl Config {
pub fn new<S: AsRef<str>>(id: S, provider_mix_address: S) -> Self {
Config {
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(),
}
}
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn default_location(&self) -> PathBuf {
default_config_filepath(&self.core.base.client.id)
}
pub fn save_to_default_location(&self) -> io::Result<()> {
let config_save_location: PathBuf = self.default_location();
save_formatted_config_to_file(self, config_save_location)
}
pub fn validate(&self) -> bool {
// no other sections have explicit requirements (yet)
self.core.validate()
}
pub fn with_port(mut self, port: u16) -> Self {
self.core.socks5.listening_port = port;
self
}
pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self {
self.core.socks5.send_anonymously = anonymous_replies;
self
}
// poor man's 'builder' method
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{
self.core = self.core.with_base(f, val);
self
}
pub fn with_optional_base<F, T>(mut self, f: F, val: Option<T>) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
{
self.core = self.core.with_optional_base(f, val);
self
}
#[allow(unused)]
pub fn with_optional_base_env<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
T: FromStr,
<T as FromStr>::Err: Debug,
{
self.core = self.core.with_optional_base_env(f, val, env_var);
self
}
pub fn with_optional_base_custom_env<F, T, G>(
mut self,
f: F,
val: Option<T>,
env_var: &str,
parser: G,
) -> Self
where
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
G: Fn(&str) -> T,
{
self.core = self
.core
.with_optional_base_custom_env(f, val, env_var, parser);
self
}
}
@@ -0,0 +1,38 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
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;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13 {
#[serde(flatten)]
pub base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
pub socks5: Socks5V1_1_20,
}
impl MigrationNymConfig for OldConfigV1_1_13 {
fn default_root_directory() -> PathBuf {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let base_dir = must_get_home();
#[cfg(any(target_os = "android", target_os = "ios"))]
let base_dir = PathBuf::from("/tmp");
base_dir.join(".nym").join("socks5-clients")
}
}
impl From<OldConfigV1_1_13> for ConfigV1_1_20 {
fn from(value: OldConfigV1_1_13) -> Self {
ConfigV1_1_20 {
base: value.base.into(),
socks5: value.socks5,
}
}
}
@@ -0,0 +1,137 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::old_config_v1_1_20_2::{
ConfigV1_1_20_2, CoreConfigV1_1_20_2, SocksClientPathsV1_1_20_2,
};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::keys_paths::ClientKeysPaths;
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
use nym_client_core::config::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::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;
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 {
#[serde(flatten)]
pub base: BaseConfigV1_1_20<ConfigV1_1_20>,
pub socks5: Socks5V1_1_20,
}
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,
nyxd_urls: value.base.client.nyxd_urls,
nym_api_urls: value.base.client.nym_api_urls,
gateway_endpoint: value.base.client.gateway_endpoint.into(),
},
debug: value.base.debug.into(),
},
socks5: value.socks5.into(),
},
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,
private_encryption_key_file: value.base.client.private_encryption_key_file,
public_encryption_key_file: value.base.client.public_encryption_key_file,
gateway_shared_key_file: value.base.client.gateway_shared_key_file,
ack_key_file: value.base.client.ack_key_file,
},
credentials_database: value.base.client.database_path,
reply_surb_database: value.base.client.reply_surb_database_path,
},
},
logging: LoggingSettings::default(),
}
}
}
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();
#[cfg(any(target_os = "android", target_os = "ios"))]
let base_dir = PathBuf::from("/tmp");
base_dir.join(".nym").join("socks5-clients")
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5V1_1_20 {
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,
}
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,
socks5_protocol_version: value.socks5_protocol_version,
send_anonymously: value.send_anonymously,
socks5_debug: value.socks5_debug.into(),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Socks5DebugV1_1_20 {
connection_start_surbs: u32,
per_request_surbs: u32,
}
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_20 {
fn default() -> Self {
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)
}
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::disk_persistence::CommonClientPaths;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct SocksClientPaths {
#[serde(flatten)]
pub common_paths: CommonClientPaths,
}
impl SocksClientPaths {
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
SocksClientPaths {
common_paths: CommonClientPaths::new_default(base_data_directory),
}
}
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs.
pub(crate) const CONFIG_TEMPLATE: &str = r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
##### main base client config options #####
[core.client]
# Version of the client for which this configuration was created.
version = '{{ core.client.version }}'
# Human readable ID of this particular client.
id = '{{ core.client.id }}'
# Indicates whether this client is running in a disabled credentials mode, thus attempting
# to claim bandwidth without presenting bandwidth credentials.
disabled_credentials_mode = {{ core.client.disabled_credentials_mode }}
# Addresses to nyxd validators via which the client can communicate with the chain.
nyxd_urls = [
{{#each core.client.nyxd_urls }}
'{{this}}',
{{/each}}
]
# Addresses to APIs running on validator from which the client gets the view of the network.
nym_api_urls = [
{{#each core.client.nym_api_urls }}
'{{this}}',
{{/each}}
]
[storage_paths]
# Path to file containing private identity key.
keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}'
# Path to file containing public identity key.
keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}'
# Path to file containing private encryption key.
keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}'
# Path to file containing public encryption key.
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
# A gateway specific, optional, base58 stringified shared key used for
# communication with particular gateway.
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
# Path to file containing key used for encrypting and decrypting the content of an
# acknowledgement so that nobody besides the client knows which packet it refers to.
keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}'
# Path to the database containing bandwidth credentials
credentials_database = '{{ storage_paths.credentials_database }}'
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
reply_surb_database = '{{ storage_paths.reply_surb_database }}'
# Path to the file containing information about gateway used by this client,
# i.e. details such as its public key, owner address or the network information.
gateway_details = '{{ storage_paths.gateway_details }}'
##### socket config options #####
[core.socks5]
# The mix address of the provider to which all requests are going to be sent.
provider_mix_address = '{{ core.socks5.provider_mix_address }}'
# The port on which the client will be listening for incoming requests
listening_port = {{ core.socks5.listening_port }}
# Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
# While this is going to hide its actual address information, it will make the actual communication
# slower and consume nearly double the bandwidth as it will require sending reply SURBs.
#
# Note that some service providers might not support this.
send_anonymously = {{ core.socks5.send_anonymously }}
##### logging configuration options #####
[logging]
# TODO
##### debug configuration options #####
# The following options should not be modified unless you know EXACTLY what you are doing
# as if set incorrectly, they may impact your anonymity.
# [core.socks5.socks5_debug]
[core.debug]
[core.debug.traffic]
average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}'
message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}'
[core.debug.acknowledgements]
average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}'
[core.debug.cover_traffic]
loop_cover_traffic_average_delay = '{{ core.debug.cover_traffic.loop_cover_traffic_average_delay }}'
"#;
+1
View File
@@ -8,6 +8,7 @@ use nym_bin_common::logging::{maybe_print_banner, setup_logging};
use nym_network_defaults::setup_env;
mod commands;
mod config;
pub mod error;
#[tokio::main]
+21 -10
View File
@@ -34,7 +34,14 @@ import {
StakeSaturationResponse,
UnbondedMixnodeResponse,
VestingAccountInfo,
ContractState, VestingAccountsCoinPaged, VestingAccountsPaged, DelegationTimes, Delegations, Period, VestingAccountNode, DelegationBlock
ContractState,
VestingAccountsCoinPaged,
VestingAccountsPaged,
DelegationTimes,
Delegations,
Period,
VestingAccountNode,
DelegationBlock,
} from '@nymproject/types';
import QueryClient from './query-client';
import SigningClient, { ISigningClient } from './signing-client';
@@ -207,7 +214,7 @@ export default class ValidatorClient implements INymClient {
let mixNodes: UnbondedMixnodeResponse[] = [];
const limit = 50;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedUnbondedMixnodesResponse = await this.client.getUnbondedMixNodes(
this.mixnetContract,
@@ -230,7 +237,7 @@ export default class ValidatorClient implements INymClient {
let mixNodes: MixNodeBond[] = [];
const limit = 50;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeBondResponse = await this.client.getMixNodeBonds(
this.mixnetContract,
@@ -252,7 +259,7 @@ export default class ValidatorClient implements INymClient {
let mixNodes: MixNodeDetails[] = [];
const limit = 50;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeDetailsResponse = await this.client.getMixNodesDetailed(
this.mixnetContract,
@@ -284,7 +291,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixDelegationsResponse = await this.client.getMixNodeDelegationsPaged(
this.mixnetContract,
@@ -307,7 +314,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedDelegatorDelegationsResponse = await this.client.getDelegatorDelegationsPaged(
this.mixnetContract,
@@ -330,7 +337,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllDelegationsPaged(
this.mixnetContract,
@@ -518,11 +525,9 @@ export default class ValidatorClient implements INymClient {
return (this.client as ISigningClient).updateContractStateParams(this.mixnetContract, newParams, fee, memo);
}
// VESTING
// TODO - MOVE TO A DIFFERENT FILE
public async getVestingAccountsPaged(): Promise<VestingAccountsPaged> {
return this.client.getVestingAccountsPaged(this.vestingContract);
}
@@ -608,7 +613,7 @@ export default class ValidatorClient implements INymClient {
}
public async getDelegation(address: string, mix_id: number): Promise<DelegationBlock> {
return this.client.getDelegation(this.vestingContract, address, mix_id );
return this.client.getDelegation(this.vestingContract, address, mix_id);
}
public async getTotalDelegationAmount(address: string, mix_id: number, block_timestamp_sec: number): Promise<Coin> {
@@ -618,4 +623,10 @@ export default class ValidatorClient implements INymClient {
public async getCurrentVestingPeriod(address: string): Promise<Period> {
return this.client.getCurrentVestingPeriod(this.vestingContract, address);
}
// SIMULATE
public async simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]) {
return (this.client as SigningClient).simulateSend(signingAddress, from, to, amount);
}
}
+47 -8
View File
@@ -40,9 +40,18 @@ import {
RewardingParams,
UnbondedMixnodeResponse,
VestingAccountInfo,
ContractState, VestingAccountsCoinPaged, VestingAccountsPaged, DelegationTimes, Delegations, Period, VestingAccountNode, DelegationBlock
ContractState,
VestingAccountsCoinPaged,
VestingAccountsPaged,
DelegationTimes,
Delegations,
Period,
VestingAccountNode,
DelegationBlock,
} from '@nymproject/types';
import NymApiQuerier from './nym-api-querier';
import { makeBankMsgSend } from './utils';
import { ISimulateClient } from './types/simulate';
// methods exposed by `SigningCosmWasmClient`
export interface ICosmWasmSigning {
@@ -148,7 +157,7 @@ export interface INymSigning {
clientAddress: string;
}
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning {
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning, ISimulateClient {
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
@@ -515,7 +524,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
getVestingAccountsPaged(vestingContractAddress: string): Promise<VestingAccountsPaged> {
return this.nyxdQuerier.getVestingAccountsPaged(vestingContractAddress);
};
}
getVestingAmountsAccountsPaged(vestingContractAddress: string): Promise<VestingAccountsCoinPaged> {
return this.nyxdQuerier.getVestingAmountsAccountsPaged(vestingContractAddress);
@@ -569,7 +578,10 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getEndTime(vestingContractAddress, vestingAccountAddress);
}
getOriginalVestingDetails(vestingContractAddress: string, vestingAccountAddress: string): Promise<OriginalVestingResponse> {
getOriginalVestingDetails(
vestingContractAddress: string,
vestingAccountAddress: string,
): Promise<OriginalVestingResponse> {
return this.nyxdQuerier.getOriginalVestingDetails(vestingContractAddress, vestingAccountAddress);
}
@@ -589,7 +601,11 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getGateway(vestingContractAddress, address);
}
getDelegationTimes(vestingContractAddress: string, mix_id: number, delegatorAddress: string): Promise<DelegationTimes> {
getDelegationTimes(
vestingContractAddress: string,
mix_id: number,
delegatorAddress: string,
): Promise<DelegationTimes> {
return this.nyxdQuerier.getDelegationTimes(vestingContractAddress, mix_id, delegatorAddress);
}
@@ -597,15 +613,38 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getAllDelegations(vestingContractAddress);
}
getDelegation(vestingContractAddress: string, vestingAccountAddress: string, mix_id: number): Promise<DelegationBlock> {
getDelegation(
vestingContractAddress: string,
vestingAccountAddress: string,
mix_id: number,
): Promise<DelegationBlock> {
return this.nyxdQuerier.getDelegation(vestingContractAddress, vestingAccountAddress, mix_id);
}
getTotalDelegationAmount(vestingContractAddress: string, vestingAccountAddress: string, mix_id: number, block_timestamp_sec: number): Promise<Coin> {
return this.nyxdQuerier.getTotalDelegationAmount(vestingContractAddress, vestingAccountAddress, mix_id, block_timestamp_sec);
getTotalDelegationAmount(
vestingContractAddress: string,
vestingAccountAddress: string,
mix_id: number,
block_timestamp_sec: number,
): Promise<Coin> {
return this.nyxdQuerier.getTotalDelegationAmount(
vestingContractAddress,
vestingAccountAddress,
mix_id,
block_timestamp_sec,
);
}
getCurrentVestingPeriod(vestingContractAddress: string, address: string): Promise<Period> {
return this.nyxdQuerier.getCurrentVestingPeriod(vestingContractAddress, address);
}
// simulation
// TODO consider adding multipling factor
simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]) {
const sendMsg = makeBankMsgSend(from, to, amount);
return this.simulate(signingAddress, [sendMsg], 'simulate send tx');
}
}
@@ -0,0 +1,31 @@
import expect from 'expect';
import ValidatorClient from '../..';
const dotenv = require('dotenv');
dotenv.config();
// TODO: implement for QA with .env for mnemonics
describe('Simualtions', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connect(
process.env.mnemonic || '',
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
it('can simulate sending tokens', async () => {
const res = await client.simulateSend(client.address, client.address, client.address, [
{ amount: '400000', denom: 'unym' },
]);
expect(typeof res).toBe('number');
}).timeout(10000);
});
+5
View File
@@ -0,0 +1,5 @@
import { Coin } from '@cosmjs/proto-signing';
export interface ISimulateClient {
simulateSend(signingAddress: string, from: string, to: string, amount: Coin[]): Promise<number>;
}
+2
View File
@@ -0,0 +1,2 @@
[build]
target = "wasm32-unknown-unknown"
-1
View File
@@ -1,6 +1,5 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
+5275
View File
File diff suppressed because it is too large Load Diff
+15 -5
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"
@@ -17,29 +17,39 @@ default = ["console_error_panic_hook"]
offline-test = []
[dependencies]
async-trait = "0.1.68"
bs58 = "0.4.0"
futures = "0.3"
js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
serde-wasm-bindgen = "0.4"
serde-wasm-bindgen = "0.5"
tokio = { version = "1.24.1", features = ["sync"] }
url = "2.2"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
thiserror = "1.0.40"
zeroize = "1.6.0"
wasm-timer = { git = "https://github.com/mmsinclair/wasm-timer", rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"}
# internal
nym-node-tester-utils = { path = "../../common/node-tester-utils" }
nym-client-core = { path = "../../common/client-core", default-features = false, features = ["wasm"] }
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-crypto = { path = "../../common/crypto" }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-sphinx-acknowledgements = { path = "../../common/nymsphinx/acknowledgements", features = ["serde"]}
nym-topology = { path = "../../common/topology" }
nym-gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" }
nym-task = { path = "../../common/task" }
wasm-utils = { path = "../../common/wasm-utils", features = ["storage"], default-features = false }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
@@ -57,7 +67,7 @@ wee_alloc = { version = "0.4", optional = true }
wasm-bindgen-test = "0.3"
[package.metadata.wasm-pack.profile.release]
wasm-opt = true
wasm-opt = false
[profile.release]
lto = true
@@ -0,0 +1,2 @@
node_modules
dist
+5
View File
@@ -0,0 +1,5 @@
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
import('./index.js')
.catch(e => console.error('Error importing `index.js`:', e));
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym WebAssembly Demo</title>
</head>
<body>
<p>
<label>Sender: </label><input disabled="true" size="85" type="text" id="sender" value="">
</p>
<p>
<label>Recipient: </label><input size="85" type="text" id="recipient" value="">
</p>
<p>
<label>Message: </label><input type="text" id="message" value="Hello mixnet!">
</p>
<p>
<button id="send-button">Send</button>
</p>
<div>
<label>Mixnode Identity: </label>
<input type="text" size = "60" id="mixnode_identity" value="...">
<button id="magic-button">✨ Magic Test Button ✨</button>
</div>
<p>Send messages from your browser, through the mixnet, and to the recipient using the "send" button.</p>
<p><span style='color: blue;'>Sent</span> messages show in blue, <span style='color: green;'>received</span>
messages show in green.</p>
<hr>
<p>
<span id="output"></span>
</p>
<script src="./bootstrap.js"></script>
</body>
</html>
+170
View File
@@ -0,0 +1,170 @@
// Copyright 2020-2023 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
class WebWorkerClient {
worker = null;
constructor() {
this.worker = new Worker('./worker.js');
this.worker.onmessage = (ev) => {
if (ev.data && ev.data.kind) {
switch (ev.data.kind) {
case 'Ready':
const {selfAddress} = ev.data.args;
displaySenderAddress(selfAddress);
break;
case 'ReceiveMessage':
const {message, senderTag, isTestPacket } = ev.data.args;
displayReceived(message, senderTag, isTestPacket);
break;
case 'DisableMagicTestButton':
const magicButton = document.querySelector('#magic-button');
magicButton.setAttribute('disabled', "true")
break;
case 'DisplayTesterResults':
const {score, sentPackets, receivedPackets, receivedAcks, duplicatePackets, duplicateAcks} = ev.data.args;
const resultText = `Test score: ${score}. Sent ${sentPackets} packets. Received ${receivedPackets} packets and ${receivedAcks} acks back. We also got ${duplicatePackets} duplicate packets and ${duplicateAcks} duplicate acks.`
displayReceivedRawString(resultText)
break;
}
}
};
}
sendMessage = (message, recipient) => {
if (!this.worker) {
console.error('Could not send message because worker does not exist');
return;
}
this.worker.postMessage({
kind: 'SendMessage',
args: {
message, recipient,
},
});
};
sendTestPacket = (mixnodeIdentity) => {
if (!this.worker) {
console.error('Could not send message because worker does not exist');
return;
}
this.worker.postMessage({
kind: 'TestPacket',
args: {
mixnodeIdentity,
},
});
}
}
let client = null;
async function main() {
client = new WebWorkerClient();
const sendButton = document.querySelector('#send-button');
sendButton.onclick = function () {
sendMessageTo();
};
const magicButton = document.querySelector('#magic-button');
magicButton.onclick = function () {
sendTestPacket();
}
}
/**
* Create a Sphinx packet and send it to the mixnet through the gateway node.
*
* Message and recipient are taken from the values in the user interface.
*
*/
async function sendMessageTo() {
const message = document.getElementById('message').value;
const recipient = document.getElementById('recipient').value;
await client.sendMessage(message, recipient);
displaySend(message);
}
async function sendTestPacket() {
const mixnodeIdentity = document.getElementById('mixnode_identity').value;
await client.sendTestPacket(mixnodeIdentity)
displaySend(`sending test packets to: ${mixnodeIdentity}...`);
}
/**
* Display messages that have been sent up the websocket. Colours them blue.
*
* @param {string} message
*/
function displaySend(message) {
let timestamp = new Date().toISOString().substr(11, 12);
let sendDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: blue');
let paragraphContent = document.createTextNode(timestamp + ' sent >>> ' + message);
paragraph.appendChild(paragraphContent);
sendDiv.appendChild(paragraph);
document.getElementById('output').appendChild(sendDiv);
}
/**
* Display received text messages in the browser. Colour them green.
*
* @param {Uint8Array} raw
*/
function displayReceived(raw, sender_tag, isTestPacket) {
let content = new TextDecoder().decode(raw);
if (sender_tag !== undefined) {
console.log("this message also contained some surbs from", sender_tag)
}
if (isTestPacket) {
const decoded = JSON.parse(content)
content = `Received packet ${decoded.msg_id} / ${decoded.total_msgs} for node ${decoded.encoded_node_identity} (test: ${decoded.test_id})`
}
displayReceivedRawString(content)
}
function displayReceivedRawString(raw) {
let timestamp = new Date().toISOString().substr(11, 12);
let receivedDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: green');
let paragraphContent = document.createTextNode(timestamp + ' received >>> ' + raw);
paragraph.appendChild(paragraphContent);
receivedDiv.appendChild(paragraph);
document.getElementById('output').appendChild(receivedDiv);
}
/**
* Display the nymClient's sender address in the user interface
*
* @param {String} address
*/
function displaySenderAddress(address) {
document.getElementById('sender').value = address;
}
main();
@@ -0,0 +1,39 @@
{
"name": "create-wasm-app",
"version": "0.1.0",
"description": "create an app to consume rust-generated wasm packages",
"main": "index.js",
"bin": {
"create-wasm-app": ".bin/create-wasm-app.js"
},
"scripts": {
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server --port 8001"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
},
"keywords": [
"webassembly",
"wasm",
"rust",
"webpack"
],
"author": "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/nymtech/nym/issues"
},
"homepage": "https://nymtech.net/docs",
"devDependencies": {
"copy-webpack-plugin": "^10.2.4",
"hello-wasm-pack": "^0.1.0",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"@nymproject/nym-client-wasm": "file:../pkg"
}
}
@@ -0,0 +1,33 @@
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
entry: {
bootstrap: './bootstrap.js',
worker: './worker.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
// mode: 'development',
mode: 'production',
plugins: [
new CopyWebpackPlugin({
patterns: [
'index.html',
{
from: 'node_modules/@nymproject/nym-client-wasm/*.(js|wasm)',
to: '[name][ext]',
},
],
}),
],
experiments: { syncWebAssembly: true },
};
+344
View File
@@ -0,0 +1,344 @@
// Copyright 2020-2023 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
importScripts('nym_client_wasm.js');
console.log('Initializing worker');
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
const {
NymNodeTester,
WasmGateway,
WasmMixNode,
WasmNymTopology,
default_debug,
NymClientBuilder,
NymClient,
set_panic_hook,
Config,
GatewayEndpointConfig,
ClientStorage,
current_network_topology,
make_key,
make_key2
} = wasm_bindgen;
let client = null;
let tester = null;
function dummyTopology() {
const l1Mixnode = new WasmMixNode(
1,
'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47',
'178.79.143.65',
1789,
'4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz',
'8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG',
1,
'1.10.0',
);
const l2Mixnode = new WasmMixNode(
2,
'n1z93z44vf8ssvdhujjvxcj4rd5e3lz0l60wdk70',
'109.74.197.180',
1789,
'7sVjiMrPYZrDWRujku9QLxgE8noT7NTgBAqizCsu7AoK',
'GepXwRnKZDd8x2nBWAajGGBVvF3mrpVMQBkgfrGuqRCN',
2,
'1.10.0',
);
const l3Mixnode = new WasmMixNode(
3,
'n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77',
'176.58.101.80',
1789,
'FoM5Mx9Pxk1g3zEqkS3APgtBeTtTo3M8k7Yu4bV6kK1R',
'DeYjrDC2AcQRVFshiKnbUo6bRvPyZ33QGYR2DLeFJ9qD',
3,
'1.10.0',
);
const gateway = new WasmGateway(
'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts',
'85.159.212.96',
1789,
9000,
'336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9',
'BtYjoWihiuFihGKQypmpSspbhmWDPxzqeTVSd8ciCpWL',
'1.10.1',
);
const mixnodes = new Map();
mixnodes.set(1, [l1Mixnode]);
mixnodes.set(2, [l2Mixnode]);
mixnodes.set(3, [l3Mixnode]);
const gateways = [gateway];
return new WasmNymTopology(mixnodes, gateways)
}
function printAndDisplayTestResult(result) {
result.log_details();
self.postMessage({
kind: 'DisplayTesterResults',
args: {
score: result.score(),
sentPackets: result.sent_packets,
receivedPackets: result.received_packets,
receivedAcks: result.received_acks,
duplicatePackets: result.duplicate_packets,
duplicateAcks: result.duplicate_acks,
},
});
}
async function testWithTester() {
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
// A) construct with hardcoded topology
const topology = dummyTopology()
const nodeTester = await new NymNodeTester(topology, preferredGateway);
// B) first get topology directly from nym-api
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const topology = await current_network_topology(validator)
// const nodeTester = await new NymNodeTester(topology, preferredGateway);
//
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const nodeTester = await NymNodeTester.new_with_api(validator, preferredGateway)
// D, E, F) you also don't have to specify the gateway. if you don't, a random one (from your topology) will be used
// const topology = dummyTopology()
// const nodeTester = await new NymNodeTester(topology);
self.onmessage = async event => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'TestPacket': {
const {mixnodeIdentity} = event.data.args;
console.log("starting node test...");
let result = await nodeTester.test_node(mixnodeIdentity);
printAndDisplayTestResult(result)
}
}
}
};
}
async function testWithNymClient() {
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
const topology = dummyTopology()
let received = 0
const onMessageHandler = (message) => {
received += 1;
self.postMessage({
kind: 'ReceiveMessage',
args: {
message,
senderTag: undefined,
isTestPacket: true,
},
});
// it's really up to the user to create proper callback here...
console.log(`received ${received} packets so far`)
};
console.log('Instantiating WASM client...');
let clientBuilder = NymClientBuilder.new_tester(topology, onMessageHandler, preferredGateway)
console.log('Web worker creating WASM client...');
let local_client = await clientBuilder.start_client();
console.log('WASM client running!');
const selfAddress = local_client.self_address();
// set the global (I guess we don't have to anymore?)
client = local_client;
console.log(`Client address is ${selfAddress}`);
self.postMessage({
kind: 'Ready',
args: {
selfAddress,
},
});
// Set callback to handle messages passed to the worker.
self.onmessage = async event => {
console.log(event)
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'SendMessage': {
const {message, recipient} = event.data.args;
let uint8Array = new TextEncoder().encode(message);
await client.send_regular_message(uint8Array, recipient);
break;
}
case 'TestPacket': {
const {mixnodeIdentity} = event.data.args;
const req = await client.try_construct_test_packet_request(mixnodeIdentity);
await client.change_hardcoded_topology(req.injectable_topology());
await client.try_send_test_packets(req);
break;
}
}
}
};
}
async function normalNymClientUsage() {
self.postMessage({kind: 'DisableMagicTestButton'});
// only really useful if you want to adjust some settings like traffic rate
// (if not needed you can just pass a null)
const debug = default_debug();
debug.disable_main_poisson_packet_distribution = true;
debug.disable_loop_cover_traffic_stream = true;
debug.use_extended_packet_size = false;
// debug.average_packet_delay_ms = BigInt(10);
// debug.average_ack_delay_ms = BigInt(10);
// debug.ack_wait_addition_ms = BigInt(3000);
// debug.ack_wait_multiplier = 10;
debug.topology_refresh_rate_ms = BigInt(60000)
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
const config = new Config('my-awesome-wasm-client', validator, debug);
const onMessageHandler = (message) => {
console.log(message);
self.postMessage({
kind: 'ReceiveMessage',
args: {
message,
},
});
};
console.log('Instantiating WASM client...');
let localClient = await new NymClient(config, onMessageHandler)
console.log('WASM client running!');
const selfAddress = localClient.self_address();
// set the global (I guess we don't have to anymore?)
client = localClient;
console.log(`Client address is ${selfAddress}`);
self.postMessage({
kind: 'Ready',
args: {
selfAddress,
},
});
// Set callback to handle messages passed to the worker.
self.onmessage = async event => {
console.log(event)
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'SendMessage': {
const {message, recipient} = event.data.args;
let uint8Array = new TextEncoder().encode(message);
await client.send_regular_message(uint8Array, recipient);
break;
}
}
}
};
}
async function messWithStorage() {
self.onmessage = async event => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'TestPacket': {
const { mixnodeIdentity } = event.data.args;
console.log("button clicked...", mixnodeIdentity);
let id1 = "one";
let id2 = "two";
console.log("making store1 NO-ENC");
let _storage1 = await ClientStorage.new_unencrypted(id1);
console.log("making store2 ENC")
let _storage2 = await new ClientStorage(id2, "my-secret-password");
//
//
//
// console.log("attempting to use store1 WITH PASSWORD")
// let _storage1_alt = await new ClientStorage(id1, "password");
//
//
//
// console.log("attempting to use store2 WITHOUT PASSWORD")
// let _storage2_alt = await ClientStorage.new_unencrypted(id2);
//
//
//
// console.log("attempting to use store2 with WRONG PASSWORD")
// let _storage2_bad = await new ClientStorage(id2, "bad-password")
//
// console.log("read1: ", await storage1.read());
// console.log("read2: ", await storage2.read());
//
// console.log("store1: ", await storage1.store("FOOMP"));
//
// console.log("read1: ", await storage1.read());
// console.log("read2: ", await storage2.read());
}
}
}
};
}
async function main() {
// load WASM package
await wasm_bindgen('nym_client_wasm_bg.wasm');
console.log('Loaded WASM');
// sets up better stack traces in case of in-rust panics
set_panic_hook();
// run test on simplified and dedicated tester:
// await testWithTester()
// hook-up the whole client for testing
// await testWithNymClient()
// 'Normal' client setup (to send 'normal' messages)
await normalNymClientUsage()
}
// Let's get started!
main();
File diff suppressed because it is too large Load Diff
+82 -73
View File
@@ -1,4 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
@@ -7,58 +7,49 @@
#![allow(clippy::drop_copy)]
use nym_client_core::config::{
Acknowledgements as ConfigAcknowledgements, CoverTraffic as ConfigCoverTraffic,
DebugConfig as ConfigDebug, GatewayConnection as ConfigGatewayConnection,
GatewayEndpointConfig, ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology,
Traffic as ConfigTraffic,
Acknowledgements as ConfigAcknowledgements, Config as BaseClientConfig,
CoverTraffic as ConfigCoverTraffic, DebugConfig as ConfigDebug,
GatewayConnection as ConfigGatewayConnection, ReplySurbs as ConfigReplySurbs,
Topology as ConfigTopology, Traffic as ConfigTraffic,
};
use nym_sphinx::params::PacketSize;
use nym_sphinx::params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
/// ID specifies the human readable ID of this particular client.
pub(crate) id: String,
pub(crate) nym_api_url: Url,
pub(crate) disabled_credentials_mode: bool,
/// Information regarding how the client should send data to gateway.
pub(crate) gateway_endpoint: GatewayEndpointConfig,
pub(crate) debug: ConfigDebug,
pub(crate) base: BaseClientConfig,
}
#[wasm_bindgen]
impl Config {
#[wasm_bindgen(constructor)]
pub fn new(
id: String,
validator_server: String,
gateway_endpoint: GatewayEndpointConfig,
debug: Option<Debug>,
) -> Self {
pub fn new(id: String, validator_server: String, debug: Option<DebugWasm>) -> Self {
Config {
id,
nym_api_url: validator_server
.parse()
.expect("provided url was malformed"),
disabled_credentials_mode: true,
gateway_endpoint,
debug: debug.map(Into::into).unwrap_or_default(),
base: BaseClientConfig::new(id, env!("CARGO_PKG_VERSION").to_string())
.with_custom_nyxd(vec![validator_server
.parse()
.expect("provided url was malformed")])
.with_debug_config(debug.map(Into::into).unwrap_or_default()),
}
}
pub(crate) fn new_tester_config<S: Into<String>>(id: S) -> Self {
Config {
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),
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct Traffic {
pub struct TrafficWasm {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
@@ -77,14 +68,23 @@ pub struct Traffic {
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
pub use_extended_packet_size: bool,
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
pub use_outfox: bool,
}
impl From<Traffic> for ConfigTraffic {
fn from(traffic: Traffic) -> Self {
impl From<TrafficWasm> for ConfigTraffic {
fn from(traffic: TrafficWasm) -> Self {
let use_extended_packet_size = traffic
.use_extended_packet_size
.then(|| PacketSize::ExtendedPacket32);
let packet_type = if traffic.use_outfox {
PacketType::Outfox
} else {
PacketType::Mix
};
ConfigTraffic {
average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms),
message_sending_average_delay: Duration::from_millis(
@@ -94,26 +94,28 @@ impl From<Traffic> for ConfigTraffic {
.disable_main_poisson_packet_distribution,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: use_extended_packet_size,
packet_type,
}
}
}
impl From<ConfigTraffic> for Traffic {
impl From<ConfigTraffic> for TrafficWasm {
fn from(traffic: ConfigTraffic) -> Self {
Traffic {
TrafficWasm {
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u64,
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
as u64,
disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution,
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
use_outfox: traffic.packet_type == PacketType::Outfox,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct CoverTraffic {
pub struct CoverTrafficWasm {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
pub loop_cover_traffic_average_delay_ms: u64,
@@ -127,8 +129,8 @@ pub struct CoverTraffic {
pub disable_loop_cover_traffic_stream: bool,
}
impl From<CoverTraffic> for ConfigCoverTraffic {
fn from(cover_traffic: CoverTraffic) -> Self {
impl From<CoverTrafficWasm> for ConfigCoverTraffic {
fn from(cover_traffic: CoverTrafficWasm) -> Self {
ConfigCoverTraffic {
loop_cover_traffic_average_delay: Duration::from_millis(
cover_traffic.loop_cover_traffic_average_delay_ms,
@@ -139,9 +141,9 @@ impl From<CoverTraffic> for ConfigCoverTraffic {
}
}
impl From<ConfigCoverTraffic> for CoverTraffic {
impl From<ConfigCoverTraffic> for CoverTrafficWasm {
fn from(cover_traffic: ConfigCoverTraffic) -> Self {
CoverTraffic {
CoverTrafficWasm {
loop_cover_traffic_average_delay_ms: cover_traffic
.loop_cover_traffic_average_delay
.as_millis() as u64,
@@ -153,14 +155,14 @@ impl From<ConfigCoverTraffic> for CoverTraffic {
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct GatewayConnection {
pub struct GatewayConnectionWasm {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
pub gateway_response_timeout_ms: u64,
}
impl From<GatewayConnection> for ConfigGatewayConnection {
fn from(gateway_connection: GatewayConnection) -> Self {
impl From<GatewayConnectionWasm> for ConfigGatewayConnection {
fn from(gateway_connection: GatewayConnectionWasm) -> Self {
ConfigGatewayConnection {
gateway_response_timeout: Duration::from_millis(
gateway_connection.gateway_response_timeout_ms,
@@ -169,9 +171,9 @@ impl From<GatewayConnection> for ConfigGatewayConnection {
}
}
impl From<ConfigGatewayConnection> for GatewayConnection {
impl From<ConfigGatewayConnection> for GatewayConnectionWasm {
fn from(gateway_connection: ConfigGatewayConnection) -> Self {
GatewayConnection {
GatewayConnectionWasm {
gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis()
as u64,
}
@@ -180,7 +182,7 @@ impl From<ConfigGatewayConnection> for GatewayConnection {
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct Acknowledgements {
pub struct AcknowledgementsWasm {
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
@@ -198,8 +200,8 @@ pub struct Acknowledgements {
pub ack_wait_addition_ms: u64,
}
impl From<Acknowledgements> for ConfigAcknowledgements {
fn from(acknowledgements: Acknowledgements) -> Self {
impl From<AcknowledgementsWasm> for ConfigAcknowledgements {
fn from(acknowledgements: AcknowledgementsWasm) -> Self {
ConfigAcknowledgements {
average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms),
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
@@ -208,9 +210,9 @@ impl From<Acknowledgements> for ConfigAcknowledgements {
}
}
impl From<ConfigAcknowledgements> for Acknowledgements {
impl From<ConfigAcknowledgements> for AcknowledgementsWasm {
fn from(acknowledgements: ConfigAcknowledgements) -> Self {
Acknowledgements {
AcknowledgementsWasm {
average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u64,
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u64,
@@ -220,7 +222,7 @@ impl From<ConfigAcknowledgements> for Acknowledgements {
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct Topology {
pub struct TopologyWasm {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
pub topology_refresh_rate_ms: u64,
@@ -229,31 +231,38 @@ pub struct Topology {
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
pub topology_resolution_timeout_ms: u64,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
}
impl From<Topology> for ConfigTopology {
fn from(topology: Topology) -> Self {
impl From<TopologyWasm> for ConfigTopology {
fn from(topology: TopologyWasm) -> Self {
ConfigTopology {
topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms),
topology_resolution_timeout: Duration::from_millis(
topology.topology_resolution_timeout_ms,
),
disable_refreshing: topology.disable_refreshing,
}
}
}
impl From<ConfigTopology> for Topology {
impl From<ConfigTopology> for TopologyWasm {
fn from(topology: ConfigTopology) -> Self {
Topology {
TopologyWasm {
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64,
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64,
disable_refreshing: topology.disable_refreshing,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct ReplySurbs {
pub struct ReplySurbsWasm {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
/// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize,
@@ -287,8 +296,8 @@ pub struct ReplySurbs {
pub maximum_reply_key_age_ms: u64,
}
impl From<ReplySurbs> for ConfigReplySurbs {
fn from(reply_surbs: ReplySurbs) -> Self {
impl From<ReplySurbsWasm> for ConfigReplySurbs {
fn from(reply_surbs: ReplySurbsWasm) -> Self {
ConfigReplySurbs {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
@@ -308,9 +317,9 @@ impl From<ReplySurbs> for ConfigReplySurbs {
}
}
impl From<ConfigReplySurbs> for ReplySurbs {
impl From<ConfigReplySurbs> for ReplySurbsWasm {
fn from(reply_surbs: ConfigReplySurbs) -> Self {
ReplySurbs {
ReplySurbsWasm {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
@@ -332,28 +341,28 @@ impl From<ConfigReplySurbs> for ReplySurbs {
// just a helper structure to more easily pass through the JS boundary
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct Debug {
pub struct DebugWasm {
/// Defines all configuration options related to traffic streams.
pub traffic: Traffic,
pub traffic: TrafficWasm,
/// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTraffic,
pub cover_traffic: CoverTrafficWasm,
/// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnection,
pub gateway_connection: GatewayConnectionWasm,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: Acknowledgements,
pub acknowledgements: AcknowledgementsWasm,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: Topology,
pub topology: TopologyWasm,
/// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbs,
pub reply_surbs: ReplySurbsWasm,
}
impl From<Debug> for ConfigDebug {
fn from(debug: Debug) -> Self {
impl From<DebugWasm> for ConfigDebug {
fn from(debug: DebugWasm) -> Self {
ConfigDebug {
traffic: debug.traffic.into(),
cover_traffic: debug.cover_traffic.into(),
@@ -365,9 +374,9 @@ impl From<Debug> for ConfigDebug {
}
}
impl From<ConfigDebug> for Debug {
impl From<ConfigDebug> for DebugWasm {
fn from(debug: ConfigDebug) -> Self {
Debug {
DebugWasm {
traffic: debug.traffic.into(),
cover_traffic: debug.cover_traffic.into(),
gateway_connection: debug.gateway_connection.into(),
@@ -379,6 +388,6 @@ impl From<ConfigDebug> for Debug {
}
#[wasm_bindgen]
pub fn default_debug() -> Debug {
pub fn default_debug() -> DebugWasm {
ConfigDebug::default().into()
}
+135 -7
View File
@@ -1,16 +1,42 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::tester::helpers::WasmTestMessageExt;
use crate::tester::{NodeTestMessage, DEFAULT_TEST_PACKETS};
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::base_client::ClientInput;
use nym_client_core::client::base_client::{ClientInput, ClientState};
use nym_client_core::client::inbound_messages::InputMessage;
use nym_topology::{MixLayer, NymTopology};
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, simple_js_error};
#[wasm_bindgen]
pub struct NymClientTestRequest {
// serialized NodeTestMessage
pub(crate) test_msgs: Vec<Vec<u8>>,
// specially constructed network topology that only contains the target
// node on the tested layer
pub(crate) testable_topology: NymTopology,
}
#[wasm_bindgen]
impl NymClientTestRequest {
pub fn injectable_topology(&self) -> WasmNymTopology {
self.testable_topology.clone().into()
}
}
// defining helper trait as we could directly call the method on the wrapper
pub(crate) trait InputSender {
fn send_message(&self, message: InputMessage) -> Promise;
fn send_messages(&self, messages: Vec<InputMessage>) -> Promise;
}
impl InputSender for Arc<ClientInput> {
@@ -19,12 +45,114 @@ impl InputSender for Arc<ClientInput> {
future_to_promise(async move {
match this.input_sender.send(message).await {
Ok(_) => Ok(JsValue::null()),
Err(_) => {
let js_error =
js_sys::Error::new("InputMessageReceiver has stopped receiving!");
Err(JsValue::from(js_error))
}
Err(_) => Err(simple_js_error(
"InputMessageReceiver has stopped receiving!",
)),
}
})
}
fn send_messages(&self, messages: Vec<InputMessage>) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
for message in messages {
if this.input_sender.send(message).await.is_err() {
return Err(simple_js_error(
"InputMessageReceiver has stopped receiving!",
));
}
}
Ok(JsValue::null())
})
}
}
pub(crate) trait WasmTopologyExt {
/// Changes the current network topology to the provided value.
fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise;
/// Returns the current network topology.
fn current_topology(&self) -> Promise;
/// Checks whether the provided node exists in the known network topology and if so, returns its layer.
fn check_for_mixnode_existence(&self, mixnode_identity: String) -> Promise;
/// Creates a `NymClientTestRequest` with a variant of `this` topology where the target node is the only one on its layer.
fn mix_test_request(
&self,
test_id: u32,
mixnode_identity: String,
num_test_packets: Option<u32>,
) -> Promise;
}
impl WasmTopologyExt for Arc<ClientState> {
fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
let nym_topology: NymTopology = topology.into();
console_log!("changing topology to {nym_topology:?}");
this.topology_accessor
.manually_change_topology(nym_topology)
.await;
Ok(JsValue::null())
})
}
fn current_topology(&self) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
match this.topology_accessor.current_topology().await {
Some(topology) => Ok(JsValue::from(WasmNymTopology::from(topology))),
None => Err(WasmClientError::UnavailableNetworkTopology.into()),
}
})
}
/// Checks whether the target mixnode exists in the known network topology and returns its layer.
fn check_for_mixnode_existence(&self, mixnode_identity: String) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
let Some(current_topology) = this.topology_accessor.current_topology().await else {
return Err(WasmClientError::UnavailableNetworkTopology.into())
};
match current_topology.find_mix_by_identity(&mixnode_identity) {
None => Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into()),
Some(node) => Ok(JsValue::from(MixLayer::from(node.layer))),
}
})
}
fn mix_test_request(
&self,
test_id: u32,
mixnode_identity: String,
num_test_packets: Option<u32>,
) -> Promise {
let num_test_packets = num_test_packets.unwrap_or(DEFAULT_TEST_PACKETS);
let this = Arc::clone(self);
future_to_promise(async move {
let Some(current_topology) = this.topology_accessor.current_topology().await else {
return Err(WasmClientError::UnavailableNetworkTopology.into())
};
let Some(mix) = current_topology.find_mix_by_identity(&mixnode_identity) else {
return Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into());
};
let ext = WasmTestMessageExt::new(test_id);
let test_msgs = NodeTestMessage::mix_plaintexts(mix, num_test_packets, ext)
.map_err(WasmClientError::from)?;
let mut updated = current_topology.clone();
updated.set_mixes_in_layer(mix.layer.into(), vec![mix.to_owned()]);
Ok(JsValue::from(NymClientTestRequest {
test_msgs,
testable_topology: updated,
}))
})
}
}
+202 -117
View File
@@ -1,27 +1,37 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use self::config::Config;
use crate::client::helpers::InputSender;
use crate::client::helpers::{InputSender, NymClientTestRequest, WasmTopologyExt};
use crate::client::response_pusher::ResponsePusher;
use crate::constants::NODE_TESTER_CLIENT_ID;
use crate::error::WasmClientError;
use crate::helpers::{
parse_recipient, parse_sender_tag, setup_from_topology, setup_gateway_from_api,
setup_reply_surb_storage_backend,
};
use crate::storage::traits::FullWasmClientStorage;
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, CredentialsToggle,
BaseClientBuilder, ClientInput, ClientOutput, ClientState,
};
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_client_core::client::inbound_messages::InputMessage;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider};
use nym_topology::NymTopology;
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use rand::RngCore;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_error, console_log};
use wasm_utils::{check_promise_result, console_log, PromisableResult};
pub mod config;
mod helpers;
@@ -31,147 +41,222 @@ mod response_pusher;
pub struct NymClient {
self_address: String,
client_input: Arc<ClientInput>,
client_state: Arc<ClientState>,
// keep track of the "old" topology for the purposes of node tester
// so that it could be restored after the check is done
_full_topology: Option<NymTopology>,
// even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline
_task_manager: TaskManager,
packet_type: PacketType,
}
#[wasm_bindgen]
pub struct NymClientBuilder {
config: Config,
custom_topology: Option<NymTopology>,
preferred_gateway: Option<IdentityKey>,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
reply_surb_storage_backend: browser_backend::Backend,
storage_passphrase: Option<String>,
on_message: js_sys::Function,
// unimplemented:
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
disabled_credentials: bool,
}
#[wasm_bindgen]
impl NymClientBuilder {
#[wasm_bindgen(constructor)]
pub fn new(config: Config, on_message: js_sys::Function) -> Self {
//, key_manager: Option<KeyManager>) {
pub fn new(
config: Config,
on_message: js_sys::Function,
preferred_gateway: Option<IdentityKey>,
storage_passphrase: Option<String>,
) -> Self {
NymClientBuilder {
reply_surb_storage_backend: Self::setup_reply_surb_storage_backend(&config),
config,
key_manager: Self::setup_key_manager(),
custom_topology: None,
storage_passphrase,
on_message,
bandwidth_controller: None,
disabled_credentials: true,
preferred_gateway,
}
}
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate
// a prior shared keypair between the client and the gateway
// no cover traffic
// no poisson delay
// hardcoded topology
// NOTE: you most likely want to use `[NymNodeTester]` instead.
pub fn new_tester(
topology: WasmNymTopology,
on_message: js_sys::Function,
gateway: Option<IdentityKey>,
) -> Self {
if let Some(gateway_id) = &gateway {
if !topology.ensure_contains_gateway_id(gateway_id) {
panic!("the specified topology does not contain the gateway used by the client")
}
}
// perhaps this should be public?
fn setup_key_manager() -> KeyManager {
let mut rng = OsRng;
// for time being generate new keys each time...
console_log!("generated new set of keys");
KeyManager::new(&mut rng)
}
let full_config = Config::new_tester_config(NODE_TESTER_CLIENT_ID);
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no persistence
fn setup_reply_surb_storage_backend(config: &Config) -> browser_backend::Backend {
browser_backend::Backend::new(
config
.debug
.reply_surbs
.minimum_reply_surb_storage_threshold,
config
.debug
.reply_surbs
.maximum_reply_surb_storage_threshold,
)
NymClientBuilder {
config: full_config,
custom_topology: Some(topology.into()),
on_message,
storage_passphrase: None,
preferred_gateway: gateway,
}
}
fn start_reconstructed_pusher(client_output: ClientOutput, on_message: js_sys::Function) {
ResponsePusher::new(client_output, on_message).start()
}
pub async fn start_client(self) -> Promise {
future_to_promise(async move {
console_log!("Starting the wasm client");
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider + Send + Sync>> {
if let Some(hardcoded_topology) = self.custom_topology.take() {
Some(Box::new(HardcodedTopologyProvider::new(hardcoded_topology)))
} else {
None
}
}
let disabled_credentials = if self.disabled_credentials {
CredentialsToggle::Disabled
} else {
CredentialsToggle::Enabled
};
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(),
}
}
let base_builder = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&self.config.debug,
self.key_manager,
self.bandwidth_controller,
self.reply_surb_storage_backend,
disabled_credentials,
vec![self.config.nym_api_url.clone()],
async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> {
console_log!("Starting the wasm client");
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.
let client_store =
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
if let Some(topology) = &self.custom_topology {
setup_from_topology(user_chosen, topology, &client_store).await?
} else {
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::<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);
}
let self_address = base_builder.as_mix_recipient().to_string();
let mut started_client = match base_builder.start_base().await {
Ok(base_client) => base_client,
Err(err) => {
let error_msg = format!("failed to start the base client components - {err}");
console_error!("{}", error_msg);
let js_error = js_sys::Error::new(&error_msg);
return Err(JsValue::from(js_error));
}
};
let mut started_client = base_builder.start_base().await?;
let self_address = started_client.address.to_string();
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
Self::start_reconstructed_pusher(client_output, self.on_message);
Self::start_reconstructed_pusher(client_output, self.on_message);
Ok(JsValue::from(NymClient {
self_address,
client_input: Arc::new(client_input),
_task_manager: started_client.task_manager,
}))
Ok(NymClient {
self_address,
client_input: Arc::new(client_input),
client_state: Arc::new(started_client.client_state),
_full_topology: None,
_task_manager: started_client.task_manager,
packet_type,
})
}
pub fn start_client(self) -> Promise {
future_to_promise(async move { self.start_client_async().await.into_promise_result() })
}
}
#[wasm_bindgen]
impl NymClient {
async fn _new(
config: Config,
on_message: js_sys::Function,
preferred_gateway: Option<IdentityKey>,
storage_passphrase: Option<String>,
) -> Result<NymClient, WasmClientError> {
NymClientBuilder::new(config, on_message, preferred_gateway, storage_passphrase)
.start_client_async()
.await
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(
config: Config,
on_message: js_sys::Function,
preferred_gateway: Option<IdentityKey>,
storage_passphrase: Option<String>,
) -> Promise {
future_to_promise(async move {
Self::_new(config, on_message, preferred_gateway, storage_passphrase)
.await
.into_promise_result()
})
}
pub fn self_address(&self) -> String {
self.self_address.clone()
}
fn parse_recipient(recipient: &str) -> Result<Recipient, JsValue> {
match Recipient::try_from_base58_string(recipient) {
Ok(recipient) => Ok(recipient),
Err(err) => {
let error_msg = format!("{recipient} is not a valid Nym network recipient - {err}");
console_error!("{}", error_msg);
let js_error = js_sys::Error::new(&error_msg);
Err(JsValue::from(js_error))
}
}
pub fn try_construct_test_packet_request(
&self,
mixnode_identity: String,
num_test_packets: Option<u32>,
) -> Promise {
// TODO: improve the source of rng (i.e. don't make it ephemeral...)
let mut ephemeral_rng = OsRng;
let test_id = ephemeral_rng.next_u32();
self.client_state
.mix_test_request(test_id, mixnode_identity, num_test_packets)
}
fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, JsValue> {
match AnonymousSenderTag::try_from_base58_string(tag) {
Ok(tag) => Ok(tag),
Err(err) => {
let error_msg = format!("{tag} is not a valid Nym AnonymousSenderTag - {err}");
console_error!("{}", error_msg);
let js_error = js_sys::Error::new(&error_msg);
Err(JsValue::from(js_error))
}
}
pub fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise {
self.client_state.change_hardcoded_topology(topology)
}
pub fn current_network_topology(&self) -> Promise {
self.client_state.current_topology()
}
/// Sends a test packet through the current network topology.
/// It's the responsibility of the caller to ensure the correct topology has been injected and
/// correct onmessage handlers have been setup.
pub fn try_send_test_packets(&mut self, request: NymClientTestRequest) -> Promise {
// TOOD: use the premade packets instead
console_log!(
"Attempting to send {} test packets",
request.test_msgs.len()
);
// our address MUST BE valid
let recipient = parse_recipient(&self.self_address()).unwrap();
let lane = TransmissionLane::General;
let input_msgs = request
.test_msgs
.into_iter()
.map(|p| InputMessage::new_regular(recipient, p, lane, None))
.collect();
self.client_input.send_messages(input_msgs)
}
/// The simplest message variant where no additional information is attached.
@@ -184,13 +269,11 @@ impl NymClient {
message.len() as f64 / 1024.0
);
let recipient = match Self::parse_recipient(&recipient) {
Ok(recipient) => recipient,
Err(err) => return Promise::reject(&err),
};
let recipient = check_promise_result!(parse_recipient(&recipient));
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane);
let input_msg = InputMessage::new_regular(recipient, message, lane, Some(self.packet_type));
self.client_input.send_message(input_msg)
}
@@ -213,13 +296,17 @@ impl NymClient {
message.len() as f64 / 1024.0
);
let recipient = match Self::parse_recipient(&recipient) {
Ok(recipient) => recipient,
Err(err) => return Promise::reject(&err),
};
let recipient = check_promise_result!(parse_recipient(&recipient));
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
let input_msg = InputMessage::new_anonymous(
recipient,
message,
reply_surbs,
lane,
Some(self.packet_type),
);
self.client_input.send_message(input_msg)
}
@@ -233,13 +320,11 @@ impl NymClient {
message.len() as f64 / 1024.0
);
let sender_tag = match Self::parse_sender_tag(&recipient_tag) {
Ok(recipient) => recipient,
Err(err) => return Promise::reject(&err),
};
let sender_tag = check_promise_result!(parse_sender_tag(&recipient_tag));
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(sender_tag, message, lane);
let input_msg = InputMessage::new_reply(sender_tag, message, lane, Some(self.packet_type));
self.client_input.send_message(input_msg)
}
}
+5
View File
@@ -0,0 +1,5 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) const NODE_TESTER_ID: &str = "_nym-node-tester";
pub(crate) const NODE_TESTER_CLIENT_ID: &str = "_nym-node-tester-client";
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::storage::errors::ClientStorageError;
use crate::topology::WasmTopologyError;
use js_sys::Promise;
use nym_client_core::config::GatewayEndpointConfig;
use nym_client_core::error::ClientCoreError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_gateway_client::error::GatewayClientError;
use nym_node_tester_utils::error::NetworkTestingError;
use nym_sphinx::addressing::clients::RecipientFormattingError;
use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation;
use nym_topology::NymTopologyError;
use nym_validator_client::ValidatorClientError;
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasm_utils::simple_js_error;
// might as well start using well-defined error enum...
#[derive(Debug, Error)]
pub enum WasmClientError {
#[error(
"A node test is already in progress. Wait for it to finish before starting another one."
)]
TestInProgress,
#[error("experienced an issue with internal client components: {source}")]
BaseClientError {
#[from]
source: ClientCoreError,
},
#[error("The provided gateway identity is invalid: {source}")]
InvalidGatewayIdentity { source: Ed25519RecoveryError },
#[error("Gateway communication failure: {source}")]
GatewayClientError {
#[from]
source: GatewayClientError,
},
#[error("failed to query nym api: {source}")]
NymApiError {
#[from]
source: ValidatorClientError,
},
#[error("The provided wasm topology was invalid: {source}")]
WasmTopologyError {
#[from]
source: WasmTopologyError,
},
#[error("The provided nym topology was invalid: {source}")]
TopologyError {
#[from]
source: NymTopologyError,
},
#[error("failed to test the node: {source}")]
NodeTestingFailure {
#[from]
source: NetworkTestingError,
},
#[error("{raw} is not a valid url: {source}")]
MalformedUrl {
raw: String,
source: url::ParseError,
},
#[error("Network topology is currently unavailable")]
UnavailableNetworkTopology,
#[error("Mixnode {mixnode_identity} is not present in the current network topology")]
NonExistentMixnode { mixnode_identity: String },
#[error("Gateway {gateway_identity} is not present in the current network topology")]
NonExistentGateway { gateway_identity: String },
#[error("{raw} is not a valid Nym network recipient: {source}")]
MalformedRecipient {
raw: String,
source: RecipientFormattingError,
},
#[error("{raw} is not a valid Nym AnonymousSenderTag: {source}")]
MalformedSenderTag {
raw: String,
source: InvalidAnonymousSenderTagRepresentation,
},
#[error(transparent)]
StorageError {
#[from]
source: ClientStorageError,
},
#[error("this client has already registered with a gateway: {gateway_config:?}")]
AlreadyRegistered {
gateway_config: GatewayEndpointConfig,
},
}
impl WasmClientError {
pub fn into_rejected_promise(self) -> Promise {
self.into()
}
}
impl From<WasmClientError> for JsValue {
fn from(value: WasmClientError) -> Self {
simple_js_error(value.to_string())
}
}
impl From<WasmClientError> for Promise {
fn from(value: WasmClientError) -> Self {
Promise::reject(&value.into())
}
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::storage::ClientStorage;
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::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::{gateway, NymTopology};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::NymApiClient;
use rand::thread_rng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen_futures::future_to_promise;
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
pub(crate) fn setup_reply_surb_storage_backend(
config: config::ReplySurbs,
) -> browser_backend::Backend {
browser_backend::Backend::new(
config.minimum_reply_surb_storage_threshold,
config.maximum_reply_surb_storage_threshold,
)
}
pub(crate) fn parse_recipient(recipient: &str) -> Result<Recipient, WasmClientError> {
Recipient::try_from_base58_string(recipient).map_err(|source| {
WasmClientError::MalformedRecipient {
raw: recipient.to_string(),
source,
}
})
}
pub(crate) fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmClientError> {
AnonymousSenderTag::try_from_base58_string(tag).map_err(|source| {
WasmClientError::MalformedSenderTag {
raw: tag.to_string(),
source,
}
})
}
pub(crate) async fn current_network_topology_async(
nym_api_url: String,
) -> Result<WasmNymTopology, WasmClientError> {
let url: Url = match nym_api_url.parse() {
Ok(url) => url,
Err(source) => {
return Err(WasmClientError::MalformedUrl {
raw: nym_api_url,
source,
})
}
};
let api_client = NymApiClient::new(url);
let mixnodes = api_client.get_cached_active_mixnodes().await?;
let gateways = api_client.get_cached_gateways().await?;
Ok(NymTopology::from_detailed(mixnodes, gateways).into())
}
#[wasm_bindgen]
pub fn current_network_topology(nym_api_url: String) -> Promise {
future_to_promise(async move {
current_network_topology_async(nym_api_url)
.await
.into_promise_result()
})
}
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<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 setup_from_topology(
explicit_gateway: Option<IdentityKey>,
topology: &NymTopology,
client_store: &ClientStorage,
) -> Result<InitialisationDetails, WasmClientError> {
let gateways = topology.gateways();
setup_gateway(client_store, explicit_gateway, gateways).await
}
+14 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use wasm_bindgen::prelude::*;
@@ -8,10 +8,23 @@ mod client;
#[cfg(target_arch = "wasm32")]
pub mod encoded_payload_helper;
#[cfg(target_arch = "wasm32")]
pub mod error;
#[cfg(target_arch = "wasm32")]
pub mod gateway_selector;
#[cfg(target_arch = "wasm32")]
pub mod storage;
#[cfg(target_arch = "wasm32")]
pub mod tester;
#[cfg(target_arch = "wasm32")]
pub mod topology;
#[cfg(target_arch = "wasm32")]
pub mod validation;
#[cfg(target_arch = "wasm32")]
mod helpers;
mod constants;
#[wasm_bindgen]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasm_utils::simple_js_error;
use wasm_utils::storage::error::StorageError;
#[derive(Debug, Error)]
pub enum ClientStorageError {
#[error("failed to use the storage: {source}")]
StorageError {
#[from]
source: StorageError,
},
#[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 {
fn from(value: ClientStorageError) -> Self {
simple_js_error(value.to_string())
}
}
+294
View File
@@ -0,0 +1,294 @@
// 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::client::base_client::storage::gateway_details::PersistedGatewayDetails;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_client::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage};
use wasm_utils::PromisableResult;
use zeroize::Zeroizing;
pub(crate) mod errors;
pub(crate) mod traits;
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
const STORAGE_VERSION: u32 = 1;
// v1 tables
mod v1 {
// stores
pub const KEYS_STORE: &str = "keys";
pub const CORE_STORE: &str = "core";
// keys
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";
// TODO: for those we could actually use the subtle crypto storage
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS: &str = "aes128ctr_blake3_hmac_gateway_keys";
}
#[wasm_bindgen]
pub struct ClientStorage {
#[allow(dead_code)]
pub(crate) name: String,
pub(crate) inner: Arc<WasmStorage>,
}
#[wasm_bindgen]
impl ClientStorage {
fn db_name(client_id: &str) -> String {
format!("{STORAGE_NAME_PREFIX}-{client_id}")
}
pub(crate) async fn new_async(
client_id: &str,
passphrase: Option<String>,
) -> Result<Self, ClientStorageError> {
let name = Self::db_name(client_id);
// make sure the password is zeroized when no longer used, especially if we error out.
// special care must be taken on JS side to ensure it's correctly used there.
let passphrase = Zeroizing::new(passphrase);
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
// works with an unsigned integer.
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
let old_version = evt.old_version() as u32;
if old_version < 1 {
// migrating to version 1
let db = evt.db();
db.create_object_store(v1::KEYS_STORE)?;
db.create_object_store(v1::CORE_STORE)?;
}
Ok(())
});
let inner = WasmStorage::new(
&name,
STORAGE_VERSION,
migrate_fn,
passphrase.as_ref().map(|p| p.as_bytes()),
)
.await?;
Ok(ClientStorage {
inner: Arc::new(inner),
name,
})
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(client_id: String, passphrase: String) -> Promise {
future_to_promise(async move {
Self::new_async(&client_id, Some(passphrase))
.await
.into_promise_result()
})
}
pub fn new_unencrypted(client_id: String) -> Promise {
future_to_promise(async move {
Self::new_async(&client_id, None)
.await
.into_promise_result()
})
}
// 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::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> {
self.inner
.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
)
.await
.map_err(Into::into)
}
async fn may_read_encryption_keypair(
&self,
) -> Result<Option<encryption::KeyPair>, ClientStorageError> {
self.inner
.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
)
.await
.map_err(Into::into)
}
async fn may_read_ack_key(&self) -> Result<Option<AckKey>, ClientStorageError> {
self.inner
.read_value(v1::KEYS_STORE, JsValue::from_str(v1::AES128CTR_ACK_KEY))
.await
.map_err(Into::into)
}
async fn may_read_gateway_shared_key(&self) -> Result<Option<SharedKeys>, ClientStorageError> {
self.inner
.read_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
)
.await
.map_err(Into::into)
}
async fn must_read_identity_keypair(&self) -> Result<identity::KeyPair, ClientStorageError> {
self.may_read_identity_keypair()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::ED25519_IDENTITY_KEYPAIR.to_string(),
})
}
async fn must_read_encryption_keypair(
&self,
) -> Result<encryption::KeyPair, ClientStorageError> {
self.may_read_encryption_keypair()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::X25519_ENCRYPTION_KEYPAIR.to_string(),
})
}
async fn must_read_ack_key(&self) -> Result<AckKey, ClientStorageError> {
self.may_read_ack_key()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::AES128CTR_ACK_KEY.to_string(),
})
}
async fn must_read_gateway_shared_key(&self) -> Result<SharedKeys, ClientStorageError> {
self.may_read_gateway_shared_key()
.await?
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
typ: v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS.to_string(),
})
}
async fn store_identity_keypair(
&self,
keypair: &identity::KeyPair,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
keypair,
)
.await
.map_err(Into::into)
}
async fn store_encryption_keypair(
&self,
keypair: &encryption::KeyPair,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
keypair,
)
.await
.map_err(Into::into)
}
async fn store_ack_key(&self, key: &AckKey) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_ACK_KEY),
key,
)
.await
.map_err(Into::into)
}
async fn store_gateway_shared_key(&self, key: &SharedKeys) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::KEYS_STORE,
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
key,
)
.await
.map_err(Into::into)
}
pub(crate) async fn store_gateway_details(
&self,
gateway_endpoint: &PersistedGatewayDetails,
) -> Result<(), ClientStorageError> {
self.inner
.store_value(
v1::CORE_STORE,
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)
}
}
+101
View File
@@ -0,0 +1,101 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
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;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
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 {
pub(crate) keys_and_gateway_store: ClientStorage,
pub(crate) reply_storage: browser_backend::Backend,
pub(crate) credential_storage: EphemeralCredentialStorage,
}
impl MixnetClientStorage for FullWasmClientStorage {
type KeyStore = ClientStorage;
type ReplyStore = browser_backend::Backend;
type CredentialStore = EphemeralCredentialStorage;
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.keys_and_gateway_store
}
fn reply_store(&self) -> &Self::ReplyStore {
&self.reply_storage
}
fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_storage
}
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore {
&self.keys_and_gateway_store
}
}
#[async_trait(?Send)]
impl KeyStore for ClientStorage {
type StorageError = ClientStorageError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
console_log!("attempting to load cryptographic keys...");
// all keys implement `ZeroizeOnDrop`, so if we return an Error, whatever was already loaded will be cleared
let identity_keypair = self.must_read_identity_keypair().await?;
let encryption_keypair = self.must_read_encryption_keypair().await?;
let ack_keypair = self.must_read_ack_key().await?;
let gateway_shared_key = self.must_read_gateway_shared_key().await?;
Ok(KeyManager::from_keys(
identity_keypair,
encryption_keypair,
gateway_shared_key,
ack_keypair,
))
}
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
console_log!("attempting to store cryptographic keys...");
self.store_identity_keypair(&keys.identity_keypair())
.await?;
self.store_encryption_keypair(&keys.encryption_keypair())
.await?;
self.store_ack_key(&keys.ack_key()).await?;
self.store_gateway_shared_key(&keys.gateway_shared_key())
.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
}
}
@@ -0,0 +1,112 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::tester::helpers::{NodeTestResult, WasmTestMessageExt};
use futures::StreamExt;
use nym_node_tester_utils::processor::Received;
use nym_node_tester_utils::receiver::ReceivedReceiver;
use nym_sphinx::chunking::fragment::FragmentIdentifier;
use std::collections::HashSet;
use std::time::Duration;
use tokio::sync::MutexGuard as AsyncMutexGuard;
use wasm_utils::{console_error, console_log, console_warn};
pub(crate) struct EphemeralTestReceiver<'a> {
sent_packets: u32,
expected_acks: HashSet<FragmentIdentifier>,
received_valid_messages: HashSet<u32>,
received_valid_acks: HashSet<FragmentIdentifier>,
duplicate_packets: u32,
duplicate_acks: u32,
timeout_duration: Duration,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
}
impl<'a> EphemeralTestReceiver<'a> {
pub(crate) fn finish(self) -> NodeTestResult {
NodeTestResult {
sent_packets: self.sent_packets,
received_packets: self.received_valid_messages.len() as u32,
received_acks: self.received_valid_acks.len() as u32,
duplicate_packets: self.duplicate_packets,
duplicate_acks: self.duplicate_acks,
}
}
pub(crate) fn new(
sent_packets: u32,
expected_acks: HashSet<FragmentIdentifier>,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
timeout: Duration,
) -> Self {
EphemeralTestReceiver {
sent_packets,
expected_acks,
received_valid_messages: Default::default(),
received_valid_acks: Default::default(),
duplicate_packets: 0,
duplicate_acks: 0,
timeout_duration: timeout,
receiver_permit,
}
}
fn on_next_received_packet(&mut self, packet: Option<Received<WasmTestMessageExt>>) -> bool {
let Some(received_packet) = packet else {
// can't do anything more...
console_error!("packet receiver has stopped processing results!");
return true
};
match received_packet {
Received::Message(msg) => {
if !self.received_valid_messages.insert(msg.msg_id) {
self.duplicate_packets += 1;
}
}
Received::Ack(frag_id) => {
if self.expected_acks.contains(&frag_id) {
if !self.received_valid_acks.insert(frag_id) {
self.duplicate_acks += 1
}
} else {
console_warn!("received an ack that was not part of the test! (id: {frag_id})")
}
}
}
if self.received_all() {
console_log!("already received all the packets! finishing the test...");
true
} else {
false
}
}
fn received_all(&self) -> bool {
self.received_valid_acks.len() == self.received_valid_messages.len()
&& self.received_valid_acks.len() == self.sent_packets as usize
}
pub(crate) async fn perform_test(mut self) -> NodeTestResult {
let mut timeout_fut = wasm_timer::Delay::new(self.timeout_duration);
loop {
tokio::select! {
_ = &mut timeout_fut => {
console_warn!("reached test timeout before receiving all packets.");
break
}
received_packet = self.receiver_permit.next() => {
let is_done = self.on_next_received_packet(received_packet);
if is_done {
break
}
}
}
}
self.finish()
}
}
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to expansion of #[wasm_bindgen] macro on NodeTestResult
#![allow(clippy::drop_non_drop)]
use nym_node_tester_utils::processor::Received;
use nym_node_tester_utils::receiver::ReceivedReceiver;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard};
use wasm_bindgen::prelude::*;
use wasm_utils::{console_log, console_warn};
#[derive(Clone)]
pub(super) struct ReceivedReceiverWrapper(Arc<AsyncMutex<ReceivedReceiver<WasmTestMessageExt>>>);
impl ReceivedReceiverWrapper {
pub(super) fn new(inner: ReceivedReceiver<WasmTestMessageExt>) -> Self {
ReceivedReceiverWrapper(Arc::new(AsyncMutex::new(inner)))
}
pub(super) async fn clear_received_channel(&self) {
let mut lost_msgs = 0;
let mut lost_acks = 0;
let mut permit = self.0.lock().await;
while let Ok(Some(received)) = permit.try_next() {
match received {
Received::Message(_) => lost_msgs += 1,
Received::Ack(_) => lost_acks += 1,
}
}
if lost_msgs > 0 || lost_acks > 0 {
console_warn!("while preparing for the test run, we cleared {lost_msgs} messages and {lost_acks} acks that were received in the meantime.")
}
}
pub(super) async fn lock(&self) -> AsyncMutexGuard<'_, ReceivedReceiver<WasmTestMessageExt>> {
self.0.lock().await
}
}
#[derive(Serialize, Deserialize, Copy, Clone)]
pub struct WasmTestMessageExt {
pub test_id: u32,
}
impl WasmTestMessageExt {
pub fn new(test_id: u32) -> Self {
WasmTestMessageExt { test_id }
}
}
// TODO: maybe put it in the tester utils
#[wasm_bindgen]
pub struct NodeTestResult {
pub sent_packets: u32,
pub received_packets: u32,
pub received_acks: u32,
pub duplicate_packets: u32,
pub duplicate_acks: u32,
}
impl Display for NodeTestResult {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Test results: ")?;
writeln!(f, "Total score: {:.2}%", self.score())?;
writeln!(f, "Sent packets: {}", self.sent_packets)?;
writeln!(f, "Received (valid) packets: {}", self.received_packets)?;
writeln!(f, "Received (valid) acks: {}", self.received_acks)?;
writeln!(f, "Received duplicate packets: {}", self.duplicate_packets)?;
write!(f, "Received duplicate acks: {}", self.duplicate_acks)
}
}
#[wasm_bindgen]
impl NodeTestResult {
pub fn log_details(&self) {
console_log!("{}", self)
}
pub fn score(&self) -> f32 {
let expected = self.sent_packets * 2;
let actual = (self.received_packets + self.received_acks)
.saturating_sub(self.duplicate_packets + self.duplicate_acks);
actual as f32 / expected as f32 * 100.
}
}
pub(crate) struct TestMarker {
value: Arc<AtomicBool>,
}
impl TestMarker {
pub fn new(value: Arc<AtomicBool>) -> Self {
Self { value }
}
}
impl Drop for TestMarker {
// make sure to clear the test flag when the marker is dropped
fn drop(&mut self) {
self.value.store(false, Ordering::SeqCst)
}
}
+321
View File
@@ -0,0 +1,321 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::NODE_TESTER_ID;
use crate::error::WasmClientError;
use crate::helpers::{current_network_topology_async, setup_from_topology};
use crate::storage::ClientStorage;
use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
use crate::tester::helpers::{
NodeTestResult, ReceivedReceiverWrapper, TestMarker, WasmTestMessageExt,
};
use crate::topology::WasmNymTopology;
use futures::channel::mpsc;
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::init::InitialisationDetails;
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_gateway_client::GatewayClient;
use nym_node_tester_utils::receiver::SimpleMessageReceiver;
use nym_node_tester_utils::{NodeTester, TestMessage};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_sphinx::params::PacketSize;
use nym_sphinx::preparer::PreparedFragment;
use nym_task::TaskManager;
use nym_topology::NymTopology;
use nym_validator_client::client::IdentityKey;
use rand::rngs::OsRng;
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex as SyncMutex};
use std::time::Duration;
use tokio::sync::Mutex as AsyncMutex;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{check_promise_result, console_log, PromisableResult};
mod ephemeral_receiver;
pub(crate) mod helpers;
pub type NodeTestMessage = TestMessage<WasmTestMessageExt>;
type LockedGatewayClient =
Arc<AsyncMutex<GatewayClient<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>>;
pub(crate) const DEFAULT_TEST_TIMEOUT: Duration = Duration::from_secs(10);
pub(crate) const DEFAULT_TEST_PACKETS: u32 = 20;
#[wasm_bindgen]
pub struct NymNodeTester {
test_in_progress: Arc<AtomicBool>,
// we need to increment the nonce between tests to distinguish the packets
// but we can't make the tester mutable because of wasm...
// so we're using the atomics
current_test_nonce: AtomicU32,
// blame all those mutexes on being unable to have an async method with internal mutability...
tester: Arc<SyncMutex<NodeTester<OsRng>>>,
gateway_client: LockedGatewayClient,
// we have to put it behind the lock due to wasm limitations and borrowing...
// the mutex acquisition should be instant as there aren't going to be any threads attempting
// to get simultaneous access
processed_receiver: ReceivedReceiverWrapper,
// even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline
_task_manager: TaskManager,
}
#[wasm_bindgen]
pub struct NymNodeTesterBuilder {
gateway: Option<IdentityKey>,
base_topology: NymTopology,
// unimplemented
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
}
fn address(keys: &ManagedKeys, gateway_identity: NodeIdentity) -> Recipient {
Recipient::new(
*keys.identity_public_key(),
*keys.encryption_public_key(),
gateway_identity,
)
}
#[wasm_bindgen]
impl NymNodeTesterBuilder {
#[wasm_bindgen(constructor)]
pub fn new(
base_topology: WasmNymTopology,
gateway: Option<IdentityKey>,
) -> NymNodeTesterBuilder {
NymNodeTesterBuilder {
gateway,
base_topology: base_topology.into(),
bandwidth_controller: None,
}
}
async fn _new_with_api(
api_url: String,
gateway: Option<IdentityKey>,
) -> Result<Self, WasmClientError> {
let topology = current_network_topology_async(api_url).await?;
Ok(NymNodeTesterBuilder::new(topology, gateway))
}
pub fn new_with_api(gateway: Option<IdentityKey>, api_url: String) -> Promise {
future_to_promise(async move {
Self::_new_with_api(api_url, gateway)
.await
.into_promise_result()
})
}
async fn gateway_info(
&self,
client_store: &ClientStorage,
) -> 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 task_manager = TaskManager::default();
let client_store = ClientStorage::new_async(NODE_TESTER_ID, None).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 managed_keys = init_details.managed_keys;
let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded();
let mut gateway_client = GatewayClient::new(
gateway_endpoint.gateway_listener,
managed_keys.identity_keypair(),
gateway_identity,
Some(managed_keys.must_get_gateway_shared_key()),
mixnet_message_sender,
ack_sender,
Duration::from_secs(10),
self.bandwidth_controller.take(),
task_manager.subscribe(),
);
gateway_client.set_disabled_credentials_mode(true);
gateway_client.authenticate_and_start().await?;
// TODO: make those values configurable later
let tester = NodeTester::new(
OsRng,
self.base_topology,
Some(address(&managed_keys, gateway_identity)),
PacketSize::default(),
Duration::from_millis(5),
Duration::from_millis(5),
managed_keys.ack_key(),
);
let (processed_sender, processed_receiver) = mpsc::unbounded();
let mut receiver = SimpleMessageReceiver::new_sphinx_receiver(
managed_keys.encryption_keypair(),
managed_keys.ack_key(),
mixnet_message_receiver,
ack_receiver,
processed_sender,
task_manager.subscribe(),
);
nym_task::spawn(async move { receiver.run().await });
Ok(NymNodeTester {
test_in_progress: Arc::new(AtomicBool::new(false)),
current_test_nonce: Default::default(),
tester: Arc::new(SyncMutex::new(tester)),
gateway_client: Arc::new(AsyncMutex::new(gateway_client)),
processed_receiver: ReceivedReceiverWrapper::new(processed_receiver),
_task_manager: task_manager,
})
}
pub fn setup_client(self) -> Promise {
future_to_promise(async move { self._setup_client().await.into_promise_result() })
}
}
async fn test_mixnode(
test_packets: Vec<PreparedFragment>,
gateway_client: LockedGatewayClient,
processed_receiver: ReceivedReceiverWrapper,
_test_marker: TestMarker,
timeout: Duration,
) -> Result<NodeTestResult, WasmClientError> {
let num_test_packets = test_packets.len() as u32;
let expected_ack_ids = test_packets
.iter()
.map(|p| p.fragment_identifier)
.collect::<HashSet<_>>();
let mix_packets = test_packets.into_iter().map(|p| p.mix_packet).collect();
// start by clearing any messages that might have been received between tests
processed_receiver.clear_received_channel().await;
// locking the gateway client so that we could get mutable access to data without having to declare
// self mutable
let mut gateway_permit = gateway_client.lock().await;
gateway_permit.batch_send_mix_packets(mix_packets).await?;
let receiver_permit = processed_receiver.lock().await;
let result =
EphemeralTestReceiver::new(num_test_packets, expected_ack_ids, receiver_permit, timeout)
.perform_test()
.await;
Ok(result)
}
#[wasm_bindgen]
impl NymNodeTester {
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(topology: WasmNymTopology, gateway: Option<IdentityKey>) -> Promise {
console_log!("constructing node tester!");
NymNodeTesterBuilder::new(topology, gateway).setup_client()
}
async fn _new_with_api(
api_url: String,
gateway: Option<IdentityKey>,
) -> Result<Self, WasmClientError> {
NymNodeTesterBuilder::_new_with_api(api_url, gateway)
.await?
._setup_client()
.await
}
pub fn new_with_api(api_url: String, gateway: Option<IdentityKey>) -> Promise {
future_to_promise(async move {
Self::_new_with_api(api_url, gateway)
.await
.into_promise_result()
})
}
fn prepare_test_packets(
&self,
mixnode_identity: String,
test_nonce: u32,
num_test_packets: u32,
) -> Result<Vec<PreparedFragment>, WasmClientError> {
let test_ext = WasmTestMessageExt::new(test_nonce);
let mut tester_permit = self.tester.lock().expect("mutex got poisoned");
tester_permit
.existing_identity_mixnode_test_packets(
mixnode_identity,
test_ext,
num_test_packets,
None,
)
.map_err(Into::into)
}
pub fn test_node(
&self,
mixnode_identity: String,
timeout_millis: Option<u64>,
num_test_packets: Option<u32>,
) -> Promise {
// establish test parameters
let timeout = timeout_millis
.map(Duration::from_millis)
.unwrap_or(DEFAULT_TEST_TIMEOUT);
let num_test_packets = num_test_packets.unwrap_or(DEFAULT_TEST_PACKETS);
// mark start of the test
if self.test_in_progress.swap(true, Ordering::SeqCst) {
return WasmClientError::TestInProgress.into_rejected_promise();
}
// prepare test packets
// (I simultaneously feel both disgusted and amazed by this workaround)
let test_nonce = self.current_test_nonce.fetch_add(1, Ordering::Relaxed);
let test_packets = check_promise_result!(self.prepare_test_packets(
mixnode_identity,
test_nonce,
num_test_packets
));
let processed_receiver_clone = self.processed_receiver.clone();
let gateway_client_clone = Arc::clone(&self.gateway_client);
let tester_marker = TestMarker::new(Arc::clone(&self.test_in_progress));
// start doing async things (send packets and watch for anything coming back)
future_to_promise(async move {
test_mixnode(
test_packets,
gateway_client_clone,
processed_receiver_clone,
tester_marker,
timeout,
)
.await
.into_promise_result()
})
}
}
+267
View File
@@ -0,0 +1,267 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::GatewayEndpointConfig;
use nym_crypto::asymmetric::{encryption, identity};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::{Layer, MixnodeConversionError};
use nym_topology::{gateway, mix, MixLayer, NymTopology};
use nym_validator_client::client::{IdentityKeyRef, MixId};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use thiserror::Error;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use wasm_utils::{console_log, simple_js_error};
#[derive(Debug, Error)]
pub enum WasmTopologyError {
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
InvalidMixLayer { value: u8 },
#[error(transparent)]
GatewayConversion(#[from] GatewayConversionError),
#[error(transparent)]
MixnodeConversion(#[from] MixnodeConversionError),
#[error("The provided mixnode map was malformed: {source}")]
MalformedMixnodeMap { source: serde_wasm_bindgen::Error },
#[error("The provided gateway list was malformed: {source}")]
MalformedGatewayList { source: serde_wasm_bindgen::Error },
}
impl From<WasmTopologyError> for JsValue {
fn from(value: WasmTopologyError) -> Self {
simple_js_error(value.to_string())
}
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct WasmNymTopology {
inner: NymTopology,
}
#[wasm_bindgen]
impl WasmNymTopology {
#[wasm_bindgen(constructor)]
pub fn new(
// expected: BTreeMap<MixLayer, Vec<WasmMixNode>>,
// HashMap<MixLayer, Vec<WasmMixNode>> will also work because it has the same json representation
mixnodes: JsValue,
// expected: Vec<WasmGateway>
gateways: JsValue,
) -> Result<WasmNymTopology, WasmTopologyError> {
let mixnodes: BTreeMap<MixLayer, Vec<WasmMixNode>> =
serde_wasm_bindgen::from_value(mixnodes)
.map_err(|source| WasmTopologyError::MalformedMixnodeMap { source })?;
let gateways: Vec<WasmGateway> = serde_wasm_bindgen::from_value(gateways)
.map_err(|source| WasmTopologyError::MalformedGatewayList { source })?;
let mut converted_mixes = BTreeMap::new();
for (layer, nodes) in mixnodes {
let layer_nodes = nodes
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
converted_mixes.insert(layer, layer_nodes);
}
let gateways = gateways
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
Ok(WasmNymTopology {
inner: NymTopology::new(converted_mixes, gateways),
})
}
#[allow(dead_code)]
pub(crate) fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
}
pub(crate) fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
self.inner
.gateways()
.iter()
.any(|g| g.identity_key.to_base58_string() == gateway_id)
}
pub fn print(&self) {
if !self.inner.mixes().is_empty() {
console_log!("mixnodes:");
for (layer, nodes) in self.inner.mixes() {
console_log!("\tlayer {layer}:");
for node in nodes {
console_log!("\t\t{} - {}", node.mix_id, node.identity_key)
}
}
} else {
console_log!("NO MIXNODES")
}
if !self.inner.gateways().is_empty() {
console_log!("gateways:");
for gateway in self.inner.gateways() {
console_log!("\t{}", gateway.identity_key)
}
} else {
console_log!("NO GATEWAYS")
}
}
}
impl From<WasmNymTopology> for NymTopology {
fn from(value: WasmNymTopology) -> Self {
value.inner
}
}
impl From<NymTopology> for WasmNymTopology {
fn from(value: NymTopology) -> Self {
WasmNymTopology { inner: value }
}
}
#[wasm_bindgen]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WasmMixNode {
pub mix_id: MixId,
#[wasm_bindgen(getter_with_clone)]
pub owner: String,
#[wasm_bindgen(getter_with_clone)]
pub host: String,
pub mix_port: u16,
#[wasm_bindgen(getter_with_clone)]
pub identity_key: String,
#[wasm_bindgen(getter_with_clone)]
pub sphinx_key: String,
pub layer: MixLayer,
#[wasm_bindgen(getter_with_clone)]
pub version: String,
}
#[wasm_bindgen]
impl WasmMixNode {
#[wasm_bindgen(constructor)]
#[allow(clippy::too_many_arguments)]
pub fn new(
mix_id: MixId,
owner: String,
host: String,
mix_port: u16,
identity_key: String,
sphinx_key: String,
layer: MixLayer,
version: String,
) -> Self {
Self {
mix_id,
owner,
host,
mix_port,
identity_key,
sphinx_key,
layer,
version,
}
}
}
impl TryFrom<WasmMixNode> for mix::Node {
type Error = WasmTopologyError;
fn try_from(value: WasmMixNode) -> Result<Self, Self::Error> {
let host = mix::Node::parse_host(&value.host)?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = mix::Node::extract_mix_host(&host, value.mix_port)?;
Ok(mix::Node {
mix_id: value.mix_id,
owner: value.owner,
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(MixnodeConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(MixnodeConversionError::from)?,
layer: Layer::try_from(value.layer)
.map_err(|_| WasmTopologyError::InvalidMixLayer { value: value.layer })?,
version: value.version,
})
}
}
#[wasm_bindgen]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WasmGateway {
#[wasm_bindgen(getter_with_clone)]
pub owner: String,
#[wasm_bindgen(getter_with_clone)]
pub host: String,
pub mix_port: u16,
pub clients_port: u16,
#[wasm_bindgen(getter_with_clone)]
pub identity_key: String,
#[wasm_bindgen(getter_with_clone)]
pub sphinx_key: String,
#[wasm_bindgen(getter_with_clone)]
pub version: String,
}
#[wasm_bindgen]
impl WasmGateway {
#[wasm_bindgen(constructor)]
pub fn new(
owner: String,
host: String,
mix_port: u16,
clients_port: u16,
identity_key: String,
sphinx_key: String,
version: String,
) -> Self {
Self {
owner,
host,
mix_port,
clients_port,
identity_key,
sphinx_key,
version,
}
}
}
impl TryFrom<WasmGateway> for gateway::Node {
type Error = WasmTopologyError;
fn try_from(value: WasmGateway) -> Result<Self, Self::Error> {
let host = gateway::Node::parse_host(&value.host)?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = gateway::Node::extract_mix_host(&host, value.mix_port)?;
Ok(gateway::Node {
owner: value.owner,
host,
mix_host,
clients_port: value.clients_port,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(GatewayConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(GatewayConversionError::from)?,
version: value.version,
})
}
}
+1 -1
View File
@@ -130,7 +130,7 @@ impl AsyncFileWatcher {
Ok(event) => {
let now = Instant::now();
if self.should_propagate(&event, now) {
self.last_received.insert(event.kind.clone(), now);
self.last_received.insert(event.kind, now);
if let Err(_err) = self.event_sender.unbounded_send(event) {
log::error!("the file watcher receiver has been dropped!");
}
@@ -55,11 +55,16 @@ where
Ok(state)
}
pub async fn get_credential<C: DkgQueryClient + Send + Sync, St: Storage>(
pub async fn get_credential<C, St>(
state: &State,
client: &C,
storage: &St,
) -> Result<(), BandwidthControllerError> {
) -> Result<(), BandwidthControllerError>
where
C: DkgQueryClient + Send + Sync,
St: Storage,
<St as Storage>::StorageError: Send + Sync + 'static,
{
let epoch_id = client.get_current_epoch().await?.epoch_id;
let threshold = client
.get_current_epoch_threshold()
@@ -83,7 +88,6 @@ pub async fn get_credential<C: DkgQueryClient + Send + Sync, St: Storage>(
signature.to_bs58(),
epoch_id.to_string(),
)
.await?;
Ok(())
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
}
+5 -1
View File
@@ -16,7 +16,11 @@ pub enum BandwidthControllerError {
Nyxd(#[from] nym_validator_client::nyxd::error::NyxdError),
#[error("There was a credential storage error - {0}")]
CredentialStorageError(#[from] StorageError),
CredentialStorageError(Box<dyn std::error::Error + Send + Sync>),
// this should really be fully incorporated into the above, but messing with coconut is the last thing I want to do now
#[error(transparent)]
StorageError(#[from] StorageError),
#[error("Coconut error - {0}")]
CoconutError(#[from] CoconutError),
+15 -4
View File
@@ -26,7 +26,7 @@ pub mod error;
#[cfg(target_arch = "wasm32")]
pub mod wasm_mockups;
pub struct BandwidthController<C, St: Storage> {
pub struct BandwidthController<C, St> {
storage: St,
client: C,
}
@@ -45,8 +45,13 @@ impl<C, St: Storage> BandwidthController<C, St> {
) -> Result<(nym_coconut_interface::Credential, i64), BandwidthControllerError>
where
C: DkgQueryClient + Sync + Send,
<St as Storage>::StorageError: Send + Sync + 'static,
{
let bandwidth_credential = self.storage.get_next_coconut_credential().await?;
let bandwidth_credential = self
.storage
.get_next_coconut_credential()
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?;
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
.map_err(|_| StorageError::InconsistentData)?;
let voucher_info = bandwidth_credential.voucher_info.clone();
@@ -82,10 +87,16 @@ impl<C, St: Storage> BandwidthController<C, St> {
))
}
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError> {
pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError>
where
<St as Storage>::StorageError: Send + Sync + 'static,
{
// JS: shouldn't we send some contract/validator/gateway message here to actually, you know,
// consume it?
Ok(self.storage.consume_coconut_credential(id).await?)
self.storage
.consume_coconut_credential(id)
.await
.map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))
}
}
+17 -5
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 }
@@ -15,15 +15,21 @@ clap_complete_fig = "4.0"
log = { workspace = true }
pretty_env_logger = "0.4.0"
semver = "0.11"
serde = { workspace = true, features = ["derive"], optional = true }
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]
@@ -36,5 +42,11 @@ vergen = { version = "=7.4.3", default-features = false, features = [
[features]
default = []
output_format = ["serde", "serde_json"]
tracing = ["tracing-appender", "tracing-subscriber", "tracing-tree"]
output_format = ["serde_json"]
tracing = [
"tracing-subscriber",
"tracing-tree",
"opentelemetry-jaeger",
"tracing-opentelemetry",
"opentelemetry",
]
@@ -4,6 +4,8 @@
// TODO: at a later date this crate should probably also expose `ContractBuildInformation`
// and be used by our smart contracts
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct BinaryBuildInformation {
// VERGEN_BUILD_TIMESTAMP
@@ -99,8 +101,7 @@ impl BinaryBuildInformation {
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::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`.
+3 -1
View File
@@ -4,5 +4,7 @@
pub mod build_information;
pub mod completions;
pub mod logging;
pub mod output_format;
pub mod version_checker;
#[cfg(feature = "output_format")]
pub mod output_format;
+34 -18
View File
@@ -1,17 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// use tracing_subscriber::{
// fmt::Layer, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry,
// };
// use tracing_tree::HierarchicalLayer;
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")]
pub use tracing_tree;
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LoggingSettings {
// well, we need to implement something here at some point...
}
// I'd argue we should start transitioning from `log` to `tracing`
pub fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
@@ -39,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();
};
}
+5 -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,12 +18,14 @@ 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"] }
tungstenite = { version = "0.13.0", default-features = false }
tokio = { version = "1.24.1", features = ["macros"]}
time = "0.3.17"
zeroize = { workspace = true }
# internal
nym-bandwidth-controller = { path = "../bandwidth-controller" }
@@ -38,6 +41,7 @@ nym-topology = { path = "../topology" }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
nym-task = { path = "../task" }
nym-credential-storage = { path = "../credential-storage" }
nym-network-defaults = { path = "../network-defaults" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-validator-client]
path = "../client-libs/validator-client"
@@ -1,6 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
//
use crate::{client::replies::reply_storage, config::DebugConfig};
pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> reply_storage::Empty {
+172 -143
View File
@@ -2,9 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
use super::received_buffer::ReceivedBufferMessage;
use crate::client::base_client::storage::MixnetClientStorage;
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
use crate::client::key_manager::KeyManager;
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ManagedKeys;
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use crate::client::real_messages_control;
use crate::client::real_messages_control::RealMessagesController;
@@ -22,10 +24,11 @@ use crate::client::topology_control::{
};
use crate::config::{Config, DebugConfig, GatewayEndpointConfig};
use crate::error::ClientCoreError;
use crate::spawn_future;
use crate::{config, spawn_future};
use futures::channel::mpsc;
use log::{debug, info};
use nym_bandwidth_controller::BandwidthController;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
@@ -34,26 +37,28 @@ use nym_gateway_client::{
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_sphinx::params::PacketType;
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 std::sync::Arc;
use std::time::Duration;
use tap::TapFallible;
use url::Url;
use nym_credential_storage::storage::Storage;
#[cfg(not(target_arch = "wasm32"))]
use nym_validator_client::nyxd::traits::DkgQueryClient;
#[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;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
pub mod non_wasm_helpers;
pub mod helpers;
pub mod storage;
#[derive(Clone)]
pub struct ClientInput {
@@ -152,76 +157,58 @@ impl From<bool> for CredentialsToggle {
}
}
pub struct BaseClientBuilder<'a, B, C, St: Storage> {
// 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: B,
custom_topology_provider: Option<Box<dyn TopologyProvider>>,
bandwidth_controller: Option<BandwidthController<C, St>>,
key_manager: KeyManager,
pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
config: &'a Config,
client_store: S,
dkg_query_client: Option<C>,
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
setup_method: GatewaySetup,
}
impl<'a, B, C, St> BaseClientBuilder<'a, B, C, St>
impl<'a, C, S> BaseClientBuilder<'a, C, S>
where
B: ReplyStorageBackend + Send + Sync + 'static,
C: DkgQueryClient + Sync + Send + 'static,
St: Storage + 'static,
S: MixnetClientStorage + 'static,
C: DkgQueryClient + Send + Sync + 'static,
{
pub fn new_from_base_config<T>(
base_config: &'a Config<T>,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController<C, St>>,
reply_storage_backend: B,
) -> BaseClientBuilder<'a, B, C, St> {
BaseClientBuilder {
gateway_config: base_config.get_gateway_endpoint_config(),
debug_config: base_config.get_debug_config(),
disabled_credentials: base_config.get_disabled_credentials_mode(),
nym_api_endpoints: base_config.get_nym_api_endpoints(),
bandwidth_controller,
reply_storage_backend,
key_manager,
custom_topology_provider: None,
}
}
pub fn new(
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController<C, St>>,
reply_storage_backend: B,
credentials_toggle: CredentialsToggle,
nym_api_endpoints: Vec<Url>,
) -> BaseClientBuilder<'a, B, C, St> {
base_config: &'a Config,
client_store: S,
dkg_query_client: Option<C>,
) -> BaseClientBuilder<'a, C, S> {
BaseClientBuilder {
gateway_config,
debug_config,
disabled_credentials: credentials_toggle.is_disabled(),
nym_api_endpoints,
reply_storage_backend,
config: base_config,
client_store,
dkg_query_client,
custom_topology_provider: None,
bandwidth_controller,
key_manager,
setup_method: GatewaySetup::MustLoad,
}
}
pub fn with_topology_provider(mut self, provider: Box<dyn TopologyProvider>) -> Self {
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
self.setup_method = setup;
self
}
pub fn with_topology_provider(
mut self,
provider: Box<dyn TopologyProvider + Send + Sync>,
) -> Self {
self.custom_topology_provider = Some(provider);
self
}
pub fn as_mix_recipient(&self) -> Recipient {
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
// because it relies on the crypto keys being already loaded
fn mix_address(
managed_keys: &ManagedKeys,
gateway_config: &GatewayEndpointConfig,
) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().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(),
)
}
@@ -263,6 +250,7 @@ where
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
shutdown: TaskClient,
packet_type: PacketType,
) {
info!("Starting real traffic stream...");
@@ -278,7 +266,7 @@ where
lane_queue_lengths,
client_connection_rx,
)
.start_with_shutdown(shutdown);
.start_with_shutdown(shutdown, packet_type);
}
// buffer controlling all messages fetched from provider
@@ -304,60 +292,55 @@ 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,
) -> Result<GatewayClient<C, St>, ClientCoreError> {
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);
}
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
where
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
{
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)?;
// disgusting wasm workaround since there's no key persistence there (nor `client init`)
let shared_key = if self.key_manager.is_gateway_key_set() {
Some(self.key_manager.gateway_shared_key())
} else {
log::info!("Gateway key not set! Will proceed anyway.");
None
};
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
managed_keys.identity_keypair(),
gateway_identity,
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);
gateway_client
let shared_key = gateway_client
.authenticate_and_start()
.await
.tap_err(|err| {
log::error!("Could not authenticate and start up the gateway connection - {err}")
})?;
managed_keys.ensure_gateway_key(shared_key);
Ok(gateway_client)
}
fn setup_topology_provider(
custom_provider: Option<Box<dyn TopologyProvider>>,
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
nym_api_urls: Vec<Url>,
) -> Box<dyn TopologyProvider> {
) -> Box<dyn TopologyProvider + Send + Sync> {
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| {
Box::new(NymApiTopologyProvider::new(
@@ -370,12 +353,13 @@ where
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
topology_provider: Box<dyn TopologyProvider>,
refresh_rate: Duration,
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
topology_config: config::Topology,
topology_accessor: TopologyAccessor,
shutdown: TaskClient,
mut shutdown: TaskClient,
) -> Result<(), ClientCoreError> {
let topology_refresher_config = TopologyRefresherConfig::new(refresh_rate);
let topology_refresher_config =
TopologyRefresherConfig::new(topology_config.topology_refresh_rate);
let mut topology_refresher = TopologyRefresher::new(
topology_refresher_config,
@@ -395,65 +379,101 @@ where
return Err(ClientCoreError::InsufficientNetworkTopology(err));
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
if topology_config.disable_refreshing {
// if we're not spawning the refresher, don't cause shutdown immediately
info!("The topology refesher is not going to be started");
shutdown.mark_as_success();
} else {
// don't spawn the refresher if we don't want to be refreshing the topology.
// only use the initial values obtained
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
}
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// controller for sending packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(
gateway_client: GatewayClient<C, St>,
gateway_client: GatewayClient<C, S::CredentialStore>,
shutdown: TaskClient,
) -> BatchMixMessageSender {
) -> BatchMixMessageSender
where
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
{
info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
}
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
async fn setup_persistent_reply_storage(
backend: B,
backend: S::ReplyStore,
shutdown: TaskClient,
) -> Result<CombinedReplyStorage, ClientCoreError>
where
<B as ReplyStorageBackend>::StorageError: Sync + Send,
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
S::ReplyStore: Send + Sync,
{
if backend.is_active() {
log::trace!("Setup persistent reply storage");
let persistent_storage = PersistentReplyStorage::new(backend);
let mem_store = persistent_storage
.load_state_from_backend()
log::trace!("Setup persistent reply storage");
let persistent_storage = PersistentReplyStorage::new(backend);
let mem_store = persistent_storage
.load_state_from_backend()
.await
.map_err(|err| ClientCoreError::SurbStorageError {
source: Box::new(err),
})?;
let store_clone = mem_store.clone();
spawn_future(async move {
persistent_storage
.flush_on_shutdown(store_clone, shutdown)
.await
.map_err(|err| ClientCoreError::SurbStorageError {
source: Box::new(err),
})?;
});
let store_clone = mem_store.clone();
spawn_future(async move {
persistent_storage
.flush_on_shutdown(store_clone, shutdown)
.await
});
Ok(mem_store)
}
Ok(mem_store)
} else {
log::trace!("Setup inactive reply storage");
Ok(backend
.get_inactive_storage()
.map_err(|err| ClientCoreError::SurbStorageError {
source: Box::new(err),
})?)
}
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
<B 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");
// 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
// rather than creating them here, so say for example the buffer controller would create the request channels
@@ -480,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.topology_refresh_rate,
self.config.debug.topology,
shared_topology_accessor.clone(),
task_manager.subscribe(),
)
.await?;
Self::start_received_messages_buffer_controller(
self.key_manager.encryption_keypair(),
managed_keys.encryption_keypair(),
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_storage.key_storage(),
@@ -515,11 +540,11 @@ where
task_manager.subscribe(),
);
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// The message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender =
let message_sender =
Self::start_mix_traffic_controller(gateway_client, task_manager.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic
@@ -531,8 +556,8 @@ where
let shared_lane_queue_lengths = LaneQueueLengths::new();
let controller_config = real_messages_control::Config::new(
self.debug_config,
self.key_manager.ack_key(),
&self.config.debug,
managed_keys.ack_key(),
self_address,
);
@@ -541,26 +566,28 @@ where
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
message_sender.clone(),
reply_storage,
reply_controller_sender.clone(),
reply_controller_receiver,
shared_lane_queue_lengths.clone(),
client_connection_rx,
task_manager.subscribe(),
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.key_manager.ack_key(),
&self.config.debug,
managed_keys.ack_key(),
self_address,
shared_topology_accessor.clone(),
sphinx_message_sender,
message_sender,
task_manager.subscribe(),
);
}
@@ -569,6 +596,7 @@ where
debug!("The address of this client is: {self_address}");
Ok(BaseClient {
address: self_address,
client_input: ClientInputStatus::AwaitingProducer {
client_input: ClientInput {
connection_command_sender: client_connection_tx,
@@ -591,6 +619,7 @@ where
}
pub struct BaseClient {
pub address: Recipient,
pub client_input: ClientInputStatus,
pub client_output: ClientOutputStatus,
pub client_state: ClientState,
@@ -1,19 +1,25 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::replies::reply_storage::{
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
};
use crate::config::DebugConfig;
use crate::config;
use crate::config::Config;
use crate::error::ClientCoreError;
use log::{error, info};
use nym_bandwidth_controller::BandwidthController;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_validator_client::nyxd::QueryNyxdClient;
use nym_validator_client::Client;
use std::path::Path;
use std::{fs, io};
use time::OffsetDateTime;
use url::Url;
async fn setup_fresh_backend<P: AsRef<Path>>(
db_path: P,
debug_config: &DebugConfig,
surb_config: &config::ReplySurbs,
) -> Result<fs_backend::Backend, ClientCoreError> {
info!("creating fresh surb database");
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
@@ -30,12 +36,8 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
// it will only be happening on the very first run and in practice won't incur huge
// costs since the storage is going to be empty
let mem_store = CombinedReplyStorage::new(
debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
surb_config.minimum_reply_surb_storage_threshold,
surb_config.maximum_reply_surb_storage_threshold,
);
storage_backend
.init_fresh(&mem_store)
@@ -47,17 +49,13 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
Ok(storage_backend)
}
fn setup_inactive_backend(debug_config: &DebugConfig) -> fs_backend::Backend {
info!("creating inactive surb database");
fs_backend::Backend::new_inactive(
debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
)
}
// fn setup_inactive_backend(surb_config: &config::ReplySurbs) -> fs_backend::Backend {
// info!("creating inactive surb database");
// fs_backend::Backend::new_inactive(
// surb_config.minimum_reply_surb_storage_threshold,
// surb_config.maximum_reply_surb_storage_threshold,
// )
// }
fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
let db_path = db_path.as_ref();
@@ -81,28 +79,73 @@ fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
}
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
db_path: Option<P>,
debug_config: &DebugConfig,
db_path: P,
surb_config: &config::ReplySurbs,
) -> Result<fs_backend::Backend, ClientCoreError> {
if let Some(db_path) = db_path {
// if the database file doesnt exist, initialise fresh storage, otherwise attempt to load
// the existing one
let db_path = db_path.as_ref();
if db_path.exists() {
info!("loading existing surb database");
match fs_backend::Backend::try_load(db_path).await {
Ok(backend) => Ok(backend),
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
// if the database file doesnt exist, initialise fresh storage, otherwise attempt to load
// the existing one
let db_path = db_path.as_ref();
if db_path.exists() {
info!("loading existing surb database");
match fs_backend::Backend::try_load(db_path).await {
Ok(backend) => Ok(backend),
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
archive_corrupted_database(db_path)?;
setup_fresh_backend(db_path, debug_config).await
}
archive_corrupted_database(db_path)?;
setup_fresh_backend(db_path, surb_config).await
}
} else {
setup_fresh_backend(db_path, debug_config).await
}
} else {
Ok(setup_inactive_backend(debug_config))
setup_fresh_backend(db_path, surb_config).await
}
}
pub fn create_bandwidth_controller<St: CredentialStorage>(
config: &Config,
storage: St,
) -> BandwidthController<Client<QueryNyxdClient>, St> {
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");
create_bandwidth_controller_with_urls(nyxd_url, api_url, storage)
}
pub fn create_bandwidth_controller_with_urls<St: CredentialStorage>(
nyxd_url: Url,
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);
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(())
}
}
@@ -0,0 +1,169 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TODO: combine those more closely. Perhaps into a single underlying store.
// Like for persistent, on-disk, storage, what's the point of having 3 different databases?
use crate::client::base_client::storage::gateway_details::{
GatewayDetailsStore, InMemGatewayDetails,
};
use crate::client::key_manager::persistence::{InMemEphemeralKeys, KeyStore};
use crate::client::replies::reply_storage;
use crate::client::replies::reply_storage::ReplyStorageBackend;
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
use nym_credential_storage::storage::Storage as CredentialStorage;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::base_client::non_wasm_helpers;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::key_manager::persistence::OnDiskKeys;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::client::replies::reply_storage::fs_backend;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::config::{self, disk_persistence::CommonClientPaths};
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
use crate::error::ClientCoreError;
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
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, 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: EphemeralCredentialStorage,
gateway_details_store: InMemGatewayDetails,
}
impl Ephemeral {
pub fn new() -> Self {
Default::default()
}
}
impl MixnetClientStorage for Ephemeral {
type KeyStore = InMemEphemeralKeys;
type ReplyStore = reply_storage::Empty;
type CredentialStore = EphemeralCredentialStorage;
type GatewayDetailsStore = InMemGatewayDetails;
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
(self.reply_store, self.credential_store)
}
fn key_store(&self) -> &Self::KeyStore {
&self.key_store
}
fn reply_store(&self) -> &Self::ReplyStore {
&self.reply_store
}
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"))]
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"))]
impl OnDiskPersistent {
pub fn new(
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,
}
}
pub async fn from_paths(
paths: CommonClientPaths,
debug_config: &config::DebugConfig,
) -> Result<Self, ClientCoreError> {
let key_store = OnDiskKeys::new(paths.keys);
let reply_store = non_wasm_helpers::setup_fs_reply_surb_backend(
paths.reply_surb_database,
&debug_config.reply_surbs,
)
.await?;
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,
})
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
impl MixnetClientStorage for OnDiskPersistent {
type KeyStore = OnDiskKeys;
type ReplyStore = fs_backend::Backend;
type CredentialStore = PersistentCredentialStorage;
type GatewayDetailsStore = OnDiskGatewayDetails;
fn into_runtime_stores(self) -> (Self::ReplyStore, Self::CredentialStore) {
(self.reply_store, self.credential_store)
}
fn key_store(&self) -> &Self::KeyStore {
&self.key_store
}
fn reply_store(&self) -> &Self::ReplyStore {
&self.reply_store
}
fn credential_store(&self) -> &Self::CredentialStore {
&self.credential_store
}
fn gateway_details_store(&self) -> &Self::GatewayDetailsStore {
&self.gateway_details_store
}
}
@@ -45,7 +45,7 @@ where
#[cfg(target_arch = "wasm32")]
next_delay: Pin<Box<wasm_timer::Delay>>,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// Channel used for sending prepared nym packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: BatchMixMessageSender,
@@ -194,6 +194,7 @@ impl LoopCoverTrafficStream<OsRng> {
self.average_ack_delay,
self.cover_traffic.loop_cover_traffic_average_delay,
cover_traffic_packet_size,
nym_sphinx::params::PacketType::Mix,
)
.expect("Somehow failed to generate a loop cover message with a valid topology");
@@ -1,5 +1,10 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
@@ -7,6 +12,14 @@ pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
#[derive(Debug)]
pub enum InputMessage {
/// Fire an already prepared mix packets into the network.
/// No guarantees are made about it. For example no retransmssion
/// will be attempted if it gets dropped.
Premade {
msgs: Vec<MixPacket>,
lane: TransmissionLane,
},
/// The simplest message variant where no additional information is attached.
/// You're simply sending your `data` to specified `recipient` without any tagging.
///
@@ -41,14 +54,49 @@ pub enum InputMessage {
data: Vec<u8>,
lane: TransmissionLane,
},
MessageWrapper {
message: Box<InputMessage>,
packet_type: PacketType,
},
}
impl InputMessage {
pub fn new_regular(recipient: Recipient, data: Vec<u8>, lane: TransmissionLane) -> Self {
InputMessage::Regular {
pub fn new_premade(
msgs: Vec<MixPacket>,
lane: TransmissionLane,
packet_type: PacketType,
) -> Self {
let message = InputMessage::Premade { msgs, lane };
if packet_type == PacketType::Mix {
message
} else {
InputMessage::new_wrapper(message, packet_type)
}
}
pub fn new_wrapper(message: InputMessage, packet_type: PacketType) -> Self {
InputMessage::MessageWrapper {
message: Box::new(message),
packet_type,
}
}
pub fn new_regular(
recipient: Recipient,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
let message = InputMessage::Regular {
recipient,
data,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -57,12 +105,18 @@ impl InputMessage {
data: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
InputMessage::Anonymous {
let message = InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -70,11 +124,17 @@ impl InputMessage {
recipient_tag: AnonymousSenderTag,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
InputMessage::Reply {
let message = InputMessage::Reply {
recipient_tag,
data,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -82,7 +142,9 @@ impl InputMessage {
match self {
InputMessage::Regular { lane, .. }
| InputMessage::Anonymous { lane, .. }
| InputMessage::Reply { lane, .. } => lane,
| InputMessage::Reply { lane, .. }
| InputMessage::Premade { lane, .. } => lane,
InputMessage::MessageWrapper { message, .. } => message.lane(),
}
}
}
@@ -1,227 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::key_pathfinder::ClientKeyPathfinder;
use log::*;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use rand::{CryptoRng, RngCore};
use std::io;
use std::sync::Arc;
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
// And I guess there will need to be some mechanism for a grace period when you can still
// use the old key after new one was issued.
// Remember that Arc<T> has Deref implementation for T
#[derive(Clone)]
pub struct KeyManager {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// shared key derived with the gateway during "registration handshake"
gateway_shared_key: Option<Arc<SharedKeys>>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
// The expected flow of a KeyManager "lifetime" is as follows:
/*
1. ::new() is called during client-init
2. after gateway registration is completed [in init] ::insert_gateway_shared_key() is called
3. ::store_keys() is called before init finishes execution.
4. ::load_keys() is called at the beginning of each subsequent client-run
5. [not implemented] ::rotate_keys() is called periodically during client-run I presume?
*/
impl KeyManager {
/// Creates new instance of a [`KeyManager`]
pub fn new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
KeyManager {
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
gateway_shared_key: None,
ack_key: Arc::new(AckKey::new(rng)),
}
}
pub fn from_keys(
id_keypair: identity::KeyPair,
enc_keypair: encryption::KeyPair,
gateway_shared_key: SharedKeys,
ack_key: AckKey,
) -> Self {
Self {
identity_keypair: Arc::new(id_keypair),
encryption_keypair: Arc::new(enc_keypair),
gateway_shared_key: Some(Arc::new(gateway_shared_key)),
ack_key: Arc::new(ack_key),
}
}
/// Loads previously stored client keys from the disk.
fn load_client_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
let identity_keypair: identity::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_identity_key().to_owned(),
client_pathfinder.public_identity_key().to_owned(),
))?;
let encryption_keypair: encryption::KeyPair =
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_encryption_key().to_owned(),
client_pathfinder.public_encryption_key().to_owned(),
))?;
let ack_key: AckKey = nym_pemstore::load_key(client_pathfinder.ack_key())?;
Ok(KeyManager {
identity_keypair: Arc::new(identity_keypair),
encryption_keypair: Arc::new(encryption_keypair),
gateway_shared_key: None,
ack_key: Arc::new(ack_key),
})
}
/// Loads previously stored keys from the disk. Fails if not all, including the shared gateway
/// key, is available.
pub fn load_keys(client_pathfinder: &ClientKeyPathfinder) -> io::Result<Self> {
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
let gateway_shared_key: SharedKeys =
nym_pemstore::load_key(client_pathfinder.gateway_shared_key())?;
key_manager.gateway_shared_key = Some(Arc::new(gateway_shared_key));
Ok(key_manager)
}
/// Loads previously stored keys from the disk. Fails if client keys are not availabe, but the
/// shared gateway key is optional.
pub fn load_keys_but_gateway_is_optional(
client_pathfinder: &ClientKeyPathfinder,
) -> io::Result<Self> {
let mut key_manager = Self::load_client_keys(client_pathfinder)?;
let gateway_shared_key: Result<SharedKeys, io::Error> =
nym_pemstore::load_key(client_pathfinder.gateway_shared_key());
// It's ok if the gateway key was not found
let gateway_shared_key = match gateway_shared_key {
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
Ok(key) => Ok(Some(key)),
}?;
key_manager.gateway_shared_key = gateway_shared_key.map(Arc::new);
Ok(key_manager)
}
/// Stores all available keys on the disk.
// While perhaps there is no much point in storing the `AckKey` on the disk,
// it is done so for the consistency sake so that you wouldn't require an rng instance
// during `load_keys` to generate the said key.
pub fn store_keys(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
nym_pemstore::store_keypair(
self.identity_keypair.as_ref(),
&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_identity_key().to_owned(),
client_pathfinder.public_identity_key().to_owned(),
),
)?;
nym_pemstore::store_keypair(
self.encryption_keypair.as_ref(),
&nym_pemstore::KeyPairPath::new(
client_pathfinder.private_encryption_key().to_owned(),
client_pathfinder.public_encryption_key().to_owned(),
),
)?;
nym_pemstore::store_key(self.ack_key.as_ref(), client_pathfinder.ack_key())?;
match self.gateway_shared_key.as_ref() {
None => debug!("No gateway shared key available to store!"),
Some(gate_key) => {
nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
}
}
Ok(())
}
pub fn store_gateway_key(&self, client_pathfinder: &ClientKeyPathfinder) -> io::Result<()> {
match self.gateway_shared_key.as_ref() {
None => {
return Err(io::Error::new(
io::ErrorKind::Other,
"trying to store a non-existing key",
))
}
Some(gate_key) => {
nym_pemstore::store_key(gate_key.as_ref(), client_pathfinder.gateway_shared_key())?
}
}
Ok(())
}
/// Overwrite the existing identity keypair
pub fn set_identity_keypair(&mut self, id_keypair: identity::KeyPair) {
self.identity_keypair = Arc::new(id_keypair);
}
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
Arc::clone(&self.identity_keypair)
}
/// Overwrite the existing encryption keypair
pub fn set_encryption_keypair(&mut self, enc_keypair: encryption::KeyPair) {
self.encryption_keypair = Arc::new(enc_keypair);
}
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
/// Overwrite the existing ack key
pub fn set_ack_key(&mut self, ack_key: AckKey) {
self.ack_key = Arc::new(ack_key);
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
/// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`].
pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc<SharedKeys>) {
self.gateway_shared_key = Some(gateway_shared_key)
}
/// Gets an atomically reference counted pointer to [`SharedKey`].
// since this function is not fully public, it is not expected to be used externally and
// hence it's up to us to ensure it's called in correct context
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
Arc::clone(
self.gateway_shared_key
.as_ref()
.expect("tried to unwrap empty gateway key!"),
)
}
pub fn is_gateway_key_set(&self) -> bool {
self.gateway_shared_key.is_some()
}
}
@@ -0,0 +1,292 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::key_manager::persistence::KeyStore;
use nym_crypto::asymmetric::{encryption, identity};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::acknowledgements::AckKey;
use rand::{CryptoRng, RngCore};
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use zeroize::ZeroizeOnDrop;
pub mod persistence;
pub enum ManagedKeys {
Initial(KeyManagerBuilder),
FullyDerived(KeyManager),
// I really hate the existence of this variant, but I couldn't come up with a better way to handle
// `Self::deal_with_gateway_key` otherwise.
Invalidated,
}
impl Debug for ManagedKeys {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ManagedKeys::Initial(_) => write!(f, "initial"),
ManagedKeys::FullyDerived(_) => write!(f, "fully derived"),
ManagedKeys::Invalidated => write!(f, "invalidated"),
}
}
}
impl From<KeyManagerBuilder> for ManagedKeys {
fn from(value: KeyManagerBuilder) -> Self {
ManagedKeys::Initial(value)
}
}
impl From<KeyManager> for ManagedKeys {
fn from(value: KeyManager) -> Self {
ManagedKeys::FullyDerived(value)
}
}
impl ManagedKeys {
pub fn is_valid(&self) -> bool {
!matches!(self, ManagedKeys::Invalidated)
}
pub async fn try_load<S: KeyStore>(key_store: &S) -> Result<Self, S::StorageError> {
Ok(ManagedKeys::FullyDerived(
KeyManager::load_keys(key_store).await?,
))
}
pub fn generate_new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
ManagedKeys::Initial(KeyManagerBuilder::new(rng))
}
pub async fn load_or_generate<R, S>(rng: &mut R, key_store: &S) -> Self
where
R: RngCore + CryptoRng,
S: KeyStore,
{
Self::try_load(key_store)
.await
.unwrap_or_else(|_| Self::generate_new(rng))
}
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
match self {
ManagedKeys::Initial(keys) => keys.identity_keypair(),
ManagedKeys::FullyDerived(keys) => keys.identity_keypair(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
match self {
ManagedKeys::Initial(keys) => keys.encryption_keypair(),
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn ack_key(&self) -> Arc<AckKey> {
match self {
ManagedKeys::Initial(keys) => keys.ack_key(),
ManagedKeys::FullyDerived(keys) => keys.ack_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn must_get_gateway_shared_key(&self) -> Arc<SharedKeys> {
self.gateway_shared_key()
.expect("failed to extract gateway shared key")
}
pub fn gateway_shared_key(&self) -> Option<Arc<SharedKeys>> {
match self {
ManagedKeys::Initial(_) => None,
ManagedKeys::FullyDerived(keys) => Some(keys.gateway_shared_key()),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn identity_public_key(&self) -> &identity::PublicKey {
match self {
ManagedKeys::Initial(keys) => keys.identity_keypair.public_key(),
ManagedKeys::FullyDerived(keys) => keys.identity_keypair.public_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn encryption_public_key(&self) -> &encryption::PublicKey {
match self {
ManagedKeys::Initial(keys) => keys.encryption_keypair.public_key(),
ManagedKeys::FullyDerived(keys) => keys.encryption_keypair.public_key(),
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
}
}
pub fn ensure_gateway_key(&self, gateway_shared_key: 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>,
key_store: &S,
) -> Result<(), S::StorageError> {
let key_manager = match std::mem::replace(self, ManagedKeys::Invalidated) {
ManagedKeys::Initial(keys) => {
let key_manager = keys.insert_gateway_shared_key(gateway_shared_key);
key_manager.persist_keys(key_store).await?;
key_manager
}
ManagedKeys::FullyDerived(key_manager) => {
self.ensure_gateway_key(gateway_shared_key);
key_manager
}
ManagedKeys::Invalidated => unreachable!("the managed keys got invalidated"),
};
*self = ManagedKeys::FullyDerived(key_manager);
Ok(())
}
}
// all of the keys really shouldn't be wrapped in `Arc`, but due to how the gateway client is currently
// constructed, changing that would require more work than what it's worth
pub struct KeyManagerBuilder {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
impl KeyManagerBuilder {
/// Creates new instance of a [`KeyManager`]
pub fn new<R>(rng: &mut R) -> Self
where
R: RngCore + CryptoRng,
{
KeyManagerBuilder {
identity_keypair: Arc::new(identity::KeyPair::new(rng)),
encryption_keypair: Arc::new(encryption::KeyPair::new(rng)),
ack_key: Arc::new(AckKey::new(rng)),
}
}
pub fn insert_gateway_shared_key(self, gateway_shared_key: Arc<SharedKeys>) -> KeyManager {
KeyManager {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
gateway_shared_key,
ack_key: self.ack_key,
}
}
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
Arc::clone(&self.identity_keypair)
}
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
}
// Note: to support key rotation in the future, all keys will require adding an extra smart pointer,
// most likely an AtomicCell, or if it doesn't work as I think it does, a Mutex. Although I think
// AtomicCell includes a Mutex implicitly if the underlying type does not work atomically.
// And I guess there will need to be some mechanism for a grace period when you can still
// use the old key after new one was issued.
// Remember that Arc<T> has Deref implementation for T
#[derive(Clone)]
pub struct KeyManager {
/// identity key associated with the client instance.
identity_keypair: Arc<identity::KeyPair>,
/// encryption key associated with the client instance.
encryption_keypair: Arc<encryption::KeyPair>,
/// shared key derived with the gateway during "registration handshake"
gateway_shared_key: Arc<SharedKeys>,
/// key used for producing and processing acknowledgement packets.
ack_key: Arc<AckKey>,
}
impl KeyManager {
pub fn from_keys(
id_keypair: identity::KeyPair,
enc_keypair: encryption::KeyPair,
gateway_shared_key: SharedKeys,
ack_key: AckKey,
) -> Self {
Self {
identity_keypair: Arc::new(id_keypair),
encryption_keypair: Arc::new(enc_keypair),
gateway_shared_key: Arc::new(gateway_shared_key),
ack_key: Arc::new(ack_key),
}
}
pub async fn load_keys<S: KeyStore>(store: &S) -> Result<Self, S::StorageError> {
store.load_keys().await
}
pub async fn persist_keys<S: KeyStore>(&self, store: &S) -> Result<(), S::StorageError> {
store.store_keys(self).await
}
/// Gets an atomically reference counted pointer to [`identity::KeyPair`].
pub fn identity_keypair(&self) -> Arc<identity::KeyPair> {
Arc::clone(&self.identity_keypair)
}
/// Gets an atomically reference counted pointer to [`encryption::KeyPair`].
pub fn encryption_keypair(&self) -> Arc<encryption::KeyPair> {
Arc::clone(&self.encryption_keypair)
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
}
/// Gets an atomically reference counted pointer to [`SharedKey`].
pub fn gateway_shared_key(&self) -> Arc<SharedKeys> {
Arc::clone(&self.gateway_shared_key)
}
pub fn remove_gateway_key(self) -> KeyManagerBuilder {
if Arc::strong_count(&self.gateway_shared_key) > 1 {
panic!("attempted to remove gateway key whilst still holding multiple references!")
}
KeyManagerBuilder {
identity_keypair: self.identity_keypair,
encryption_keypair: self.encryption_keypair,
ack_key: self.ack_key,
}
}
}
fn _assert_keys_zeroize_on_drop() {
fn _assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
_assert_zeroize_on_drop::<identity::KeyPair>();
_assert_zeroize_on_drop::<encryption::KeyPair>();
_assert_zeroize_on_drop::<AckKey>();
_assert_zeroize_on_drop::<SharedKeys>();
}
@@ -0,0 +1,237 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
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;
#[cfg(not(target_arch = "wasm32"))]
use nym_crypto::asymmetric::{encryption, identity};
#[cfg(not(target_arch = "wasm32"))]
use nym_gateway_requests::registration::handshake::SharedKeys;
#[cfg(not(target_arch = "wasm32"))]
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
#[cfg(not(target_arch = "wasm32"))]
use nym_pemstore::KeyPairPath;
#[cfg(not(target_arch = "wasm32"))]
use nym_sphinx::acknowledgements::AckKey;
// we have to define it as an async trait since wasm storage is async
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait KeyStore {
type StorageError: Error;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError>;
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError>;
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Debug, thiserror::Error)]
pub enum OnDiskKeysError {
#[error("failed to load {keys} keys from {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)]
KeyPairLoadFailure {
keys: String,
paths: nym_pemstore::KeyPairPath,
#[source]
err: std::io::Error,
},
#[error("failed to store {keys} keys to {:?} (private key) and {:?} (public key): {err}", .paths.private_key_path, .paths.public_key_path)]
KeyPairStoreFailure {
keys: String,
paths: nym_pemstore::KeyPairPath,
#[source]
err: std::io::Error,
},
#[error("failed to load {key} key from {path}: {err}")]
KeyLoadFailure {
key: String,
path: String,
#[source]
err: std::io::Error,
},
#[error("failed to store {key} key to {path}: {err}")]
KeyStoreFailure {
key: String,
path: String,
#[source]
err: std::io::Error,
},
}
#[cfg(not(target_arch = "wasm32"))]
pub struct OnDiskKeys {
paths: ClientKeysPaths,
}
#[cfg(not(target_arch = "wasm32"))]
impl From<ClientKeysPaths> for OnDiskKeys {
fn from(paths: ClientKeysPaths) -> Self {
OnDiskKeys { paths }
}
}
#[cfg(not(target_arch = "wasm32"))]
impl OnDiskKeys {
pub fn new(paths: ClientKeysPaths) -> Self {
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,
name: impl Into<String>,
) -> Result<T, OnDiskKeysError> {
nym_pemstore::load_key(path).map_err(|err| OnDiskKeysError::KeyLoadFailure {
key: name.into(),
path: path.to_str().map(|s| s.to_owned()).unwrap_or_default(),
err,
})
}
fn load_keypair<T: PemStorableKeyPair>(
&self,
paths: KeyPairPath,
name: impl Into<String>,
) -> Result<T, OnDiskKeysError> {
nym_pemstore::load_keypair(&paths).map_err(|err| OnDiskKeysError::KeyPairLoadFailure {
keys: name.into(),
paths,
err,
})
}
fn store_key<T: PemStorableKey>(
&self,
key: &T,
path: &std::path::Path,
name: impl Into<String>,
) -> Result<(), OnDiskKeysError> {
nym_pemstore::store_key(key, path).map_err(|err| OnDiskKeysError::KeyStoreFailure {
key: name.into(),
path: path.to_str().map(|s| s.to_owned()).unwrap_or_default(),
err,
})
}
fn store_keypair<T: PemStorableKeyPair>(
&self,
keys: &T,
paths: KeyPairPath,
name: impl Into<String>,
) -> Result<(), OnDiskKeysError> {
nym_pemstore::store_keypair(keys, &paths).map_err(|err| {
OnDiskKeysError::KeyPairStoreFailure {
keys: name.into(),
paths,
err,
}
})
}
fn load_keys(&self) -> Result<KeyManager, OnDiskKeysError> {
let identity_keypair = self.load_identity_keypair()?;
let encryption_keypair = self.load_encryption_keypair()?;
let ack_key: AckKey = self.load_key(self.paths.ack_key(), "ack key")?;
let gateway_shared_key: SharedKeys =
self.load_key(self.paths.gateway_shared_key(), "gateway shared keys")?;
Ok(KeyManager::from_keys(
identity_keypair,
encryption_keypair,
gateway_shared_key,
ack_key,
))
}
fn store_keys(&self, keys: &KeyManager) -> Result<(), OnDiskKeysError> {
let identity_paths = self.paths.identity_key_pair_path();
let encryption_paths = self.paths.encryption_key_pair_path();
self.store_keypair(
keys.identity_keypair.as_ref(),
identity_paths,
"identity keys",
)?;
self.store_keypair(
keys.encryption_keypair.as_ref(),
encryption_paths,
"encryption keys",
)?;
self.store_key(keys.ack_key.as_ref(), self.paths.ack_key(), "ack key")?;
self.store_key(
keys.gateway_shared_key.as_ref(),
self.paths.gateway_shared_key(),
"gateway shared keys",
)?;
Ok(())
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl KeyStore for OnDiskKeys {
type StorageError = OnDiskKeysError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
self.load_keys()
}
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
self.store_keys(keys)
}
}
#[derive(Default)]
pub struct InMemEphemeralKeys {
keys: Mutex<Option<KeyManager>>,
}
#[derive(Debug, thiserror::Error)]
#[error("old ephemeral keys can't be loaded from storage")]
pub struct EphemeralKeysError;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl KeyStore for InMemEphemeralKeys {
type StorageError = EphemeralKeysError;
async fn load_keys(&self) -> Result<KeyManager, Self::StorageError> {
self.keys.lock().await.clone().ok_or(EphemeralKeysError)
}
async fn store_keys(&self, keys: &KeyManager) -> Result<(), Self::StorageError> {
*self.keys.lock().await = Some(keys.clone());
Ok(())
}
}
+4 -3
View File
@@ -35,19 +35,20 @@ impl<C, St> MixTrafficController<C, St>
where
C: DkgQueryClient + Sync + Send + 'static,
St: Storage + 'static,
<St as Storage>::StorageError: Send + Sync + 'static,
{
pub fn new(
gateway_client: GatewayClient<C, St>,
) -> (MixTrafficController<C, St>, BatchMixMessageSender) {
let (sphinx_message_sender, sphinx_message_receiver) =
let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
(
MixTrafficController {
gateway_client,
mix_rx: sphinx_message_receiver,
mix_rx: message_receiver,
consecutive_gateway_failure_count: 0,
},
sphinx_message_sender,
message_sender,
)
}
@@ -71,7 +71,7 @@ impl AcknowledgementListener {
while !shutdown.is_shutdown() {
tokio::select! {
acks = self.ack_receiver.next() => match acks {
Some(acks) => self.handle_ack_receiver_item(acks).await,
Some(acks) => {self.handle_ack_receiver_item(acks).await}
None => {
log::trace!("AcknowledgementListener: Stopping since channel closed");
break;
@@ -3,10 +3,13 @@
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver};
use crate::client::real_messages_control::message_handler::MessageHandler;
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use crate::client::replies::reply_controller::ReplyControllerSender;
use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng};
@@ -41,6 +44,18 @@ where
}
}
async fn handle_premade_packets(&mut self, packets: Vec<MixPacket>, lane: TransmissionLane) {
self.message_handler
.send_premade_mix_packets(
packets
.into_iter()
.map(|p| RealMessage::new(p, None))
.collect(),
lane,
)
.await
}
async fn handle_reply(
&mut self,
recipient_tag: AnonymousSenderTag,
@@ -57,10 +72,11 @@ where
recipient: Recipient,
content: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
) {
if let Err(err) = self
.message_handler
.try_send_plain_message(recipient, content, lane)
.try_send_plain_message(recipient, content, lane, packet_type)
.await
{
warn!("failed to send a plain message - {err}")
@@ -73,10 +89,11 @@ where
content: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
) {
if let Err(err) = self
.message_handler
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane)
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type)
.await
{
warn!("failed to send a repliable message - {err}")
@@ -89,14 +106,17 @@ where
recipient,
data,
lane,
} => self.handle_plain_message(recipient, data, lane).await,
} => {
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
.await
}
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane)
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
.await
}
InputMessage::Reply {
@@ -106,6 +126,41 @@ where
} => {
self.handle_reply(recipient_tag, data, lane).await;
}
InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
InputMessage::MessageWrapper {
message,
packet_type,
} => match *message {
InputMessage::Regular {
recipient,
data,
lane,
} => {
self.handle_plain_message(recipient, data, lane, packet_type)
.await
}
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
.await
}
InputMessage::Reply {
recipient_tag,
data,
lane,
} => {
self.handle_reply(recipient_tag, data, lane).await;
}
InputMessage::Premade { msgs, lane } => {
self.handle_premade_packets(msgs, lane).await
}
// MessageWrappers can't be nested
InputMessage::MessageWrapper { .. } => unimplemented!(),
},
};
}
@@ -16,7 +16,7 @@ use futures::channel::mpsc;
use log::*;
use nym_gateway_client::AcknowledgementReceiver;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketSize;
use nym_sphinx::params::{PacketSize, PacketType};
use nym_sphinx::{
acknowledgements::AckKey,
addressing::clients::Recipient,
@@ -249,7 +249,11 @@ where
}
}
pub(super) fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
pub(super) fn start_with_shutdown(
self,
shutdown: nym_task::TaskClient,
packet_type: PacketType,
) {
let mut acknowledgement_listener = self.acknowledgement_listener;
let mut input_message_listener = self.input_message_listener;
let mut retransmission_request_listener = self.retransmission_request_listener;
@@ -275,7 +279,7 @@ where
let shutdown_handle = shutdown.clone();
spawn_future(async move {
retransmission_request_listener
.run_with_shutdown(shutdown_handle)
.run_with_shutdown(shutdown_handle, packet_type)
.await;
debug!("The retransmission request listener has finished execution!");
});
@@ -11,9 +11,9 @@ use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use crate::client::replies::reply_controller::ReplyControllerSender;
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::chunking::fragment::Fragment;
use nym_sphinx::preparer::PreparedFragment;
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak};
@@ -48,17 +48,20 @@ where
&mut self,
packet_recipient: Recipient,
chunk_data: Fragment,
packet_type: PacketType,
) -> Result<PreparedFragment, PreparationError> {
debug!("retransmitting normal packet...");
// TODO: Figure out retransmission packet type signaling
self.message_handler
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data)
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type)
.await
}
async fn on_retransmission_request(
&mut self,
weak_timed_out_ack: Weak<PendingAcknowledgement>,
packet_type: PacketType,
) {
let timed_out_ack = match weak_timed_out_ack.upgrade() {
Some(timed_out_ack) => timed_out_ack,
@@ -85,6 +88,7 @@ where
self.prepare_normal_retransmission_chunk(
**recipient,
timed_out_ack.message_chunk.clone(),
packet_type,
)
.await
}
@@ -131,19 +135,26 @@ where
// send to `OutQueueControl` to eventually send to the mix network
self.message_handler
.forward_messages(
vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
vec![RealMessage::new(
prepared_fragment.mix_packet,
Some(frag_id),
)],
TransmissionLane::Retransmission,
)
.await
}
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
pub(super) async fn run_with_shutdown(
&mut self,
mut shutdown: nym_task::TaskClient,
packet_type: PacketType,
) {
debug!("Started RetransmissionRequestListener with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack).await,
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack, packet_type).await,
None => {
log::trace!("RetransmissionRequestListener: Stopping since channel closed");
break;
@@ -15,7 +15,7 @@ use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessa
use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey};
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx::message::NymMessage;
use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::params::{PacketSize, PacketType, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
use nym_sphinx::Delay;
use nym_task::connections::TransmissionLane;
@@ -27,7 +27,7 @@ use std::time::Duration;
use thiserror::Error;
// TODO: move that error elsewhere since it seems to be contaminating different files
#[derive(Debug, Clone, Error)]
#[derive(Debug, Error)]
pub enum PreparationError {
#[error(transparent)]
NymTopologyError(#[from] NymTopologyError),
@@ -291,8 +291,10 @@ where
.try_prepare_single_reply_chunk_for_sending(reply_surb, chunk_clone)
.await?;
let real_messages =
RealMessage::new(prepared_fragment.mix_packet, chunk.fragment_identifier());
let real_messages = RealMessage::new(
prepared_fragment.mix_packet,
Some(chunk.fragment_identifier()),
);
let delay = prepared_fragment.total_delay;
let pending_ack =
PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request);
@@ -384,7 +386,8 @@ where
let lane = raw.0;
let fragment = raw.1;
let real_message = RealMessage::new(prepared.mix_packet, prepared.fragment_identifier);
let real_message =
RealMessage::new(prepared.mix_packet, Some(prepared.fragment_identifier));
let delay = prepared.total_delay;
let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false);
@@ -401,14 +404,23 @@ where
Ok(())
}
pub(crate) async fn send_premade_mix_packets(
&mut self,
msgs: Vec<RealMessage>,
lane: TransmissionLane,
) {
self.forward_messages(msgs, lane).await;
}
pub(crate) async fn try_send_plain_message(
&mut self,
recipient: Recipient,
message: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), PreparationError> {
let message = NymMessage::new_plain(message);
self.try_split_and_send_non_reply_message(message, recipient, lane)
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
.await
}
@@ -417,7 +429,9 @@ where
message: NymMessage,
recipient: Recipient,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), PreparationError> {
debug!("Sending non-reply message with packet type {packet_type}");
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
debug_assert!(!matches!(message, NymMessage::Reply(_)));
@@ -425,7 +439,11 @@ where
let topology_permit = self.topology_access.get_read_permit().await;
let topology = self.get_topology(&topology_permit)?;
let packet_size = self.optimal_packet_size(&message);
let packet_size = if packet_type == PacketType::Outfox {
PacketSize::OutfoxRegularPacket
} else {
self.optimal_packet_size(&message)
};
debug!("Using {packet_size} packets for {message}");
let fragments = self
.message_preparer
@@ -442,10 +460,13 @@ where
topology,
&self.config.ack_key,
&recipient,
packet_type,
)?;
let real_message =
RealMessage::new(prepared_fragment.mix_packet, fragment.fragment_identifier());
let real_message = RealMessage::new(
prepared_fragment.mix_packet,
Some(fragment.fragment_identifier()),
);
let delay = prepared_fragment.total_delay;
let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient);
@@ -463,7 +484,9 @@ where
&mut self,
recipient: Recipient,
amount: u32,
packet_type: PacketType,
) -> Result<(), PreparationError> {
debug!("Sending additional reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
let (reply_surbs, reply_keys) =
self.generate_reply_surbs_with_keys(amount as usize).await?;
@@ -477,6 +500,7 @@ where
message,
recipient,
TransmissionLane::AdditionalReplySurbs,
packet_type,
)
.await?;
@@ -492,7 +516,9 @@ where
message: Vec<u8>,
num_reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), SurbWrappedPreparationError> {
debug!("Sending message with reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
let (reply_surbs, reply_keys) = self
.generate_reply_surbs_with_keys(num_reply_surbs as usize)
@@ -501,7 +527,7 @@ where
let message =
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
self.try_split_and_send_non_reply_message(message, recipient, lane)
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
.await?;
log::trace!("storing {} reply keys", reply_keys.len());
@@ -514,13 +540,21 @@ where
&mut self,
recipient: Recipient,
chunk: Fragment,
packet_type: PacketType,
) -> Result<PreparedFragment, PreparationError> {
debug!("Sending single chunk with packet type {packet_type}");
let topology_permit = self.topology_access.get_read_permit().await;
let topology = self.get_topology(&topology_permit)?;
let prepared_fragment = self
.message_preparer
.prepare_chunk_for_sending(chunk, topology, &self.config.ack_key, &recipient)
.prepare_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
&recipient,
packet_type,
)
.unwrap();
Ok(prepared_fragment)
@@ -531,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",
@@ -556,6 +590,7 @@ where
topology,
&self.config.ack_key,
reply_surb,
PacketType::Mix,
)
.unwrap()
})
@@ -575,7 +610,13 @@ where
let prepared_fragment = self
.message_preparer
.prepare_reply_chunk_for_sending(chunk, topology, &self.config.ack_key, reply_surb)
.prepare_reply_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
reply_surb,
PacketType::Mix,
)
.unwrap();
Ok(prepared_fragment)
@@ -26,6 +26,7 @@ use log::*;
use nym_gateway_client::AcknowledgementReceiver;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::PacketType;
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
@@ -207,7 +208,7 @@ impl RealMessagesController<OsRng> {
}
}
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient) {
pub fn start_with_shutdown(self, shutdown: nym_task::TaskClient, packet_type: PacketType) {
let mut out_queue_control = self.out_queue_control;
let ack_control = self.ack_control;
let mut reply_control = self.reply_control;
@@ -223,6 +224,6 @@ impl RealMessagesController<OsRng> {
debug!("The reply controller has finished execution!");
});
ack_control.start_with_shutdown(shutdown);
ack_control.start_with_shutdown(shutdown, packet_type);
}
}
@@ -1,4 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use self::sending_delay_controller::SendingDelayController;
@@ -92,7 +92,7 @@ where
// messages.
sending_delay_controller: SendingDelayController,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// Channel used for sending prepared packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: BatchMixMessageSender,
@@ -121,7 +121,7 @@ where
#[derive(Debug)]
pub(crate) struct RealMessage {
mix_packet: MixPacket,
fragment_id: FragmentIdentifier,
fragment_id: Option<FragmentIdentifier>,
// TODO: add info about it being constructed with reply-surb
}
@@ -129,17 +129,17 @@ impl From<PreparedFragment> for RealMessage {
fn from(fragment: PreparedFragment) -> Self {
RealMessage {
mix_packet: fragment.mix_packet,
fragment_id: fragment.fragment_identifier,
fragment_id: Some(fragment.fragment_identifier),
}
}
}
impl RealMessage {
pub(crate) fn packet_size(&self) -> usize {
self.mix_packet.sphinx_packet().len()
self.mix_packet.packet().len()
}
pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self {
pub(crate) fn new(mix_packet: MixPacket, fragment_id: Option<FragmentIdentifier>) -> Self {
RealMessage {
mix_packet,
fragment_id,
@@ -247,6 +247,7 @@ where
self.config.average_ack_delay,
self.config.traffic.average_packet_delay,
cover_traffic_packet_size,
self.config.traffic.packet_type,
)
.expect(
"Somehow failed to generate a loop cover message with a valid topology",
@@ -255,7 +256,7 @@ where
)
}
StreamMessage::Real(real_message) => {
(real_message.mix_packet, Some(real_message.fragment_id))
(real_message.mix_packet, real_message.fragment_id)
}
};
@@ -386,7 +387,7 @@ where
// On every iteration we get new messages from upstream. Given that these come bunched
// in `Vec`, this ensures that on average we will fetch messages faster than we can
// send, which is a condition for being able to multiplex sphinx packets from multiple
// send, which is a condition for being able to multiplex packets from multiple
// data streams.
match Pin::new(&mut self.real_receiver).poll_recv(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
@@ -512,7 +512,11 @@ where
let to_send = min(remaining, 100);
if let Err(err) = self
.message_handler
.try_send_additional_reply_surbs(recipient, to_send)
.try_send_additional_reply_surbs(
recipient,
to_send,
nym_sphinx::params::PacketType::Mix,
)
.await
{
warn!("failed to send additional surbs to {recipient} - {err}");
@@ -5,8 +5,6 @@ use crate::client::replies::reply_storage::backend::Empty;
use crate::client::replies::reply_storage::{CombinedReplyStorage, ReplyStorageBackend};
use async_trait::async_trait;
use std::path::PathBuf;
// well, right now we don't have the browser storage : (
// so we keep everything in memory
#[derive(Debug)]
@@ -29,22 +27,6 @@ impl Backend {
impl ReplyStorageBackend for Backend {
type StorageError = <Empty as ReplyStorageBackend>::StorageError;
async fn new(
debug_config: &crate::config::DebugConfig,
_db_path: Option<PathBuf>,
) -> Result<Self, Self::StorageError> {
Ok(Backend {
empty: Empty {
min_surb_threshold: debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
max_surb_threshold: debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
},
})
}
async fn flush_surb_storage(
&mut self,
storage: &CombinedReplyStorage,
@@ -59,8 +41,4 @@ impl ReplyStorageBackend for Backend {
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
self.empty.load_surb_storage().await
}
fn get_inactive_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
self.empty.get_inactive_storage()
}
}

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