Compare commits

...

598 Commits

Author SHA1 Message Date
Dave Hrycyszyn c3a66c32ca wip 2022-12-05 16:41:55 +00:00
Dave Hrycyszyn 60884849d4 Merge branch 'develop' into feature/rust-sdk 2022-12-02 12:00:35 +00:00
Jon Häggblad 25212f23ad nym-connect: update lock file 2022-12-01 14:40:27 +01:00
Jędrzej Stuczyński 033333bb52 Feature/pledge more (#1679)
* New transactions for increasing amount of pledged tokens

* unit tests

* Added an option to pledge extra tokens through the vesting contract

* Introduced wallet endpoints for new operations

* Using updated pledge cap in the vesting contract

* Changelog update
2022-12-01 12:44:20 +00:00
Jon Häggblad 52d06785fb Fix a few errors in socks5 client and network-requester (#1823)
* Fix two unwraps in socks5 and network-requester

* Make sure client task never sends shutdown signal

* Fix panic on getting socks version
2022-12-01 11:20:31 +01:00
Jon Häggblad 1507938c65 socks5-client: SOCKS4a support (#1822)
* socks5-client: SOCKS4a support

* Tidy
2022-12-01 09:19:17 +01:00
Dave Hrycyszyn 41b37984a4 Bumping nym-cli version, missed it last time 2022-11-29 17:51:37 +00:00
Dave Hrycyszyn b541d1a034 Merge branch 'master' into develop 2022-11-29 17:40:30 +00:00
Dave Hrycyszyn e4f34833ef Bumping final version numbers for 1.1.1 2022-11-29 17:22:33 +00:00
Dave Hrycyszyn 5044764a80 Changelog for v1.1.1 2022-11-29 17:21:05 +00:00
Dave Hrycyszyn c178438f06 Bumping version numbers 2022-11-29 17:18:57 +00:00
Dave Hrycyszyn ef4accdfa0 Starting with Rust sdk 2022-11-29 16:52:24 +00:00
Jon Häggblad a9be8a6abd Merge remote-tracking branch 'origin/release/v1.1.2' into develop 2022-11-29 16:49:33 +01:00
Jędrzej Stuczyński 8de9f36b69 Experiment/client refactoring (#1814)
* experimenting with extracting more common client code

* drying up the wasm client

* allowing some dead code for the time being

* fixed formatting in nym-connect

* made socks5 client inside nym-connect immutable

* made clippy a bit happier

* hidden away target locking for recv timeout
2022-11-29 15:48:12 +00:00
Fouad 22f3c8aa40 Update CHANGELOG.md 2022-11-29 14:57:04 +00:00
Jon Häggblad de2e721ba7 Fix typo in changelog 2022-11-29 15:36:54 +01:00
Jon Häggblad b94fbcb6db More entries in nym-connect CHANGELOG 2022-11-29 15:36:14 +01:00
Jon Häggblad 7735f64c6d Fix links in nym-connect changelog 2022-11-29 15:29:59 +01:00
Jon Häggblad 3857313808 Add socks5-client changes to nym-connect changelog 2022-11-29 15:19:34 +01:00
Jon Häggblad 24e2eee547 Merge remote-tracking branch 'origin/release/v1.1.2' into develop 2022-11-29 12:47:52 +01:00
Gala bdb724e9ca Merge pull request #1812 from nymtech/no-display-maintenance
No display maintenance banner
2022-11-29 09:51:55 +01:00
Jon Häggblad 89b35c1483 Fix wait_for_signal_and_error on win (#1811) 2022-11-29 09:43:08 +01:00
Gala 76ef50dc17 Merge branch 'develop' into no-display-maintenance 2022-11-29 09:32:55 +01:00
Gala f663623768 set flag to false 2022-11-29 09:32:22 +01:00
Fouad 822a3b70b7 update nym connect error text (#1809) 2022-11-28 14:37:06 +00:00
Jon Häggblad 136202f329 Merge remote-tracking branch 'origin/release/v1.1.2' into develop 2022-11-28 13:49:46 +01:00
Fouad 66257669fc fix ui overflow bug (#1808) 2022-11-28 12:26:11 +00:00
Jon Häggblad c605a9dd9a Merge remote-tracking branch 'origin/release/v1.1.1' into release/v1.1.2 2022-11-28 13:05:58 +01:00
Bogdan-Ștefan Neacşu f3e226b2bf Use default serde value for upgrade (#1807) 2022-11-28 13:27:48 +02:00
Fouad d004db8037 Feature/nym connect UI updates (#1784)
* create custom titlebar

* create help page

* create generic modal component

* create separate connection time component

* link to shipyard docs

* move timer to separate component and update connection status component usage

* use separate component for copying ip and port details

* only show infomodal once after connection

* set service provider on tauri side

* Emit events when stopped

* listen and unlisten for tauri events

* connect: add trace log to get_services

* Add back CI notifications

* Update README

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com>
2022-11-28 11:22:11 +00:00
Jon Häggblad 018bf8c241 client-core: less frequent status logging (#1806) 2022-11-28 12:14:27 +01:00
Jon Häggblad 65a69b2cba socks5: if any task panics, signal all other tasks to shutdown (#1805)
* socks5: signal shutdown on error

* Mark as success

* Tidy

* Reduce wait to 5 sec

* Replace unwrap with expect

* Two more unwraps

* Update changelog
2022-11-28 10:49:00 +01:00
Raphaël Walther d25848e6f8 Added nightly build workflow on second latest release 2022-11-28 10:41:17 +01:00
Raphaël Walther 0084ba221b Set build on latest release on schedule event 2022-11-25 16:03:43 +01:00
Jędrzej Stuczyński 186896bb37 Feature/gateway client protocol version (#1795)
* Introducing concept of gateway protocol version

* Remove version-based gateway filtering

* Fixed the unit test

* grammar
2022-11-25 13:29:42 +00:00
Mark Sinclair df90ff8658 nym-cli: improve error reporting/handling and changed vesting-schedule queries to use query client instead of signing client 2022-11-25 13:16:44 +00:00
Bogdan-Ștefan Neacşu bff079a3f8 Fix export dkg contract addr (#1800)
* Export dkg contract for mainnet when no config file present

* Remove redundant env files
2022-11-25 14:18:07 +02:00
Jon Häggblad e2ba85c9bf websocket-requests: fix length check before deserialize (#1799) 2022-11-25 10:54:12 +01:00
Jon Häggblad cb7e57b5f8 changelog: add missing entry for fixing message decrypt in gateway-client 2022-11-25 10:54:12 +01:00
Jon Häggblad 17f89aecd5 Make connection_id optional in ClientRequest::Send (#1798) 2022-11-25 10:54:12 +01:00
Jędrzej Stuczyński 0be6fe5079 Feature/use expect instead of panicking (#1797)
* Implementation of 'Debug' on 'RealMessage'

* expect with failed channel name instead of throwing empty panics

* Introduced Debug trait constraint in ProxyRunner

* Derive Debug for socks5_requests::Message
2022-11-25 10:54:12 +01:00
Jon Häggblad 358687f43a Fix decrypting stored received msg (#1786)
* Fix decrypting stored received msg

* rustfmt

* Moving binary message recovery to separate function

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2022-11-25 10:54:12 +01:00
Jon Häggblad fb31dbee16 client-core: add warning when delay multiplier is larger than 1 2022-11-25 10:54:12 +01:00
Jon Häggblad bb98d796a8 Update wallet and connect lock files (#1793) 2022-11-25 10:54:12 +01:00
Jon Häggblad 30dc929e40 real_traffic_stream: reduce frequency of status print (#1794) 2022-11-25 10:54:12 +01:00
Mark Sinclair f1378c3488 Update contracts-build.yml 2022-11-25 10:54:12 +01:00
cgi-bin/ 39ca9c22af Possibilty to change gateway ws listener (#1779)
* add: set gatewayListener

* Update types.ts

* Update worker.ts
2022-11-25 10:54:12 +01:00
Fran Arbanas 4ff741ed9a Add step to release GH actions (#1792)
* feat: add a release step to nym contracts GH action

* feat: add shrinking the size of wasm
2022-11-25 10:54:12 +01:00
Jon Häggblad c9779df2a4 rust: bump required version to 1.65 in some crates that need it 2022-11-25 10:54:12 +01:00
Jon Häggblad c6d624a3b3 Network-requester: throttle inbound connections (#1789)
* Return and handle ClientRequest::LaneQueueLenghts

* Pass lane queue lengths to inbound future

* Remove unused self reference

* Request lane queue lengths periodically for all open connections

* Add timeouts

* Rename to ConnectionCommandSender and Receiver

* Rename to client_connection_tx/rx

* Fix wasm build

* Replace bool with enum
2022-11-25 10:54:12 +01:00
Jon Häggblad 9c361385a7 websocket-requests: fix length check before deserialize (#1799) 2022-11-24 23:45:25 +01:00
Jon Häggblad a9983003d4 changelog: add missing entry for fixing message decrypt in gateway-client 2022-11-24 23:29:37 +01:00
Jon Häggblad e645d14005 Make connection_id optional in ClientRequest::Send (#1798) 2022-11-24 23:13:51 +01:00
Jędrzej Stuczyński cbf9db91ab Feature/use expect instead of panicking (#1797)
* Implementation of 'Debug' on 'RealMessage'

* expect with failed channel name instead of throwing empty panics

* Introduced Debug trait constraint in ProxyRunner

* Derive Debug for socks5_requests::Message
2022-11-24 17:02:31 +00:00
Jon Häggblad 8304146195 Fix decrypting stored received msg (#1786)
* Fix decrypting stored received msg

* rustfmt

* Moving binary message recovery to separate function

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2022-11-24 10:26:09 +01:00
Jon Häggblad c5c16cd6b0 client-core: add warning when delay multiplier is larger than 1 2022-11-23 21:00:48 +01:00
Jon Häggblad 258fa41271 Update wallet and connect lock files (#1793) 2022-11-23 20:59:12 +01:00
Jon Häggblad 0a41834fbe real_traffic_stream: reduce frequency of status print (#1794) 2022-11-23 16:56:09 +01:00
Mark Sinclair 9637afea85 Update contracts-build.yml 2022-11-23 15:51:21 +00:00
cgi-bin/ c8b454a085 Possibilty to change gateway ws listener (#1779)
* add: set gatewayListener

* Update types.ts

* Update worker.ts
2022-11-23 15:14:43 +00:00
Fran Arbanas 81f7457e0e Add step to release GH actions (#1792)
* feat: add a release step to nym contracts GH action

* feat: add shrinking the size of wasm
2022-11-23 15:13:18 +00:00
Jon Häggblad 63ae568cc2 rust: bump required version to 1.65 in some crates that need it 2022-11-23 15:52:58 +01:00
Jon Häggblad f3c1ff02e2 Network-requester: throttle inbound connections (#1789)
* Return and handle ClientRequest::LaneQueueLenghts

* Pass lane queue lengths to inbound future

* Remove unused self reference

* Request lane queue lengths periodically for all open connections

* Add timeouts

* Rename to ConnectionCommandSender and Receiver

* Rename to client_connection_tx/rx

* Fix wasm build

* Replace bool with enum
2022-11-23 12:03:58 +01:00
Bogdan-Ștefan Neacşu f4fb0d6d6c Remove required deposit from signers (#1791) 2022-11-23 12:10:24 +02:00
Jon Häggblad 236594f0c6 Fix clippy::derive-partial-eq-without-eq (#1790) 2022-11-23 10:09:50 +01:00
Jon Häggblad e873845178 Fix some client send unwraps encountered during use (#1787) 2022-11-23 10:09:22 +01:00
Raphaël Walther 2e2f2bb702 atty is unmaintained 2022-11-22 15:28:08 +01:00
Bogdan-Ștefan Neacşu 1cec2ddff0 Remove debugging transaction (#1788) 2022-11-22 14:33:35 +02:00
Bogdan-Ștefan Neacşu 2db1bc8efa Feature/dkg publish vk (#1747)
* Save to disk coconut keypair

* Check verification keys of the other signers

* Post verification key to chain

* Add multisig propose/vote for vks

* Execute the proposal

* Parse announce address argument

* Gateway uses chain data

* Network requester uses chain data

* Native&socks5 clients use chain data

* Credential client signature uses chain data

* Remove redundant api endpoints

* Undo debugging logging

* Fix some tests

* Fix clippy

* Fix wasm client and contract test

* More contract clippy

* Update CHANGELOG

* Use a bigger expiry period then the testing one
2022-11-22 11:16:02 +02:00
Jon Häggblad f1deebc0f1 ci: check formatting first, and add all targets to coconut clippy step 2022-11-22 09:29:32 +01:00
Jon Häggblad 9063a86d26 client: log and handle error when cant load reply key storage (#1785)
* client: log and handle error when cant load reply key storage

* clippy
2022-11-22 01:15:50 +01:00
Jon Häggblad d82fd620ad Update blake2 and ed25519 deps (#1762) 2022-11-22 01:09:12 +01:00
Jon Häggblad fa95d15eac socks5-client: throttle connection inbound from application until data is sent (#1783)
* socks5: throttle send

* client-connections: add additional methods

* WIP

* Update

* Input message sender bounded

* WIP

* Remove the delay that is no longer needed

* rustfmt

* clippy

* Fix wasm build

* clippy

* Try to use MixProxySender/Reader type alias

* Extract out wait function

* Wait on every msg

* changelog: add note

* rustfmt
2022-11-21 23:52:30 +01:00
Bogdan-Ștefan Neacşu b71a8708db Feature/dkg dealing (#1708)
* Reintroduce epoch states

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Use admin address for sensible txs

* Validator-api watch contract and handle events

* Handle dealing exchange

* Dealing exchange

* Recover raw verification keys for 5 dkgs

* Test coconut with dkg keys

* Split dealing storage

* Finish dkg task when it achieved its purpose

* Temporary fix for clippy

* Fix clippy

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2022-11-21 18:00:47 +02:00
Bogdan-Ștefan Neacşu fea6f44a57 Feature/dkg (#1678)
* Port code without epoch and blacklisting

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Add dkg contract to validator client

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Introduce publisher

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Fix mock testing client

* Apply fmt to contract

* Get data from attributes

* Minor fixes

* Fix wasm client

* Add pem files for dkg keys

* Save/load dkg keys in/from pem files

* Get dealer old or fresh dealer index

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2022-11-21 17:28:26 +02:00
Raphaël Walther 23e97e9643 Set schedule for nightly build on release 2022-11-21 16:23:36 +01:00
Raphaël Walther 3f5bfcc696 Cleaned 2022-11-21 16:20:44 +01:00
Raphaël Walther f568673fbc Debugging workflow 2022-11-21 16:13:46 +01:00
Jon Häggblad f6576939d9 Merge remote-tracking branch 'origin/release/v1.1.1' into develop 2022-11-21 16:09:29 +01:00
Raphaël Walther ce17196d48 Debugging workflow 2022-11-21 16:04:12 +01:00
Raphaël Walther 6dde8ecd0a Debugging 2022-11-21 15:49:55 +01:00
Jon Häggblad 1db5e6af05 Merge remote-tracking branch 'origin/release/v1.1.0' into release/v1.1.1 2022-11-21 15:45:18 +01:00
Bogdan-Ștefan Neacşu c4ee964557 Setup with 1 epoch and full test that skips key update (#1647)
* Setup with 1 epoch and full test that skips key update

* Remove a bunch of epoch code

* Remove unnecessary map from one element vector

* Remove tau, epoch and lambda_t

* Removed lambda_t completely

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2022-11-21 16:34:50 +02:00
Raphaël Walther 9337821712 Removed extra spaces in sed expression 2022-11-21 15:07:50 +01:00
Raphaël Walther 279ba7034c Typo 2022-11-21 14:53:30 +01:00
Jon Häggblad 1859ca0a30 client-core: expose lane queue length state to other components (#1777)
* client-core: publish lane queue lengths

* client-connections: rename to LaneQueueLenghts plural

* Fix clippy

* Fix wasm build

* rustfmt

* clippy
2022-11-21 11:21:28 +01:00
Mark Sinclair e30fd270a1 Publish @nymproject/sdk v1.1.4 2022-11-18 15:32:57 +00:00
Jon Häggblad 3ae16fbf1d network-requester: make arguments to run into flags (#1776) 2022-11-18 15:22:33 +01:00
Mark Sinclair e3cda93919 WASM client: update crate version so that the client works with mainnet 2022-11-18 13:06:03 +00:00
benedetta davico 87fb4daeda Release/v1.1.1 nym wallet (#1775)
* fix undelegating with vesting tokens

* update version number

* update tauri conf version

* fix(wallet): explorer links

* refactor(explorer): rename mixnodeidentitykey to mixid

* fix(wallet): broken explorer links

Co-authored-by: fmtabbara <fmtabbara@hotmail.co.uk>
Co-authored-by: pierre <dommerc.pierre@gmail.com>
2022-11-18 12:38:16 +00:00
Dave Hrycyszyn 9f56796bf6 Removing unused nym-chitchat experiment 2022-11-18 12:28:21 +00:00
Jon Häggblad 09b51226c2 client: add LaneQueueLength to ServerResponse (#1772)
* client: add LaneQueueLength to ServerResponse

* fix test compilation

* changlog: add note
2022-11-18 10:24:28 +01:00
Jędrzej Stuczyński 6946151b25 Made vesting_contract_address argument peroperly optional (#1769)
* Made vesting_contract_address argument peroperly optional

* Updated changelog
2022-11-17 17:13:58 +00:00
Jon Häggblad a4aee465fa Merge pull request #1758 from nymtech/jon/fix/deserialize-length-check
Fix deserialize length check
2022-11-17 16:09:54 +01:00
Jon Häggblad 0d71ac5e75 websocket-requests: fix lints in file 2022-11-17 15:32:12 +01:00
Jon Häggblad ce14e40968 websocket-requests: fix deserialize length check 2022-11-17 15:32:12 +01:00
Pierre Dommerc d108edb424 fix(nym-cli): typo in sign command json output (#1768) 2022-11-17 15:15:45 +01:00
Tommy Verrall 18f5623b05 Merge pull request #1759 from nymtech/feature/mixnet-contract-migratemsg
Added migration code for updating vesting contract address
2022-11-17 12:48:57 +01:00
Mark Sinclair 8e92801929 Reduce logging in explorer api 2022-11-16 16:45:59 +00:00
Gala 3fc1bc4e7c Merge pull request #1764 from nymtech/no-display-maintenance
Update banner
2022-11-16 17:25:40 +01:00
Gala 72de726762 another text update 2022-11-16 16:08:47 +00:00
Gala cb9dfa8188 update banner text 2022-11-16 16:03:27 +00:00
Mark Sinclair f102ed53a7 Merge remote-tracking branch 'origin/release/v1.1.0' into develop
# Conflicts:
#	.github/workflows/network-explorer-api.yml
2022-11-16 15:54:08 +00:00
Raphaël Walther a803c7f25e Set output 2022-11-16 16:06:03 +01:00
Gala f20b620cbb turn maintenance banner display false 2022-11-16 15:04:24 +00:00
Raphaël Walther d771d15959 Corrected reference 2022-11-16 15:50:53 +01:00
Raphaël Walther 49e6f387ff Hardcoded branch name 2022-11-16 15:12:50 +01:00
Raphaël Walther 9568c0ba1d Upgraded checkout action 2022-11-16 14:50:46 +01:00
Gala 0b7b705e56 Merge pull request #1602 from nymtech/327-nym-connect-colours
Nym Connect: Various ui updates
2022-11-16 14:30:41 +01:00
Raphaël Walther 5daea675e7 Sent errors through the pipe 2022-11-16 14:28:45 +01:00
Gala ebd18586a8 Merge branch '327-nym-connect-colours' of github.com:nymtech/nym into 327-nym-connect-colours 2022-11-16 13:14:40 +00:00
Gala 585610295f Merge branch 'develop' into 327-nym-connect-colours 2022-11-16 13:14:17 +00:00
Raphaël Walther 91653d13c6 Added echo 2022-11-16 14:03:34 +01:00
Mark Sinclair b84486c0f4 GitHub Actions: install packages needed for build 2022-11-16 11:56:26 +00:00
Mark Sinclair b6a765481a GitHub Actions: add workflow to build network explorer api 2022-11-16 11:43:53 +00:00
Mark Sinclair 8f52f34bc4 GitHub Actions: add workflow to build network explorer api 2022-11-16 11:43:17 +00:00
Raphaël Walther 39798de1e8 Fixed dependency 2022-11-16 11:40:28 +01:00
Raphaël Walther c650587e4c Fixed typo 2022-11-16 11:35:06 +01:00
Raphaël Walther 660d5d8b05 Added missing property 2022-11-16 11:18:12 +01:00
Raphaël Walther 79f9db91ae Fixed typo 2022-11-16 11:16:07 +01:00
Raphaël Walther 43822f27a8 Switched to job outputs 2022-11-16 11:13:36 +01:00
Raphaël Walther e500d154dd Fixed issue with environment variable 2022-11-16 10:37:28 +01:00
Raphaël Walther 3ceb00fae1 Added matrix 2022-11-16 10:24:49 +01:00
Raphaël Walther d019343fd9 Added nightly build on latest release 2022-11-16 10:10:59 +01:00
Raphaël Walther f55a55b784 Cleaned workflows 2022-11-16 10:10:14 +01:00
Raphaël Walther 0ea8da79c8 Enabled yanked crates warning 2022-11-16 09:24:58 +01:00
Jon Häggblad 0e12251773 Update some deps suggested by cargo deny (#1761)
* Update yanked cpufeatures dependency

* Update yanked textwrap version

* Updated yanked crossbeam-channel version

* Update client-core dep to 1.1.0
2022-11-16 09:10:40 +01:00
Jon Häggblad f886326014 wallet_storage: fix clippy (#1757) 2022-11-16 07:43:49 +01:00
Jess c73c2beb33 Update CHANGELOG.md 2022-11-15 19:04:38 +00:00
benedettadavico b7aa84cd5a updating mainnet mixnet conract address 2022-11-15 19:54:28 +01:00
Raphaël Walther b6b40163c6 Added security audit for whole tree 2022-11-15 17:43:51 +01:00
Jędrzej Stuczyński f46c0142e7 Updated changelog 2022-11-15 16:28:15 +00:00
Jędrzej Stuczyński 8774b22d84 Added migration code for updating vesting contract address 2022-11-15 16:06:03 +00:00
Mark Sinclair c74a880838 Update README for SDK 2022-11-14 15:58:00 +00:00
Mark Sinclair ccbb254b1a Add wildcard glob for all files to SDK npm package.json 2022-11-14 15:36:30 +00:00
Dave Hrycyszyn 2bd0cfc870 Moving towards a publishable npm sdk package 2022-11-14 15:08:58 +00:00
Gala aeaf31ed59 Merge pull request #1756 from nymtech/feature-457-banner
Feature 457 banner
2022-11-14 14:04:07 +01:00
Gala 05820cfca7 mantenance banner text update 2022-11-14 12:58:49 +00:00
Gala 0469d5b602 adding a comment and different alignment 2022-11-11 11:53:49 +01:00
Jon Häggblad d912844543 Client: multiplex connection data streams (#1720)
* WIP: QA network details

* Initial implementation to multiplex socks5-client sends

* Introduce TransmissionLane enum

* WIP

* WIP: client requests connection id

* WIP

* mulitplex somewhat done

* Remove closed lanes

* WIP: connection handling over ws

* Remove unused published active connections shared data

* Start on status timer

* Max number of connections, and prune

* Some tidy

* Remove commented out code and tweak log

* Tidy

* Tweak log output

* Rename to TransmissionBuffer

* Use number of msg sent instead of time to rank age of lanes

* Create client-connections crate

* Remove waker call that probably are not needed

* Extract out some types from real traffic stream module

* Revert to develop qa.env

* Tweak comments, tidy for getting ready to merge

* Update changelog

* wasm client compile fixes

* rustfmt
2022-11-11 11:04:49 +01:00
Gala 64757ebc83 adding missing spaces 2022-11-11 07:16:13 +01:00
Gala ba55affe0a reducing ne banner height 2022-11-10 20:43:09 +01:00
Gala e9f826e705 adding a mintenance banner 2022-11-10 20:11:59 +01:00
Fouad bfbd509e4b Update/last minute release updates (#1753)
* fix vesting update bond settings

* style and text updates

* show tx fee when updating node settings

* allow cost param update with vesting tokens
2022-11-10 16:22:09 +00:00
Mark Sinclair b68fb4f5dd Merge branch 'release/v1.1.0' into develop 2022-11-10 15:06:36 +00:00
Dave Hrycyszyn 7461fe88d0 Merge remote-tracking branch 'origin/feature/nym-sdk' into develop 2022-11-10 14:09:49 +00:00
Jon Häggblad 510d0333a1 Add -c for --config-env-file (#1748)
* Add -c for --config-env-file

* changelog: add note
2022-11-10 13:38:55 +01:00
Jon Häggblad cea4887080 Fix clippy in beta toolchain (#1749) 2022-11-10 13:38:40 +01:00
Jon Häggblad 8f1cb67bf7 Update client-core to 1.1.0 2022-11-10 09:44:20 +01:00
Jon Häggblad 09b9601c7e Update client-core to 1.1.0 2022-11-10 09:38:38 +01:00
Mark Sinclair 2a1dd138e0 GH Actions: Install same dependencies as build..yml 2022-11-09 18:21:28 +00:00
Mark Sinclair 89925e49e8 GH Actions: Install same dependencies as build..yml 2022-11-09 18:20:45 +00:00
Mark Sinclair 96fc7208a2 Merge branch 'release/v1.1.0' into develop 2022-11-09 15:27:56 +00:00
Mark Sinclair 9874daa061 Release v1.1.0: bump versions and update CHANGELOGs (#1746)
* Bump version of nym-cli to 1.1.0 and move CHANGELOG to standalone file

* Bump version of nym-connect to v1.1.0 and update CHANGELOG

* Bump version of nym-wallet to v1.1.0 and update CHANGELOG

* Bump version of explorer-api to v1.1.0

* Bump versions of binaries (native-client, socks5-client, mixnode, gateway, network-requester) to v1.1.0

* Bump version of validator-api to v1.1.0

* Bump version of mixnet contract to v1.1.0 (vesting contract already v1.1.0 from #1472)

* Bump Nym Platform version to v1.1.0 and update CHANGELOG

* Update CHANGELOG.md

* Update CHANGELOG.md

* Updated changelog with v2-related changes

* Update CHANGELOG.md

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: Fouad <fmtabbara@hotmail.co.uk>
Co-authored-by: Pierre Dommerc <dommerc.pierre@gmail.com>
Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: Jess <31625607+jessgess@users.noreply.github.com>
2022-11-09 15:04:41 +00:00
Fouad baa61c07d5 Bug fix/ne snag list (#1741)
* use uncapped saturation

* display uncapped saturation

update profit margin tooltip

update operating cost

update rewards tooltip

update stake saturation tooltip

update reward tooltips

update profit margin tooltip

* allow full gateway field to be clickable

use uncapped saturation on mixnode details page
2022-11-09 12:39:14 +00:00
Jon Häggblad b8cb683da0 Add qwerty qa net env file 2022-11-09 11:27:28 +01:00
Jon Häggblad 62e9c8236a Remove log from vesting-contract (#1745)
* Remove log from vesting-contract

* rustfmt
2022-11-09 11:04:32 +01:00
Jon Häggblad d79c25861b Merge pull request #1740 from nymtech/develop-with-release-1.1.0-merged-in
Merge release/v1.1.0 into develop
2022-11-09 11:02:12 +01:00
Jon Häggblad b89ec2e0be clippy 2022-11-09 10:43:53 +01:00
Jon Häggblad 269f50bdd4 rustfmt 2022-11-09 10:25:39 +01:00
durch e3c02dc80a Actually save updated pledge cap 2022-11-09 10:22:14 +01:00
Drazen Urch 65a9320d35 Set default pledge cap to 10% (#1739)
* Set default pledge cap to 10%

* fix clippy beta lints
2022-11-09 10:22:08 +01:00
durch cf268ffcd5 Actually save updated pledge cap 2022-11-09 10:19:21 +01:00
Mark Sinclair bdcfe42a1e Add docs and diagrams 2022-11-08 17:47:10 +00:00
Mark Sinclair 51e30b2a89 Add "attach file" to chat demo
Uses custom binary payload, that includes headers with the file and mime-type, so the receiving browser can save the file correctly.
2022-11-08 17:47:10 +00:00
Mark Sinclair 76d4d0e7cb Add makefile 2022-11-08 17:47:10 +00:00
Mark Sinclair 9df432b8a2 Add arbitrary text headers to binary payload helpers 2022-11-08 17:47:10 +00:00
Mark Sinclair 4021059e76 Send all chat messages with new payload 2022-11-08 17:47:10 +00:00
Mark Sinclair 43ef098aad Add utilities to handle text and binary payloads for mixnet messages 2022-11-08 17:47:10 +00:00
Mark Sinclair bf1d2a12bc Fix html file 2022-11-08 17:47:10 +00:00
Mark Sinclair c3f214ffad Change to sync build 2022-11-08 17:47:10 +00:00
Mark Sinclair 324fb6afe7 Build SDK in dependencies 2022-11-08 17:47:10 +00:00
Mark Sinclair ace020b5cf Upgrade favicons (to upgrade sharp) 2022-11-08 17:47:10 +00:00
Mark Sinclair 4b8fa4805e Tweak dependency build script 2022-11-08 17:47:10 +00:00
Mark Sinclair eb18b49f3e Add a typescript version of the old js-example with basic HTML 2022-11-08 17:47:10 +00:00
Mark Sinclair 2dc45fda1e Tweak dependency build script 2022-11-08 17:47:10 +00:00
Mark Sinclair c2a113f1b3 Remove bootstrap async load, as it isn't needed when loading the wasm from a worker 2022-11-08 17:47:10 +00:00
Mark Sinclair f805eebce7 UI tweaks 2022-11-08 17:47:10 +00:00
Mark Sinclair ad81160760 Build dependencies 2022-11-08 17:47:10 +00:00
Mark Sinclair 0931236a98 Add README and example structure 2022-11-08 17:47:05 +00:00
Drazen Urch b28ff17c30 Set default pledge cap to 10% (#1739)
* Set default pledge cap to 10%

* fix clippy beta lints
2022-11-07 14:40:52 +01:00
Jon Häggblad 9b14e00653 Fix merge error 2022-11-07 13:55:36 +01:00
Jon Häggblad ec8b5e6e9d Merge remote-tracking branch 'origin/release/v1.1.0' into develop 2022-11-07 10:13:33 +01:00
Raphaël Walther d4584c305a Set cron 2022-11-04 15:50:08 +01:00
Raphaël Walther afc53d4379 Added audit notification 2022-11-04 15:12:29 +01:00
Raphaël Walther 4278e88d3c Added audit notification 2022-11-04 14:55:47 +01:00
Raphaël Walther e12a34ce6b Added audit notification 2022-11-04 11:58:23 +01:00
Raphaël Walther 1de64f7b52 Added audit notification 2022-11-04 11:42:31 +01:00
Raphaël Walther 66dbe09e66 Added audit notification 2022-11-04 11:22:54 +01:00
Raphaël Walther dcce269921 Added audit notification 2022-11-04 09:56:00 +01:00
Jędrzej Stuczyński c043f0096a Notify about sent packet after actually pushing it through mix_tx (#1735) 2022-11-03 15:11:58 +00:00
Fouad a7cd7a58f2 Bugfix/delegations sort by no bonded node (#1737)
* use sorting function

* create hardcoded examples in  storybook
2022-11-03 13:44:09 +00:00
Jędrzej Stuczyński fe6da046dc Fixed beta clippy warnings (#1736) 2022-11-03 10:13:16 +00:00
Gala 8bbdb94b13 Merge pull request #1733 from nymtech/417-inputs-label
Wallet: make input's label always shrink
2022-11-02 16:42:17 +01:00
Pierre Dommerc e32601ab86 feat(wallet): normalize decimal places in ui (#1731) 2022-11-02 15:48:49 +01:00
Gala 161138bdff Merge branch 'release/v1.1.0' into 417-inputs-label 2022-11-02 14:26:03 +01:00
Fouad 0529e84a31 add account how to links (#1732) 2022-11-02 13:15:48 +00:00
Gala 95f98016de make label always shrink 2022-11-02 13:49:29 +01:00
Fouad 4967bbb5bd Feature/delegations without bonded node (#1727)
* refactor delegations list to include separate delegation and pending delegation item

* show tooltip on delegation with unbonded node

* feat(wallet): add operating cost in delegations list

* add additional state to check for unbonding event

* disable actions when pending unbond event

* add request and type guard for pending unbond event

* add mixnode_is_unbonding to delegation item type

Co-authored-by: pierre <dommerc.pierre@gmail.com>
2022-11-02 10:46:45 +00:00
Fouad 2952144d32 add profit margin percent to response (#1729)
* add profit margin percent to response

* use display percentage function

* fix profit margin display

* fix up filters
2022-11-01 13:02:34 +00:00
Jędrzej Stuczyński 80c21b3ed9 (chore) setting up a common/logging crate (#1730) 2022-11-01 11:46:47 +00:00
Jędrzej Stuczyński 1f0d5f8ad0 Feature/vesting contract version query (#1726)
* Introduced vesting contract query for build information

* Fixed import paths

* Changelog
2022-10-31 17:37:16 +00:00
Jędrzej Stuczyński 49ce56c367 Jedrzej/feature/version field in framed sphinx packets (#1723)
* introduced PacketVersion into FramedSphinxPacket

* Using legacy mode by default in mixnodes and gateways

* fixed unit tests
2022-10-31 16:56:37 +00:00
Pierre Dommerc 4ab6f4c3a9 refactor(explorer-api): route ping use mix_id as param (#1728) 2022-10-31 16:58:30 +01:00
Jędrzej Stuczyński 3727370b9e Improved error propagation for fallible validator api queries (#1681)
* Improved error propagation for fallible validator api queries

* Updated changelog
2022-10-31 15:28:54 +00:00
Jędrzej Stuczyński b3272097f9 Jedrzej/bugfix/historical uptimes recording (#1721)
* Removed commented out type alias

* typos

* Using the same  underlying timer for uptime updater

* Updating uptimes at 23:00 UTC each day

* Changelog
2022-10-31 12:19:14 +00:00
Jon Häggblad ebc13c4327 client: make channel to mix traffic controller bounded and add backpressure handling v1.1 (#1725)
* clients: change mix traffic channel to bounded

* clients: dynamically adjust sending delay in steps

* rustfmt

* wasm-client: update channel

* client: introduce SendingDelayController

* client-core: downgrade two debug statements to trace

* sending delay controller: tweak parameters

* wasm-client: add tokio dependency

* client-core: rework delay controller

* Revert "client-core: downgrade two debug statements to trace"

This reverts commit e0a7772fafac7bff0e4a2c50ba25e94b52b794e6.

* Remove outdated comment

* Remove WIP comments

* changelog: add note

* out queue controller: simplify with just send

* client-core: document constants

* client: move creating mix msg channel to mix traffic controller

* client-core: downgrade a warning log msg to debug

* changelog: update
2022-10-31 12:21:02 +01:00
Jon Häggblad ec3a6b3e27 socks5: wait to close buffer (v1.1 branch) (#1724)
* socks5: wait to close buffer

This is the fix proposed by @simonwicky in
https://github.com/nymtech/nym/issues/1701

* socks5: fix typo in patch

* socks5: fix tests

* socks5: add type for returned data and index

* socks5: make closed_at_index an Option

* changelog: add note

* changelog: update
2022-10-31 11:56:31 +01:00
Pierre Dommerc 19f3c76f72 Feature/explorer operating cost (#1719)
* feat(explorer): operating cost
2022-10-28 16:24:52 +02:00
Pierre Dommerc 90cc239999 Feature/ne gateway details (#1722)
* create gateway details page

* adding uptime chart

* adding loading state for gateways

* adding link style

* fixing gateways pagination

* remove gateway name and desc

* adding correct toolpit text and cleaning

* fix build

* PR requested changes

* fix build

* requested changes

* fix build a rever console utility addition

Co-authored-by: Gala <calero.vg@gmail.com>
2022-10-28 15:12:37 +02:00
Jędrzej Stuczyński c1bd5db902 comment regarding ts-rs compilation warning 2022-10-28 14:00:54 +01:00
Pierre Dommerc fb1649bab5 fix(explorer): gateway list location column (#1718)
* fix(explorer): gateway list location column

* fix(explorer): gateway list columns width
2022-10-28 12:17:23 +01:00
Jędrzej Stuczyński b21ca41e16 Adding staking supply scale factor in rewarding params update (#1716) 2022-10-28 12:17:17 +01:00
Pierre Dommerc 8656abcbde fix(explorer): minoxde details page (#1715)
* fix(explorer): minoxde details page

* feat(explorer): add mix_id column in mixnodes list
2022-10-27 17:16:30 +02:00
Jon Häggblad 99b30c2570 client: additional error handling in client + socks5-client + network-requester (#1713)
* client: add error type to native client, and start handling them

* client: handle two more error cases

* changelog: add note

* socks5: add error type and start handle run errors

* network-requester: add some error types

* rustfmt

* changelog: update note

* network-requester: remove unused import
2022-10-27 16:00:26 +02:00
Jon Häggblad 2c5d31e685 client: make channel to mix traffic controller bounded and add backpressure handling (#1703)
* clients: change mix traffic channel to bounded

* clients: dynamically adjust sending delay in steps

* rustfmt

* wasm-client: update channel

* client: introduce SendingDelayController

* client-core: downgrade two debug statements to trace

* sending delay controller: tweak parameters

* wasm-client: add tokio dependency

* client-core: rework delay controller

* Revert "client-core: downgrade two debug statements to trace"

This reverts commit e0a7772fafac7bff0e4a2c50ba25e94b52b794e6.

* Remove outdated comment

* Remove WIP comments

* changelog: add note

* out queue controller: simplify with just send

* client-core: document constants

* client: move creating mix msg channel to mix traffic controller

* client-core: downgrade a warning log msg to debug
2022-10-27 15:23:25 +02:00
Tommy Verrall 3ae9ea5de6 Merge pull request #1709 from nymtech/fix/explorerapi-geoip-lookup
fix(explorer-api): geoip lookup
2022-10-27 12:47:36 +02:00
Jon Häggblad cf65bc1295 socks5: wait to close buffer (#1702)
* socks5: wait to close buffer

This is the fix proposed by @simonwicky in
https://github.com/nymtech/nym/issues/1701

* socks5: fix typo in patch

* socks5: fix tests

* socks5: add type for returned data and index

* socks5: make closed_at_index an Option

* changelog: add note
2022-10-27 12:42:01 +02:00
Jędrzej Stuczyński 8bcec241a2 Moves Percent tests to the crate with the type definition (#1714) 2022-10-27 11:33:33 +01:00
Jędrzej Stuczyński 306e9b9dc2 Bugfix/correct staking supply accounting (#1706)
* Added staking_supply_scale_factor field on RewardingParams

* Scaling the amount of tokens released to the circulating/staking supplies
2022-10-27 10:51:09 +01:00
Jędrzej Stuczyński 2d5f851252 Workaround for clippy #9612 issue 2022-10-27 10:47:13 +01:00
Fouad d36e349cc6 Display routing scores for bonded gateway (#1693)
* access gateway report from node status api

* Create 4 response types for gateway and mixnode uptime and status

* Add the three remaining validator-client functions

* display gatways routing scores

* handle undefined gateway report

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2022-10-27 10:34:11 +01:00
Jon Häggblad 4990a4745f Add two more sphinx extended packet sizes (8kb and 16kb) (#1694)
* Rename to ExtendedPacketSize32

* Add two more extended packet sizes

* Update config handling for new packet sizes

* Update wasm-client

* Changelog: update

* wasm-client: fix ref

* Switch use enum instead of string for config
2022-10-27 10:52:57 +02:00
Jędrzej Stuczyński 5ce087dafe Chore/remove unwraps (#1707)
* Disallowing the use of unwraps and expects in vesting and mixnet contracts

* Removed dodgy unwraps from the mixnet contract

* Removed dodgy unwraps from the vesting contract

* Removed unwraps/expects from common contracts crate

* ...but adding the unwraps in tests
2022-10-26 16:48:06 +01:00
Dave Hrycyszyn caf03a09c8 Adding in wss fix for web version 2022-10-26 16:34:09 +01:00
Jon Häggblad 0d399f7d70 connect: tidy error enum (#1712) 2022-10-26 17:26:31 +02:00
Jon Häggblad 56cf181770 validator-api: use response type for history and report endpoints (#1711) 2022-10-26 17:26:17 +02:00
Tommy Verrall f0aa2feb76 Merge pull request #1710 from nymtech/fix/explorer-table-ui
fix(explorer): mixnode location overflow
2022-10-26 17:13:29 +02:00
pierre 4df927cc3d fix(explorer): mixnode location overflow 2022-10-26 16:51:25 +02:00
Dave Hrycyszyn 5db47b8931 Optimizing for fat wasm 2022-10-26 15:40:03 +01:00
Dave Hrycyszyn 27c1b29615 Removing accidental yarn lockfile 2022-10-26 15:38:54 +01:00
Dave Hrycyszyn c80c8ef899 Bumping version number of wasm client package 2022-10-26 14:36:33 +01:00
pierre 3f4373eb98 feat(explorer-api): add debug logs 2022-10-26 12:29:01 +02:00
pierre cf10bb12ef feat(explorer-api): use mixnode port in ip lookup 2022-10-26 12:23:33 +02:00
pierre cb1e93e58d fix(explorer-api): geoip lookup 2022-10-26 12:06:47 +02:00
pierre d0cd22c4da Revert "fix(explorer-api): geoip, ip address from domain"
This reverts commit a721e97c06.
2022-10-26 11:57:04 +02:00
pierre a721e97c06 fix(explorer-api): geoip, ip address from domain 2022-10-26 11:52:54 +02:00
Drazen Urch f4f98027a0 Add per account pledge caps (#1687)
* Add per account pledge caps

* Address PR comments

* Update CHANGELOG

* No cap if no locked

* Fail account creation if taking account already exists

* Delegated free should be counted from vesting period start
2022-10-26 10:51:40 +02:00
Dave Hrycyszyn dee27e805d Adding a README for the nym-api 2022-10-25 12:25:09 +01:00
Dave Hrycyszyn 6f7dc36e5c Removing unused attribute 2022-10-25 11:45:02 +01:00
Dave Hrycyszyn ef50f361ba Adding funding notice 2022-10-25 11:45:02 +01:00
Pierre Dommerc 3c55b28e69 feat(explorer-api): auto update geoip database (#1684)
* feat(explorer-api): auto update geoip database

* feat(explorer-api): read dotenv file

* fix(explorer-api): gitignore

* feat: move geoipupdate service to root compose file
2022-10-24 18:17:50 +02:00
Jędrzej Stuczyński f1624e658e Feature/adjusting epoch events (#1696)
* Added (not yet using) source height for pending events

* Fixed existing unit tests

* Emitting source height for resolved pending events

* Removed unused attribute keys

* Emitting initial total unit reward at time of delegation

* Updated ts types

* regenerated corresponding typescript types

* missed changes

* Piggybacking on breaking change to remove serde aliases
2022-10-24 09:30:51 +01:00
Jon Häggblad fc44f2fe1c Fix typo in Makefile 2022-10-21 22:11:04 +02:00
Fouad cc26e4043c only display general settings for mixnodes (#1700)
* only display general settings for mixnodes
2022-10-21 12:21:55 +01:00
Jon Häggblad bb242080cf Update qa.env mixnet contract address 2022-10-21 12:34:44 +02:00
Jon Häggblad 3ebaf48aa3 Makefile: add wasm-client (#1699) 2022-10-21 11:35:20 +02:00
Fouad 2d7a55daba fix duplicate delegations (#1697) 2022-10-21 09:55:58 +01:00
Fouad 5f36742ce6 fix up operating cost (#1698) 2022-10-21 09:51:19 +01:00
Fouad 8547e770da Display interval timings (#1690)
* make reusable Alert component

* get current interval

* display next interval and epoch times

* display pending events
2022-10-20 17:11:02 +01:00
Gala 862178c9c5 Merge pull request #1688 from nymtech/ne-changes
Network Explorer: narrowing columns and re-orden them
2022-10-20 17:24:16 +02:00
Jędrzej Stuczyński 33a339ae2c Feature/multi node simulator (#1692)
* simple multi-node simulator

* Extending simulator with multi-node feature + testing against known good values

* Mixnet contract test fixes

* comment explaining the epsilon choice
2022-10-19 17:52:00 +01:00
Pierre Dommerc 5d583548ec feat(wallet): new account howto modals (#1689) 2022-10-19 11:18:09 +02:00
Fouad ba979c2e60 Feature/operator costs (#1680)
* add new operator cost field

* change uptime label to routing score

* update validation for operator cost

* fix profit margin type
2022-10-19 09:56:22 +01:00
Jędrzej Stuczyński dbb674f042 Fixes rewarding-epoch emitted events + unit tests for reward recalculation (#1691) 2022-10-18 12:59:27 +01:00
Jędrzej Stuczyński c3bea668d5 Renamed the type alias NodeId to MixId and fixed some usages (#1682)
* Renamed the type alias NodeId to MixId and fixed some usages

* fix(wallet): bonding context

* fix(wallet): remove ip field (type error)

Co-authored-by: pierre <dommerc.pierre@gmail.com>
2022-10-17 17:25:40 +01:00
Gala e0dd9b533e create theme variables 2022-10-17 17:17:20 +02:00
Gala 5ab3f95b8f cleaning 2022-10-17 15:36:42 +02:00
Gala 46097c80fe Merge branch 'develop' into ne-changes 2022-10-17 14:30:59 +02:00
Gala ab0eb35906 columns re-order and narrowing 2022-10-17 14:28:41 +02:00
Gala 8bb3b066ba nav width 2022-10-17 14:28:00 +02:00
Jon Häggblad 6a3ac6b9be client-core: fix clippy (#1686)
* client-core: fix clippy in beta toolchain

* nym-connect: update Cargo.lock
2022-10-17 10:58:38 +02:00
Pierre Dommerc da95e4e903 Wallet - bonding, new node stats component (#1535)
* feature(wallet-bonding): add bond more skeleton

* fix(wallet-bonding): post godzilla merge

fix: post rebase

feature(wallet-bonding): wip

* feat(wallet-bonding): add node stats component

feat(wallet-bonding): add node stats component

* feat(wallet-bonding): fix tooltip icon size

* fix(wallet-bonding): node stats fix responsiveness

fix(wallet-bonding): post godzilla merge

* feat(wallet): fetch active set probabilities

* feat(wallet): operation mixnode avg uptime

* fix: typo

* fix(wallet): theme colors

fix(wallet): theme colors

* fix(wallet): destructuring

* feat(wallet): request total reward data

* refactor(wallet): clean code

* fix(wallet): typo, better error logging

* fix(wallet): some nym decimal values

* fix(wallet): deal with unym nym values

* fix(wallet): routing score chart

* feat(wallet): new node stats design

* feat(wallet): new node stats design

* fix(wallet): increase component width

* fix(wallet): some vertical alignements
2022-10-14 10:40:53 +02:00
Fouad 732235afc0 update account wording (#1683) 2022-10-13 14:44:28 +01:00
Jędrzej Stuczyński 27a81df79e Require authorisation to reconcile pending events (#1676) 2022-10-11 12:32:39 +01:00
Jon Häggblad 03d654214f wallet: remove leftover debug log 2022-10-10 18:30:58 +02:00
Jon Häggblad a9dcd8e6c7 validator-api: add operating cost and profit margin to compute reward est (#1672)
* validator-api: add oper cost and profit margin to compute reward est

* changelog: add note

* rustfmt

* rustfmt
2022-10-10 18:13:23 +02:00
Jędrzej Stuczyński a43d183b4f Feature/wasm client updates (#1673)
* Compiles but runtime time fails

* wip

* Beginning of clean-up - creation of config to keep things together

* Removed unused module

* Removed hardcoded constants

* Easier way of sending binary messages

* WIP cleanup before machine switch

* Upgrade wasm-bindgen to 0.2.83

* Fixed compilation warnings for wasm client

* all clients compiling without warnings

* disabling topology refresh in wasm

* Added a config option to disable loop cover traffic stream

* config changes

* Make webassembly work in a web worker
- `wasm-timer` modified to work in web worker
- add worker target to webpack
- add client to call from HTML
- update README to build WASM for bundling (this does not build ES modules)

* Restored topology refreshing

* correctly polling items in the wasm delay_queue

* Allow client to read up to 8 messages at once from gateway connection (#1669)

* Allow client to read up to 8 messages at once from gateway connection

* Importing tokio::select in wasm32 target

* Updated changelog

* missing imports

* Introduced disable_main_poisson_packet_distribution to force real_traffic_stream to disable poisson sending (#1664)

* Introduced disable_main_poisson_packet_distribution to force real_traffic_stream to disable poisson sending

* Updated changelog

* Adjusting default settings

* Introduced a client-configurable option to force it to use extended packet size

* local adjustments

* Removed warning associated with receiving extended packets

* Minimal v2-required changes

* Updated changelog

* explicitly allowing clippy drop_non_drop

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
2022-10-10 16:27:51 +01:00
Jędrzej Stuczyński 54d97fdbec Introduced a client-configurable option to force it to use extended packet size (#1671)
* Introduced a client-configurable option to force it to use extended packet size

* cargo fmt

* Removed warning associated with receiving extended packets

* Updated changelog
2022-10-10 15:07:30 +01:00
Gala 69e5abaed9 Merge pull request #1516 from nymtech/340-ne-content
NE: Change mentions of "Uptime" to "Routing Score"
2022-10-10 14:35:33 +02:00
Gala e3284f30a8 Merge pull request #1665 from nymtech/348-bonding-settings
Wallet: Bonded node settings
2022-10-10 14:09:26 +02:00
Gala 46d2d1f88b Merge branch 'develop' into 340-ne-content 2022-10-10 13:55:04 +02:00
Gala 8f11b39e95 consistency 2022-10-10 12:32:43 +02:00
Gala 2192777485 prevent build fail 2022-10-10 12:06:35 +02:00
Gala 11b8c52b30 pr refactoring suggestion 2022-10-10 12:03:38 +02:00
Gala 99cfdab601 using Consoles and some refactor 2022-10-10 11:48:22 +02:00
Jędrzej Stuczyński 11a67adc04 Introduced disable_main_poisson_packet_distribution to force real_traffic_stream to disable poisson sending (#1664)
* Introduced disable_main_poisson_packet_distribution to force real_traffic_stream to disable poisson sending

* Updated changelog
2022-10-10 10:10:01 +01:00
Jędrzej Stuczyński 65f75c5fe5 Allow client to read up to 8 messages at once from gateway connection (#1669)
* Allow client to read up to 8 messages at once from gateway connection

* Importing tokio::select in wasm32 target

* Updated changelog
2022-10-10 09:44:47 +01:00
Jędrzej Stuczyński 68c2cf5f95 Feature-locked TestingResolveAllPendingEvents transaction (#1667) 2022-10-07 11:25:35 +01:00
Jędrzej Stuczyński 1dd89ea1aa Try to use up to date list of nodes when performing rewarding (#1668) 2022-10-07 11:15:49 +01:00
Jędrzej Stuczyński 6593605834 Exposed all debug fields in client configs 2022-10-07 10:37:36 +01:00
Jędrzej Stuczyński 9d4c62cad6 Added a config option to disable loop cover traffic stream (#1666)
* Added a config option to disable loop cover traffic stream

* Updated changelog
2022-10-07 10:35:25 +01:00
Gala f72a38a5a8 use ts type predicates 2022-10-06 15:53:14 +02:00
durch cc641052b3 Force add Makefile 2022-10-06 15:33:13 +02:00
Gala 545c8b76a7 use location state instead of a query 2022-10-06 15:31:13 +02:00
durch be07e4997e Add wasm-opt to build 2022-10-06 15:16:14 +02:00
Gala 139a0dca2f now is the refactor : ) 2022-10-06 15:12:58 +02:00
Gala e9c0b9bef3 navigation refactor from CR 2022-10-06 15:12:33 +02:00
Gala 9b28de4a06 use iconbutton 2022-10-06 14:22:12 +02:00
Gala f0c50556ad don't update not used files 2022-10-06 12:37:57 +02:00
Gala f50af85fb1 cleaning up a bit the code 2022-10-06 12:20:08 +02:00
Mark Sinclair 25f1fb2eb8 Wrap up Bity order signature and verification into simple structs, so it is easy for them to use (#1661) 2022-10-06 11:00:49 +01:00
Gala 27ab849018 unbonf new flow 2022-10-06 11:50:19 +02:00
Jędrzej Stuczyński 803f7117ea Removes humantime_serde serialization of epoch_length (#1662) 2022-10-06 09:05:07 +01:00
Gala 611d37e46f update with develop 2022-10-05 15:47:17 +02:00
Gala 99b35f8d01 wip unbonding page 2022-10-05 15:38:47 +02:00
Gala f35bfc63e2 connect also info settings and adding schemas for both forms 2022-10-05 15:35:22 +02:00
Gala 1be85dced6 bonded node param set settings and validate first 2022-10-05 15:22:08 +02:00
Pierre Dommerc 7a3253e025 fix: typescript generate types (#1660) 2022-10-05 13:53:03 +02:00
Mark Sinclair 8a3351bf82 common commands - add prefix and account id to the signature verification helper (#1659) 2022-10-05 12:47:29 +01:00
Jon Häggblad 55e45a0d88 validator-client: remove left-over log::trace 2022-10-05 11:41:53 +02:00
Pierre Dommerc 5a55c320cb fix(wallet): reward types (#1658) 2022-10-04 17:38:42 +02:00
Gala ba64c57283 use react useForm 2022-10-04 15:53:19 +02:00
Pierre Dommerc 739b2f88f9 Wallet - update of bonding flow part 1 (#1528) 2022-10-04 13:46:51 +02:00
Gala ce269e60e4 Merge branch 'develop' into 348-bonding-settings 2022-10-04 12:45:04 +02:00
Gala ad9ea03683 Merge branch 'node-settings-copy' into 348-bonding-settings 2022-10-04 12:44:33 +02:00
Gala 47cae50e68 profit margin only for mixnode 2022-10-04 11:51:34 +02:00
Gala 2a04234c26 wip scroll bar styles 2022-10-03 14:45:01 +02:00
Pierre Dommerc c582d6dcba config(wallet): use new mixnet contract address for qa (#1656) 2022-10-03 13:38:56 +02:00
Gala ef8f6ed07b some ui changes at the navigation 2022-09-29 17:54:31 +03:00
Gala c644956576 wip 2022-09-29 17:08:31 +03:00
Gala c329724f8c Merge branch 'develop' into node-settings-copy 2022-09-29 15:44:52 +03:00
Gala a96383e714 wip 2022-09-28 09:52:57 +03:00
Jędrzej Stuczyński 49b3a5aa90 Made config.toml values in validator-api take precedence over mainnet defaults (#1645)
* Made config.toml values in validator-api take precedence over mainnet defaults

* Updated mixnode and gateway configs with similar priority adjustments

* Changelog
2022-09-23 16:59:42 +01:00
Jędrzej Stuczyński 879ce3f2d5 Feature/field renaming (#1654)
* Replaced serde renames to aliases

Ideally I would have removed all serde macros, but then it would have broken existing QA deployments - perhaps we should do it later

* Renamed 'node_id' in Delegation to 'mix_id'

* Further renamings of 'node_id' to 'mix_id' in various places
2022-09-23 15:01:39 +01:00
Pierre Dommerc 996f0bf732 feature(explorer-api): rewrite geoip2 location service (#1651) 2022-09-23 14:53:56 +02:00
Jędrzej Stuczyński b289a3570a Prefixing all mixnet contract events with a v2_ prefix (#1652) 2022-09-23 12:33:29 +01:00
Gala 1b689edb43 Merge pull request #1653 from nymtech/ne-stak-sat-filter
NE: fix upper saturation value used on filters
2022-09-23 11:37:47 +02:00
Jon Häggblad 95c5b70eb7 Remove the old deprecated parts of network-defaults (#1646)
* network-defaults: remove all the old crap and simplity

* validator-client: remove commented out code
2022-09-23 11:32:23 +02:00
Jędrzej Stuczyński a5b4504b0a Emitting information about prior delegation data before rewarding (#1650)
* Emitting information about prior delegation data before rewarding

* Cargo fmt
2022-09-23 09:32:37 +01:00
Gala 995a61b7ea fix upper saturation value use on filters 2022-09-23 10:20:51 +02:00
Drazen Urch 0adf4df094 Fig and shell completions (#1638)
* Fig and shell completions lib

* Complete all the things
2022-09-22 17:26:37 +02:00
Gala 6eb482fc4b CR: use a parameter instead of cardcoded value 2022-09-22 16:54:07 +02:00
Gala f9be735d4f various ui updates 2022-09-22 16:54:06 +02:00
Gala 8c877d64d6 Merge pull request #1649 from nymtech/fix-validator-url-access
NE: Fix validators url
2022-09-22 15:32:34 +02:00
Gala 9ae1f046c4 avoid destructuring 2022-09-22 15:13:33 +02:00
Gala 7dc776f98a wip fixing conflicts 2022-09-22 13:16:39 +02:00
Gala 9717bcbb17 WIP: Merge branch 'develop' into 348-bonding-settings 2022-09-22 12:22:24 +02:00
Gala f401266d1b Merge pull request #1644 from nymtech/ne-transition-v1-v2
NE: Adding a workaround for v1 to v2 transition
2022-09-22 10:47:32 +02:00
Gala 0fd178a304 Merge branch 'develop' into 327-nym-connect-colours 2022-09-22 10:37:06 +02:00
Gala 1878b50752 Merge branch 'develop' into ne-transition-v1-v2 2022-09-22 10:34:16 +02:00
Gala 8bd7b69bf9 Merge pull request #1597 from nymtech/305-ui-fixes-inputs
Wallet: address Figma differences on modals, dialogs and inputs
2022-09-22 10:33:28 +02:00
Gala cf2ede1040 adding a temporaly solution for transition v1 to v2 2022-09-21 17:57:19 +02:00
Gala af1bf57f24 anothe conflict fix 2022-09-21 17:41:31 +02:00
Gala 0de6a0f1ca Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-21 17:41:07 +02:00
Gala 978cbc4f00 fix conflicts 2022-09-21 17:03:56 +02:00
Gala ebb06d4beb Merge branch 'develop' - wip fixing clonflicts 2022-09-21 17:03:47 +02:00
Gala 03974f9cb3 Merge pull request #1628 from nymtech/409-ne-filters-info
NE & Wallet: NE TableToolbar ui and Wallet new display when no delegations
2022-09-21 16:52:21 +02:00
Jędrzej Stuczyński d322f5e91b Removed the concept of exiting validator API after X consecutive failures (#1643) 2022-09-21 15:20:33 +01:00
Gala 0dee6d9db7 spacing between title and content on pages 2022-09-21 16:05:03 +02:00
Gala 05bd6d6a9a signup mnemonic change 2022-09-21 16:04:06 +02:00
Jędrzej Stuczyński c43dbf6f4d Feature/remove deprecated routes (#1642)
* Removed deprecated routes in validator-api

* Removed deprecated routes in explorer-api
2022-09-21 14:54:51 +01:00
Dave Hrycyszyn 848ace1e0b Using the pre-built package in the chat demo 2022-09-21 13:28:41 +01:00
Mark Sinclair f98d9d89bc GitHub Actions - add input to manual dispatch
Adds `RUST_FLAGS="--cfg tokio_unstable"` when the input is true to add the tokio console to the validator api
2022-09-21 12:24:09 +01:00
Jon Häggblad b9015c1321 Update to latest set of selection chance buckets (#1626)
* Update to latest set of selection chance buckets

* Fixup after rebase

* Lock file update

* storybook update

* update storybook

Co-authored-by: Gala <calero.vg@gmail.com>
2022-09-21 12:38:42 +02:00
Fouad 59b0fe2f94 change input placeholders to labels (#1575)
* change input placeholders to labels

* use back button on 'show mnemonic' modal
2022-09-21 11:28:30 +01:00
Gala bf98c1b369 Merge branch 'develop' into 409-ne-filters-info 2022-09-21 12:01:01 +02:00
Gala d7e3b2c6f2 update branch 2022-09-21 11:57:54 +02:00
Gala 7302b64be7 Merge pull request #1617 from nymtech/309-figma-diff-mnemonic
Wallet: Figma diff, change mnemonic textfield
2022-09-21 11:55:15 +02:00
Gala d5365a7602 Merge pull request #1524 from nymtech/317-forms-menus-titles-ui
317 forms menus titles UI
2022-09-21 11:54:53 +02:00
Jędrzej Stuczyński 524d563077 Added additional log statement to show config file save location 2022-09-21 10:12:53 +01:00
Jon Häggblad e44ddc419c clippy: fix warnings in beta toolchain (#1639) 2022-09-21 09:25:22 +02:00
Jon Häggblad be20e17ebb ci: add libudev-dev to nightly linux build 2022-09-21 08:52:41 +02:00
Dave Hrycyszyn 7b76beab76 Switch from deprecated substr() to slice() 2022-09-20 16:00:18 +01:00
Bogdan-Ștefan Neacşu 9ed013b418 Add library to communicate with ledger (#1640)
* Add library to communicate with ledger

* Update changelog

* Install libudev-dev on linux
2022-09-20 17:57:14 +03:00
Dave Hrycyszyn 33ae43b86d Removing commented code 2022-09-20 15:44:56 +01:00
Dave Hrycyszyn 2c1ad1388d removing surb noise display 2022-09-20 15:35:53 +01:00
Jędrzej Stuczyński 136666d759 Feature/rewarding revamp (#1472)
* Query for node stake saturation

* Queries for currently pending events

* Rewarded set query

* Moved ContractState to common types

since it's being returned as a result of one of the queries on the mixnet contract and thus it needs to be accessible outside the contract itself

* Cleaend up storage initialisation

* started restoring unit tests

* Removed attached 1ucoin for cross-contract execute msgs

* wip

* query for rewarding details of a mix node

* Changes for mixnodes and gateways

* Furher progress on v2 changelog(-ish) description

* wip

* first version of the description

* mixnode bonding queries tests and fixes

* ibid for storage

* MixnodeEventType enum + created events for missing mixnode txs

* tests for adding new mixnode

* Additional mixnode-related tests + bug fixes

* Display for Percent

* Bunch of tests for try_reward_mixnode

* More tests and fixes

* ibid

* tests for updating rewarding params + important bug fix

* Started removing unused imports

* rewarding queries tests + undelegation bugfix

* A lot of todo()-ing and commenting out unimplemented code

* implements https://github.com/nymtech/team-core/issues/113

* Delegation tests + fixes

* Emiting events by top level interval txs + incorporating limit

* question

* Missing events emissions

* removed some code duplication

* wip

* pending delegation tests

* Vesting contract update

* More tests (and fixes) for pending events txs

* Restored gateway tx tests

* Another cleanup iteration

* removed redundant comment

* Unit tests, fixes and simplifcations for interval-related txs

* Unit tests for helper functions

* Interval queries unit tests

* Test for correct contract initialisation

* Another round of cleanup

* Work on mixnet_query_client trait

* mixnet_signing_client trait

* Removed redundant methods

* Slowly restoring validator client functionality

* Added deprecated query for mix details by identity

* wip restoration of validator-api

* Work on deprecating validator API routes

* Further validator-api routes

* Restored rest of status api routes

* Resolved all todos in ValidatorApiStorage

There's still bunch left in StorageManager though

* Changed NodeId from u64 to u32

* Updating sql code

* Network monitor internals

* Changed behaviour of full_epoch_id and updated epoch operations

* Fixed sql queries

* [most likely] finished updating rest of the validator API

* Post rebasing fixes

* Feature/rewarding revamp explorer api changes (#1511)

* Changed cache to allow for non-string keys

* Helper method for best-effort conversion of pubkey to nodeid

* Updated validator-api client routes

* Updated routes to use mix-id indexing

* Introduction of deprecated routes callable by identity key

* Fixed mixnode compatibility by changing read node details fields (#1512)

* Fixed bond to topology conversion for client compatibility (#1513)

* Updated 'verify_gateway_owner' to use correct nymd_client method for obtaining gateway details (#1515)

* Updated constructor for ValidatorCacheInner

* Fixed wasm client topology construction

* Run cargo fmt on the entire codebase

* Feature/rewarding revamp wallet backend changes (#1529)

* Updated mixnode-related ts types

* Updated nym-wallet-types

* Updated 'get_contract_settings' and commented out code of other tauri commands

* 'update_contract_settings'

* 'bond_gateway'

* unbond_gateway'

* Utility commands for the transition period

* 'bond_mixnode'

* 'unbond_mixnode'

* Ability to update mixnode cost paramaters

* Mixnode config update

* Updated mixnode_bond_details

It also returns a different underlying type now

* Updated 'gateway_bond_details'

* Obtaining pending operator rewards

* Improved way of obtaining number of mixnode delegators

* simplified error handling in 'fetch_mix_node_description'

* mixnode and gateway ownership queries

* updated get_number_of_mixnode_delegators to use mix_id since we have the conversion utils helper

* mixnode delegation

* undelegating

* Obtaining pending delegator rewards

* Command for obtaining current interval details

* Queries to handle paging for pending events

* Additional level of indirection to pending events to incorporate event id into response

* Wallet compatible pending event types

* Commands fo obtaining pending events

* Re-implemented pending delegation events

* Further work on delegation

* Removed unused imports

* Commands for withdrawing rewards

* Admin-related simulations

* mixnet-related simulation commands

* Validator-api related routes

* Bond-related vesting operations

* Vesting simulations

* Vesting handler for UpdateMixnodeCostParams

* Vesting reward claiming

* Vesting queries

* claim_locked_and_unlocked_delegator_reward

* The massive delegation query

* cleanup

* updated typescript requests

* sorted the new type exports in ts-rs-cli

* Regenerated typescript types

* temporarily ignoring unreachable code in vesting migration

* Updated missed test fixture

* Fixed missing coconut-specific import

* cargo fmt

* Exporting reward-related types

* utility to convert stringified decimal to cosmjs Decimal

* deriving Eq alongside PartialEq

* wip - typescript fixes

* using default operating cost when bonding mixnode

* Using default operating cost when updating mixnode cost params

* most delegation fixes

* Wrapping delegation with node identity

* Added MultiIndex on owner and identity key to unbonded mixnodes

* Support for queries for unbonded nodes by owner or by identity key

* Cargo fmt + ts types update

* feature locking unused imports

* fix(nym-wallet): typing and error (#1548)

* post-rebase fixes

* Changed storage key for new delegations map in vesting contract

* fix(wallet): typing issues (#1562)

* fix(wallet): error UI feedback (#1565)

* clean(wallet): remove useless files (with flamethrowers 🔥) (#1567)

* Changed default_mixnode_cost_params to allow accepting f32 instead

* Revert "Changed default_mixnode_cost_params to allow accepting f32 instead"

This reverts commit fb62a0014f.

* Fixed APY calculation for 0 pledge value

* Don't send rewarding transactions for empty rewarded set

* Fixed mixnode rewarding in validator API

* fix(nym-wallet): profit margin (#1574)

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Removed todo!() from vesting contract migration since its going to be dealt with differently

* fix(nym-wallet): stake saturation and delegations (#1578)

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* fix(nym-wallet): get rid of delegation history

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): get rid of delegation history

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): fix clippy

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Updated vesting contract migration to update the mixnet contract address

* feat(wallet): add confirmation/warning modal for unbonding

* Post rebasing fixes

* commented out all code

* Bunch of work in progres, but working simulator

* Removing redundant fields + increased precision to 9decimal places

* deserialization of Percent with value validation

* wip

* Further moving things around + mixnode bonding

* Mixnode unbonding

* Starting restoration on contract state

* Revamping interval

* More work on epoch/interval

* progress on mixnode rewarding

* Moved MixNodeRewarding to rewards storage

* wip on delegations

* Removed concept of periods and historical records and moved cummulative reward ratio directly to delegation

* more wip delegation

* Full delegation flow

* mixnode config updates

* Mixnode cost function updates

* Work on moving mixnode unbonding to post-epoch actions

* Unbonding

* Processing undelegation

* changing cost params

* Uncommented existing gateways features

without much changes so far, however, things like unbonding should probably also go to epoch queue

* ExecuteMsg cleanup

* unit tests for withdrawing rewards against known values

* Transactions for withdrawing rewards

* Transactions for updating various parameters

* First round of post-tx cleanup

* Moved all storage keys to constants.rs

* Using correct initial gateway pledge amount

* Renamed sybil_resistance_percent to sybil_resistance with percent being implicit from the typ

* Starting with contract queries

* Keeping minimal details of unbonded mixnoces

* Checking for owner address rather than rewarding validator when updating rewarding params

* Mixnode-related queries

* Gateway-related queries

* Query for paged unbonded mixnodes

* Delegations queries

* Query for current interval details

* Removed 'fixed' dependency from the mixnet common

* wip on implementing rewards-related queries

* Pending rewards queries

* Query for node stake saturation

* Queries for currently pending events

* Rewarded set query

* Moved ContractState to common types

since it's being returned as a result of one of the queries on the mixnet contract and thus it needs to be accessible outside the contract itself

* Cleaend up storage initialisation

* started restoring unit tests

* Removed attached 1ucoin for cross-contract execute msgs

* wip

* query for rewarding details of a mix node

* Changes for mixnodes and gateways

* Furher progress on v2 changelog(-ish) description

* wip

* first version of the description

* mixnode bonding queries tests and fixes

* ibid for storage

* MixnodeEventType enum + created events for missing mixnode txs

* tests for adding new mixnode

* Additional mixnode-related tests + bug fixes

* Display for Percent

* Bunch of tests for try_reward_mixnode

* More tests and fixes

* ibid

* tests for updating rewarding params + important bug fix

* Started removing unused imports

* rewarding queries tests + undelegation bugfix

* A lot of todo()-ing and commenting out unimplemented code

* implements https://github.com/nymtech/team-core/issues/113

* Delegation tests + fixes

* Emiting events by top level interval txs + incorporating limit

* question

* Missing events emissions

* removed some code duplication

* wip

* pending delegation tests

* Vesting contract update

* More tests (and fixes) for pending events txs

* Restored gateway tx tests

* Another cleanup iteration

* removed redundant comment

* Unit tests, fixes and simplifcations for interval-related txs

* Unit tests for helper functions

* Interval queries unit tests

* Test for correct contract initialisation

* Another round of cleanup

* Work on mixnet_query_client trait

* mixnet_signing_client trait

* Removed redundant methods

* Slowly restoring validator client functionality

* Added deprecated query for mix details by identity

* wip restoration of validator-api

* Work on deprecating validator API routes

* Further validator-api routes

* Restored rest of status api routes

* Resolved all todos in ValidatorApiStorage

There's still bunch left in StorageManager though

* Changed NodeId from u64 to u32

* Updating sql code

* Network monitor internals

* Changed behaviour of full_epoch_id and updated epoch operations

* Fixed sql queries

* [most likely] finished updating rest of the validator API

* Post rebasing fixes

* Feature/rewarding revamp explorer api changes (#1511)

* Changed cache to allow for non-string keys

* Helper method for best-effort conversion of pubkey to nodeid

* Updated validator-api client routes

* Updated routes to use mix-id indexing

* Introduction of deprecated routes callable by identity key

* Fixed mixnode compatibility by changing read node details fields (#1512)

* Fixed bond to topology conversion for client compatibility (#1513)

* Updated 'verify_gateway_owner' to use correct nymd_client method for obtaining gateway details (#1515)

* Updated constructor for ValidatorCacheInner

* Fixed wasm client topology construction

* Run cargo fmt on the entire codebase

* Feature/rewarding revamp wallet backend changes (#1529)

* Updated mixnode-related ts types

* Updated nym-wallet-types

* Updated 'get_contract_settings' and commented out code of other tauri commands

* 'update_contract_settings'

* 'bond_gateway'

* unbond_gateway'

* Utility commands for the transition period

* 'bond_mixnode'

* 'unbond_mixnode'

* Ability to update mixnode cost paramaters

* Mixnode config update

* Updated mixnode_bond_details

It also returns a different underlying type now

* Updated 'gateway_bond_details'

* Obtaining pending operator rewards

* Improved way of obtaining number of mixnode delegators

* simplified error handling in 'fetch_mix_node_description'

* mixnode and gateway ownership queries

* updated get_number_of_mixnode_delegators to use mix_id since we have the conversion utils helper

* mixnode delegation

* undelegating

* Obtaining pending delegator rewards

* Command for obtaining current interval details

* Queries to handle paging for pending events

* Additional level of indirection to pending events to incorporate event id into response

* Wallet compatible pending event types

* Commands fo obtaining pending events

* Re-implemented pending delegation events

* Further work on delegation

* Removed unused imports

* Commands for withdrawing rewards

* Admin-related simulations

* mixnet-related simulation commands

* Validator-api related routes

* Bond-related vesting operations

* Vesting simulations

* Vesting handler for UpdateMixnodeCostParams

* Vesting reward claiming

* Vesting queries

* claim_locked_and_unlocked_delegator_reward

* The massive delegation query

* cleanup

* updated typescript requests

* sorted the new type exports in ts-rs-cli

* Regenerated typescript types

* temporarily ignoring unreachable code in vesting migration

* Updated missed test fixture

* Fixed missing coconut-specific import

* cargo fmt

* Exporting reward-related types

* utility to convert stringified decimal to cosmjs Decimal

* deriving Eq alongside PartialEq

* wip - typescript fixes

* using default operating cost when bonding mixnode

* Using default operating cost when updating mixnode cost params

* most delegation fixes

* Wrapping delegation with node identity

* Added MultiIndex on owner and identity key to unbonded mixnodes

* Support for queries for unbonded nodes by owner or by identity key

* Cargo fmt + ts types update

* feature locking unused imports

* fix(nym-wallet): typing and error (#1548)

* post-rebase fixes

* Changed storage key for new delegations map in vesting contract

* fix(wallet): typing issues (#1562)

* fix(wallet): error UI feedback (#1565)

* clean(wallet): remove useless files (with flamethrowers 🔥) (#1567)

* Changed default_mixnode_cost_params to allow accepting f32 instead

* Revert "Changed default_mixnode_cost_params to allow accepting f32 instead"

This reverts commit fb62a0014f.

* Fixed APY calculation for 0 pledge value

* Don't send rewarding transactions for empty rewarded set

* Fixed mixnode rewarding in validator API

* fix(nym-wallet): profit margin (#1574)

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Removed todo!() from vesting contract migration since its going to be dealt with differently

* fix(nym-wallet): stake saturation and delegations (#1578)

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* fix(nym-wallet): get rid of delegation history

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): get rid of delegation history

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): fix clippy

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Updated vesting contract migration to update the mixnet contract address

* feat(wallet): add confirmation/warning modal for unbonding

* Post rebasing fixes

* Removed deprecation on GetMixnodeDetailsByIdentity

* Fixed nym-cli

* Removed needless borrow

* Updated colorMap and textMap

Co-authored-by: durch <durch@users.noreply.github.com>
Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
Co-authored-by: Pierre Dommerc <dommerc.pierre@gmail.com>
2022-09-20 14:44:57 +01:00
Gala 16ccbd9e48 CR: use a parameter instead of cardcoded value 2022-09-20 13:30:09 +02:00
Gala bd0ea45f35 Merge branch 'develop' into 327-nym-connect-colours 2022-09-20 13:17:15 +02:00
Gala 4648967e93 fix build and refactor from CR 2022-09-20 12:43:51 +02:00
Gala 824e980647 Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-20 11:49:33 +02:00
Jon Häggblad ecbf5296a5 validator-api: guard against funny numbers in apy calc (#1636) 2022-09-19 14:37:09 +02:00
Jon Häggblad f3454409f8 Upgrade nym-connect to tauri 1.1.1 (#1635) 2022-09-19 11:34:33 +02:00
Gala 7d2e90b69f Merge pull request #1625 from nymtech/407-ne-filters-granularity
NE: 407 filters granularity
2022-09-19 11:28:26 +02:00
Gala bb2732bcc6 Merge pull request #1619 from nymtech/413-ne-content
NE: Display raw bond instead of self %
2022-09-19 11:16:16 +02:00
Gala d196993993 fix build 2022-09-19 11:08:16 +02:00
Gala b4fe8af890 refactor from CR 2022-09-19 11:04:26 +02:00
Gala 1c8ab2395d Merge branch 'develop' into 409-ne-filters-info 2022-09-19 10:24:18 +02:00
Jon Häggblad 065fe812ae Rename crate to nym-wallet-recovery-cli (#1633)
* Rename to nym-wallet-recovery-cli

* Rename crate name too

* Rename workspace member too

* Cargo.lock
2022-09-19 10:22:37 +02:00
Gala 02e75ea5cd display different content when user doesn't have delegations 2022-09-16 12:24:32 +02:00
Jon Häggblad 2aa18fb77c nym-wallet: add log window (#1618)
* Add a second entry point to the webpack config for the logging window

* tauri operations to show a log window

* LogViewer react component

* Upgrade tauri and use default tauri app menu for MacOS and add `Help` menu with `Show log` entry to show the logging window

* wip

* Proof of concept

* Fix format inside debug with ferm

* Put new menubar and log behind env variable flag

* Remove unused deps

* rustfmt

* Add changelog note

* Fix up imports

* Remove old code

* Improve log viewer

* Remove old code

* Add color to output, even if tauri hides it

* Remove redundant level from tauri log msg

* Since menu bar visible by default, change feature flag name

* Fix up webpack config so correct chunks get injected into entry points and remove inline CSS causing CSP issue

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
2022-09-16 09:38:52 +01:00
Jon Häggblad 8a0e7fb9d6 validator-api: add missing clap argument (#1629) 2022-09-15 22:31:31 +02:00
Dave Hrycyszyn 13c2ca4a78 Noted a correct npm publish command 2022-09-15 16:28:56 +01:00
Dave Hrycyszyn 6af84535fa Formatting 2022-09-15 16:27:44 +01:00
Mark Sinclair c8699cbe8d wasm-client: fix up dependencies and feature flags so that wasm-pack build works (#1585)
* wasm-client: fix up dependencies and feature flags so that wasm-pack build works

* Update CHANGELOG
2022-09-15 16:11:49 +01:00
Drazen Urch 974163da97 Reduce iterations when delegator last_claimed_height is 0 (#1609)
* Reduce iterations when delegator last_claimed_height is 0

* Fix typo
2022-09-15 17:10:35 +02:00
Raphaël Walther 7290e479db Notification test 2022-09-15 17:01:49 +02:00
Raphaël Walther c7b728318c Notification test 2022-09-15 16:57:25 +02:00
Dave Hrycyszyn 5c6d31bcb5 README fix 2022-09-15 15:48:58 +01:00
Dave Hrycyszyn 3823292ba8 Bumping version number to 1.0.0 2022-09-15 15:48:42 +01:00
Bogdan-Ștefan Neacşu 3eeda4a421 Apply fix for socket_state too (#1627) 2022-09-15 17:35:41 +03:00
Gala ebb3f6eebb fixing the build 2022-09-15 16:06:14 +02:00
Gala 2c15d22e1e Merge branch 'develop' into 409-ne-filters-info 2022-09-15 15:55:58 +02:00
Gala f1dca2c9a8 styling the mobile view 2022-09-15 14:50:33 +02:00
Gala d039c25b55 create a custom label for stake sat 2022-09-15 14:27:52 +02:00
Drazen Urch 16ef1c547b Remove eth feature, and BBBC related code (#1612)
* Remove eth feature, and BBBC related code

* Burn some more, especially clients
2022-09-15 13:58:44 +02:00
Dave Hrycyszyn e804b014a8 Cargo lock excluding webassembly client 2022-09-15 12:25:36 +01:00
Dave Hrycyszyn e406a05694 Re-excluding clients/webassembly which breaks cargo build --all 2022-09-15 12:24:04 +01:00
Gala 057b3456a7 more granulatity on filters 2022-09-15 12:49:27 +02:00
Dave Hrycyszyn 5ac124e159 Adding the webassembly client to the main build
This gives type hinting in editors. I can no longer remember why we
didn't set it up like this a long time ago, but it builds and tests
fine. Feel free to revert if it causes any problems.
2022-09-15 11:33:16 +01:00
Jon Häggblad f29c6a0550 Merge pull request #1622 from nymtech/jon/fix/shutdown-in-gateway-client
gateway-client: fix shutdown
2022-09-14 17:00:48 +02:00
Jon Häggblad 242a6d13af gateway-client: fix shutdown 2022-09-14 16:24:05 +02:00
Gala dee2c50b50 ne toolbar changes 2022-09-14 16:04:39 +02:00
Gala a394c9b59a PR requested changes 2022-09-14 14:48:30 +02:00
Gala 17768bab0b Merge branch 'develop' into 317-forms-menus-titles-ui 2022-09-14 14:11:10 +02:00
Gala 7816b4c839 some refactor 2022-09-14 13:58:04 +02:00
Gala e7ed48e55e trying to fix the build 2022-09-14 13:35:22 +02:00
Gala 63855f6ca4 display pledge_amount instead of self on mix nodes page 2022-09-14 13:13:08 +02:00
Dave Hrycyszyn 2f53e40355 Switching to mainnet validator API 2022-09-14 11:49:06 +01:00
Dave Hrycyszyn bb9753cda6 Switching webassembly client to sync web assembly (async loading doens't work) 2022-09-14 11:49:06 +01:00
Dave Hrycyszyn 95d0afdeb6 Whitespace fix 2022-09-14 11:49:06 +01:00
mx fbe02fa7fb added new blockstream endpoint to example allowed.list (#1611)
* added new blockstream endpoint to example allowed.list

* updated changelog
2022-09-14 11:07:19 +01:00
dependabot[bot] 46e206e8f0 build(deps): bump terser from 4.8.0 to 4.8.1 (#1468)
Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 11:07:05 +01:00
dependabot[bot] 55bdcecffb Bump parse-url from 6.0.0 to 6.0.5 (#1497)
Bumps [parse-url](https://github.com/IonicaBizau/parse-url) from 6.0.0 to 6.0.5.
- [Release notes](https://github.com/IonicaBizau/parse-url/releases)
- [Commits](https://github.com/IonicaBizau/parse-url/compare/6.0.0...6.0.5)

---
updated-dependencies:
- dependency-name: parse-url
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 11:05:39 +01:00
dependabot[bot] 2ee4b8fec6 Bump @openzeppelin/contracts in /contracts/basic-bandwidth-generation (#1545)
Bumps [@openzeppelin/contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) from 4.5.0 to 4.7.3.
- [Release notes](https://github.com/OpenZeppelin/openzeppelin-contracts/releases)
- [Changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md)
- [Commits](https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.5.0...v4.7.3)

---
updated-dependencies:
- dependency-name: "@openzeppelin/contracts"
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 11:05:07 +01:00
dependabot[bot] 67900956f8 Bump terser in /clients/native/examples/js-examples/websocket (#1620)
Bumps [terser](https://github.com/terser/terser) from 5.12.1 to 5.15.0.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/compare/v5.12.1...v5.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 11:04:14 +01:00
dependabot[bot] 29166c1d6a Bump terser from 5.10.0 to 5.15.0 in /testnet-faucet (#1621)
Bumps [terser](https://github.com/terser/terser) from 5.10.0 to 5.15.0.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/compare/v5.10.0...v5.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-14 11:03:56 +01:00
Gala 27d566dd47 some styling 2022-09-14 11:46:06 +02:00
Gala bfa0144594 text change 2022-09-14 11:38:10 +02:00
Gala 8924f9642f wip 2022-09-14 11:35:22 +02:00
Dave Hrycyszyn db24170752 Fixing npm audit in js example code 2022-09-14 10:32:39 +01:00
Gala 58541defad Merge branch 'develop' into 309-figma-diff-mnemonic 2022-09-14 11:29:55 +02:00
Gala c513014913 taking advantage to deal with some other differences 2022-09-14 11:27:41 +02:00
Gala 5985ba5182 Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-14 10:08:17 +02:00
Jon Häggblad fc90d5a389 ci: more cargo clean for windows
Not ideal, we need runners with more disk space
2022-09-14 08:31:05 +02:00
Raphaël Walther af1a83fe83 Test for notification 2022-09-13 19:35:22 +02:00
Raphaël Walther e12b69e58f Test for notification 2022-09-13 19:28:46 +02:00
Raphaël Walther d38614b15c Test for notification 2022-09-13 19:22:09 +02:00
Raphaël Walther 627d12239e Added keybase team variable (#1537)
* Added keybase team variable

* Amended notification
2022-09-13 18:13:18 +01:00
Gala 1af1370f23 display raw bond instead of self % 2022-09-13 17:00:37 +02:00
Gala ade15d3c60 change mnemonic textfield 2022-09-13 14:53:04 +02:00
Gala 1241a81514 changing alert behaviour 2022-09-13 13:49:42 +02:00
Gala 08a190c1cb Merge branch 'develop' into 348-bonding-settings 2022-09-13 12:44:11 +02:00
Mark Sinclair 124103d51b Mixnet and vesting contracts v1.0.2 (#1616) 2022-09-13 09:47:18 +01:00
Drazen Urch 6154b0c24c Fix claim -> undelegate (#1613)
* Fix claim -> undelegate

* Fix ordering

* Changelog
2022-09-13 09:38:23 +01:00
Pierre Dommerc b43657f42d feat(wallet): add tauri ops to sign and verify (#1606)
* Fix compile error

* Expose signing wallet for signing client

* Add stub tauri operation to sign a message with the current account

* feat(wallet): add a request to verify a signature

* feat(wallet): add support to verify from account address

* feat(wallet): add support to verify from account address

* fix(wallet): verify tauri request signature

* wallet-recovery-cli: upgrade clap to 3.2

* Fix compile error

* Expose signing wallet for signing client

* Add stub tauri operation to sign a message with the current account

* feat(wallet): add a request to verify a signature

* feat(wallet): add support to verify from account address

* feat(wallet): add support to verify from account address

* fix(wallet): verify tauri request signature

* Fix deserializtion of U128

* feat(wallet): avoid unwrap

* refactor(wallet):suggested feedbacks

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
Co-authored-by: durch <durch@users.noreply.github.com>
2022-09-12 12:32:10 +02:00
Jon Häggblad 20624243c0 ci: another ugly fix for windows low disk space
Crazy. I hope we can get runners with more disk space soon
2022-09-12 10:38:48 +02:00
Mark Sinclair fdff4bf1b7 Nym Wallet v1.0.9 (#1605)
* network-defaults: update mainnet default nymd_url

* update tests

* Bump nym-wallet version to 1.0.9 and update CHANGELOG

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2022-09-09 14:47:17 +01:00
Jon Häggblad 47726d3561 Merge pull request #1591 from nymtech/jon/feat/socks5-client-graceful-shutdown
socks5-client: graceful shutdown
2022-09-09 15:45:19 +02:00
Jon Häggblad f3ed0bb11f gateway-client: disable shutdown signalling for wasm 2022-09-09 15:00:13 +02:00
Jon Häggblad 351adb7f7b socks5: add missing SHUTDOWN_HAS_BEEN_SIGNALLED.store 2022-09-09 13:02:53 +02:00
Jon Häggblad e0567dddf2 gateway-client: additional shutdown handling 2022-09-09 12:10:41 +02:00
Jon Häggblad d109c53370 socks5: tasks with closed channels should all exit 2022-09-09 12:10:41 +02:00
Jon Häggblad baed6c89fc socks5: assert that tasks are not exiting when not shutdown 2022-09-09 12:10:32 +02:00
Jon Häggblad 106491ef01 more clippy 2022-09-09 12:09:59 +02:00
Jon Häggblad 46135146ea clippy 2022-09-09 12:09:59 +02:00
Jon Häggblad 04e5cfabb8 changelog: add note 2022-09-09 12:09:59 +02:00
Jon Häggblad 10be112279 socks5-client: graceful shutdown 2022-09-09 12:09:59 +02:00
Raphaël Walther 634818a988 Added notification workflow 2022-09-09 11:41:44 +02:00
Raphaël Walther 5cb80f7648 Revert "Added audit workflow"
This reverts commit a7afd2a1c7.
2022-09-09 11:36:49 +02:00
Bogdan-Ștefan Neacşu 4d5565d8b6 Remove migration code (#1604)
* Remove migration code

* Leave blacklist functionality, just in case
2022-09-08 18:41:56 +03:00
Gala 81f36e8da7 some refactor 2022-09-08 13:36:44 +02:00
Gala f230229ce9 change save button label 2022-09-08 12:18:09 +02:00
Gala 912fb4ab38 Merge branch 'develop' into 348-bonding-settings 2022-09-08 12:05:34 +02:00
Gala 28cc772d7b various ui updates 2022-09-08 11:43:59 +02:00
Jon Häggblad f172a23ef8 clients: remove cluster of unwrap and expects for gateway handling (#1599)
* clients: remove cluster of unwraps and expects for gateway handling

* rustfmt
2022-09-07 15:35:26 +02:00
Gala 1ab6bce821 Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-07 11:27:57 +02:00
Drazen Urch 2363f3ad0a Initial docs pass (#1582) 2022-09-07 01:41:51 +02:00
Jędrzej Stuczyński 6d3e5f22d4 Removed mix_denom from vesting MigrateMsg 2022-09-06 16:45:32 +01:00
Jędrzej Stuczyński c74ea43b94 Removed migration artifacts from mixnet and vesting contracts (#1598)
* Removed migration artifacts from mixnet and vesting contracts

* Workaround for CI build

* unused imports
2022-09-06 16:20:16 +01:00
Gala 70624e9062 modals and dialogs border in dark mode 2022-09-06 16:40:50 +02:00
Gala 2407285121 fixing paddings on modals 2022-09-06 14:33:41 +02:00
Drazen Urch 49d8424e30 Update network-requester dependencies (#1588) 2022-09-06 14:24:17 +02:00
Drazen Urch 5a7f296328 Update validator-api dependencies (#1589) 2022-09-06 14:15:22 +02:00
Gala db3171ea09 ModalListItem fix font size 2022-09-06 13:58:54 +02:00
Gala 2aaaa0deb7 globally no colon at the end of ModalListItem 2022-09-06 13:55:20 +02:00
Gala d410277d14 change all placeholders on textfields into labels 2022-09-06 13:41:35 +02:00
Gala 99ceabb0b0 using the wallet Tab component 2022-09-06 13:18:43 +02:00
Fouad 8a6f8185db Update/update modal background color (#1594)
* update receive bg color + all other modals
2022-09-06 09:56:12 +01:00
Bogdan-Ștefan Neacşu 30dfa09e18 Simplify the migration to trust the owner more (#1593) 2022-09-05 17:31:06 +03:00
Mark Sinclair 7e7072258d Add nym-cli tool (#1577)
Co-authored-by: tommy <tommyvez@protonmail.com>
2022-09-05 12:06:35 +01:00
Bogdan-Ștefan Neacşu 10221a1767 Migrate heights to timestamps (#1584)
* Migrate heights to timestamps

* Improve test with a possible scenario

* Use account id instead of address, as returned by the query
2022-09-05 12:25:57 +03:00
Gala 25df7bcd4d Merge branch 'develop' into 348-bonding-settings 2022-09-02 09:39:21 +02:00
Jędrzej Stuczyński a2c6abd3dd Made nodes_to_remove field in mixnet MigrateMsg public 2022-09-01 17:10:17 +01:00
Gala 1cdca7bec3 unbond modal verification step 2022-09-01 16:57:48 +02:00
Gala c809c7733d logic refactor 2022-09-01 15:06:23 +02:00
Gala 7b53003edb wip verification modal 2022-08-31 18:49:47 +02:00
Gala 831d9d2bf8 update alert text 2022-08-31 18:20:40 +02:00
Gala cb7c51ba12 remove node settings modal trigger 2022-08-31 17:37:00 +02:00
Gala 0310f0a8a9 some refactor 2022-08-31 17:23:53 +02:00
Gala bb79d08f6d dynamic values and remove hard coded code 2022-08-31 16:58:53 +02:00
Bogdan-Ștefan Neacşu d289c46e87 Feature/nr err report (#1576)
* common/socks5: Use thiserror and add copyright notice

* Send allowlist failure msg back to socks5 client

* Add some serde unit tests

* Fix clippy after rustup update

* Update changelog
2022-08-31 16:27:02 +03:00
Gala 414c86b500 fix button width 2022-08-31 12:13:40 +02:00
Gala 4304ffcf3c adding notification span 2022-08-31 11:44:23 +02:00
Gala 309b23e18a adding confirmation modals 2022-08-31 10:55:13 +02:00
Gala 52703583f0 adding validation to parameters settings 2022-08-31 10:10:37 +02:00
Gala 6473ef13c6 validate info fields 2022-08-30 18:51:39 +02:00
Gala 6de7d060e3 keep same margin on between pages 2022-08-30 17:00:48 +02:00
Gala 9a45f15ba4 wip 2022-08-30 16:49:07 +02:00
Gala 66b5eb13b0 fixing nymcard title size and margin in delegations page 2022-08-30 14:32:15 +02:00
Bogdan-Ștefan Neacşu a6aba3defd Feature/validator api shutdown (#1573)
* Rewarded set updater shutdown (partial) handling

* Shutdown handling in monitor

* Remove shutdown from packet receiver

* Configurable shutdown timeout

* Select on test_run too

* Remove unnecessary await/async

* Add bias to shutdown select and concurrency for big tasks

* Put cpu-bound packet prep on separate thread, to avoid blocking

* Use a better fit timeout value

* Fix clippy warnings

* Update changelog

* Fix wasm client
2022-08-30 14:14:50 +03:00
Jędrzej Stuczyński 6557be3738 Delegator compoudning to bypass pending events (#1571)
* Delegator compoudning to bypass pending events

* Updated changelog
2022-08-30 11:56:13 +01:00
Gala 4b37d4f050 Merge branch 'develop' into 317-forms-menus-titles-ui 2022-08-30 12:50:11 +02:00
Gala 746795b7ce mook bonded node 2022-08-30 12:49:50 +02:00
Gala 8b81247044 Merge branch 'develop' into 348-bonding-settings 2022-08-30 11:08:19 +02:00
Bogdan-Ștefan Neacşu 7134755073 Feature/credential mode (#1570)
* Activate enabled cred mode for coconut feature

* Fix disable cred mode propagation bug
2022-08-26 16:45:32 +03:00
Sachin Kamath dd1420a65a Wallet - set gateway default port to 9000 (#1568)
* fix(wallet): client port to gateway defaults

* fix(wallet): refactor variable name
2022-08-26 13:28:58 +02:00
Jędrzej Stuczyński df1bc60464 Feature/vesting delegations queries (#1569)
* Added contract queries for vesting delegations

* Added the queries on NymdClient

* Added account_id to DelegationTimesResponse

* Returning raw u64 as opposed to wrapped Timestamp

* Updated changelog
2022-08-26 11:24:16 +01:00
Bogdan-Ștefan Neacşu 865e809342 Remove hard-coded values from credential client (#1566) 2022-08-25 17:52:43 +03:00
Jon Häggblad 51f9c1ca29 Connect: remove some sources of panics when initializing socks5-client (#1563)
* connect: remove panic when unable to load previous gateway conf

* connect: dont panic when cant get config file
2022-08-25 16:25:31 +02:00
Jon Häggblad 303b014a59 types: fix missing entries in SelectionChance (#1564) 2022-08-25 14:06:54 +02:00
Jon Häggblad e1e20fb13e inclusion-prob: make rng generic and predictable in tests (#1561) 2022-08-25 08:47:04 +02:00
Mark Sinclair 0c3c13ae88 Change nym-connect window title to NymConnect 2022-08-24 15:48:26 +01:00
Jon Häggblad 8c8b7d71d0 validator-api: create node status cache with selection probabilies (#1547)
* validator-api: create node status cache with selection probabilies

Create a node status cache to complement the contract cache. Initially
we store the simulated active set selection probabilities.

* validator-api: add validator cache watch channel

* changelog: add note

* validator-api: clippy fixes

* validator-api: fix clippy

* validator-api: additional fields to inclusion probabilities response

* selection chance: revert back to 3 buckets

* selection chance: revert buckets again

* rustfmt

* validator-api: remove the old get_mixnode_inclusion_probability

* node-status-cache: return error when refreshing

* inclusion-simulator: cap on wall clock time

* node status cache: tidy
2022-08-24 14:29:21 +02:00
Jon Häggblad 3163c5f054 gateway-client: clippy::question-mark 2022-08-24 09:51:47 +02:00
Jon Häggblad 4a1794b2f1 nymcoconut: fix named-arguments-used-positionally 2022-08-24 09:51:47 +02:00
Jon Häggblad 1898b8ed96 validator-api: add bounds checks in compute_mixnode_reward_estimation (#1559) 2022-08-24 09:30:12 +02:00
Mark Sinclair a23471859d GitHub Actions: fix up artifacts on nym-cli-publish 2022-08-23 20:19:57 +01:00
Mark Sinclair 9d8c9edf22 Add GitHub Action to build nym-cli on all platforms 2022-08-23 19:27:03 +01:00
Pierre Dommerc 5ea7b24efc fix(nym-wallet): bonding, enhance error handling (#1543)
open a dialog for user feedback when error occurs
add some better error logging
2022-08-23 17:53:57 +02:00
Jędrzej Stuczyński a43a24faa8 Exposed direct queries to smart contract on NymdClient (#1558)
* Exposed direct queries to smart contract on NymdClient

* Updated changelog
2022-08-23 15:45:43 +01:00
Jędrzej Stuczyński 39ee215005 Storing delegations using block timestamp as opposed to block height (#1544)
* Storing delegations using block timestamp as opposed to block height

* Updated changelog
2022-08-23 09:42:34 +01:00
Mark Sinclair ef7961f58e Update nym-release-publish.yml 2022-08-19 18:32:28 +01:00
Mark Sinclair e628338b33 GitHub Actions: add manual dispatch and artifact upload 2022-08-19 18:23:16 +01:00
Gala c6cd787950 adding unbonding modal 2022-08-19 18:06:04 +02:00
Pierre Dommerc 1bb137f87f Nym-connect - service provider persistant storage (#1540)
* feat(nym-connect): local storage service provider

* feat(nym-connect): local storage service provider

* feat(nym-connect): local storage service provider

* Add some extra height to the window to stop the scrollbar apearing

* Show the service description when selecting a Service Provider

* Bump version

* Update changelog

* fix(nym-connect): hotfix

* fix(nym-connect): hotfix

* fix(nym-connect): wrong disabled state for connection button

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
2022-08-18 18:53:03 +02:00
Gala f9ab20b10f more styling in node
settings page
2022-08-18 17:27:28 +02:00
Gala acffd496ed nav styles 2022-08-18 17:07:59 +02:00
Gala 466ac1a1e0 settings general page 2022-08-18 16:39:05 +02:00
Dave Hrycyszyn 773f9e5ead Moving helpful comment into docs comment 2022-08-18 10:45:42 +01:00
Pierre Dommerc 79f695f138 Wallet: new UI for pending delegation (#1499)
* refactor(wallet-delegation): new delegation pending ui

* refactor(wallet-delegation): remove pending delegation table

* refactor(wallet-delegation): new pending delegation ui

* refactor(wallet-delegation): remove logs

* refactor(wallet-delegation): better typing

* refactor(wallet-balance): feedback review
2022-08-18 11:20:32 +02:00
Gala d53adcd17e nodesettings page and logic to browse 2022-08-17 18:55:58 +02:00
Pierre Dommerc 3aabbcf876 fix(nym-wallet): make get_operator_rewards request success optional (#1542) 2022-08-17 17:21:36 +02:00
Jędrzej Stuczyński d96b7408db Allowing optional fee for top-level NymdClient (#1541)
* Allowing optional fee for top-level NymdClient

* Updated changelog

* Fixed usages of said methods in validator-api
2022-08-17 13:27:53 +01:00
Gala 36e82e831f Merge branch 'develop' into 348-bonding-settings 2022-08-17 13:55:06 +02:00
Gala cbe0115f01 wip 2022-08-17 11:10:10 +02:00
Jon Häggblad 728b0f4549 inclusion-probability: new crate for simulation active set inclusion (#1534) 2022-08-16 14:19:55 +02:00
Gala 1dae3c3fc2 remove vAxis label 2022-08-16 14:08:00 +02:00
Gala 574e5cf10a change legend 2022-08-16 13:42:48 +02:00
Gala b3fcbb6726 remove log 2022-08-16 12:28:09 +02:00
Gala f96a60b6a2 log in staging 2022-08-16 12:10:17 +02:00
tommy 0a37c81709 remove not needed prior state of denom 2022-08-16 12:05:38 +02:00
Jędrzej Stuczyński 957cbb45b0 Chore/linter updates (#1530)
* Removed needless return

* Added Eq derivation where applicable for types with PartialEq
2022-08-12 17:56:49 +01:00
Pierre Dommerc 8ec074cb1f fix(wallet): typo (#1527) 2022-08-12 16:02:27 +02:00
Jess ab5740087f Update CHANGELOG.md 2022-08-12 14:55:39 +01:00
Jess 6af59c303e Update CHANGELOG.md 2022-08-12 14:54:09 +01:00
Jess 27b384e034 Update CHANGELOG.md 2022-08-12 14:53:40 +01:00
Dave Hrycyszyn 7f5ce3ffeb Removing wallet entries from main changelog 2022-08-12 12:58:49 +01:00
Dave Hrycyszyn 89b6667c75 Splitting changelogs into their own files 2022-08-12 12:50:17 +01:00
Tommy Verrall a94a9aeaf5 Update README.md 2022-08-11 16:18:06 +01:00
tommy 6bc8b88a20 WIP - validator-api-tests
- Test base line
- Jest / Typescript / Node
2022-08-11 17:14:04 +02:00
Gala 37d501f16d fix delegate more focus state 2022-08-11 16:20:05 +02:00
Gala 1e76169178 fixing spacing in modals 2022-08-11 14:57:37 +02:00
Gala 7406eeff14 ui top bar 2022-08-11 14:11:57 +02:00
Gala bdabe31fc9 side navigation 2022-08-11 14:03:02 +02:00
Fouad 21f3991714 update sample env (#1523) 2022-08-11 12:21:19 +01:00
Tommy Verrall cd8eba988a Merge pull request #1522 from nymtech/release/wallet-v1.0.8
Release/wallet v1.0.8
2022-08-11 11:45:53 +01:00
Tommy Verrall d2b3841bbd Update Cargo.toml 2022-08-11 11:45:28 +01:00
Tommy Verrall de877fb337 Merge pull request #1521 from nymtech/release/nym-binaries-1.0.2
Release/nym binaries 1.0.2
2022-08-11 11:42:36 +01:00
Tommy Verrall d4c2b9060f Update changelog for v1.0.2 -reference 2022-08-11 10:02:34 +01:00
Bogdan-Ștefan Neacşu 41ac866729 Feature/validator api panics (#1520)
* Coconut unwraps

* Fail softly on epoch queries

* Update changelog

* Retry a few times before failing as before
2022-08-11 11:58:17 +03:00
Raphaël Walther a7afd2a1c7 Added audit workflow 2022-08-11 10:41:57 +02:00
Jon Häggblad df03daf2cc socks5: remove pub mod not needed anymore (#1517)
* socks5: remove pub mod not needed anymore

* all: dedup parse_validators and move to common

* rustfmt
2022-08-10 21:14:12 +02:00
Gala f7b979825b Merge branch 'develop' into 340-ne-content 2022-08-10 16:44:02 +02:00
Raphaël Walther b3f5a4f496 Added audit workflow 2022-08-10 16:35:49 +02:00
Raphaël Walther d080d661f7 Added audit workflow 2022-08-10 16:29:54 +02:00
Raphaël Walther 6deb481e5d Added audit workflow 2022-08-10 16:09:05 +02:00
Raphaël Walther 5b98e18a4e Added audit workflow 2022-08-10 16:03:11 +02:00
Raphaël Walther 506a0da89c Added audit workflow 2022-08-10 16:00:50 +02:00
Bogdan-Ștefan Neacșu c7fdcf0a79 Change for default mainnet and fix typo 2022-08-10 13:40:28 +03:00
Gala 6ac1259f7a changing content 2022-08-10 11:24:02 +02:00
Fouad f7d38a7ec6 modal style updates (#1508)
remove commented code

remove unused props

fix props
2022-08-10 10:14:48 +01:00
tommy 8edc762df9 Update
- update the cargo toml
- amend the work flow file
- and the envs to mainnet (disclaimer add qa after)
2022-08-10 11:10:38 +02:00
Pierre Dommerc 4459aca933 fix(wallet-delegation): add error feedback on wrong address (#1510)
* fix(wallet-delegation): add error feedback on wrong address

* fix(wallet-delegation): use generic component for modal error
2022-08-10 09:31:14 +01:00
Pierre Dommerc 5b84c58985 feat(wallet-balance): hide vesting ui when vesting balance is empty (#1505)
* feat(wallet-balance): hide vesting ui when vesting balance is empty

* refactor(wallet-balance): feedback review
2022-08-10 09:29:01 +01:00
Fouad 4301d91f6c Feature/wallet style updates (#1506)
* update buttons hover colours

* fix nymcard title size

* style updates for accounts!

* fix delegations page lock-up on network switch
2022-08-10 09:28:03 +01:00
Fouad 03d28c115e delegations page style updates (#1507) 2022-08-10 09:27:03 +01:00
tommy 7b15f350cd add service-provider vesioning to match other binaries 2022-08-10 10:14:07 +02:00
tommy 2b4917b8b1 fix messaging on init for nym-client 2022-08-10 10:11:00 +02:00
Jędrzej Stuczyński de78ca8d9b Removed unnecessary to-owned conversion 2022-08-09 15:25:45 +01:00
Gala 58d09e382a Merge pull request #1504 from nymtech/smooth-filter-slider
Smooth filter slider
2022-08-09 14:59:11 +02:00
Gala 0cef12d05b Merge pull request #1502 from nymtech/1501-contrast-text
fixing text color
2022-08-09 14:58:07 +02:00
Jon Häggblad 30e73ee795 Explorer: tweak selection probability categories (#1503)
* validator-api: tweak SelectionChance

* wallet: update SelectionChance colorMap

* changelog: add note
2022-08-09 12:57:47 +02:00
Tommy Verrall d918b69664 Update .env.qa
Fix wrong denoms for the qa.env
2022-08-09 11:53:11 +01:00
tommy 921e558660 Update versions on binaries 2022-08-09 11:56:54 +02:00
Gala b3b8d2ab46 adding the value to the cursor 2022-08-08 17:03:05 +02:00
tommy d62638b8e2 update wallet version 2022-08-08 16:53:00 +02:00
Gala 67130a1289 adding smooth prop 2022-08-08 16:30:10 +02:00
Fouad 0dabff72bd Feature/bonding refactor (#1481)
* feat(wallet-bonding): bonding page, new bond form wip

* feat(wallet-bonding): add node table component

feat(wallet-bonding): new dialog component

* feat(wallet-bonding): node settings flow

* feat(wallet-bonding): bond more flow (done)

* feat(wallet): use confirmation modal component

* feat(wallet-bonding): node menu ui

* refactor(wallet-bonding): bonding flow with new gasFee estimation

* feat(wallet-bonding): unbond with gasFee and request

* refactor(wallet-bonding): switch to simpledialog component to keep modals consistency

* feat(wallet-bonding): fetch mixnode status

* update coin types in new bonding page

* fix displayed denom

* rebuild BondedNodeCard using existing shared components

* create reuseable ActionMenu component

* new mixnode form

* add gateway bond form

* check balance and fetch fee on bond mixnode request

* node settings

* get node description

* fix up rust request

* lint fixes + used NodeTypeSelector component

* temporarily remove estimated operator reward

* update return on rust function

* dont display node name UI if name doesnt exist

* rebase develop

* fix uppercase address bug

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
Co-authored-by: pierre <dommerc.pierre@gmail.com>
2022-08-08 14:09:57 +01:00
Gala e8e2f195e6 fixing text color 2022-08-08 14:09:33 +02:00
Jędrzej Stuczyński fa354016e0 Removed useless into() conversion 2022-08-08 09:49:52 +01:00
Tommy Verrall 935ee765e9 Merge pull request #1498 from nymtech/feature/fix-envs
fix .env files for explorer.
2022-08-05 16:36:08 +01:00
tommy 4c8e59e6fc fix .env files for explorer. 2022-08-05 17:11:35 +02:00
Bogdan-Ștefan Neacşu 067f3e6f1a validator-api: handle SIGTERM (#1496)
* validator-api: handle SIGTERM

* Update CHANGELOG
2022-08-05 15:59:34 +03:00
Gala 6f09d46dce Merge pull request #1492 from nymtech/feature/delegating_ui_update
feat(wallet-delegation): new confirmation modal ui
2022-08-05 12:08:51 +02:00
Gala bdef48331b Merge pull request #1489 from nymtech/332-fix-vesting-ui
Wallet: Fixing dark mode colours in vesting shedule
2022-08-05 12:05:19 +02:00
Gala 51a6936e51 Merge pull request #1491 from nymtech/330-mix-wallet-ui
Wallet: Fix light mode bg and nav bar UI
2022-08-05 12:02:50 +02:00
Gala fd456d2952 Merge pull request #1490 from nymtech/334-ne-changes
NE: Filter by use routing score instead of stake parameter and adding filters info
2022-08-04 18:24:04 +02:00
Gala eee1abe593 removing extra styles 2022-08-04 18:15:12 +02:00
Gala fffad43937 Avoiding CAPS in button 2022-08-04 17:51:24 +02:00
Gala 3a79f43a8d styling 2022-08-04 17:23:32 +02:00
pierre 2e495f87ab refactor(wallet-delegation): ModalListItem component, pass colon in label value 2022-08-04 16:35:14 +02:00
Gala 57a9f18f5a fixing padding marging and wording 2022-08-04 16:30:33 +02:00
Gala 0c6a0a9cae Merge branch 'develop' into 334-ne-changes 2022-08-04 15:18:54 +02:00
Gala c80d8d354a adding nodes number info 2022-08-04 15:17:44 +02:00
Gala 3f544dbc69 adding Advanced filtering text and hover fix 2022-08-04 14:59:17 +02:00
pierre d1e1f15db0 Merge branch 'develop' into feature/delegating_ui_update 2022-08-04 13:26:41 +02:00
pierre 651c314182 feat(wallet-delegation): new confirmation modal ui 2022-08-04 13:20:00 +02:00
Gala a57545521d some refactor 2022-08-04 11:28:04 +02:00
Gala da60606921 hover bg color in dark and light mode 2022-08-03 17:20:46 +02:00
Gala 14f9bf7234 left nav spacing 2022-08-03 16:50:02 +02:00
Gala c1fa92869a fix light bg color 2022-08-03 16:49:47 +02:00
Gala c8533e3ec8 cleaning 2022-08-03 14:15:03 +02:00
Gala 06c4dd601d filter by use routing score instead of stake parameter 2022-08-03 14:06:33 +02:00
Gala 4ff80bbab2 fixing dark mode progress bar 2022-08-03 14:02:50 +02:00
Gala d7220b1fec adding info text in filters and renaming 2022-08-03 13:42:48 +02:00
Gala d92df9ada3 fixing dark mode colours 2022-08-03 12:24:21 +02:00
1029 changed files with 68087 additions and 35161 deletions
+18
View File
@@ -3,3 +3,21 @@
RUST_LOG=info
RUST_BACKTRACE=1
#########################################
# geoipupdate (needed for explorer-api) #
#########################################
# MaxMind account ID (change it to a valid account ID)
GEOIPUPDATE_ACCOUNT_ID=xxx
# MaxMind license key (change it to a valid license key)
GEOIPUPDATE_LICENSE_KEY=xxx
# List of space-separated database edition IDs. Edition IDs may
# consist of letters, digits, and dashes. For example, GeoIP2-City
# would download the GeoIP2 City database (GeoIP2-City).
GEOIPUPDATE_EDITION_IDS=GeoLite2-Country
# The number of hours between geoipupdate runs. If this is not set
# or is set to 0, geoipupdate will run once and exit.
GEOIPUPDATE_FREQUENCY=72
# The path to the directory where geoipupdate will download the
# database.
GEOIP_DB_DIRECTORY=./explorer-api/geo_ip
+52
View File
@@ -0,0 +1,52 @@
name: Daily security audit
on:
schedule:
- cron: '5 9 * * *'
jobs:
cargo-deny:
runs-on: ubuntu-latest
steps:
- name: Checkout repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install cargo deny
run: cargo install --locked cargo-deny
- name: Run cargo deny
run: |
find . -name Cargo.toml -exec cargo deny --manifest-path {} check \
advisories -A advisory-not-detected --hide-inclusion-graph \; &> \
>(uniq &> .github/workflows/support-files/notifications/deny.message )
- uses: actions/upload-artifact@v3
with:
name: report
path: .github/workflows/support-files/notifications/deny.message
notification:
needs: cargo-deny
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v2
- name: Download report from previous job
uses: actions/download-artifact@v3
with:
name: report
path: .github/workflows/support-files/notifications
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
env:
NYM_NOTIFICATION_KIND: security
NYM_PROJECT_NAME: "Daily security report"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "security"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+10 -8
View File
@@ -16,7 +16,7 @@ jobs:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check out repository code
uses: actions/checkout@v2
@@ -29,6 +29,12 @@ jobs:
override: true
components: rustfmt, clippy
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
@@ -48,12 +54,6 @@ jobs:
command: test
args: --workspace --all-features -- --ignored
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
@@ -66,6 +66,8 @@ jobs:
command: clippy
args: --workspace -- -D warnings
# COCONUT stuff
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
@@ -82,4 +84,4 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --features=coconut -- -D warnings
args: --all-targets --features=coconut -- -D warnings
-72
View File
@@ -1,72 +0,0 @@
name: Continuous integration on dispatch
on: workflow_dispatch
jobs:
build:
runs-on: [ self-hosted, custom-linux ]
# Enable sccache via environment variable
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace -- -D warnings
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
with:
command: clippy
args: --features=coconut -- -D warnings
+18 -5
View File
@@ -1,16 +1,21 @@
name: Build release of Nym smart contracts
on:
workflow_dispatch:
defaults:
run:
working-directory: contracts
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check the release tag starts with `nym-contracts-`
if: startsWith(github.ref, 'refs/tags/nym-contracts-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-contracts-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
@@ -21,7 +26,7 @@ jobs:
components: rustfmt, clippy
- name: Build release contracts
run: RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
run: make wasm
- name: Upload Mixnet Contract Artifact
uses: actions/upload-artifact@v3
@@ -36,3 +41,11 @@ jobs:
name: vesting_contract.wasm
path: contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
retention-days: 5
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm
@@ -0,0 +1,56 @@
name: CI for Network Explorer API
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check the release tag starts with `nym-explorer-api-`
if: startsWith(github.ref, 'refs/tags/nym-explorer-api-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-explorer-api-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build all explorer-api
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path explorer-api/Cargo.toml --workspace --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact
path: |
target/release/explorer-api
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/explorer-api
+13 -1
View File
@@ -24,7 +24,7 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code
@@ -44,6 +44,12 @@ jobs:
command: build
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
@@ -101,6 +107,12 @@ jobs:
command: build
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
@@ -1,50 +0,0 @@
[
{
"os":"ubuntu-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"stable",
"runOnEvent":"workflow_dispatch"
},
{
"os":"ubuntu-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"beta",
"runOnEvent":"workflow_dispatch"
},
{
"os":"ubuntu-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
},
{
"os":"windows-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
},
{
"os":"macos-latest",
"rust":"nightly",
"runOnEvent":"workflow_dispatch"
}
]
@@ -1,6 +1,8 @@
name: Nightly builds on dispatch
name: Nightly builds on latest release
on: workflow_dispatch
on:
schedule:
- cron: '14 2 * * *'
jobs:
matrix_prep:
runs-on: ubuntu-latest
@@ -8,25 +10,40 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_matrix_on_dispatch.json'
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
build:
get_release:
runs-on: ubuntu-latest
needs: matrix_prep
outputs:
output1: ${{ steps.step2.outputs.latest_release }}
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Fetch all branches
run: git fetch --all
- name: Set output variable to latest release branch
id: step2
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+' | tail -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
build:
needs: [get_release,matrix_prep]
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code
uses: actions/checkout@v2
- name: Check out latest release branch
uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
@@ -42,6 +59,12 @@ jobs:
command: build
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
@@ -99,6 +122,12 @@ jobs:
command: build
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
@@ -145,13 +174,13 @@ jobs:
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: build
needs: [build,get_release]
runs-on: ubuntu-latest
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Keybase - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
@@ -160,14 +189,14 @@ jobs:
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build"
NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
GIT_BRANCH: "https://github.com/nymtech/nym/tree/${{needs.get_release.outputs.output1}}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly"
KEYBASE_NYM_CHANNEL: "ci-nightly-release"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
@@ -0,0 +1,203 @@
name: Nightly builds on second latest release
on:
schedule:
- cron: '24 2 * * *'
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v3
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
get_release:
runs-on: ubuntu-latest
needs: matrix_prep
outputs:
output1: ${{ steps.step2.outputs.latest_release }}
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Fetch all branches
run: git fetch --all
- name: Set output variable to latest release branch
id: step2
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+' | tail -n 2 | head -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
build:
needs: [get_release,matrix_prep]
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out latest release branch
uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run expensive tests
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features -- --ignored
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Reclaim some disk space
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-latest' }}
with:
command: clean
# COCONUT stuff
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets --features=coconut -- -D warnings
# nym-wallet (the rust part)
- name: Build nym-wallet rust code
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run nym-wallet tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check nym-wallet formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- name: Run clippy for nym-wallet
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: [build,get_release]
runs-on: ubuntu-latest
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code
uses: actions/checkout@v3
- name: Keybase - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "https://github.com/nymtech/nym/tree/${{needs.get_release.outputs.output1}}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly-release"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+50
View File
@@ -0,0 +1,50 @@
name: Publish Nym CLI binaries
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym-cli:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Check the release tag starts with `nym-cli-`
if: startsWith(github.ref, 'refs/tags/nym-cli-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-cli-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build binary
run: make build-nym-cli
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: nym-cli-${{ matrix.platform }}
path: |
target/release/nym-cli*
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/nym-cli
+16 -16
View File
@@ -41,19 +41,19 @@ jobs:
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
# - name: Keybase - Send Notification
# env:
# NYM_NOTIFICATION_KIND: nym-connect
# NYM_PROJECT_NAME: "nym-connect"
# NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
# NYM_CI_WWW_LOCATION: "nym-connect-${{ env.GITHUB_REF_SLUG }}"
# GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
# GIT_BRANCH: "${GITHUB_REF##*/}"
# KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
# KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
# KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
# KEYBASE_NYM_CHANNEL: "ci-nym-connect"
# IS_SUCCESS: "${{ job.status == 'success' }}"
# uses: docker://keybaseio/client:stable-node
# with:
# args: .github/workflows/support-files/notifications/entry_point.sh
- name: Keybase - Send Notification
env:
NYM_NOTIFICATION_KIND: nym-connect
NYM_PROJECT_NAME: "nym-connect"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "nym-connect-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nym-connect"
IS_SUCCESS: "${{ job.status == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+35 -1
View File
@@ -1,5 +1,13 @@
name: Publish Nym binaries
on:
workflow_dispatch:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
release:
types: [created]
@@ -17,12 +25,20 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check the release tag starts with `nym-binaries-`
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-binaries-...')
- name: Sets env vars for tokio if set in manual dispatch inputs
run: |
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
@@ -35,8 +51,24 @@ jobs:
command: build
args: --workspace --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact
path: |
target/release/nym-client
target/release/nym-gateway
target/release/nym-mixnode
target/release/nym-socks5-client
target/release/nym-validator-api
target/release/nym-network-requester
target/release/nym-network-statistics
target/release/nym-cli
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/nym-client
@@ -45,3 +77,5 @@ jobs:
target/release/nym-socks5-client
target/release/nym-validator-api
target/release/nym-network-requester
target/release/nym-network-statistics
target/release/nym-cli
@@ -3,7 +3,7 @@ require('dotenv').config();
const Bot = require('keybase-bot');
let context = {
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect'],
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect','security'],
};
/**
@@ -89,7 +89,7 @@ async function sendKeybaseMessage(messageBody) {
});
const channel = {
name: 'nymtech_bot',
name: context.env.KEYBASE_NYMBOT_TEAM || 'nymtech_bot',
membersType: 'team',
topicName: context.keybase.channel,
topic_type: 'CHAT',
@@ -0,0 +1,24 @@
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
const { Octokit, App } = require('octokit');
async function addToContextAndValidate(context) {
return
}
async function getMessageBody(context) {
try {
const source = fs
.readFileSync("deny.message").toString();
return source;
} catch (error) {
console.error(error);
}
}
module.exports = {
addToContextAndValidate,
getMessageBody,
};
+108 -254
View File
@@ -2,8 +2,104 @@
Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## [Unreleased]
### Added
- socks5-client/network-requester: add support for socks4a protocol
## [v1.1.1](https://github.com/nymtech/nym/tree/v1.1.1) (2022-11-29)
### Added
- binaries: add `-c` shortform for `--config-env-file`
- websocket-requests: add server response signalling current packet queue length in the client
- contracts: DKG contract that handles coconut key generation ([#1678][#1708][#1747])
- validator-api: generate coconut keys interactively, using DKG and multisig contracts ([#1678][#1708][#1747])
### Changed
- clients: add concept of transmission lanes to better handle multiple data streams ([#1720])
- clients,validator-api: take coconut signers from the chain instead of specifying them via CLI ([#1747])
- multisig contract: add DKG contract to the list of addresses that can create proposals ([#1747])
- socks5-client: wait closing inbound connection until data is sent, and throttle incoming data in general ([#1783])
- nym-cli: improve error reporting/handling and changed `vesting-schedule` queries to use query client instead of signing client
### Fixed
- gateway-client: fix decrypting stored messages on reconnect ([#1786])
### Fixed
- gateway-client: fix decrypting stored messages on reconnect ([#1786])
- socks5-client: fix shutting down all tasks if anyone of them panics or errors out ([#1805])
[#1678]: https://github.com/nymtech/nym/pull/1678
[#1708]: https://github.com/nymtech/nym/pull/1708
[#1720]: https://github.com/nymtech/nym/pull/1720
[#1747]: https://github.com/nymtech/nym/pull/1747
[#1783]: https://github.com/nymtech/nym/pull/1783
[#1786]: https://github.com/nymtech/nym/pull/1786
[#1805]: https://github.com/nymtech/nym/pull/1805
## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09)
### Added
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
- common/ledger: new library for communicating with a Ledger device ([#1640])
- native-client/socks5-client/wasm-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
- native-client/socks5-client/wasm-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
- native-client/socks5-client/wasm-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611])
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to compute reward estimation endpoint
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
- wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673])
### Fixed
- socks5-client: fix bug where in some cases packet reordering could trigger a connection being closed too early ([#1702],[#1724])
- validator-api: mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
- validator-api: should now correctly update historical uptimes for all mixnodes and gateways every 24h ([#1721])
### Changed
- clients: bound the sphinx packet channel and reduce sending rate if gateway can't keep up ([#1703],[#1725])
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
- moved `Percent` struct to `contracts-common`, change affects explorer-api
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
- validator-api: changes to internal SQL schema due to the mixnet contract revamp ([#1472])
- validator-api: changes to internal data structures due to the mixnet contract revamp ([#1472])
- validator-api: split epoch-operations into multiple separate transactions ([#1472])
[#1472]: https://github.com/nymtech/nym/pull/1472
[#1541]: https://github.com/nymtech/nym/pull/1541
[#1558]: https://github.com/nymtech/nym/pull/1558
[#1577]: https://github.com/nymtech/nym/pull/1577
[#1585]: https://github.com/nymtech/nym/pull/1585
[#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640
[#1645]: https://github.com/nymtech/nym/pull/1645
[#1611]: https://github.com/nymtech/nym/pull/1611
[#1664]: https://github.com/nymtech/nym/pull/1664
[#1666]: https://github.com/nymtech/nym/pull/1645
[#1669]: https://github.com/nymtech/nym/pull/1669
[#1671]: https://github.com/nymtech/nym/pull/1671
[#1673]: https://github.com/nymtech/nym/pull/1673
[#1681]: https://github.com/nymtech/nym/pull/1681
[#1702]: https://github.com/nymtech/nym/pull/1702
[#1703]: https://github.com/nymtech/nym/pull/1703
[#1721]: https://github.com/nymtech/nym/pull/1721
[#1724]: https://github.com/nymtech/nym/pull/1724
[#1725]: https://github.com/nymtech/nym/pull/1725
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
### Added
@@ -19,12 +115,14 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- validator-api: add Swagger to document the REST API ([#1249]).
- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261])
- validator-api: add `uptime`, `estimated_operator_apy`, `estimated_delegators_apy` to `/mixnodes/detailed` endpoint ([#1393])
- validator-api: add node info cache storing simulated active set inclusion probabilities
- network-statistics: a new mixnet service that aggregates and exposes anonymized data about mixnet services ([#1328])
- mixnode: Added basic mixnode hardware reporting to the HTTP API ([#1308]).
- validator-api: endpoint, in coconut mode, for returning the validator-api cosmos address ([#1404]).
- validator-client: add `denom` argument and add simple test for querying an account balance
- gateway, validator-api: Checks for coconut credential double spending attempts, taking the coconut bandwidth contract as source of truth ([#1457])
- coconut-bandwidth-contract: Record the state of a coconut credential; create specific proposal for releasing funds ([#1457])
- inclusion-probability: add simulator for active set inclusion probability
### Fixed
@@ -37,6 +135,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- validator: fixed local docker-compose setup to work on Apple M1 ([#1329])
- explorer-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service ([#1482]).
- network-requester: fix filter for suffix-only domains ([#1487])
- validator-api: listen out for SIGTERM and SIGQUIT too, making it play nicely as a system service; cleaner shutdown, without panics ([#1496], [#1573]).
### Changed
@@ -51,6 +150,9 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- multisig-contract: Limit the proposal creating functionality to one address (coconut-bandwidth-contract address) ([#1457])
- All binaries and cosmwasm blobs are configured at runtime now; binaries are configured using environment variables or .env files and contracts keep the configuration parameters in storage ([#1463])
- gateway, network-statistics: include gateway id in the sent statistical data ([#1478])
- network explorer: tweak how active set probability is shown ([#1503])
- validator-api: rewarder set update fails without panicking on possible nymd queries ([#1520])
- network-requester, socks5 client (nym-connect): send and receive respectively a message error to be displayed about filter check failure ([#1576])
[#1249]: https://github.com/nymtech/nym/pull/1249
@@ -77,90 +179,11 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1478]: https://github.com/nymtech/nym/pull/1478
[#1482]: https://github.com/nymtech/nym/pull/1482
[#1487]: https://github.com/nymtech/nym/pull/1487
## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22)
### Added
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
- nym-connect: add ability to select network requester and gateway ([#1427])
- nym-connect: add ability to export gateway keys as JSON
- nym-connect: add auto updater
### Changed
- nym-connect: reuse config id instead of creating a new id on each connection
[#1427]: https://github.com/nymtech/nym/pull/1427
## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11)
- wallet: dark mode
- wallet: when simulating gas costs, an automatic adjustment is being used ([#1388]).
[#1388]: https://github.com/nymtech/nym/pull/1388
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
### Added
- mixnet-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- mixnet-contract: Replace all naked `-` with `saturating_sub`.
- mixnet-contract: Added staking_supply field to ContractStateParams.
- mixnet-contract: Added a query to get MixnodeBond by identity key ([#1369]).
- mixnet-contract: Added a query to get GatewayBond by identity key ([#1369]).
- vesting-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- vesting-contract: Added limit to the amount of tokens one can pledge ([#1331])
### Fixed
- mixnet-contract: `estimated_delegator_reward` calculation ([#1284])
- mixnet-contract: delegator and operator rewards use lambda and sigma instead of lambda_ticked and sigma_ticked ([#1284])
- mixnet-contract: removed `expect` in `query_delegator_reward` and queries containing invalid proxy address should now return a more human-readable error ([#1257])
- mixnet-contract: replaced integer division with fixed for performance calculations ([#1284])
- mixnet-contract: Under certain circumstances nodes could not be unbonded ([#1255](https://github.com/nymtech/nym/issues/1255)) ([#1258])
- mixnet-contract: Using correct staking supply when distributing rewards. ([#1373])
- vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275])
[#1255]: https://github.com/nymtech/nym/pull/1255
[#1257]: https://github.com/nymtech/nym/pull/1257
[#1258]: https://github.com/nymtech/nym/pull/1258
[#1275]: https://github.com/nymtech/nym/pull/1275
[#1284]: https://github.com/nymtech/nym/pull/1284
[#1292]: https://github.com/nymtech/nym/pull/1292
[#1331]: https://github.com/nymtech/nym/pull/1331
[#1369]: https://github.com/nymtech/nym/pull/1369
[#1373]: https://github.com/nymtech/nym/pull/1373
## [nym-wallet-v1.0.6](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.6) (2022-06-21)
- wallet: undelegating now uses either the mixnet or vesting contract, or both, depending on how delegations were made
- wallet: redeeming and compounding now uses both the mixnet and vesting contract
- wallet: the wallet backend learned how to archive wallet files
- wallet: add ENABLE_QA_MODE environment variable to enable QA mode on built wallet
## [nym-wallet-v1.0.5](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.5) (2022-06-14)
- wallet: add simple CLI tool for decrypting and recovering the wallet file.
- wallet: added support for multiple accounts ([#1265])
- wallet: compound and claim reward endpoints for operators and delegators ([#1302])
- wallet: require password to switch accounts
- wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint.
- wallet: new delegation and rewards UI
- wallet: show version in nav bar
- wallet: contract admin route put back
- wallet: staking_supply field to StateParams
- wallet: show transaction hash for redeeming or compounding rewards
[#1265]: https://github.com/nymtech/nym/pull/1265
[#1302]: https://github.com/nymtech/nym/pull/1302
## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04)
### Changed
- all: the default behaviour of validator client is changed to use `broadcast_sync` and poll for transaction inclusion instead of using `broadcast_commit` to deal with timeouts ([#1246])
[#1496]: https://github.com/nymtech/nym/pull/1496
[#1503]: https://github.com/nymtech/nym/pull/1503
[#1520]: https://github.com/nymtech/nym/pull/1520
[#1573]: https://github.com/nymtech/nym/pull/1573
[#1576]: https://github.com/nymtech/nym/pull/1576
## [v1.0.1](https://github.com/nymtech/nym/tree/v1.0.1) (2022-05-04)
@@ -193,77 +216,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.3...nym-binaries-1.0.0)
## [nym-wallet-v1.0.3](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.3) (2022-04-25)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-binaries-1.0.0-rc.2...nym-wallet-v1.0.3)
**Fixed bugs:**
- \[Issue\] Wallet 1.0.2 cannot send NYM tokens from a DelayedVestingAccount [\#1215](https://github.com/nymtech/nym/issues/1215)
- Main README not showing properly with GitHub dark mode [\#1211](https://github.com/nymtech/nym/issues/1211)
**Merged pull requests:**
- Bugfix - wallet undelegation for vesting accounts [\#1220](https://github.com/nymtech/nym/pull/1220) ([mmsinclair](https://github.com/mmsinclair))
- Bugfix/delegation reconcile [\#1219](https://github.com/nymtech/nym/pull/1219) ([jstuczyn](https://github.com/jstuczyn))
- Bugfix/query proxied pending delegations [\#1218](https://github.com/nymtech/nym/pull/1218) ([jstuczyn](https://github.com/jstuczyn))
- Using custom gas multiplier in the wallet [\#1217](https://github.com/nymtech/nym/pull/1217) ([jstuczyn](https://github.com/jstuczyn))
- Feature/vesting accounts support [\#1216](https://github.com/nymtech/nym/pull/1216) ([jstuczyn](https://github.com/jstuczyn))
- Release/1.0.0 rc.2 [\#1214](https://github.com/nymtech/nym/pull/1214) ([jstuczyn](https://github.com/jstuczyn))
- chore: fix dark mode rendering [\#1212](https://github.com/nymtech/nym/pull/1212) ([pwnfoo](https://github.com/pwnfoo))
- Feature/spend coconut [\#1210](https://github.com/nymtech/nym/pull/1210) ([neacsu](https://github.com/neacsu))
- Bugfix/unique sphinx key [\#1207](https://github.com/nymtech/nym/pull/1207) ([jstuczyn](https://github.com/jstuczyn))
- Add cache read and write timeouts [\#1206](https://github.com/nymtech/nym/pull/1206) ([durch](https://github.com/durch))
- Additional, more informative routes [\#1204](https://github.com/nymtech/nym/pull/1204) ([durch](https://github.com/durch))
- Feature/aggregated econ dynamics explorer endpoint [\#1203](https://github.com/nymtech/nym/pull/1203) ([jstuczyn](https://github.com/jstuczyn))
- Debugging validator [\#1198](https://github.com/nymtech/nym/pull/1198) ([durch](https://github.com/durch))
- wallet: expose additional validator configuration functionality to the frontend [\#1195](https://github.com/nymtech/nym/pull/1195) ([octol](https://github.com/octol))
- Update rewarding validator address [\#1193](https://github.com/nymtech/nym/pull/1193) ([durch](https://github.com/durch))
- Crypto part of the Groth's NIDKG [\#1182](https://github.com/nymtech/nym/pull/1182) ([jstuczyn](https://github.com/jstuczyn))
- fix unbond page [\#1180](https://github.com/nymtech/nym/pull/1180) ([tommyv1987](https://github.com/tommyv1987))
- Type safe bounds [\#1179](https://github.com/nymtech/nym/pull/1179) ([durch](https://github.com/durch))
- Fix delegation paging [\#1174](https://github.com/nymtech/nym/pull/1174) ([durch](https://github.com/durch))
- Update binaries to rc version [\#1172](https://github.com/nymtech/nym/pull/1172) ([tommyv1987](https://github.com/tommyv1987))
- Bump ansi-regex from 4.1.0 to 4.1.1 in /docker/typescript\_client/upload\_contract [\#1171](https://github.com/nymtech/nym/pull/1171) ([dependabot[bot]](https://github.com/apps/dependabot))
## [nym-binaries-1.0.0-rc.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.0-rc.2) (2022-04-15)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.2...nym-binaries-1.0.0-rc.2)
## [nym-wallet-v1.0.2](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.2) (2022-04-05)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.1...nym-wallet-v1.0.2)
**Merged pull requests:**
- Wallet 1.0.2 visual tweaks [\#1197](https://github.com/nymtech/nym/pull/1197) ([mmsinclair](https://github.com/mmsinclair))
- Password for wallet with routes [\#1196](https://github.com/nymtech/nym/pull/1196) ([fmtabbara](https://github.com/fmtabbara))
- Add auto-updater to Nym Wallet [\#1194](https://github.com/nymtech/nym/pull/1194) ([mmsinclair](https://github.com/mmsinclair))
- Fix clippy warnings for beta toolchain [\#1191](https://github.com/nymtech/nym/pull/1191) ([octol](https://github.com/octol))
- wallet: expose validator urls to the frontend [\#1190](https://github.com/nymtech/nym/pull/1190) ([octol](https://github.com/octol))
- wallet: add test for decrypting stored wallet file [\#1189](https://github.com/nymtech/nym/pull/1189) ([octol](https://github.com/octol))
- Fix clippy warnings [\#1188](https://github.com/nymtech/nym/pull/1188) ([octol](https://github.com/octol))
- Password for wallet with routes [\#1187](https://github.com/nymtech/nym/pull/1187) ([mmsinclair](https://github.com/mmsinclair))
- wallet: add validate\_mnemonic [\#1186](https://github.com/nymtech/nym/pull/1186) ([octol](https://github.com/octol))
- wallet: support removing accounts from the wallet file [\#1185](https://github.com/nymtech/nym/pull/1185) ([octol](https://github.com/octol))
- Feature/adding discord [\#1184](https://github.com/nymtech/nym/pull/1184) ([gala1234](https://github.com/gala1234))
- wallet: config backend for validator selection [\#1183](https://github.com/nymtech/nym/pull/1183) ([octol](https://github.com/octol))
- Add storybook to wallet [\#1178](https://github.com/nymtech/nym/pull/1178) ([mmsinclair](https://github.com/mmsinclair))
- wallet: connection test nymd and api urls independently [\#1170](https://github.com/nymtech/nym/pull/1170) ([octol](https://github.com/octol))
- wallet: wire up account storage [\#1153](https://github.com/nymtech/nym/pull/1153) ([octol](https://github.com/octol))
- Feature/signature on deposit [\#1151](https://github.com/nymtech/nym/pull/1151) ([neacsu](https://github.com/neacsu))
## [nym-wallet-v1.0.1](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.1) (2022-04-05)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-binaries-1.0.0-rc.1...nym-wallet-v1.0.1)
**Closed issues:**
- Check enabling bbbc simultaneously with open access. Estimate what it would take to make this the default compilation target. [\#1175](https://github.com/nymtech/nym/issues/1175)
- Get coconut credential for deposited tokens [\#1138](https://github.com/nymtech/nym/issues/1138)
- Make payments lazy [\#1135](https://github.com/nymtech/nym/issues/1135)
- Uptime on node selection for sets [\#1049](https://github.com/nymtech/nym/issues/1049)
## [nym-binaries-1.0.0-rc.1](https://github.com/nymtech/nym/tree/nym-binaries-1.0.0-rc.1) (2022-03-28)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.0...nym-binaries-1.0.0-rc.1)
@@ -342,108 +298,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- feature/pedersen-commitments [\#1048](https://github.com/nymtech/nym/pull/1048) ([danielementary](https://github.com/danielementary))
- Feature/reuse init owner [\#970](https://github.com/nymtech/nym/pull/970) ([neacsu](https://github.com/neacsu))
## [nym-wallet-v1.0.0](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.0) (2022-02-03)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.1...nym-wallet-v1.0.0)
**Implemented enhancements:**
- \[Feature Request\] Please enable registration without need for Telegram account [\#1016](https://github.com/nymtech/nym/issues/1016)
- Fast mixnode launch with a pre-built ISO + VM software [\#1001](https://github.com/nymtech/nym/issues/1001)
**Fixed bugs:**
- \[Issue\] [\#1000](https://github.com/nymtech/nym/issues/1000)
- \[Issue\] `nym-client` requires multiple attempts to run a server [\#869](https://github.com/nymtech/nym/issues/869)
- De-'float'-ing `Interval` \(`Display` impl + `serde`\) [\#1065](https://github.com/nymtech/nym/pull/1065) ([jstuczyn](https://github.com/jstuczyn))
- display client address on wallet creation [\#1058](https://github.com/nymtech/nym/pull/1058) ([fmtabbara](https://github.com/fmtabbara))
**Closed issues:**
- Rewarded set inclusion probability API endpoint [\#1037](https://github.com/nymtech/nym/issues/1037)
- Update cw-storage-plus to 0.11 [\#1032](https://github.com/nymtech/nym/issues/1032)
- Change `u128` fields in `RewardEstimationResponse` to `u64` [\#1029](https://github.com/nymtech/nym/issues/1029)
- Test out the mainnet Gravity Bridge [\#1006](https://github.com/nymtech/nym/issues/1006)
- Add vesting contract interface to nym-wallet [\#959](https://github.com/nymtech/nym/issues/959)
- Mixnode crash [\#486](https://github.com/nymtech/nym/issues/486)
**Merged pull requests:**
- create custom urls for mainnet [\#1095](https://github.com/nymtech/nym/pull/1095) ([fmtabbara](https://github.com/fmtabbara))
- Wallet signing on MacOS [\#1093](https://github.com/nymtech/nym/pull/1093) ([mmsinclair](https://github.com/mmsinclair))
- Fix rust 2018 idioms warnings [\#1092](https://github.com/nymtech/nym/pull/1092) ([octol](https://github.com/octol))
- Prevent contract overwriting [\#1090](https://github.com/nymtech/nym/pull/1090) ([durch](https://github.com/durch))
- Logout operation [\#1087](https://github.com/nymtech/nym/pull/1087) ([jstuczyn](https://github.com/jstuczyn))
- Update to rust edition 2021 everywhere [\#1086](https://github.com/nymtech/nym/pull/1086) ([octol](https://github.com/octol))
- Tag contract errors, and print out lines for easier QA [\#1084](https://github.com/nymtech/nym/pull/1084) ([durch](https://github.com/durch))
- Feature/flexible vesting + utility queries [\#1083](https://github.com/nymtech/nym/pull/1083) ([durch](https://github.com/durch))
- Bump @openzeppelin/contracts from 4.3.1 to 4.4.2 in /contracts/basic-bandwidth-generation [\#1082](https://github.com/nymtech/nym/pull/1082) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump nth-check from 2.0.0 to 2.0.1 in /clients/native/examples/js-examples/websocket [\#1081](https://github.com/nymtech/nym/pull/1081) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump url-parse from 1.5.1 to 1.5.4 in /clients/native/examples/js-examples/websocket [\#1080](https://github.com/nymtech/nym/pull/1080) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump follow-redirects from 1.14.1 to 1.14.7 in /clients/native/examples/js-examples/websocket [\#1079](https://github.com/nymtech/nym/pull/1079) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump nanoid from 3.1.23 to 3.2.0 in /clients/native/examples/js-examples/websocket [\#1078](https://github.com/nymtech/nym/pull/1078) ([dependabot[bot]](https://github.com/apps/dependabot))
- Setup basic test for mixnode stats reporting [\#1077](https://github.com/nymtech/nym/pull/1077) ([octol](https://github.com/octol))
- Make wallet\_address mandatory for mixnode init [\#1076](https://github.com/nymtech/nym/pull/1076) ([octol](https://github.com/octol))
- Tidy nym-mixnode module visibility [\#1075](https://github.com/nymtech/nym/pull/1075) ([octol](https://github.com/octol))
- Feature/wallet login with password [\#1074](https://github.com/nymtech/nym/pull/1074) ([fmtabbara](https://github.com/fmtabbara))
- Add trait to mock client dependency in DelayForwarder [\#1073](https://github.com/nymtech/nym/pull/1073) ([octol](https://github.com/octol))
- Bump rust-version to latest stable for nym-mixnode [\#1072](https://github.com/nymtech/nym/pull/1072) ([octol](https://github.com/octol))
- Fixes CI for our wasm build [\#1069](https://github.com/nymtech/nym/pull/1069) ([jstuczyn](https://github.com/jstuczyn))
- Add @octol as codeowner [\#1068](https://github.com/nymtech/nym/pull/1068) ([octol](https://github.com/octol))
- set-up inclusion probability [\#1067](https://github.com/nymtech/nym/pull/1067) ([fmtabbara](https://github.com/fmtabbara))
- Feature/wasm client [\#1066](https://github.com/nymtech/nym/pull/1066) ([neacsu](https://github.com/neacsu))
- Changed bech32\_prefix from punk to nymt [\#1064](https://github.com/nymtech/nym/pull/1064) ([jstuczyn](https://github.com/jstuczyn))
- Bump nanoid from 3.1.30 to 3.2.0 in /testnet-faucet [\#1063](https://github.com/nymtech/nym/pull/1063) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump nanoid from 3.1.30 to 3.2.0 in /nym-wallet [\#1062](https://github.com/nymtech/nym/pull/1062) ([dependabot[bot]](https://github.com/apps/dependabot))
- Rework vesting contract storage [\#1061](https://github.com/nymtech/nym/pull/1061) ([durch](https://github.com/durch))
- Mixnet Contract constants extraction [\#1060](https://github.com/nymtech/nym/pull/1060) ([jstuczyn](https://github.com/jstuczyn))
- fix: make explorer footer year dynamic [\#1059](https://github.com/nymtech/nym/pull/1059) ([martinyung](https://github.com/martinyung))
- Add mnemonic just on creation, to display it [\#1057](https://github.com/nymtech/nym/pull/1057) ([neacsu](https://github.com/neacsu))
- Network Explorer: updates to API and UI to show the active set [\#1056](https://github.com/nymtech/nym/pull/1056) ([mmsinclair](https://github.com/mmsinclair))
- Made contract addresses for query NymdClient construction optional [\#1055](https://github.com/nymtech/nym/pull/1055) ([jstuczyn](https://github.com/jstuczyn))
- Introduced RPC query for total token supply [\#1053](https://github.com/nymtech/nym/pull/1053) ([jstuczyn](https://github.com/jstuczyn))
- Feature/tokio console [\#1052](https://github.com/nymtech/nym/pull/1052) ([durch](https://github.com/durch))
- Implemented beta clippy lint recommendations [\#1051](https://github.com/nymtech/nym/pull/1051) ([jstuczyn](https://github.com/jstuczyn))
- add new function to update profit percentage [\#1050](https://github.com/nymtech/nym/pull/1050) ([fmtabbara](https://github.com/fmtabbara))
- Upgrade Clap and use declarative argument parsing for nym-mixnode [\#1047](https://github.com/nymtech/nym/pull/1047) ([octol](https://github.com/octol))
- Feature/additional bond validation [\#1046](https://github.com/nymtech/nym/pull/1046) ([fmtabbara](https://github.com/fmtabbara))
- Fix clippy on relevant lints [\#1044](https://github.com/nymtech/nym/pull/1044) ([neacsu](https://github.com/neacsu))
- Bump shelljs from 0.8.4 to 0.8.5 in /contracts/basic-bandwidth-generation [\#1043](https://github.com/nymtech/nym/pull/1043) ([dependabot[bot]](https://github.com/apps/dependabot))
- Endpoint for rewarded set inclusion probabilities [\#1042](https://github.com/nymtech/nym/pull/1042) ([durch](https://github.com/durch))
- Bump follow-redirects from 1.14.4 to 1.14.7 in /contracts/basic-bandwidth-generation [\#1041](https://github.com/nymtech/nym/pull/1041) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump follow-redirects from 1.14.5 to 1.14.7 in /testnet-faucet [\#1040](https://github.com/nymtech/nym/pull/1040) ([dependabot[bot]](https://github.com/apps/dependabot))
- Feature/node settings update [\#1036](https://github.com/nymtech/nym/pull/1036) ([fmtabbara](https://github.com/fmtabbara))
- Migrate to cw-storage-plus 0.11.1 [\#1035](https://github.com/nymtech/nym/pull/1035) ([durch](https://github.com/durch))
- Bump @openzeppelin/contracts from 4.4.1 to 4.4.2 in /contracts/basic-bandwidth-generation [\#1034](https://github.com/nymtech/nym/pull/1034) ([dependabot[bot]](https://github.com/apps/dependabot))
- Feature/configurable wallet [\#1033](https://github.com/nymtech/nym/pull/1033) ([neacsu](https://github.com/neacsu))
- Feature/downcast reward estimation [\#1031](https://github.com/nymtech/nym/pull/1031) ([durch](https://github.com/durch))
- Wallet UI updates [\#1028](https://github.com/nymtech/nym/pull/1028) ([fmtabbara](https://github.com/fmtabbara))
- Remove migration code [\#1027](https://github.com/nymtech/nym/pull/1027) ([neacsu](https://github.com/neacsu))
- Chore/stricter dependency requirements [\#1025](https://github.com/nymtech/nym/pull/1025) ([jstuczyn](https://github.com/jstuczyn))
- Feature/validator api client endpoints [\#1024](https://github.com/nymtech/nym/pull/1024) ([jstuczyn](https://github.com/jstuczyn))
- Updated cosmrs to 0.4.1 [\#1023](https://github.com/nymtech/nym/pull/1023) ([jstuczyn](https://github.com/jstuczyn))
- Feature/testnet deploy scripts [\#1022](https://github.com/nymtech/nym/pull/1022) ([mfahampshire](https://github.com/mfahampshire))
- Changed wallet's client to a full validator client [\#1021](https://github.com/nymtech/nym/pull/1021) ([jstuczyn](https://github.com/jstuczyn))
- Fix 404 link [\#1020](https://github.com/nymtech/nym/pull/1020) ([RiccardoMasutti](https://github.com/RiccardoMasutti))
- Feature/additional mixnode endpoints [\#1019](https://github.com/nymtech/nym/pull/1019) ([jstuczyn](https://github.com/jstuczyn))
- Introduced denom check when trying to withdraw vested coins [\#1018](https://github.com/nymtech/nym/pull/1018) ([jstuczyn](https://github.com/jstuczyn))
- Add network defaults for qa [\#1017](https://github.com/nymtech/nym/pull/1017) ([neacsu](https://github.com/neacsu))
- Feature/expanded events [\#1015](https://github.com/nymtech/nym/pull/1015) ([jstuczyn](https://github.com/jstuczyn))
- update frontend to use new profit update api [\#1014](https://github.com/nymtech/nym/pull/1014) ([fmtabbara](https://github.com/fmtabbara))
- Feature/node state endpoint [\#1013](https://github.com/nymtech/nym/pull/1013) ([jstuczyn](https://github.com/jstuczyn))
- Feature/hourly set updates [\#1012](https://github.com/nymtech/nym/pull/1012) ([durch](https://github.com/durch))
- Feature/remove unused profit margin [\#1011](https://github.com/nymtech/nym/pull/1011) ([neacsu](https://github.com/neacsu))
- Feature/explorer node status [\#1010](https://github.com/nymtech/nym/pull/1010) ([jstuczyn](https://github.com/jstuczyn))
- Use serial integer instead of random [\#1009](https://github.com/nymtech/nym/pull/1009) ([durch](https://github.com/durch))
- Feature/configure profit [\#1008](https://github.com/nymtech/nym/pull/1008) ([neacsu](https://github.com/neacsu))
- Feature/fix gateway sign [\#1004](https://github.com/nymtech/nym/pull/1004) ([neacsu](https://github.com/neacsu))
- Fix clippy [\#1003](https://github.com/nymtech/nym/pull/1003) ([neacsu](https://github.com/neacsu))
- Update wallet version [\#998](https://github.com/nymtech/nym/pull/998) ([tommyv1987](https://github.com/tommyv1987))
- Fix wallet build instructions [\#997](https://github.com/nymtech/nym/pull/997) ([tommyv1987](https://github.com/tommyv1987))
- Make the separation between testnet-mode and erc20 bandwidth mode clearer [\#994](https://github.com/nymtech/nym/pull/994) ([neacsu](https://github.com/neacsu))
- Bump @openzeppelin/contracts from 3.4.0 to 4.4.1 in /contracts/basic-bandwidth-generation [\#983](https://github.com/nymtech/nym/pull/983) ([dependabot[bot]](https://github.com/apps/dependabot))
- Feature/implicit runtime [\#973](https://github.com/nymtech/nym/pull/973) ([jstuczyn](https://github.com/jstuczyn))
- Differentiate staking and ownership [\#961](https://github.com/nymtech/nym/pull/961) ([durch](https://github.com/durch))
## [v0.12.1](https://github.com/nymtech/nym/tree/v0.12.1) (2021-12-23)
Generated
+916 -661
View File
File diff suppressed because it is too large Load Diff
+19 -8
View File
@@ -22,22 +22,28 @@ members = [
"clients/native",
"clients/native/websocket-requests",
"clients/socks5",
"common/bandwidth-claim-contract",
"common/client-libs/gateway-client",
"common/client-libs/mixnet-client",
"common/client-libs/validator-client",
"common/credential-storage",
"common/client-connections",
"common/coconut-interface",
"common/commands",
"common/config",
"common/credentials",
"common/crypto",
"common/crypto/dkg",
"common/execute",
"common/bandwidth-claim-contract",
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/coconut-dkg",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/credential-storage",
"common/credentials",
"common/crypto",
"common/crypto/dkg",
"common/execute",
"common/inclusion-probability",
"common/ledger",
"common/logging",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
@@ -53,21 +59,25 @@ members = [
"common/nymsphinx/params",
"common/nymsphinx/types",
"common/pemstore",
"common/statistics",
"common/socks5/proxy-helpers",
"common/socks5/requests",
"common/statistics",
"common/task",
"common/topology",
"common/types",
"common/wasm-utils",
"common/completions",
"explorer-api",
"gateway",
"gateway/gateway-requests",
"integrations/bity",
"mixnode",
"sdk/rust/nym-sdk",
"service-providers/network-requester",
"service-providers/network-statistics",
"validator-api",
"validator-api/validator-api-requests",
"tools/nym-cli",
"tools/ts-rs-cli"
]
@@ -76,9 +86,10 @@ default-members = [
"clients/socks5",
"gateway",
"service-providers/network-requester",
"service-providers/network-statistics",
"mixnode",
"validator-api",
"explorer-api",
]
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly", "nym-wallet", "nym-connect"]
exclude = ["explorer", "contracts", "clients/webassembly", "nym-wallet", "nym-connect"]
+47 -9
View File
@@ -2,12 +2,12 @@ test: clippy-all cargo-test wasm fmt
test-all: test cargo-test-expensive
no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet clippy-all-connect
clippy-all: clippy-main clippy-coconut clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-wasm-client
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect
cargo-test: test-main test-contracts test-wallet test-connect
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive
build: build-contracts build-wallet build-main build-connect
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect
cargo-test: test-main test-contracts test-wallet test-connect test-coconut test-wasm-client
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive test-coconut-expensive
build: build-contracts build-wallet build-main build-connect build-wasm-client
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-wasm-client
clippy-happy-main:
cargo clippy
@@ -21,8 +21,15 @@ clippy-happy-wallet:
clippy-happy-connect:
cargo clippy --manifest-path nym-connect/Cargo.toml
clippy-all-main:
cargo clippy --workspace --all-features -- -D warnings
clippy-main:
cargo clippy --workspace -- -D warnings
clippy-coconut:
cargo clippy --workspace --features coconut -- -D warnings
clippy-wasm:
cargo clippy --workspace --features wasm -- -D warnings
clippy-all-contracts:
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
@@ -33,11 +40,24 @@ clippy-all-wallet:
clippy-all-connect:
cargo clippy --workspace --manifest-path nym-connect/Cargo.toml --all-features -- -D warnings
clippy-all-wasm-client:
cargo clippy --workspace --manifest-path clients/webassembly/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
test-main:
cargo test --all-features --workspace
cargo test --workspace
test-coconut:
cargo test --workspace --features coconut
test-wasm:
cargo test --workspace --features wasm
test-main-expensive:
cargo test --all-features --workspace -- --ignored
cargo test --workspace -- --ignored
test-coconut-expensive:
cargo test --workspace --features coconut -- --ignored
test-contracts:
cargo test --manifest-path contracts/Cargo.toml --all-features
@@ -51,6 +71,9 @@ test-wallet:
test-wallet-expensive:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features -- --ignored
test-wasm-client:
cargo test --workspace --manifest-path clients/webassembly/Cargo.toml --all-features
test-connect:
cargo test --manifest-path nym-connect/Cargo.toml --all-features
@@ -69,6 +92,15 @@ build-wallet:
build-connect:
cargo build --manifest-path nym-connect/Cargo.toml --workspace
build-explorer-api:
cargo build --manifest-path explorer-api/Cargo.toml --workspace
build-wasm-client:
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
build-nym-cli:
cargo build --release --manifest-path tools/nym-cli/Cargo.toml
fmt-main:
cargo fmt --all
@@ -81,9 +113,15 @@ fmt-wallet:
fmt-connect:
cargo fmt --manifest-path nym-connect/Cargo.toml --all
fmt-wasm-client:
cargo fmt --manifest-path clients/webassembly/Cargo.toml --all
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
mixnet-opt: wasm
cd contracts/mixnet && make opt
generate-typescript:
cd tools/ts-rs-cli && cargo run && cd ../..
yarn types:lint:fix
+34 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "1.0.1"
version = "1.1.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
@@ -13,23 +13,53 @@ humantime-serde = "1.0"
log = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
sled = "0.34"
tokio = { version = "1.19.1", features = ["macros"] }
sled = { version = "0.34", optional = true }
tap = "1.0.1"
thiserror = "1.0.34"
url = { version ="2.2", features = ["serde"] }
tokio = { version = "1.21.2", features = ["time", "macros"]}
# internal
config = { path = "../../common/config" }
client-connections = { path = "../../common/client-connections" }
crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
gateway-requests = { path = "../../gateway/gateway-requests" }
nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" }
nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client" }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
task = { path = "../../common/task" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
version = "0.1.9"
features = ["time"]
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
version = "0.4"
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2.83"
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-timer]
git = "https://github.com/mmsinclair/wasm-timer"
rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"
[target."cfg(target_arch = \"wasm32\")".dependencies.gloo-timers]
version = "0.2.4"
features = ["futures"]
#[target."cfg(not(target_arch = \"wasm32\"))".dependencies.task]
#path = "../../common/task"
[dev-dependencies]
tempfile = "3.1.0"
[features]
default = ["reply-surb"]
wasm = ["gateway-client/wasm"]
coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
reply-surb = ["sled"]
@@ -0,0 +1,441 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
use crate::client::key_manager::KeyManager;
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use crate::client::real_messages_control;
use crate::client::real_messages_control::RealMessagesController;
use crate::client::received_buffer::{
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
};
use crate::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use crate::config::{Config, DebugConfig, GatewayEndpointConfig};
use crate::error::ClientCoreError;
use client_connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use log::info;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
#[cfg(feature = "reply-surb")]
use std::path::PathBuf;
#[cfg(feature = "reply-surb")]
use tap::TapFallible;
use task::{ShutdownListener, ShutdownNotifier};
use url::Url;
// it's fine to do this disgusting compilation flag business here as this problem
// is going to go away in 1.2.0
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorage;
pub struct ClientInput {
pub shared_lane_queue_lengths: LaneQueueLengths,
pub connection_command_sender: ConnectionCommandSender,
pub input_sender: InputMessageSender,
}
pub struct ClientOutput {
pub received_buffer_request_sender: ReceivedBufferRequestSender,
}
pub enum ClientInputStatus {
AwaitingProducer { client_input: ClientInput },
Connected,
}
impl ClientInputStatus {
pub fn register_producer(&mut self) -> ClientInput {
match std::mem::replace(self, ClientInputStatus::Connected) {
ClientInputStatus::AwaitingProducer { client_input } => client_input,
ClientInputStatus::Connected => panic!("producer was already registered before"),
}
}
}
pub enum ClientOutputStatus {
AwaitingConsumer { client_output: ClientOutput },
Connected,
}
impl ClientOutputStatus {
pub fn register_consumer(&mut self) -> ClientOutput {
match std::mem::replace(self, ClientOutputStatus::Connected) {
ClientOutputStatus::AwaitingConsumer { client_output } => client_output,
ClientOutputStatus::Connected => panic!("consumer was already registered before"),
}
}
}
pub struct BaseClientBuilder<'a> {
// due to wasm limitations I had to split it like this : (
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
disabled_credentials: bool,
validator_api_endpoints: Vec<Url>,
#[cfg(feature = "reply-surb")]
reply_surb_keys_store_path: PathBuf,
bandwidth_controller: Option<BandwidthController>,
key_manager: KeyManager,
}
impl<'a> BaseClientBuilder<'a> {
pub fn new_from_base_config<T>(
base_config: &'a Config<T>,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController>,
) -> BaseClientBuilder<'a> {
BaseClientBuilder {
gateway_config: base_config.get_gateway_endpoint_config(),
debug_config: base_config.get_debug_config(),
disabled_credentials: base_config.get_disabled_credentials_mode(),
validator_api_endpoints: base_config.get_validator_api_endpoints(),
bandwidth_controller,
key_manager,
#[cfg(feature = "reply-surb")]
reply_surb_keys_store_path: base_config.get_reply_encryption_key_store_path(),
}
}
pub fn new(
gateway_config: &'a GatewayEndpointConfig,
debug_config: &'a DebugConfig,
key_manager: KeyManager,
bandwidth_controller: Option<BandwidthController>,
disabled_credentials: bool,
validator_api_endpoints: Vec<Url>,
#[cfg(feature = "reply-surb")] reply_surb_keys_store_path: PathBuf,
) -> BaseClientBuilder<'a> {
BaseClientBuilder {
gateway_config,
debug_config,
disabled_credentials,
validator_api_endpoints,
bandwidth_controller,
key_manager,
#[cfg(feature = "reply-surb")]
reply_surb_keys_store_path,
}
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(&self.gateway_config.gateway_id).unwrap(),
)
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
shutdown: ShutdownListener,
) {
info!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.debug_config.average_ack_delay,
self.debug_config.average_packet_delay,
self.debug_config.loop_cover_traffic_average_delay,
mix_tx,
self.as_mix_recipient(),
topology_accessor,
);
if let Some(size) = self.debug_config.use_extended_packet_size {
log::debug!("Setting extended packet size: {:?}", size);
stream.set_custom_packet_size(size.into());
}
stream.start_with_shutdown(shutdown);
}
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
shutdown: ShutdownListener,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) {
let mut controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(),
self.debug_config.ack_wait_multiplier,
self.debug_config.ack_wait_addition,
self.debug_config.average_ack_delay,
self.debug_config.message_sending_average_delay,
self.debug_config.average_packet_delay,
self.debug_config.disable_main_poisson_packet_distribution,
self.as_mix_recipient(),
);
if let Some(size) = self.debug_config.use_extended_packet_size {
log::debug!("Setting extended packet size: {:?}", size);
controller_config.set_custom_packet_size(size.into());
}
info!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
lane_queue_lengths,
client_connection_rx,
#[cfg(feature = "reply-surb")]
reply_key_storage,
)
.start_with_shutdown(shutdown);
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
shutdown: ShutdownListener,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) {
info!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
#[cfg(feature = "reply-surb")]
reply_key_storage,
)
.start_with_shutdown(shutdown)
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
shutdown: ShutdownListener,
) -> GatewayClient {
let gateway_id = self.gateway_config.gateway_id.clone();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_owner = self.gateway_config.gateway_owner.clone();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.gateway_config.gateway_listener.clone();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
// disgusting wasm workaround since there's no key persistence there (nor `client init`)
let shared_key = if self.key_manager.gateway_key_set() {
Some(self.key_manager.gateway_shared_key())
} else {
None
};
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
shared_key,
mixnet_message_sender,
ack_sender,
self.debug_config.gateway_response_timeout,
self.bandwidth_controller.take(),
Some(shutdown),
);
gateway_client.set_disabled_credentials_mode(self.disabled_credentials);
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) -> Result<(), ClientCoreError> {
let topology_refresher_config = TopologyRefresherConfig::new(
self.validator_api_endpoints.clone(),
self.debug_config.topology_refresh_rate,
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology);
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx 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,
shutdown: ShutdownListener,
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError> {
info!("Starting nym 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
// and would allow anyone to clone the sender channel
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
#[cfg(feature = "reply-surb")]
let reply_key_storage =
ReplyKeyStorage::load(&self.reply_surb_keys_store_path).tap_err(|err| {
log::error!("Failed to load reply key storage - is it perhaps already in use?");
log::error!("{:?}", err);
})?;
// Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default();
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await?;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
shutdown.subscribe(),
#[cfg(feature = "reply-surb")]
reply_key_storage.clone(),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
.await;
// The sphinx_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 =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic
// controller that connections are closed.
let (client_connection_tx, client_connection_rx) = mpsc::unbounded();
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections (e.g socks5 for attached network-requesters)
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
shared_lane_queue_lengths.clone(),
client_connection_rx,
shutdown.subscribe(),
#[cfg(feature = "reply-surb")]
reply_key_storage,
);
if !self.debug_config.disable_loop_cover_traffic_stream {
self.start_cover_traffic_stream(
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
}
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
Ok(BaseClient {
client_input: ClientInputStatus::AwaitingProducer {
client_input: ClientInput {
shared_lane_queue_lengths,
connection_command_sender: client_connection_tx,
input_sender,
},
},
client_output: ClientOutputStatus::AwaitingConsumer {
client_output: ClientOutput {
received_buffer_request_sender,
},
},
shutdown_notifier: shutdown,
})
}
}
pub struct BaseClient {
pub client_input: ClientInputStatus,
pub client_output: ClientOutputStatus,
pub shutdown_notifier: ShutdownNotifier,
}
@@ -3,19 +3,27 @@
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::topology_control::TopologyAccessor;
use crate::spawn_future;
use futures::task::{Context, Poll};
use futures::{Future, Stream, StreamExt};
use log::*;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::cover::generate_loop_cover_packet;
use nymsphinx::params::PacketSize;
use nymsphinx::utils::sample_poisson_duration;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::pin::Pin;
use std::sync::Arc;
use tokio::task::JoinHandle;
use std::time::Duration;
use tokio::sync::mpsc::error::TrySendError;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
pub struct LoopCoverTrafficStream<R>
where
R: CryptoRng + Rng,
@@ -24,18 +32,22 @@ where
ack_key: Arc<AckKey>,
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
average_ack_delay: time::Duration,
average_ack_delay: Duration,
/// Average delay a data packet is going to get delay at a single mixnode.
average_packet_delay: time::Duration,
average_packet_delay: Duration,
/// Average delay between sending subsequent cover packets.
average_cover_message_sending_delay: time::Duration,
average_cover_message_sending_delay: Duration,
/// Internal state, determined by `average_message_sending_delay`,
/// used to keep track of when a next packet should be sent out.
#[cfg(not(target_arch = "wasm32"))]
next_delay: Pin<Box<time::Sleep>>,
#[cfg(target_arch = "wasm32")]
next_delay: Pin<Box<wasm_timer::Delay>>,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: BatchMixMessageSender,
@@ -48,6 +60,9 @@ where
/// Accessor to the common instance of network topology.
topology_access: TopologyAccessor,
/// Predefined packet size used for the loop cover messages.
packet_size: PacketSize,
}
impl<R> Stream for LoopCoverTrafficStream<R>
@@ -69,13 +84,21 @@ where
// we know it's time to send a message, so let's prepare delay for the next one
// Get the `now` by looking at the current `delay` deadline
let avg_delay = self.average_cover_message_sending_delay;
let now = self.next_delay.deadline();
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
// The next interval value is `next_poisson_delay` after the one that just
// yielded.
let next = now + next_poisson_delay;
self.next_delay.as_mut().reset(next);
#[cfg(not(target_arch = "wasm32"))]
{
let now = self.next_delay.deadline();
let next = now + next_poisson_delay;
self.next_delay.as_mut().reset(next);
}
#[cfg(target_arch = "wasm32")]
{
self.next_delay.as_mut().reset(next_poisson_delay);
}
Poll::Ready(Some(()))
}
@@ -84,30 +107,52 @@ where
// obviously when we finally make shared rng that is on 'higher' level, this should become
// generic `R`
impl LoopCoverTrafficStream<OsRng> {
#[allow(clippy::too_many_arguments)]
pub fn new(
ack_key: Arc<AckKey>,
average_ack_delay: time::Duration,
average_packet_delay: time::Duration,
average_cover_message_sending_delay: time::Duration,
average_ack_delay: Duration,
average_packet_delay: Duration,
average_cover_message_sending_delay: Duration,
mix_tx: BatchMixMessageSender,
our_full_destination: Recipient,
topology_access: TopologyAccessor,
) -> Self {
let rng = OsRng;
#[cfg(not(target_arch = "wasm32"))]
let next_delay = Box::pin(time::sleep(Default::default()));
#[cfg(target_arch = "wasm32")]
let next_delay = Box::pin(wasm_timer::Delay::new(Default::default()));
LoopCoverTrafficStream {
ack_key,
average_ack_delay,
average_packet_delay,
average_cover_message_sending_delay,
next_delay: Box::pin(time::sleep(Default::default())),
next_delay,
mix_tx,
our_full_destination,
rng,
topology_access,
packet_size: Default::default(),
}
}
pub fn set_custom_packet_size(&mut self, packet_size: PacketSize) {
self.packet_size = packet_size;
}
fn set_next_delay(&mut self, amount: Duration) {
#[cfg(not(target_arch = "wasm32"))]
let next_delay = Box::pin(time::sleep(amount));
#[cfg(target_arch = "wasm32")]
let next_delay = Box::pin(wasm_timer::Delay::new(amount));
self.next_delay = next_delay;
}
async fn on_new_message(&mut self) {
trace!("next cover message!");
@@ -129,18 +174,30 @@ impl LoopCoverTrafficStream<OsRng> {
let cover_message = generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&*self.ack_key,
&self.ack_key,
&self.our_full_destination,
self.average_ack_delay,
self.average_packet_delay,
self.packet_size,
)
.expect("Somehow failed to generate a loop cover message with a valid topology");
// if this one fails, there's no retrying because it means that either:
// - we run out of memory
// - the receiver channel is closed
// in either case there's no recovery and we can only panic
self.mix_tx.unbounded_send(vec![cover_message]).unwrap();
if let Err(err) = self.mix_tx.try_send(vec![cover_message]) {
match err {
TrySendError::Full(_) => {
// This isn't a problem, if the channel is full means we're already sending the
// max amount of messages downstream can handle.
log::debug!("Failed to send cover message - channel full");
// However it's still useful to alert the user that the gateway or the link to
// the gateway can't keep up. Either due to insufficient bandwidth on the
// client side, or that the gateway is overloaded.
log::warn!("Failed to send: gateway appears to not keep up");
}
TrySendError::Closed(_) => {
log::warn!("Failed to send cover message - channel closed");
}
}
}
// TODO: I'm not entirely sure whether this is really required, because I'm not 100%
// sure how `yield_now()` works - whether it just notifies the scheduler or whether it
@@ -149,24 +206,54 @@ impl LoopCoverTrafficStream<OsRng> {
// JS: due to identical logical structure to OutQueueControl::on_message(), this is also
// presumably required to prevent bugs in the future. Exact reason is still unknown to me.
// TODO: temporary and BAD workaround for wasm (we should find a way to yield here in wasm)
#[cfg(not(target_arch = "wasm32"))]
tokio::task::yield_now().await;
}
async fn run(&mut self) {
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
// we should set initial delay only when we actually start the stream
self.next_delay = Box::pin(time::sleep(sample_poisson_duration(
&mut self.rng,
self.average_cover_message_sending_delay,
)));
let sampled =
sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay);
self.set_next_delay(sampled);
while self.next().await.is_some() {
self.on_new_message().await;
}
spawn_future(async move {
debug!("Started LoopCoverTrafficStream with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("LoopCoverTrafficStream: Received shutdown");
}
next = self.next() => {
if next.is_some() {
self.on_new_message().await;
} else {
log::trace!("LoopCoverTrafficStream: Stopping since channel closed");
break;
}
}
}
}
shutdown.recv_timeout().await;
log::debug!("LoopCoverTrafficStream: Exiting");
})
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
self.run().await;
pub fn start(mut self) {
// we should set initial delay only when we actually start the stream
let sampled =
sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay);
self.set_next_delay(sampled);
spawn_future(async move {
debug!("Started LoopCoverTrafficStream without graceful shutdown support");
while self.next().await.is_some() {
self.on_new_message().await;
}
})
}
}
@@ -1,9 +1,9 @@
use futures::channel::mpsc;
use client_connections::TransmissionLane;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::ReplySurb;
pub type InputMessageSender = mpsc::UnboundedSender<InputMessage>;
pub type InputMessageReceiver = mpsc::UnboundedReceiver<InputMessage>;
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
#[derive(Debug)]
pub enum InputMessage {
@@ -11,6 +11,7 @@ pub enum InputMessage {
recipient: Recipient,
data: Vec<u8>,
with_reply_surb: bool,
lane: TransmissionLane,
},
Reply {
reply_surb: ReplySurb,
@@ -19,11 +20,17 @@ pub enum InputMessage {
}
impl InputMessage {
pub fn new_fresh(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Self {
pub fn new_fresh(
recipient: Recipient,
data: Vec<u8>,
with_reply_surb: bool,
lane: TransmissionLane,
) -> Self {
InputMessage::Fresh {
recipient,
data,
with_reply_surb,
lane,
}
}
@@ -149,6 +149,10 @@ impl KeyManager {
)
}
pub fn gateway_key_set(&self) -> bool {
self.gateway_shared_key.is_some()
}
/// Gets an atomically reference counted pointer to [`AckKey`].
pub fn ack_key(&self) -> Arc<AckKey> {
Arc::clone(&self.ack_key)
+46 -21
View File
@@ -1,16 +1,16 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc;
use futures::StreamExt;
use crate::spawn_future;
use gateway_client::GatewayClient;
use log::*;
use nymsphinx::forwarding::packet::MixPacket;
use tokio::task::JoinHandle;
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
pub type BatchMixMessageReceiver = mpsc::UnboundedReceiver<Vec<MixPacket>>;
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
// We remind ourselves that 32 x 32kb = 1024kb, a reasonable size for a network buffer.
pub const MIX_MESSAGE_RECEIVER_BUFFER_SIZE: usize = 32;
const MAX_FAILURE_COUNT: usize = 100;
pub struct MixTrafficController {
@@ -25,15 +25,17 @@ pub struct MixTrafficController {
}
impl MixTrafficController {
pub fn new(
mix_rx: BatchMixMessageReceiver,
gateway_client: GatewayClient,
) -> MixTrafficController {
MixTrafficController {
gateway_client,
mix_rx,
consecutive_gateway_failure_count: 0,
}
pub fn new(gateway_client: GatewayClient) -> (MixTrafficController, BatchMixMessageSender) {
let (sphinx_message_sender, sphinx_message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
(
MixTrafficController {
gateway_client,
mix_rx: sphinx_message_receiver,
consecutive_gateway_failure_count: 0,
},
sphinx_message_sender,
)
}
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
@@ -65,15 +67,38 @@ impl MixTrafficController {
}
}
pub async fn run(&mut self) {
while let Some(mix_packets) = self.mix_rx.next().await {
self.on_messages(mix_packets).await;
}
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
spawn_future(async move {
debug!("Started MixTrafficController with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
mix_packets = self.mix_rx.recv() => match mix_packets {
Some(mix_packets) => {
self.on_messages(mix_packets).await;
},
None => {
log::trace!("MixTrafficController: Stopping since channel closed");
break;
}
},
_ = shutdown.recv() => {
log::trace!("MixTrafficController: Received shutdown");
}
}
}
shutdown.recv_timeout().await;
log::debug!("MixTrafficController: Exiting");
})
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
self.run().await;
pub fn start(mut self) {
spawn_future(async move {
debug!("Started MixTrafficController without graceful shutdown support");
while let Some(mix_packets) = self.mix_rx.recv().await {
self.on_messages(mix_packets).await;
}
})
}
}
+5
View File
@@ -1,8 +1,13 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod base_client;
pub mod cover_traffic_stream;
pub mod inbound_messages;
pub mod key_manager;
pub mod mix_traffic;
pub mod real_messages_control;
pub mod received_buffer;
#[cfg(feature = "reply-surb")]
pub mod reply_key_storage;
pub mod topology_control;
@@ -33,7 +33,7 @@ impl AcknowledgementListener {
}
async fn on_ack(&mut self, ack_content: Vec<u8>) {
debug!("Received an ack");
trace!("Received an ack");
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
.map(FragmentIdentifier::try_from_bytes)
{
@@ -63,14 +63,41 @@ impl AcknowledgementListener {
.unwrap();
}
pub(super) async fn run(&mut self) {
debug!("Started AcknowledgementListener");
while let Some(acks) = self.ack_receiver.next().await {
// realistically we would only be getting one ack at the time
for ack in acks {
self.on_ack(ack).await;
async fn handle_ack_receiver_item(&mut self, item: Vec<Vec<u8>>) {
// realistically we would only be getting one ack at the time
for ack in item {
self.on_ack(ack).await;
}
}
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started AcknowledgementListener with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
acks = self.ack_receiver.next() => match acks {
Some(acks) => self.handle_ack_receiver_item(acks).await,
None => {
log::trace!("AcknowledgementListener: Stopping since channel closed");
break;
}
},
_ = shutdown.recv() => {
log::trace!("AcknowledgementListener: Received shutdown");
}
}
}
error!("TODO: error msg. Or maybe panic?")
shutdown.recv_timeout().await;
log::debug!("AcknowledgementListener: Exiting");
}
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started AcknowledgementListener without graceful shutdown support");
while let Some(acks) = self.ack_receiver.next().await {
self.handle_ack_receiver_item(acks).await
}
}
}
@@ -245,13 +245,47 @@ impl ActionController {
}
}
pub(super) async fn run(&mut self) {
loop {
// at some point there will be a global shutdown signal here as the third option
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started ActionController with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
action = self.incoming_actions.next() => match action {
Some(action) => self.process_action(action),
None => {
log::trace!(
"ActionController: Stopping since incoming actions channel closed"
);
break;
}
},
expired_ack = self.pending_acks_timers.next() => match expired_ack {
Some(expired_ack) => self.handle_expired_ack_timer(expired_ack),
None => {
log::trace!("ActionController: Stopping since ack channel closed");
break;
}
},
_ = shutdown.recv() => {
log::trace!("ActionController: Received shutdown");
}
}
}
#[cfg(not(target_arch = "wasm32"))]
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("ActionController: Exiting");
}
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started ActionController without graceful shutdown support");
loop {
tokio::select! {
// we NEVER expect for ANY sender to get dropped so unwrap here is fine
action = self.incoming_actions.next() => self.process_action(action.unwrap()),
// pending ack queue Stream CANNOT return a `None` so unwrap here is fine
expired_ack = self.pending_acks_timers.next() => self.handle_expired_ack_timer(expired_ack.unwrap())
}
}
@@ -3,13 +3,12 @@
use super::action_controller::{Action, ActionSender};
use super::PendingAcknowledgement;
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::client::{
inbound_messages::{InputMessage, InputMessageReceiver},
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
topology_control::TopologyAccessor,
};
use futures::StreamExt;
use client_connections::TransmissionLane;
use log::*;
use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::preparer::MessagePreparer;
@@ -17,6 +16,9 @@ use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
use rand::{CryptoRng, Rng};
use std::sync::Arc;
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorage;
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
/// putting everything into sphinx packets, etc.
/// It also makes an initial sending attempt for said messages.
@@ -31,6 +33,7 @@ where
action_sender: ActionSender,
real_message_sender: BatchRealMessageSender,
topology_access: TopologyAccessor,
#[cfg(feature = "reply-surb")]
reply_key_storage: ReplyKeyStorage,
}
@@ -49,7 +52,7 @@ where
action_sender: ActionSender,
real_message_sender: BatchRealMessageSender,
topology_access: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
InputMessageListener {
ack_key,
@@ -59,6 +62,7 @@ where
action_sender,
real_message_sender,
topology_access,
#[cfg(feature = "reply-surb")]
reply_key_storage,
}
}
@@ -100,6 +104,7 @@ where
content: Vec<u8>,
with_reply_surb: bool,
) -> Option<Vec<RealMessage>> {
log::trace!("handling msg size: {}", content.len());
let topology_permit = self.topology_access.get_read_permit().await;
let topology = match topology_permit
.try_get_valid_topology_ref(&self.ack_recipient, Some(&recipient))
@@ -117,12 +122,16 @@ where
.prepare_and_split_message(content, with_reply_surb, topology)
.expect("somehow the topology was invalid after all!");
#[cfg(feature = "reply-surb")]
if let Some(reply_key) = reply_key {
self.reply_key_storage
.insert_encryption_key(reply_key)
.expect("Failed to insert surb reply key to the store!")
}
#[cfg(not(feature = "reply-surb"))]
let _reply_key = reply_key;
// encrypt chunks, put them inside sphinx packets and generate acks
let mut pending_acks = Vec::with_capacity(split_message.len());
let mut real_messages = Vec::with_capacity(split_message.len());
@@ -133,7 +142,6 @@ where
let prepared_fragment = self
.message_preparer
.prepare_chunk_for_sending(chunk_clone, topology, &self.ack_key, &recipient)
.await
.unwrap();
real_messages.push(RealMessage::new(
@@ -157,35 +165,64 @@ where
}
async fn on_input_message(&mut self, msg: InputMessage) {
let real_messages = match msg {
let (real_messages, lane) = match msg {
InputMessage::Fresh {
recipient,
data,
with_reply_surb,
} => {
lane,
} => (
self.handle_fresh_message(recipient, data, with_reply_surb)
.await,
lane,
),
InputMessage::Reply { reply_surb, data } => (
self.handle_reply(reply_surb, data)
.await
}
InputMessage::Reply { reply_surb, data } => self
.handle_reply(reply_surb, data)
.await
.map(|message| vec![message]),
.map(|message| vec![message]),
TransmissionLane::Reply,
),
};
// there's no point in trying to send nothing
if let Some(real_messages) = real_messages {
// tells real message sender (with the poisson timer) to send this to the mix network
self.real_message_sender
.unbounded_send(real_messages)
.unwrap();
.send((real_messages, lane))
.await
.expect("BatchRealMessageReceiver has stopped receiving!");
}
}
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started InputMessageListener with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
input_msg = self.input_receiver.recv() => match input_msg {
Some(input_msg) => {
self.on_input_message(input_msg).await;
},
None => {
log::trace!("InputMessageListener: Stopping since channel closed");
break;
}
},
_ = shutdown.recv() => {
log::trace!("InputMessageListener: Received shutdown");
}
}
}
shutdown.recv_timeout().await;
log::debug!("InputMessageListener: Exiting");
}
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started InputMessageListener");
while let Some(input_msg) = self.input_receiver.next().await {
debug!("Started InputMessageListener without graceful shutdown support");
while let Some(input_msg) = self.input_receiver.recv().await {
self.on_input_message(input_msg).await;
}
error!("TODO: error msg. Or maybe panic?")
}
}
@@ -8,11 +8,12 @@ use self::{
sent_notification_listener::SentNotificationListener,
};
use super::real_traffic_stream::BatchRealMessageSender;
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::client::{inbound_messages::InputMessageReceiver, topology_control::TopologyAccessor};
use crate::spawn_future;
use futures::channel::mpsc;
use gateway_client::AcknowledgementReceiver;
use log::*;
use nymsphinx::params::PacketSize;
use nymsphinx::{
acknowledgements::AckKey,
addressing::clients::Recipient,
@@ -25,7 +26,9 @@ use std::{
sync::{Arc, Weak},
time::Duration,
};
use tokio::task::JoinHandle;
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorage;
mod acknowledgement_listener;
mod action_controller;
@@ -119,6 +122,9 @@ pub(super) struct Config {
/// Average delay a data packet is going to get delayed at a single mixnode.
average_packet_delay: Duration,
/// Predefined packet size used for the encapsulated messages.
packet_size: PacketSize,
}
impl Config {
@@ -133,33 +139,40 @@ impl Config {
ack_wait_multiplier,
average_ack_delay,
average_packet_delay,
packet_size: Default::default(),
}
}
pub fn with_custom_packet_size(mut self, packet_size: PacketSize) -> Self {
self.packet_size = packet_size;
self
}
}
pub(super) struct AcknowledgementController<R>
where
R: CryptoRng + Rng,
{
acknowledgement_listener: Option<AcknowledgementListener>,
input_message_listener: Option<InputMessageListener<R>>,
retransmission_request_listener: Option<RetransmissionRequestListener<R>>,
sent_notification_listener: Option<SentNotificationListener>,
action_controller: Option<ActionController>,
acknowledgement_listener: AcknowledgementListener,
input_message_listener: InputMessageListener<R>,
retransmission_request_listener: RetransmissionRequestListener<R>,
sent_notification_listener: SentNotificationListener,
action_controller: ActionController,
}
impl<R> AcknowledgementController<R>
where
R: 'static + CryptoRng + Rng + Clone + Send,
{
#[allow(clippy::too_many_arguments)]
pub(super) fn new(
config: Config,
rng: R,
topology_access: TopologyAccessor,
ack_key: Arc<AckKey>,
ack_recipient: Recipient,
reply_key_storage: ReplyKeyStorage,
connectors: AcknowledgementControllerConnectors,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
@@ -173,7 +186,8 @@ where
ack_recipient,
config.average_packet_delay,
config.average_ack_delay,
);
)
.with_custom_real_message_packet_size(config.packet_size);
// will listen for any acks coming from the network
let acknowledgement_listener = AcknowledgementListener::new(
@@ -191,6 +205,7 @@ where
action_sender.clone(),
connectors.real_message_sender.clone(),
topology_access.clone(),
#[cfg(feature = "reply-surb")]
reply_key_storage,
);
@@ -211,66 +226,87 @@ where
SentNotificationListener::new(connectors.sent_notifier, action_sender);
AcknowledgementController {
acknowledgement_listener: Some(acknowledgement_listener),
input_message_listener: Some(input_message_listener),
retransmission_request_listener: Some(retransmission_request_listener),
sent_notification_listener: Some(sent_notification_listener),
action_controller: Some(action_controller),
acknowledgement_listener,
input_message_listener,
retransmission_request_listener,
sent_notification_listener,
action_controller,
}
}
pub(super) async fn run(&mut self) {
let mut acknowledgement_listener = self.acknowledgement_listener.take().unwrap();
let mut input_message_listener = self.input_message_listener.take().unwrap();
let mut retransmission_request_listener =
self.retransmission_request_listener.take().unwrap();
let mut sent_notification_listener = self.sent_notification_listener.take().unwrap();
let mut action_controller = self.action_controller.take().unwrap();
pub(super) fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
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;
let mut sent_notification_listener = self.sent_notification_listener;
let mut action_controller = self.action_controller;
// the below are log messages are errors as at the current stage we do not expect any of
// the task to ever finish. This will of course change once we introduce
// graceful shutdowns.
let ack_listener_fut = tokio::spawn(async move {
let shutdown_handle = shutdown.clone();
spawn_future(async move {
acknowledgement_listener
.run_with_shutdown(shutdown_handle)
.await;
debug!("The acknowledgement listener has finished execution!");
});
let shutdown_handle = shutdown.clone();
spawn_future(async move {
input_message_listener
.run_with_shutdown(shutdown_handle)
.await;
debug!("The input listener has finished execution!");
});
let shutdown_handle = shutdown.clone();
spawn_future(async move {
retransmission_request_listener
.run_with_shutdown(shutdown_handle)
.await;
debug!("The retransmission request listener has finished execution!");
});
let shutdown_handle = shutdown.clone();
spawn_future(async move {
sent_notification_listener
.run_with_shutdown(shutdown_handle)
.await;
debug!("The sent notification listener has finished execution!");
});
spawn_future(async move {
action_controller.run_with_shutdown(shutdown).await;
debug!("The controller has finished execution!");
});
}
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) fn start(self) {
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;
let mut sent_notification_listener = self.sent_notification_listener;
let mut action_controller = self.action_controller;
spawn_future(async move {
acknowledgement_listener.run().await;
error!("The acknowledgement listener has finished execution!");
acknowledgement_listener
});
let input_listener_fut = tokio::spawn(async move {
spawn_future(async move {
input_message_listener.run().await;
error!("The input listener has finished execution!");
input_message_listener
});
let retransmission_req_fut = tokio::spawn(async move {
spawn_future(async move {
retransmission_request_listener.run().await;
error!("The retransmission request listener has finished execution!");
retransmission_request_listener
});
let sent_notification_fut = tokio::spawn(async move {
spawn_future(async move {
sent_notification_listener.run().await;
error!("The sent notification listener has finished execution!");
sent_notification_listener
});
let action_controller_fut = tokio::spawn(async move {
spawn_future(async move {
action_controller.run().await;
error!("The controller has finished execution!");
action_controller
});
// technically we don't have to bring `AcknowledgementController` back to a valid state
// but we can do it, so why not? Perhaps it might be useful if we wanted to allow
// for restarts of certain modules without killing the entire process.
self.acknowledgement_listener = Some(ack_listener_fut.await.unwrap());
self.input_message_listener = Some(input_listener_fut.await.unwrap());
self.retransmission_request_listener = Some(retransmission_req_fut.await.unwrap());
self.sent_notification_listener = Some(sent_notification_fut.await.unwrap());
self.action_controller = Some(action_controller_fut.await.unwrap());
}
#[allow(dead_code)]
pub(super) fn start(mut self) -> JoinHandle<Self> {
tokio::spawn(async move {
self.run().await;
self
})
}
}
@@ -1,17 +1,21 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::action_controller::{Action, ActionSender};
use super::PendingAcknowledgement;
use super::RetransmissionRequestReceiver;
use super::{
action_controller::{Action, ActionSender},
PendingAcknowledgement, RetransmissionRequestReceiver,
};
use crate::client::{
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
topology_control::TopologyAccessor,
};
use client_connections::TransmissionLane;
use futures::StreamExt;
use log::*;
use nymsphinx::preparer::MessagePreparer;
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
use nymsphinx::{
acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer,
};
use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak};
@@ -33,6 +37,7 @@ impl<R> RetransmissionRequestListener<R>
where
R: CryptoRng + Rng,
{
#[allow(clippy::too_many_arguments)]
pub(super) fn new(
ack_key: Arc<AckKey>,
ack_recipient: Recipient,
@@ -83,7 +88,6 @@ where
let prepared_fragment = self
.message_preparer
.prepare_chunk_for_sending(chunk_clone, topology_ref, &self.ack_key, packet_recipient)
.await
.unwrap();
// if we have the ONLY strong reference to the ack data, it means it was removed from the
@@ -113,18 +117,42 @@ where
// send to `OutQueueControl` to eventually send to the mix network
self.real_message_sender
.unbounded_send(vec![RealMessage::new(
prepared_fragment.mix_packet,
frag_id,
)])
.unwrap();
.send((
vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
TransmissionLane::Retransmission,
))
.await
.expect("BatchRealMessageReceiver has stopped receiving!");
}
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
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,
None => {
log::trace!("RetransmissionRequestListener: Stopping since channel closed");
break;
}
},
_ = shutdown.recv() => {
log::trace!("RetransmissionRequestListener: Received shutdown");
}
}
}
shutdown.recv_timeout().await;
log::debug!("RetransmissionRequestListener: Exiting");
}
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started RetransmissionRequestListener");
debug!("Started RetransmissionRequestListener without graceful shutdown support");
while let Some(timed_out_ack) = self.request_receiver.next().await {
self.on_retransmission_request(timed_out_ack).await;
}
error!("TODO: error msg. Or maybe panic?")
}
}
@@ -42,11 +42,36 @@ impl SentNotificationListener {
.unwrap();
}
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started SentNotificationListener with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
frag_id = self.sent_notifier.next() => match frag_id {
Some(frag_id) => {
self.on_sent_message(frag_id).await;
}
None => {
log::trace!("SentNotificationListener: Stopping since channel closed");
break;
}
},
_ = shutdown.recv() => {
log::trace!("SentNotificationListener: Received shutdown");
}
}
}
assert!(shutdown.is_shutdown_poll());
log::debug!("SentNotificationListener: Exiting");
}
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started SentNotificationListener");
debug!("Started SentNotificationListener without graceful shutdown support");
while let Some(frag_id) = self.sent_notifier.next().await {
self.on_sent_message(frag_id).await;
}
error!("TODO: error msg. Or maybe panic?")
}
}
@@ -8,21 +8,27 @@
use self::{
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
};
use crate::client::real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors;
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::client::{
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
topology_control::TopologyAccessor,
use crate::{
client::{
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors,
topology_control::TopologyAccessor,
},
spawn_future,
};
use client_connections::{ConnectionCommandReceiver, LaneQueueLengths};
use futures::channel::mpsc;
use gateway_client::AcknowledgementReceiver;
use log::*;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::params::PacketSize;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
use std::time::Duration;
use tokio::task::JoinHandle;
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorage;
mod acknowledgement_control;
mod real_traffic_stream;
@@ -49,9 +55,18 @@ pub struct Config {
/// Average delay an acknowledgement packet is going to get delayed at a single mixnode.
average_ack_delay_duration: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
disable_main_poisson_packet_distribution: bool,
/// Predefined packet size used for the encapsulated messages.
packet_size: PacketSize,
}
impl Config {
// TODO: change the config into a builder
#[allow(clippy::too_many_arguments)]
pub fn new(
ack_key: Arc<AckKey>,
ack_wait_multiplier: f64,
@@ -59,6 +74,7 @@ impl Config {
average_ack_delay_duration: Duration,
average_message_sending_delay: Duration,
average_packet_delay_duration: Duration,
disable_main_poisson_packet_distribution: bool,
self_recipient: Recipient,
) -> Self {
Config {
@@ -69,32 +85,41 @@ impl Config {
average_message_sending_delay,
average_packet_delay_duration,
average_ack_delay_duration,
disable_main_poisson_packet_distribution,
packet_size: Default::default(),
}
}
pub fn set_custom_packet_size(&mut self, packet_size: PacketSize) {
self.packet_size = packet_size;
}
}
pub struct RealMessagesController<R>
where
R: CryptoRng + Rng,
{
out_queue_control: Option<OutQueueControl<R>>,
ack_control: Option<AcknowledgementController<R>>,
out_queue_control: OutQueueControl<R>,
ack_control: AcknowledgementController<R>,
}
// obviously when we finally make shared rng that is on 'higher' level, this should become
// generic `R`
impl RealMessagesController<OsRng> {
#[allow(clippy::too_many_arguments)]
pub fn new(
config: Config,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
topology_access: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
let rng = OsRng;
let (real_message_sender, real_message_receiver) = mpsc::unbounded();
let (real_message_sender, real_message_receiver) = tokio::sync::mpsc::channel(1);
let (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded();
let ack_controller_connectors = AcknowledgementControllerConnectors::new(
@@ -109,7 +134,8 @@ impl RealMessagesController<OsRng> {
config.ack_wait_multiplier,
config.average_ack_delay_duration,
config.average_packet_delay_duration,
);
)
.with_custom_packet_size(config.packet_size);
let ack_control = AcknowledgementController::new(
ack_control_config,
@@ -117,15 +143,18 @@ impl RealMessagesController<OsRng> {
topology_access.clone(),
Arc::clone(&config.ack_key),
config.self_recipient,
reply_key_storage,
ack_controller_connectors,
#[cfg(feature = "reply-surb")]
reply_key_storage,
);
let out_queue_config = real_traffic_stream::Config::new(
config.average_ack_delay_duration,
config.average_packet_delay_duration,
config.average_message_sending_delay,
);
config.disable_main_poisson_packet_distribution,
)
.with_custom_cover_packet_size(config.packet_size);
let out_queue_control = OutQueueControl::new(
out_queue_config,
@@ -136,43 +165,37 @@ impl RealMessagesController<OsRng> {
rng,
config.self_recipient,
topology_access,
lane_queue_lengths,
client_connection_rx,
);
RealMessagesController {
out_queue_control: Some(out_queue_control),
ack_control: Some(ack_control),
out_queue_control,
ack_control,
}
}
pub(super) async fn run(&mut self) {
let mut out_queue_control = self.out_queue_control.take().unwrap();
let mut ack_control = self.ack_control.take().unwrap();
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut out_queue_control = self.out_queue_control;
let ack_control = self.ack_control;
// the below are log messages are errors as at the current stage we do not expect any of
// the task to ever finish. This will of course change once we introduce
// graceful shutdowns.
let out_queue_control_fut = tokio::spawn(async move {
out_queue_control.run_out_queue_control().await;
error!("The out queue controller has finished execution!");
out_queue_control
let shutdown_handle = shutdown.clone();
spawn_future(async move {
out_queue_control.run_with_shutdown(shutdown_handle).await;
debug!("The out queue controller has finished execution!");
});
let ack_control_fut = tokio::spawn(async move {
ack_control.run().await;
error!("The acknowledgement controller has finished execution!");
ack_control
});
// technically we don't have to bring `RealMessagesController` back to a valid state
// but we can do it, so why not? Perhaps it might be useful if we wanted to allow
// for restarts of certain modules without killing the entire process.
self.out_queue_control = Some(out_queue_control_fut.await.unwrap());
self.ack_control = Some(ack_control_fut.await.unwrap());
ack_control.start_with_shutdown(shutdown);
}
pub fn start(mut self) -> JoinHandle<Self> {
tokio::spawn(async move {
self.run().await;
self
})
#[cfg(target_arch = "wasm32")]
pub fn start(self) {
let mut out_queue_control = self.out_queue_control;
let ack_control = self.ack_control;
spawn_future(async move {
out_queue_control.run().await;
debug!("The out queue controller has finished execution!");
});
ack_control.start();
}
}
@@ -4,7 +4,9 @@
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
use crate::client::topology_control::TopologyAccessor;
use futures::channel::mpsc;
use client_connections::{
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
};
use futures::task::{Context, Poll};
use futures::{Future, Stream, StreamExt};
use log::*;
@@ -13,14 +15,36 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::chunking::fragment::FragmentIdentifier;
use nymsphinx::cover::generate_loop_cover_packet;
use nymsphinx::forwarding::packet::MixPacket;
use nymsphinx::params::PacketSize;
use nymsphinx::utils::sample_poisson_duration;
use rand::{CryptoRng, Rng};
use std::collections::VecDeque;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
use self::{
sending_delay_controller::SendingDelayController, transmission_buffer::TransmissionBuffer,
};
mod sending_delay_controller;
mod transmission_buffer;
#[cfg(not(target_arch = "wasm32"))]
fn get_time_now() -> time::Instant {
time::Instant::now()
}
#[cfg(target_arch = "wasm32")]
fn get_time_now() -> wasm_timer::Instant {
wasm_timer::Instant::now()
}
/// Configurable parameters of the `OutQueueControl`
pub(crate) struct Config {
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
@@ -31,6 +55,13 @@ pub(crate) struct Config {
/// Average delay between sending subsequent packets.
average_message_sending_delay: Duration,
/// Controls whether the stream constantly produces packets according to the predefined
/// poisson distribution.
disable_poisson_packet_distribution: bool,
/// Predefined packet size used for the loop cover messages.
cover_packet_size: PacketSize,
}
impl Config {
@@ -38,13 +69,21 @@ impl Config {
average_ack_delay: Duration,
average_packet_delay: Duration,
average_message_sending_delay: Duration,
disable_poisson_packet_distribution: bool,
) -> Self {
Config {
average_ack_delay,
average_packet_delay,
average_message_sending_delay,
disable_poisson_packet_distribution,
cover_packet_size: Default::default(),
}
}
pub fn with_custom_cover_packet_size(mut self, packet_size: PacketSize) -> Self {
self.cover_packet_size = packet_size;
self
}
}
pub(crate) struct OutQueueControl<R>
@@ -62,7 +101,15 @@ where
/// Internal state, determined by `average_message_sending_delay`,
/// used to keep track of when a next packet should be sent out.
next_delay: Pin<Box<time::Sleep>>,
#[cfg(not(target_arch = "wasm32"))]
next_delay: Option<Pin<Box<time::Sleep>>>,
#[cfg(target_arch = "wasm32")]
next_delay: Option<Pin<Box<wasm_timer::Delay>>>,
// To make sure we don't overload the mix_tx channel, we limit the rate we are pushing
// messages.
sending_delay_controller: SendingDelayController,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
@@ -81,10 +128,19 @@ where
/// Accessor to the common instance of network topology.
topology_access: TopologyAccessor,
/// Buffer containing all real messages received. It is first exhausted before more are pulled.
received_buffer: VecDeque<RealMessage>,
/// Buffer containing all incoming real messages keyed by transmission lane, that we will send
/// out to the mixnet.
transmission_buffer: TransmissionBuffer,
/// Incoming channel for being notified of closed connections, so that we can close lanes
/// corresponding to connections. To avoid sending traffic unnecessary
client_connection_rx: ConnectionCommandReceiver,
/// Report queue lengths so that upstream can backoff sending data, and keep connections open.
lane_queue_lengths: LaneQueueLengths,
}
#[derive(Debug)]
pub(crate) struct RealMessage {
mix_packet: MixPacket,
fragment_id: FragmentIdentifier,
@@ -101,63 +157,15 @@ impl RealMessage {
// messages are already prepared, etc. the real point of it is to forward it to mix_traffic
// after sufficient delay
pub(crate) type BatchRealMessageSender = mpsc::UnboundedSender<Vec<RealMessage>>;
type BatchRealMessageReceiver = mpsc::UnboundedReceiver<Vec<RealMessage>>;
pub(crate) type BatchRealMessageSender =
tokio::sync::mpsc::Sender<(Vec<RealMessage>, TransmissionLane)>;
type BatchRealMessageReceiver = tokio::sync::mpsc::Receiver<(Vec<RealMessage>, TransmissionLane)>;
pub(crate) enum StreamMessage {
Cover,
Real(Box<RealMessage>),
}
impl<R> Stream for OutQueueControl<R>
where
R: CryptoRng + Rng + Unpin,
{
type Item = StreamMessage;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
// it is not yet time to return a message
if self.next_delay.as_mut().poll(cx).is_pending() {
return Poll::Pending;
};
// we know it's time to send a message, so let's prepare delay for the next one
// Get the `now` by looking at the current `delay` deadline
let avg_delay = self.config.average_message_sending_delay;
let now = self.next_delay.deadline();
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
// The next interval value is `next_poisson_delay` after the one that just
// yielded.
let next = now + next_poisson_delay;
self.next_delay.as_mut().reset(next);
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
}
// decide what kind of message to send
match Pin::new(&mut self.real_receiver).poll_next(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(Box::new(
self.received_buffer.pop_front().unwrap(),
))))
}
// otherwise construct a dummy one
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
}
}
}
impl<R> OutQueueControl<R>
where
R: CryptoRng + Rng + Unpin,
@@ -174,18 +182,23 @@ where
rng: R,
our_full_destination: Recipient,
topology_access: TopologyAccessor,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
) -> Self {
OutQueueControl {
config,
ack_key,
sent_notifier,
next_delay: Box::pin(time::sleep(Default::default())),
next_delay: None,
sending_delay_controller: Default::default(),
mix_tx,
real_receiver,
our_full_destination,
rng,
topology_access,
received_buffer: VecDeque::with_capacity(0), // we won't be putting any data into this guy directly
transmission_buffer: Default::default(),
client_connection_rx,
lane_queue_lengths,
}
}
@@ -200,7 +213,7 @@ where
async fn on_message(&mut self, next_message: StreamMessage) {
trace!("created new message");
let next_message = match next_message {
let (next_message, fragment_id) = match next_message {
StreamMessage::Cover => {
// TODO for way down the line: in very rare cases (during topology update) we might have
// to wait a really tiny bit before actually obtaining the permit hence messing with our
@@ -219,51 +232,340 @@ where
}
let topology_ref = topology_ref_option.unwrap();
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&*self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
(
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
self.config.cover_packet_size,
)
.expect(
"Somehow failed to generate a loop cover message with a valid topology",
),
None,
)
.expect("Somehow failed to generate a loop cover message with a valid topology")
}
StreamMessage::Real(real_message) => {
self.sent_notify(real_message.fragment_id);
real_message.mix_packet
(real_message.mix_packet, Some(real_message.fragment_id))
}
};
// if this one fails, there's no retrying because it means that either:
// - we run out of memory
// - the receiver channel is closed
// in either case there's no recovery and we can only panic
self.mix_tx.unbounded_send(vec![next_message]).unwrap();
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
log::error!("Failed to send: {}", err);
}
// notify ack controller about sending our message only after we actually managed to push it
// through the channel
if let Some(fragment_id) = fragment_id {
self.sent_notify(fragment_id);
}
// In addition to closing connections on receiving messages throught client_connection_rx,
// also close connections when sufficiently stale.
self.transmission_buffer.prune_stale_connections();
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
// the UnboundedReceiver [of mix_rx] will not get a chance to read anything
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
// ready and hence was immediately re-scheduled causing other tasks to be starved;
// yield makes it go back the scheduling queue regardless of its value availability
// TODO: temporary and BAD workaround for wasm (we should find a way to yield here in wasm)
#[cfg(not(target_arch = "wasm32"))]
tokio::task::yield_now().await;
}
// Send messages at certain rate and if no real traffic is available, send cover message.
async fn run_normal_out_queue(&mut self) {
// we should set initial delay only when we actually start the stream
self.next_delay = Box::pin(time::sleep(sample_poisson_duration(
&mut self.rng,
self.config.average_message_sending_delay,
)));
fn on_close_connection(&mut self, connection_id: ConnectionId) {
log::debug!("Removing lane for connection: {connection_id}");
self.transmission_buffer
.remove(&TransmissionLane::ConnectionId(connection_id));
}
fn current_average_message_sending_delay(&self) -> Duration {
self.config.average_message_sending_delay
* self.sending_delay_controller.current_multiplier()
}
fn adjust_current_average_message_sending_delay(&mut self) {
let used_slots = self.mix_tx.max_capacity() - self.mix_tx.capacity();
log::trace!(
"used_slots: {used_slots}, current_multiplier: {}",
self.sending_delay_controller.current_multiplier()
);
// Even just a single used slot is enough to signal backpressure
if used_slots > 0 {
log::trace!("Backpressure detected");
self.sending_delay_controller.record_backpressure_detected();
}
// If the buffer is running out, slow down the sending rate
if self.mix_tx.capacity() == 0
&& self.sending_delay_controller.not_increased_delay_recently()
{
self.sending_delay_controller.increase_delay_multiplier();
}
// Very carefully step up the sending rate in case it seems like we can solidly handle the
// current rate.
if self.sending_delay_controller.is_sending_reliable() {
self.sending_delay_controller.decrease_delay_multiplier();
}
}
fn pop_next_message(&mut self) -> Option<RealMessage> {
// Pop the next message from the transmission buffer
let (lane, real_next) = self.transmission_buffer.pop_next_message_at_random()?;
// Update the published queue length
let lane_length = self.transmission_buffer.lane_length(&lane);
self.lane_queue_lengths.set(&lane, lane_length);
Some(real_next)
}
fn poll_poisson(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// The average delay could change depending on if backpressure in the downstream channel
// (mix_tx) was detected.
self.adjust_current_average_message_sending_delay();
let avg_delay = self.current_average_message_sending_delay();
// Start by checking if we have any incoming messages about closed connections
// NOTE: this feels a bit iffy, the `OutQueueControl` is getting ripe for a rewrite to
// something simpler.
if let Poll::Ready(Some(id)) = Pin::new(&mut self.client_connection_rx).poll_next(cx) {
match id {
ConnectionCommand::Close(id) => self.on_close_connection(id),
ConnectionCommand::ActiveConnections(_) => panic!(),
}
}
if let Some(ref mut next_delay) = &mut self.next_delay {
// it is not yet time to return a message
if next_delay.as_mut().poll(cx).is_pending() {
return Poll::Pending;
};
// we know it's time to send a message, so let's prepare delay for the next one
// Get the `now` by looking at the current `delay` deadline
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
// The next interval value is `next_poisson_delay` after the one that just
// yielded.
#[cfg(not(target_arch = "wasm32"))]
{
let now = next_delay.deadline();
let next = now + next_poisson_delay;
next_delay.as_mut().reset(next);
}
#[cfg(target_arch = "wasm32")]
{
next_delay.as_mut().reset(next_poisson_delay);
}
// 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
// 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
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
Poll::Ready(Some((real_messages, conn_id))) => {
log::trace!("handling real_messages: size: {}", real_messages.len());
self.transmission_buffer.store(&conn_id, real_messages);
let real_next = self.pop_next_message().expect("Just stored one");
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
}
Poll::Pending => {
if let Some(real_next) = self.pop_next_message() {
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
} else {
// otherwise construct a dummy one
Poll::Ready(Some(StreamMessage::Cover))
}
}
}
} else {
// we never set an initial delay - let's do it now
cx.waker().wake_by_ref();
let sampled =
sample_poisson_duration(&mut self.rng, self.config.average_message_sending_delay);
#[cfg(not(target_arch = "wasm32"))]
let next_delay = Box::pin(time::sleep(sampled));
#[cfg(target_arch = "wasm32")]
let next_delay = Box::pin(wasm_timer::Delay::new(sampled));
self.next_delay = Some(next_delay);
Poll::Pending
}
}
fn poll_immediate(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// Start by checking if we have any incoming messages about closed connections
if let Poll::Ready(Some(id)) = Pin::new(&mut self.client_connection_rx).poll_next(cx) {
match id {
ConnectionCommand::Close(id) => self.on_close_connection(id),
ConnectionCommand::ActiveConnections(_) => panic!(),
}
}
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
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
Poll::Ready(Some((real_messages, conn_id))) => {
log::trace!("handling real_messages: size: {}", real_messages.len());
// First store what we got for the given connection id
self.transmission_buffer.store(&conn_id, real_messages);
let real_next = self.pop_next_message().expect("we just added one");
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
}
Poll::Pending => {
if let Some(real_next) = self.pop_next_message() {
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
} else {
Poll::Pending
}
}
}
}
fn poll_next_message(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<StreamMessage>> {
if self.config.disable_poisson_packet_distribution {
self.poll_immediate(cx)
} else {
self.poll_poisson(cx)
}
}
#[cfg(not(target_arch = "wasm32"))]
fn log_status(&self) {
let packets = self.transmission_buffer.total_size();
let backlog = self.transmission_buffer.total_size_in_bytes() as f64 / 1024.0;
let lanes = self.transmission_buffer.num_lanes();
let mult = self.sending_delay_controller.current_multiplier();
let delay = self.current_average_message_sending_delay().as_millis();
let status_str = if self.config.disable_poisson_packet_distribution {
format!(
"Status: {lanes} lanes, backlog: {:.2} kiB ({packets}), no delay",
backlog
)
} else {
format!(
"Status: {lanes} lanes, backlog: {:.2} kiB ({packets}), avg delay: {}ms ({mult})",
backlog, delay
)
};
if packets > 1000 {
log::warn!("{status_str}");
} else if packets > 0 {
log::info!("{status_str}");
} else {
log::debug!("{status_str}");
}
}
#[cfg(not(target_arch = "wasm32"))]
fn log_status_infrequent(&self) {
if self.sending_delay_controller.current_multiplier() > 1 {
log::warn!(
"Unable to send packets fast enough - sending delay multiplier set to: {}",
self.sending_delay_controller.current_multiplier()
);
}
}
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started OutQueueControl with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
{
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
let mut infrequent_status_timer = tokio::time::interval(Duration::from_secs(60));
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
}
_ = status_timer.tick() => {
self.log_status();
}
_ = infrequent_status_timer.tick() => {
self.log_status_infrequent();
}
next_message = self.next() => if let Some(next_message) = next_message {
self.on_message(next_message).await;
} else {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
}
}
tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
}
#[cfg(target_arch = "wasm32")]
{
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
}
next_message = self.next() => if let Some(next_message) = next_message {
self.on_message(next_message).await;
} else {
log::trace!("OutQueueControl: Stopping since channel closed");
break;
}
}
}
}
log::debug!("OutQueueControl: Exiting");
}
// todo: think whether this is still required
#[allow(dead_code)]
pub(super) async fn run(&mut self) {
debug!("Started OutQueueControl without graceful shutdown support");
while let Some(next_message) = self.next().await {
self.on_message(next_message).await;
}
}
}
pub(crate) async fn run_out_queue_control(&mut self) {
debug!("Starting out queue controller...");
self.run_normal_out_queue().await
impl<R> Stream for OutQueueControl<R>
where
R: CryptoRng + Rng + Unpin,
{
type Item = StreamMessage;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.poll_next_message(cx)
}
}
@@ -0,0 +1,124 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::get_time_now;
use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
// the available buffer space we want to take somewhat swift action, but we still need to give a
// short time to give the channel a chance reduce pressure.
const INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 1;
// The minimum time between decreasing the average delay between packets. We don't want to change
// to quickly to keep things somewhat stable. Also there are buffers downstreams meaning we need to
// wait a little to see the effect before we decrease further.
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 30;
// If we enough time passes without any sign of backpressure in the channel, we can consider
// lowering the average delay. The goal is to keep somewhat stable, rather than maxing out
// bandwidth at all times.
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 30;
// The maximum multiplier we apply to the base average Poisson delay.
const MAX_DELAY_MULTIPLIER: u32 = 6;
// The minium multiplier we apply to the base average Poisson delay.
const MIN_DELAY_MULTIPLIER: u32 = 1;
pub(crate) struct SendingDelayController {
/// Multiply the average sending delay.
/// This is normally set to unity, but if we detect backpressure we increase this
/// multiplier. We use discrete steps.
current_multiplier: u32,
/// Maximum delay multiplier
upper_bound: u32,
/// Minimum delay multiplier
lower_bound: u32,
/// To make sure we don't change the multiplier to fast, we limit a change to some duration
#[cfg(not(target_arch = "wasm32"))]
time_when_changed: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_changed: wasm_timer::Instant,
/// If we have a long enough time without any backpressure detected we try reducing the sending
/// delay multiplier
#[cfg(not(target_arch = "wasm32"))]
time_when_backpressure_detected: time::Instant,
#[cfg(target_arch = "wasm32")]
time_when_backpressure_detected: wasm_timer::Instant,
}
impl Default for SendingDelayController {
fn default() -> Self {
SendingDelayController::new(MIN_DELAY_MULTIPLIER, MAX_DELAY_MULTIPLIER)
}
}
impl SendingDelayController {
pub(crate) fn new(lower_bound: u32, upper_bound: u32) -> Self {
assert!(lower_bound <= upper_bound);
let now = get_time_now();
SendingDelayController {
current_multiplier: MIN_DELAY_MULTIPLIER,
upper_bound,
lower_bound,
time_when_changed: now,
time_when_backpressure_detected: now,
}
}
pub(crate) fn current_multiplier(&self) -> u32 {
self.current_multiplier
}
pub(crate) fn increase_delay_multiplier(&mut self) {
if self.current_multiplier < self.upper_bound {
self.current_multiplier =
(self.current_multiplier + 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::warn!(
"Increasing sending delay multiplier to: {}",
self.current_multiplier
);
} else {
log::warn!("Trying to increase delay multipler higher than allowed");
}
}
pub(crate) fn decrease_delay_multiplier(&mut self) {
if self.current_multiplier > self.lower_bound {
self.current_multiplier =
(self.current_multiplier - 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
"Decreasing sending delay multiplier to: {}",
self.current_multiplier
);
}
}
pub(crate) fn record_backpressure_detected(&mut self) {
self.time_when_backpressure_detected = get_time_now();
}
pub(crate) fn not_increased_delay_recently(&self) -> bool {
get_time_now()
> self.time_when_changed + Duration::from_secs(INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
}
pub(crate) fn is_sending_reliable(&self) -> bool {
let now = get_time_now();
let delay_change_interval = Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS);
let acceptable_time_without_backpressure =
Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS);
now > self.time_when_backpressure_detected + acceptable_time_without_backpressure
&& now > self.time_when_changed + delay_change_interval
}
}
@@ -0,0 +1,211 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::TransmissionLane;
use rand::seq::SliceRandom;
use std::{
collections::{HashMap, HashSet, VecDeque},
time::Duration,
};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
use super::{get_time_now, RealMessage};
// The number of lanes included in the oldest set. Used when we need to prioritize traffic.
const OLDEST_LANE_SET_SIZE: usize = 5;
// As a way of prune connections we also check for timeouts.
const MSG_CONSIDERED_STALE_AFTER_SECS: u64 = 10 * 60;
#[derive(Default)]
pub(crate) struct TransmissionBuffer {
buffer: HashMap<TransmissionLane, LaneBufferEntry>,
}
impl TransmissionBuffer {
#[allow(unused)]
pub(crate) fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option<LaneBufferEntry> {
self.buffer.remove(lane)
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn num_lanes(&self) -> usize {
self.buffer.keys().count()
}
pub(crate) fn lane_length(&self, lane: &TransmissionLane) -> Option<usize> {
self.buffer.get(lane).map(LaneBufferEntry::len)
}
#[allow(unused)]
pub(crate) fn connections(&self) -> HashSet<u64> {
self.buffer
.keys()
.filter_map(|lane| match lane {
TransmissionLane::ConnectionId(id) => Some(id),
_ => None,
})
.copied()
.collect()
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn total_size(&self) -> usize {
self.buffer.values().map(LaneBufferEntry::len).sum()
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn total_size_in_bytes(&self) -> usize {
self.buffer
.values()
.map(|lane_buffer_entry| {
lane_buffer_entry
.real_messages
.iter()
.map(|real_message| real_message.mix_packet.sphinx_packet().len())
.sum::<usize>()
})
.sum()
}
fn get_oldest_set(&self) -> Vec<TransmissionLane> {
let mut buffer: Vec<_> = self
.buffer
.iter()
.map(|(k, v)| (k, v.messages_transmitted))
.collect();
buffer.sort_by_key(|v| v.1);
buffer
.iter()
.rev()
.map(|(k, _)| *k)
.take(OLDEST_LANE_SET_SIZE)
.copied()
.collect()
}
pub(crate) fn store(&mut self, lane: &TransmissionLane, real_messages: Vec<RealMessage>) {
if let Some(lane_buffer_entry) = self.buffer.get_mut(lane) {
lane_buffer_entry.append(real_messages);
} else {
self.buffer
.insert(*lane, LaneBufferEntry::new(real_messages));
}
}
fn pick_random_lane(&self) -> Option<&TransmissionLane> {
let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect();
lanes.choose(&mut rand::thread_rng()).copied()
}
fn pick_random_small_lane(&self) -> Option<&TransmissionLane> {
let lanes: Vec<&TransmissionLane> = self
.buffer
.iter()
.filter(|(_, v)| v.is_small())
.map(|(k, _)| k)
.collect();
lanes.choose(&mut rand::thread_rng()).copied()
}
fn pick_random_old_lane(&self) -> Option<TransmissionLane> {
let lanes = self.get_oldest_set();
lanes.choose(&mut rand::thread_rng()).copied()
}
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<RealMessage> {
let real_msgs_queued = self.buffer.get_mut(lane)?;
let real_next = real_msgs_queued.pop_front()?;
real_msgs_queued.messages_transmitted += 1;
if real_msgs_queued.is_empty() {
self.buffer.remove(lane);
}
Some(real_next)
}
pub(crate) fn pop_next_message_at_random(&mut self) -> Option<(TransmissionLane, RealMessage)> {
if self.buffer.is_empty() {
return None;
}
// Very basic heuristic where we prioritize according to small lanes first, the older lanes
// to try to finish lanes when possible, then the rest.
let lane = if let Some(small_lane) = self.pick_random_small_lane() {
*small_lane
} else if let Some(old_lane) = self.pick_random_old_lane() {
old_lane
} else {
*self.pick_random_lane()?
};
let msg = self.pop_front_from_lane(&lane)?;
log::trace!("picking to send from lane: {:?}", lane);
Some((lane, msg))
}
pub(crate) fn prune_stale_connections(&mut self) {
let stale_entries: Vec<_> = self
.buffer
.iter()
.filter_map(|(lane, entry)| if entry.is_stale() { Some(lane) } else { None })
.copied()
.collect();
for lane in stale_entries {
self.remove(&lane);
}
}
}
pub(crate) struct LaneBufferEntry {
pub real_messages: VecDeque<RealMessage>,
pub messages_transmitted: usize,
#[cfg(not(target_arch = "wasm32"))]
pub time_for_last_activity: time::Instant,
#[cfg(target_arch = "wasm32")]
pub time_for_last_activity: wasm_timer::Instant,
}
impl LaneBufferEntry {
fn new(real_messages: Vec<RealMessage>) -> Self {
LaneBufferEntry {
real_messages: real_messages.into(),
messages_transmitted: 0,
time_for_last_activity: get_time_now(),
}
}
fn append(&mut self, real_messages: Vec<RealMessage>) {
self.real_messages.append(&mut real_messages.into());
self.time_for_last_activity = get_time_now();
}
fn pop_front(&mut self) -> Option<RealMessage> {
self.real_messages.pop_front()
}
fn is_small(&self) -> bool {
self.real_messages.len() < 100
}
fn is_stale(&self) -> bool {
get_time_now() - self.time_for_last_activity
> Duration::from_secs(MSG_CONSIDERED_STALE_AFTER_SECS)
}
fn len(&self) -> usize {
self.real_messages.len()
}
fn is_empty(&self) -> bool {
self.real_messages.is_empty()
}
}
+151 -54
View File
@@ -1,21 +1,25 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::spawn_future;
use crypto::asymmetric::encryption;
use crypto::symmetric::stream_cipher;
use crypto::Digest;
use futures::channel::mpsc;
use futures::lock::Mutex;
use futures::StreamExt;
use gateway_client::MixnetMessageReceiver;
use log::*;
use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey};
use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm};
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
use std::collections::HashSet;
use std::sync::Arc;
use tokio::task::JoinHandle;
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorage;
#[cfg(feature = "reply-surb")]
use crypto::{symmetric::stream_cipher, Digest};
#[cfg(feature = "reply-surb")]
use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey};
#[cfg(feature = "reply-surb")]
use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm};
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
// or to say "hey, I'm going offline, don't send anything more to me. Just buffer them instead"
@@ -113,13 +117,14 @@ struct ReceivedMessagesBuffer {
/// Storage containing keys to all [`ReplySURB`]s ever sent out that we did not receive back.
// There's no need to put it behind a Mutex since it's already properly concurrent
#[cfg(feature = "reply-surb")]
reply_key_storage: ReplyKeyStorage,
}
impl ReceivedMessagesBuffer {
fn new(
local_encryption_keypair: Arc<encryption::KeyPair>,
reply_key_storage: ReplyKeyStorage,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
ReceivedMessagesBuffer {
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
@@ -129,6 +134,7 @@ impl ReceivedMessagesBuffer {
message_sender: None,
recently_reconstructed: HashSet::new(),
})),
#[cfg(feature = "reply-surb")]
reply_key_storage,
}
}
@@ -177,6 +183,7 @@ impl ReceivedMessagesBuffer {
self.inner.lock().await.messages.extend(msgs)
}
#[cfg(feature = "reply-surb")]
fn process_received_reply(
reply_ciphertext: &[u8],
reply_key: SurbEncryptionKey,
@@ -201,7 +208,7 @@ impl ReceivedMessagesBuffer {
}
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
debug!(
trace!(
"Processing {:?} new message that might get added to the buffer!",
msgs.len()
);
@@ -209,38 +216,50 @@ impl ReceivedMessagesBuffer {
let mut completed_messages = Vec::new();
let mut inner_guard = self.inner.lock().await;
let reply_surb_digest_size = ReplySurbKeyDigestAlgorithm::output_size();
// first check if this is a reply or a chunked message
// TODO: verify with @AP if this way of doing it is safe or whether it could
// cause some attacks due to, I don't know, stupid edge case collisions?
// Update: this DOES introduce a possible leakage: https://github.com/nymtech/nym/issues/296
for msg in msgs {
let possible_key_digest =
EncryptionKeyDigest::clone_from_slice(&msg[..reply_surb_digest_size]);
// TODO:
// 1. make it nicer
// 2. make it not feature-locked
// check first `HasherOutputSize` bytes if they correspond to known encryption key
// if yes - this is a reply message
// TODO: this might be a bottleneck - since the keys are stored on disk we, presumably,
// are doing a disk operation every single received fragment
if let Some(reply_encryption_key) = self
.reply_key_storage
.get_and_remove_encryption_key(possible_key_digest)
.expect("storage operation failed!")
#[cfg(feature = "reply-surb")]
{
if let Some(completed_message) = Self::process_received_reply(
&msg[reply_surb_digest_size..],
reply_encryption_key,
) {
completed_messages.push(completed_message)
}
} else {
// otherwise - it's a 'normal' message
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
completed_messages.push(completed_message)
let reply_surb_digest_size = ReplySurbKeyDigestAlgorithm::output_size();
let possible_key_digest =
EncryptionKeyDigest::clone_from_slice(&msg[..reply_surb_digest_size]);
// check first `HasherOutputSize` bytes if they correspond to known encryption key
// if yes - this is a reply message
// TODO: this might be a bottleneck - since the keys are stored on disk we, presumably,
// are doing a disk operation every single received fragment
if let Some(reply_encryption_key) = self
.reply_key_storage
.get_and_remove_encryption_key(possible_key_digest)
.expect("storage operation failed!")
{
if let Some(completed_message) = Self::process_received_reply(
&msg[reply_surb_digest_size..],
reply_encryption_key,
) {
completed_messages.push(completed_message)
}
} else {
// otherwise - it's a 'normal' message
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
completed_messages.push(completed_message)
}
}
}
#[cfg(not(feature = "reply-surb"))]
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
completed_messages.push(completed_message)
}
}
if !completed_messages.is_empty() {
@@ -290,19 +309,48 @@ impl RequestReceiver {
}
}
fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
while let Some(request) = self.query_receiver.next().await {
match request {
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
self.received_buffer.connect_sender(sender).await;
}
ReceivedBufferMessage::ReceiverDisconnect => {
self.received_buffer.disconnect_sender().await
}
}
async fn handle_message(&mut self, message: ReceivedBufferMessage) {
match message {
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
self.received_buffer.connect_sender(sender).await;
}
})
ReceivedBufferMessage::ReceiverDisconnect => {
self.received_buffer.disconnect_sender().await
}
}
}
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started RequestReceiver with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("RequestReceiver: Received shutdown");
}
request = self.query_receiver.next() => {
match request {
Some(message) => self.handle_message(message).await,
None => {
log::trace!("RequestReceiver: Stopping since channel closed");
break;
},
}
},
}
}
shutdown.recv_timeout().await;
log::debug!("RequestReceiver: Exiting");
}
// todo: think whether this is still required
#[allow(dead_code)]
async fn run(&mut self) {
debug!("Started RequestReceiver without graceful shutdown support");
while let Some(message) = self.query_receiver.next().await {
self.handle_message(message).await
}
}
}
@@ -321,12 +369,37 @@ impl FragmentedMessageReceiver {
mixnet_packet_receiver,
}
}
fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
self.received_buffer.handle_new_received(new_messages).await;
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
new_messages = self.mixnet_packet_receiver.next() => match new_messages {
Some(new_messages) => {
self.received_buffer.handle_new_received(new_messages).await;
}
None => {
log::trace!("FragmentedMessageReceiver: Stopping since channel closed");
break;
}
},
_ = shutdown.recv() => {
log::trace!("FragmentedMessageReceiver: Received shutdown");
}
}
})
}
shutdown.recv_timeout().await;
log::debug!("FragmentedMessageReceiver: Exiting");
}
// todo: think whether this is still required
#[allow(dead_code)]
async fn run(&mut self) {
debug!("Started FragmentedMessageReceiver without graceful shutdown support");
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
self.received_buffer.handle_new_received(new_messages).await;
}
}
}
@@ -340,10 +413,13 @@ impl ReceivedMessagesBufferController {
local_encryption_keypair: Arc<encryption::KeyPair>,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_packet_receiver: MixnetMessageReceiver,
reply_key_storage: ReplyKeyStorage,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
let received_buffer =
ReceivedMessagesBuffer::new(local_encryption_keypair, reply_key_storage);
let received_buffer = ReceivedMessagesBuffer::new(
local_encryption_keypair,
#[cfg(feature = "reply-surb")]
reply_key_storage,
);
ReceivedMessagesBufferController {
fragmented_message_receiver: FragmentedMessageReceiver::new(
@@ -354,9 +430,30 @@ impl ReceivedMessagesBufferController {
}
}
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut fragmented_message_receiver = self.fragmented_message_receiver;
let mut request_receiver = self.request_receiver;
let shutdown_handle = shutdown.clone();
spawn_future(async move {
fragmented_message_receiver
.run_with_shutdown(shutdown_handle)
.await;
});
spawn_future(async move {
request_receiver.run_with_shutdown(shutdown).await;
});
}
#[cfg(target_arch = "wasm32")]
pub fn start(self) {
// TODO: should we do anything with JoinHandle(s) returned by start methods?
self.fragmented_message_receiver.start();
self.request_receiver.start();
let mut fragmented_message_receiver = self.fragmented_message_receiver;
let mut request_receiver = self.request_receiver;
spawn_future(async move {
fragmented_message_receiver.run().await;
});
spawn_future(async move {
request_receiver.run().await;
});
}
}
@@ -8,10 +8,13 @@ use nymsphinx::anonymous_replies::{
};
use std::path::Path;
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum ReplyKeyStorageError {
#[error("DB Read Error: {0}")]
DbReadError(sled::Error),
#[error("DB Write Error: {0}")]
DbWriteError(sled::Error),
#[error("DB Open Error: {0}")]
DbOpenError(sled::Error),
}
@@ -1,6 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
use futures::StreamExt;
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::params::DEFAULT_NUM_MIX_HOPS;
@@ -11,8 +13,7 @@ use std::sync::Arc;
use std::time;
use std::time::Duration;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use topology::{nym_topology_from_bonds, NymTopology};
use topology::{nym_topology_from_detailed, NymTopology};
use url::Url;
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
@@ -57,24 +58,15 @@ impl<'a> TopologyReadPermit<'a> {
) -> Option<&'a NymTopology> {
// Note: implicit deref with Deref for TopologyReadPermit is happening here
let topology_ref_option = self.permit.as_ref();
match topology_ref_option {
None => None,
Some(topology_ref) => {
// see if it's possible to route the packet to both gateways
if !topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|| if let Some(packet_recipient) = packet_recipient {
!topology_ref.gateway_exists(packet_recipient.gateway())
} else {
false
}
{
None
topology_ref_option.as_ref().filter(|topology_ref| {
!(!topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|| if let Some(packet_recipient) = packet_recipient {
!topology_ref.gateway_exists(packet_recipient.gateway())
} else {
Some(topology_ref)
}
}
}
false
})
})
}
}
@@ -147,7 +139,7 @@ impl TopologyRefresherConfig {
}
pub struct TopologyRefresher {
validator_client: validator_client::ApiClient,
validator_client: validator_client::client::ApiClient,
client_version: String,
validator_api_urls: Vec<Url>,
@@ -163,7 +155,9 @@ impl TopologyRefresher {
cfg.validator_api_urls.shuffle(&mut thread_rng());
TopologyRefresher {
validator_client: validator_client::ApiClient::new(cfg.validator_api_urls[0].clone()),
validator_client: validator_client::client::ApiClient::new(
cfg.validator_api_urls[0].clone(),
),
client_version: cfg.client_version,
validator_api_urls: cfg.validator_api_urls,
topology_accessor,
@@ -265,8 +259,8 @@ impl TopologyRefresher {
};
let mixnodes_count = mixnodes.len();
let topology =
nym_topology_from_bonds(mixnodes, gateways).filter_system_version(&self.client_version);
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
if !self.check_layer_distribution(&topology, mixnodes_count) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
@@ -303,10 +297,46 @@ impl TopologyRefresher {
self.topology_accessor.is_routable().await
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
loop {
tokio::time::sleep(self.refresh_rate).await;
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
spawn_future(async move {
debug!("Started TopologyRefresher with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while !shutdown.is_shutdown() {
tokio::select! {
_ = interval.next() => {
self.refresh().await;
},
_ = shutdown.recv() => {
log::trace!("TopologyRefresher: Received shutdown");
},
}
}
shutdown.recv_timeout().await;
log::debug!("TopologyRefresher: Exiting");
})
}
pub fn start(mut self) {
spawn_future(async move {
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while (interval.next().await).is_some() {
self.refresh().await;
}
})
+101 -57
View File
@@ -2,12 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
use config::NymConfig;
use nymsphinx::params::PacketSize;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::path::PathBuf;
use std::time::Duration;
use url::Url;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
pub mod persistence;
pub const MISSING_VALUE: &str = "MISSING VALUE";
@@ -30,7 +34,7 @@ pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Config<T> {
client: Client<T>,
@@ -38,17 +42,23 @@ pub struct Config<T> {
#[serde(default)]
logging: Logging,
#[serde(default)]
debug: Debug,
debug: DebugConfig,
}
impl<T: NymConfig> Config<T> {
pub fn new<S: Into<String>>(id: S) -> Self {
impl<T> Config<T> {
pub fn new<S: Into<String>>(id: S) -> Self
where
T: NymConfig,
{
let mut cfg = Config::default();
cfg.with_id(id);
cfg
}
pub fn with_id<S: Into<String>>(&mut self, id: S) {
pub fn with_id<S: Into<String>>(&mut self, id: S)
where
T: NymConfig,
{
let id = id.into();
// identity key setting
@@ -113,7 +123,7 @@ impl<T: NymConfig> Config<T> {
self.client.disabled_credentials_mode = disabled_credentials_mode;
}
pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpoint) {
pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) {
self.client.gateway_endpoint = gateway_endpoint;
}
@@ -121,14 +131,8 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_endpoint.gateway_id = id.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_private_key<S: Into<String>>(&mut self, eth_private_key: S) {
self.client.eth_private_key = eth_private_key.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_endpoint<S: Into<String>>(&mut self, eth_endpoint: S) {
self.client.eth_endpoint = eth_endpoint.into();
pub fn set_custom_validators(&mut self, validator_urls: Vec<Url>) {
self.client.validator_urls = validator_urls;
}
pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec<Url>) {
@@ -185,6 +189,10 @@ impl<T: NymConfig> Config<T> {
self.client.ack_key_file.clone()
}
pub fn get_validator_endpoints(&self) -> Vec<Url> {
self.client.validator_urls.clone()
}
pub fn get_validator_api_endpoints(&self) -> Vec<Url> {
self.client.validator_api_urls.clone()
}
@@ -201,7 +209,11 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_endpoint.gateway_listener.clone()
}
pub fn get_gateway_endpoint(&self) -> &GatewayEndpoint {
pub fn get_gateway_endpoint_config(&self) -> &GatewayEndpointConfig {
&self.client.gateway_endpoint
}
pub fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig {
&self.client.gateway_endpoint
}
@@ -209,17 +221,11 @@ impl<T: NymConfig> Config<T> {
self.client.database_path.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_endpoint(&self) -> String {
self.client.eth_endpoint.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_private_key(&self) -> String {
self.client.eth_private_key.clone()
}
// Debug getters
pub fn get_debug_config(&self) -> &DebugConfig {
&self.debug
}
pub fn get_average_packet_delay(&self) -> Duration {
self.debug.average_packet_delay
}
@@ -256,6 +262,18 @@ impl<T: NymConfig> Config<T> {
self.debug.topology_resolution_timeout
}
pub fn get_disabled_loop_cover_traffic_stream(&self) -> bool {
self.debug.disable_loop_cover_traffic_stream
}
pub fn get_disabled_main_poisson_packet_distribution(&self) -> bool {
self.debug.disable_main_poisson_packet_distribution
}
pub fn get_use_extended_packet_size(&self) -> Option<ExtendedPacketSize> {
self.debug.use_extended_packet_size
}
pub fn get_version(&self) -> &str {
&self.client.version
}
@@ -272,7 +290,8 @@ impl<T: NymConfig> Default for Config<T> {
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayEndpoint {
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
pub struct GatewayEndpointConfig {
/// gateway_id specifies ID of the gateway to which the client should send messages.
/// If initially omitted, a random gateway will be chosen from the available topology.
pub gateway_id: String,
@@ -284,10 +303,10 @@ pub struct GatewayEndpoint {
pub gateway_listener: String,
}
impl From<topology::gateway::Node> for GatewayEndpoint {
fn from(node: topology::gateway::Node) -> GatewayEndpoint {
impl From<topology::gateway::Node> for GatewayEndpointConfig {
fn from(node: topology::gateway::Node) -> GatewayEndpointConfig {
let gateway_listener = node.clients_address();
GatewayEndpoint {
GatewayEndpointConfig {
gateway_id: node.identity_key.to_base58_string(),
gateway_owner: node.owner,
gateway_listener,
@@ -295,7 +314,7 @@ impl From<topology::gateway::Node> for GatewayEndpoint {
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct Client<T> {
/// Version of the client for which this configuration was created.
#[serde(default = "missing_string_value")]
@@ -309,6 +328,10 @@ pub struct Client<T> {
#[serde(default)]
disabled_credentials_mode: bool,
/// Addresses to nymd validators via which the client can communicate with the chain.
#[serde(default)]
validator_urls: Vec<Url>,
/// Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls: Vec<Url>,
@@ -337,19 +360,11 @@ pub struct Client<T> {
reply_encryption_key_store_path: PathBuf,
/// Information regarding how the client should send data to gateway.
gateway_endpoint: GatewayEndpoint,
gateway_endpoint: GatewayEndpointConfig,
/// Path to the database containing bandwidth credentials of this client.
database_path: PathBuf,
/// Ethereum private key.
#[cfg(not(feature = "coconut"))]
eth_private_key: String,
/// Address to an Ethereum full node.
#[cfg(not(feature = "coconut"))]
eth_endpoint: String,
/// nym_home_directory specifies absolute path to the home nym Clients directory.
/// It is expected to use default value and hence .toml file should not redefine this field.
nym_root_directory: PathBuf,
@@ -365,6 +380,7 @@ impl<T: NymConfig> Default for Client<T> {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
disabled_credentials_mode: true,
validator_urls: vec![],
validator_api_urls: vec![],
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
@@ -375,10 +391,6 @@ impl<T: NymConfig> Default for Client<T> {
reply_encryption_key_store_path: Default::default(),
gateway_endpoint: Default::default(),
database_path: Default::default(),
#[cfg(not(feature = "coconut"))]
eth_private_key: "".to_string(),
#[cfg(not(feature = "coconut"))]
eth_endpoint: "".to_string(),
nym_root_directory: T::default_root_directory(),
super_struct: Default::default(),
}
@@ -418,70 +430,89 @@ impl<T: NymConfig> Client<T> {
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Logging {}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Debug {
pub struct DebugConfig {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
average_packet_delay: Duration,
pub average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
average_ack_delay: Duration,
pub average_ack_delay: Duration,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 1.
ack_wait_multiplier: f64,
pub ack_wait_multiplier: f64,
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
#[serde(with = "humantime_serde")]
ack_wait_addition: Duration,
pub ack_wait_addition: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
#[serde(with = "humantime_serde")]
loop_cover_traffic_average_delay: Duration,
pub loop_cover_traffic_average_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
#[serde(with = "humantime_serde")]
message_sending_average_delay: Duration,
pub message_sending_average_delay: Duration,
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
#[serde(with = "humantime_serde")]
gateway_response_timeout: Duration,
pub gateway_response_timeout: Duration,
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
#[serde(with = "humantime_serde")]
topology_refresh_rate: Duration,
pub topology_refresh_rate: Duration,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
#[serde(with = "humantime_serde")]
topology_resolution_timeout: Duration,
pub topology_resolution_timeout: Duration,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Controls whether the sent sphinx packet use a NON-DEFAULT bigger size.
pub use_extended_packet_size: Option<ExtendedPacketSize>,
}
impl Default for Debug {
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ExtendedPacketSize {
Extended8,
Extended16,
Extended32,
}
impl Default for DebugConfig {
fn default() -> Self {
Debug {
DebugConfig {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
@@ -491,6 +522,19 @@ impl Default for Debug {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_loop_cover_traffic_stream: false,
disable_main_poisson_packet_distribution: false,
use_extended_packet_size: None,
}
}
}
impl From<ExtendedPacketSize> for PacketSize {
fn from(size: ExtendedPacketSize) -> PacketSize {
match size {
ExtendedPacketSize::Extended8 => PacketSize::ExtendedPacket8,
ExtendedPacketSize::Extended16 => PacketSize::ExtendedPacket16,
ExtendedPacketSize::Extended32 => PacketSize::ExtendedPacket32,
}
}
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorageError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
#[derive(thiserror::Error, Debug)]
pub enum ClientCoreError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Gateway client error: {0}")]
GatewayClientError(#[from] GatewayClientError),
#[error("Ed25519 error: {0}")]
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[cfg(feature = "reply-surb")]
#[error("Reply key storage error: {0}")]
ReplyKeyStorageError(#[from] ReplyKeyStorageError),
#[error("No gateway with id: {0}")]
NoGatewayWithId(String),
#[error("No gateways on network")]
NoGatewaysOnNetwork,
#[error("List of validator apis is empty")]
ListOfValidatorApisIsEmpty,
#[error("Could not load existing gateway configuration: {0}")]
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
#[error("The current network topology seem to be insufficient to route any packets through")]
InsufficientNetworkTopology,
#[error("Unexpected exit")]
UnexpectedExit,
}
+37 -26
View File
@@ -14,25 +14,27 @@ use nymsphinx::addressing::nodes::NodeIdentity;
use rand::rngs::OsRng;
use rand::seq::SliceRandom;
use rand::thread_rng;
use tap::TapFallible;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
use crate::{
client::key_manager::KeyManager,
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
error::ClientCoreError,
};
pub async fn query_gateway_details(
validator_servers: Vec<Url>,
chosen_gateway_id: Option<&str>,
) -> gateway::Node {
) -> Result<gateway::Node, ClientCoreError> {
let validator_api = validator_servers
.choose(&mut thread_rng())
.expect("The list of validator apis is empty");
let validator_client = validator_client::ApiClient::new(validator_api.clone());
.ok_or(ClientCoreError::ListOfValidatorApisIsEmpty)?;
let validator_client = validator_client::client::ApiClient::new(validator_api.clone());
log::trace!("Fetching list of gateways from: {}", validator_api);
let gateways = validator_client.get_cached_gateways().await.unwrap();
let gateways = validator_client.get_cached_gateways().await?;
let valid_gateways = gateways
.into_iter()
.filter_map(|gateway| gateway.try_into().ok())
@@ -47,38 +49,40 @@ pub async fn query_gateway_details(
filtered_gateways
.iter()
.find(|gateway| gateway.identity_key.to_base58_string() == gateway_id)
.expect(&*format!("no gateway with id {} exists!", gateway_id))
.clone()
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_id.to_string()))
.cloned()
} else {
filtered_gateways
.choose(&mut rand::thread_rng())
.expect("there are no gateways on the network!")
.clone()
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
}
}
pub async fn register_with_gateway_and_store_keys<T>(
gateway_details: gateway::Node,
config: &Config<T>,
) where
) -> Result<(), ClientCoreError>
where
T: NymConfig,
{
let mut rng = OsRng;
let mut key_manager = KeyManager::new(&mut rng);
let shared_keys = register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
let shared_keys =
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await?;
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config);
key_manager
Ok(key_manager
.store_keys(&pathfinder)
.expect("Failed to generated keys");
.tap_err(|err| log::error!("Failed to generate keys: {err}"))?)
}
async fn register_with_gateway(
gateway: &gateway::Node,
our_identity: Arc<identity::KeyPair>,
) -> Arc<SharedKeys> {
) -> Result<Arc<SharedKeys>, ClientCoreError> {
let timeout = Duration::from_millis(1500);
let mut gateway_client = GatewayClient::new_init(
gateway.clients_address(),
@@ -86,52 +90,59 @@ async fn register_with_gateway(
gateway.owner.clone(),
our_identity.clone(),
timeout,
None,
);
gateway_client
.establish_connection()
.await
.expect("failed to establish connection with the gateway!");
gateway_client
.tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?;
let shared_keys = gateway_client
.perform_initial_authentication()
.await
.expect("failed to register with the gateway!")
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
Ok(shared_keys)
}
pub fn show_address<T>(config: &Config<T>)
pub fn show_address<T>(config: &Config<T>) -> Result<(), ClientCoreError>
where
T: config::NymConfig,
{
fn load_identity_keys(pathfinder: &ClientKeyPathfinder) -> identity::KeyPair {
fn load_identity_keys(
pathfinder: &ClientKeyPathfinder,
) -> Result<identity::KeyPair, ClientCoreError> {
let identity_keypair: identity::KeyPair =
pemstore::load_keypair(&pemstore::KeyPairPath::new(
pathfinder.private_identity_key().to_owned(),
pathfinder.public_identity_key().to_owned(),
))
.expect("Failed to read stored identity key files");
identity_keypair
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
Ok(identity_keypair)
}
fn load_sphinx_keys(pathfinder: &ClientKeyPathfinder) -> encryption::KeyPair {
fn load_sphinx_keys(
pathfinder: &ClientKeyPathfinder,
) -> Result<encryption::KeyPair, ClientCoreError> {
let sphinx_keypair: encryption::KeyPair =
pemstore::load_keypair(&pemstore::KeyPairPath::new(
pathfinder.private_encryption_key().to_owned(),
pathfinder.public_encryption_key().to_owned(),
))
.expect("Failed to read stored sphinx key files");
sphinx_keypair
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
Ok(sphinx_keypair)
}
let pathfinder = ClientKeyPathfinder::new_from_config(config);
let identity_keypair = load_identity_keys(&pathfinder);
let sphinx_keypair = load_sphinx_keys(&pathfinder);
let identity_keypair = load_identity_keys(&pathfinder)?;
let sphinx_keypair = load_sphinx_keys(&pathfinder)?;
let client_recipient = Recipient::new(
*identity_keypair.public_key(),
*sphinx_keypair.public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(config.get_gateway_id()).unwrap(),
NodeIdentity::from_base58_string(config.get_gateway_id())?,
);
println!("\nThe address of this client is: {}", client_recipient);
Ok(())
}
+20
View File
@@ -1,3 +1,23 @@
use std::future::Future;
pub mod client;
pub mod config;
pub mod error;
pub mod init;
#[cfg(target_arch = "wasm32")]
pub(crate) fn spawn_future<F>(future: F)
where
F: Future<Output = ()> + 'static,
{
wasm_bindgen_futures::spawn_local(future);
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn spawn_future<F>(future: F)
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
tokio::spawn(future);
}
+4 -2
View File
@@ -9,15 +9,17 @@ edition = "2021"
async-trait = "0.1.52"
bip39 = "1.0.1"
cfg-if = "0.1"
clap = { version = "3.0.10", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
pickledb = "0.4.1"
rand = "0.7.3"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
url = "2.2"
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
coconut-interface = { path = "../../common/coconut-interface" }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
+3 -4
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::Result;
use crate::{MNEMONIC, NYMD_URL};
use bip39::Mnemonic;
use network_defaults::{NymNetworkDetails, VOUCHER_INFO};
use std::str::FromStr;
@@ -17,9 +16,9 @@ pub(crate) struct Client {
}
impl Client {
pub fn new() -> Self {
let nymd_url = Url::from_str(NYMD_URL).unwrap();
let mnemonic = Mnemonic::from_str(MNEMONIC).unwrap();
pub fn new(nymd_url: &str, mnemonic: &str) -> Self {
let nymd_url = Url::from_str(nymd_url).unwrap();
let mnemonic = Mnemonic::from_str(mnemonic).unwrap();
let network_details = NymNetworkDetails::new_from_env();
let config = nymd::Config::try_from_nym_network_details(&network_details)
.expect("failed to construct valid validator client config with the provided network");
+30 -7
View File
@@ -3,10 +3,10 @@
use async_trait::async_trait;
use clap::{Args, Subcommand};
use completions::ArgShell;
use pickledb::PickleDb;
use rand::rngs::OsRng;
use std::str::FromStr;
use url::Url;
use coconut_interface::{Attribute, Base58, BlindSignRequest, Bytable, Parameters};
use credential_storage::storage::Storage;
@@ -14,13 +14,13 @@ use credential_storage::PersistentStorage;
use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use credentials::coconut::utils::obtain_aggregate_signature;
use crypto::asymmetric::{encryption, identity};
use network_defaults::VOUCHER_INFO;
use network_defaults::{NymNetworkDetails, VOUCHER_INFO};
use validator_client::nymd::tx::Hash;
use validator_client::{CoconutApiClient, Config};
use crate::client::Client;
use crate::error::{CredentialClientError, Result};
use crate::state::{KeyPair, RequestData, State};
use crate::SIGNER_AUTHORITIES;
#[derive(Subcommand)]
pub(crate) enum Commands {
@@ -30,6 +30,12 @@ pub(crate) enum Commands {
ListDeposits(ListDeposits),
/// Get a credential for a given deposit
GetCredential(GetCredential),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
#[async_trait]
@@ -39,6 +45,12 @@ pub(crate) trait Execute {
#[derive(Args, Clone)]
pub(crate) struct Deposit {
/// The nymd URL that should be used
#[clap(long)]
nymd_url: String,
/// A mnemonic for the account that does the deposit
#[clap(long)]
mnemonic: String,
/// The amount that needs to be deposited
#[clap(long)]
amount: u64,
@@ -51,7 +63,7 @@ impl Execute for Deposit {
let signing_keypair = KeyPair::from(identity::KeyPair::new(&mut rng));
let encryption_keypair = KeyPair::from(encryption::KeyPair::new(&mut rng));
let client = Client::new();
let client = Client::new(&self.nymd_url, &self.mnemonic);
let tx_hash = client
.deposit(
self.amount,
@@ -96,6 +108,9 @@ pub(crate) struct GetCredential {
/// The hash of a successful deposit transaction
#[clap(long)]
tx_hash: String,
/// The nymd URL that should be used
#[clap(long)]
nymd_url: String,
/// If we want to get the signature without attaching a blind sign request; it is expected that
/// there is already a signature stored on the signer
#[clap(long, parse(from_flag))]
@@ -108,7 +123,11 @@ impl Execute for GetCredential {
let mut state = db
.get::<State>(&self.tx_hash)
.ok_or(CredentialClientError::NoDeposit)?;
let urls = SIGNER_AUTHORITIES.map(|addr| Url::from_str(addr).unwrap());
let network_details = NymNetworkDetails::new_from_env();
let config = Config::try_from_nym_network_details(&network_details)?;
let client = validator_client::Client::new_query(config)?;
let coconut_api_clients = CoconutApiClient::all_coconut_api_clients(&client).await?;
let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let bandwidth_credential_attributes = if self.__no_request {
@@ -162,8 +181,12 @@ impl Execute for GetCredential {
)?);
db.set(&self.tx_hash, &state).unwrap();
let signature =
obtain_aggregate_signature(&params, &bandwidth_credential_attributes, &urls).await?;
let signature = obtain_aggregate_signature(
&params,
&bandwidth_credential_attributes,
&coconut_api_clients,
)
.await?;
shared_storage
.insert_coconut_credential(
state.amount.to_string(),
+4
View File
@@ -8,6 +8,7 @@ use credentials::error::Error as CredentialError;
use crypto::asymmetric::encryption::KeyRecoveryError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use validator_client::nymd::error::NymdError;
use validator_client::ValidatorClientError;
pub type Result<T> = std::result::Result<T, CredentialClientError>;
@@ -16,6 +17,9 @@ pub enum CredentialClientError {
#[error("Nymd error: {0}")]
Nymd(#[from] NymdError),
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("Credential error: {0}")]
Credential(#[from] CredentialError),
+19 -8
View File
@@ -11,20 +11,26 @@ cfg_if::cfg_if! {
use commands::{Commands, Execute};
use error::Result;
use network_defaults::setup_env;
use clap::CommandFactory;
use completions::fig_generate;
use clap::Parser;
use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
pub const MNEMONIC: &str = "jazz fatigue diagram account outer wrist slide cherry mother grid network pause wolf pig round answer mail junior better hair dismiss toward access end";
pub const NYMD_URL: &str = "http://127.0.0.1:26657";
pub const CONTRACT_ADDRESS: &str = "nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s";
pub const SIGNER_AUTHORITIES: [&str; 1] = [
"http://127.0.0.1:8080",
];
#[derive(Parser)]
#[clap(author = "Nymtech", version, about)]
struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
/// Path where the sqlite credental database will be located.
/// It should point to a $HOME/$CLIENT_ID/data/db.sqlite file of
/// the client that is supposed to use the credential.
#[clap(long)]
pub(crate) credential_db_path: std::path::PathBuf,
#[clap(subcommand)]
command: Commands,
}
@@ -32,8 +38,9 @@ cfg_if::cfg_if! {
#[tokio::main]
async fn main() -> Result<()> {
let args = Cli::parse();
setup_env(args.config_env_file.clone());
let shared_storage = credential_storage::initialise_storage(std::path::PathBuf::from("/tmp/credential.db")).await;
let shared_storage = credential_storage::initialise_storage(args.credential_db_path.clone()).await;
let mut db = match PickleDb::load(
"credential.db",
PickleDbDumpPolicy::AutoDump,
@@ -47,10 +54,14 @@ cfg_if::cfg_if! {
),
};
let bin_name = "nym-credential-client";
match &args.command {
Commands::Deposit(m) => m.execute(&mut db, shared_storage).await?,
Commands::ListDeposits(m) => m.execute(&mut db, shared_storage).await?,
Commands::GetCredential(m) => m.execute(&mut db, shared_storage).await?,
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name)
}
Ok(())
+14 -9
View File
@@ -1,10 +1,10 @@
[package]
name = "nym-client"
version = "1.0.1"
version = "1.1.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
rust-version = "1.56"
rust-version = "1.65"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -20,36 +20,41 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it
# and the single instance of abortable we have should really be refactored anyway
url = "2.2"
clap = { version = "3.2.8", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
dirs = "4.0"
log = "0.4" # self explanatory
pretty_env_logger = "0.4" # for formatting log messages
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
sled = "0.34" # for storage of replySURB decryption keys
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal"] } # async runtime
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime
tokio-tungstenite = "0.14" # websocket
## internal
client-core = { path = "../client-core" }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
credential-storage = { path = "../../common/credential-storage" }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
logging = { path = "../../common/logging"}
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
network-defaults = { path = "../../common/network-defaults" }
nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
task = { path = "../../common/task" }
topology = { path = "../../common/topology" }
websocket-requests = { path = "websocket-requests" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path = "../../common/version-checker" }
network-defaults = { path = "../../common/network-defaults" }
websocket-requests = { path = "websocket-requests" }
tap = "1.0.1"
[features]
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
eth = []
[dev-dependencies]
serde_json = "1.0" # for the "textsend" example
+103 -21
View File
@@ -27,6 +27,58 @@
"node": ">=10.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3529,13 +3581,13 @@
}
},
"node_modules/terser": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"bin": {
@@ -3583,14 +3635,6 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"node_modules/terser/node_modules/source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"engines": {
"node": ">= 8"
}
},
"node_modules/thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -4330,6 +4374,49 @@
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
"dev": true
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
},
"@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
},
"@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"requires": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"@jridgewell/trace-mapping": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -7058,13 +7145,13 @@
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
},
"terser": {
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"requires": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"dependencies": {
@@ -7072,11 +7159,6 @@
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
}
}
},
@@ -43,6 +43,7 @@ async fn send_file_with_reply() {
recipient,
message: read_data,
with_reply_surb: true,
connection_id: Some(0),
};
println!("sending content of 'dummy_file' over the mix network...");
@@ -91,6 +92,7 @@ async fn send_file_without_reply() {
recipient,
message: read_data,
with_reply_surb: false,
connection_id: Some(0),
};
println!("sending content of 'dummy_file' over the mix network...");
+8
View File
@@ -27,6 +27,10 @@ impl SocketType {
_ => SocketType::None,
}
}
pub fn is_websocket(&self) -> bool {
matches!(self, SocketType::WebSocket)
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
@@ -50,6 +54,10 @@ impl NymConfig for Config {
.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()
}
+7 -6
View File
@@ -23,6 +23,13 @@ id = '{{ client.id }}'
# to claim bandwidth without presenting bandwidth credentials.
disabled_credentials_mode = {{ client.disabled_credentials_mode }}
# Addresses to nymd validators via which the client can communicate with the chain.
validator_urls = [
{{#each client.validator_urls }}
'{{this}}',
{{/each}}
]
# Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [
{{#each client.validator_api_urls }}
@@ -49,12 +56,6 @@ reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to the database containing bandwidth credentials
database_path = '{{ client.database_path }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options #####
# A gateway specific, optional, base58 stringified shared key used for
+164 -337
View File
@@ -1,292 +1,216 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
};
use crate::client::config::Config;
use crate::error::ClientError;
use crate::websocket;
use client_connections::TransmissionLane;
use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
use client_core::client::inbound_messages::InputMessage;
use client_core::client::key_manager::KeyManager;
use client_core::client::mix_traffic::{
BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController,
};
use client_core::client::real_messages_control;
use client_core::client::real_messages_control::RealMessagesController;
use client_core::client::received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestReceiver, ReceivedBufferRequestSender,
ReceivedMessagesBufferController, ReconstructedMessagesReceiver,
};
use client_core::client::reply_key_storage::ReplyKeyStorage;
use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use crate::client::config::{Config, SocketType};
use crate::websocket;
use task::{wait_for_signal, ShutdownNotifier};
pub(crate) mod config;
pub struct NymClient {
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,
/// Channel used for transforming 'raw' messages into sphinx packets and sending them
/// through the mix network.
/// It is only available if the client started with the websocket listener disabled.
input_tx: Option<InputMessageSender>,
/// Channel used for obtaining reconstructed messages received from the mix network.
/// It is only available if the client started with the websocket listener disabled.
receive_tx: Option<ReconstructedMessagesReceiver>,
}
impl NymClient {
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");
NymClient {
SocketClient {
config,
key_manager,
input_tx: None,
receive_tx: None,
}
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(self.config.get_base().get_gateway_id()).unwrap(),
)
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
) {
info!("Starting loop cover traffic stream...");
LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_average_packet_delay(),
self.config
.get_base()
.get_loop_cover_traffic_average_delay(),
mix_tx,
self.as_mix_recipient(),
topology_accessor,
)
.start();
}
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
) {
let controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(),
self.config.get_base().get_ack_wait_multiplier(),
self.config.get_base().get_ack_wait_addition(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_message_sending_average_delay(),
self.config.get_base().get_average_packet_delay(),
self.as_mix_recipient(),
);
info!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
reply_key_storage,
)
.start();
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
reply_key_storage: ReplyKeyStorage,
) {
info!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
reply_key_storage,
)
.start()
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
) -> GatewayClient {
let gateway_id = self.config.get_base().get_gateway_id();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_owner = self.config.get_base().get_gateway_owner();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.config.get_base().get_gateway_listener();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
async fn create_bandwidth_controller(config: &Config) -> BandwidthController {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
self.config.get_base().get_validator_api_endpoints(),
);
let bandwidth_controller = {
let details = network_defaults::NymNetworkDetails::new_from_env();
let client_config = validator_client::Config::try_from_nym_network_details(&details)
.expect("failed to construct validator client config");
let client = validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
let coconut_api_clients =
validator_client::CoconutApiClient::all_coconut_api_clients(&client)
.await
.expect("Could not query api clients");
BandwidthController::new(
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
coconut_api_clients,
)
};
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
if self.config.get_base().get_disabled_credentials_mode() {
gateway_client.set_disabled_credentials_mode(true)
}
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
);
}
info!("Starting topology refresher...");
topology_refresher.start();
}
// controller for sending sphinx 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(
&mut self,
mix_rx: BatchMixMessageReceiver,
gateway_client: GatewayClient,
) {
info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client).start();
bandwidth_controller
}
fn start_websocket_listener(
&self,
buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender,
config: &Config,
client_input: ClientInput,
client_output: ClientOutput,
self_address: Recipient,
) {
info!("Starting websocket listener...");
let websocket_handler =
websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient());
let ClientInput {
shared_lane_queue_lengths,
connection_command_sender,
input_sender,
} = client_input;
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
let received_buffer_request_sender = client_output.received_buffer_request_sender;
let websocket_handler = websocket::Handler::new(
input_sender,
connection_command_sender,
received_buffer_request_sender,
self_address,
shared_lane_queue_lengths,
);
websocket::Listener::new(config.get_listening_port()).start(websocket_handler);
}
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
pub async fn run_socket_forever(self) -> Result<(), ClientError> {
let shutdown = self.start_socket().await?;
wait_for_signal().await;
println!(
"Received signal - the client will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)."
);
log::info!("Sending shutdown");
shutdown.signal_shutdown().ok();
// Some of these components have shutdown signalling implemented as part of socks5 work,
// but since it's not fully implemented (yet) for all the components of the native client,
// we don't try to wait and instead just stop immediately.
//log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
//shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-client");
Ok(())
}
pub async fn start_socket(self) -> Result<ShutdownNotifier, ClientError> {
if !self.config.get_socket_type().is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
let base_builder = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await),
);
let self_address = base_builder.as_mix_recipient();
let mut started_client = base_builder.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
Self::start_websocket_listener(&self.config, client_input, client_output, self_address);
info!("Client startup finished!");
info!("The address of this client is: {}", self_address);
Ok(started_client.shutdown_notifier)
}
pub async fn start_direct(self) -> Result<DirectClient, ClientError> {
if self.config.get_socket_type().is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
let base_client = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await),
);
let mut started_client = base_client.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
// register our receiver
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
client_output
.received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
Ok(DirectClient {
client_input,
reconstructed_receiver,
_shutdown_notifier: started_client.shutdown_notifier,
})
}
}
pub struct DirectClient {
client_input: ClientInput,
reconstructed_receiver: ReconstructedMessagesReceiver,
// we need to keep reference to this guy otherwise things will start dropping
_shutdown_notifier: ShutdownNotifier,
}
impl DirectClient {
/// EXPERIMENTAL DIRECT RUST API
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests)
pub async fn send_message(
&mut self,
recipient: Recipient,
message: Vec<u8>,
with_reply_surb: bool,
) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb, lane);
self.client_input
.input_sender
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
}
/// EXPERIMENTAL DIRECT RUST API
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests)
pub fn send_message(&mut self, recipient: Recipient, message: Vec<u8>, with_reply_surb: bool) {
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
self.input_tx
.as_ref()
.expect("start method was not called before!")
.unbounded_send(input_msg)
.unwrap();
}
/// EXPERIMENTAL DIRECT RUST API
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests)
pub fn send_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) {
pub async fn send_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) {
let input_msg = InputMessage::new_reply(reply_surb, message);
self.input_tx
.as_ref()
.expect("start method was not called before!")
.unbounded_send(input_msg)
.unwrap();
self.client_input
.input_sender
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
}
/// EXPERIMENTAL DIRECT RUST API
@@ -298,106 +222,9 @@ impl NymClient {
pub async fn wait_for_messages(&mut self) -> Vec<ReconstructedMessage> {
use futures::StreamExt;
self.receive_tx
.as_mut()
.expect("start method was not called before!")
self.reconstructed_receiver
.next()
.await
.expect("buffer controller seems to have somehow died!")
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) {
self.start().await;
if let Err(e) = tokio::signal::ctrl_c().await {
error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e
);
}
println!(
"Received SIGINT - the client will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)."
);
}
pub async fn start(&mut self) {
info!("Starting nym 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
// and would allow anyone to clone the sender channel
// sphinx_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
// sphinx_message_receiver is the receiver used by MixTrafficController that sends the actual traffic
let (sphinx_message_sender, sphinx_message_receiver) = mpsc::unbounded();
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
let reply_key_storage =
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
.expect("Failed to load reply key storage!");
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_key_storage.clone(),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
reply_key_storage,
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
);
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
match self.config.get_socket_type() {
SocketType::WebSocket => {
self.start_websocket_listener(received_buffer_request_sender, input_sender)
}
SocketType::None => {
// if we did not start the socket, it means we're running (supposedly) in the native mode
// and hence we should announce 'ourselves' to the buffer
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
self.receive_tx = Some(reconstructed_receiver);
self.input_tx = Some(input_sender);
}
}
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
}
}
+41 -56
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use clap::Args;
use client_core::config::GatewayEndpoint;
use client_core::{config::GatewayEndpointConfig, error::ClientCoreError};
use config::NymConfig;
use crate::{
@@ -10,9 +10,6 @@ use crate::{
commands::{override_config, OverrideConfig},
};
#[cfg(all(feature = "eth", not(feature = "coconut")))]
use crate::commands::{DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY};
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
@@ -28,9 +25,13 @@ pub(crate) struct Init {
#[clap(long)]
force_register_gateway: bool,
/// Comma separated list of rest endpoints of the validators
/// Comma separated list of rest endpoints of the nymd validators
#[clap(long)]
validators: Option<String>,
nymd_validators: Option<String>,
/// Comma separated list of rest endpoints of the API validators
#[clap(long)]
api_validators: Option<String>,
/// Whether to not start the websocket
#[clap(long)]
@@ -46,47 +47,23 @@ pub(crate) struct Init {
fastmode: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
/// --eth-private_key don't need to be set.
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_ENDPOINT))
)]
eth_endpoint: String,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_PRIVATE_KEY))
)]
eth_private_key: String,
}
impl From<Init> for OverrideConfig {
fn from(init_config: Init) -> Self {
OverrideConfig {
validators: init_config.validators,
nymd_validators: init_config.nymd_validators,
api_validators: init_config.api_validators,
disable_socket: init_config.disable_socket,
port: init_config.port,
fastmode: init_config.fastmode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: init_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Some(init_config.eth_private_key),
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Some(init_config.eth_endpoint),
}
}
}
@@ -121,7 +98,12 @@ pub(crate) async fn execute(args: &Init) {
let override_config_fields = OverrideConfig::from(args.clone());
config = override_config(config, override_config_fields);
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await;
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config)
.await
.unwrap_or_else(|err| {
eprintln!("Failed to setup gateway\nError: {err}");
std::process::exit(1)
});
config.get_base_mut().with_gateway_endpoint(gateway);
let config_save_location = config.get_config_file_save_location();
@@ -139,7 +121,10 @@ pub(crate) async fn execute(args: &Init) {
);
println!("Client configuration completed.");
client_core::init::show_address(config.get_base());
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
eprintln!("Failed to show address\nError: {err}");
std::process::exit(1)
});
}
async fn setup_gateway(
@@ -147,7 +132,7 @@ async fn setup_gateway(
register: bool,
user_chosen_gateway_id: Option<&str>,
config: &Config,
) -> GatewayEndpoint {
) -> Result<GatewayEndpointConfig, ClientCoreError> {
if register {
// Get the gateway details by querying the validator-api. Either pick one at random or use
// the chosen one if it's among the available ones.
@@ -156,16 +141,16 @@ async fn setup_gateway(
config.get_base().get_validator_api_endpoints(),
user_chosen_gateway_id,
)
.await;
.await?;
log::debug!("Querying gateway gives: {}", gateway);
// Registering with gateway by setting up and writing shared keys to disk
log::trace!("Registering gateway");
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
.await;
.await?;
println!("Saved all generated keys");
gateway.into()
Ok(gateway.into())
} else if user_chosen_gateway_id.is_some() {
// Just set the config, don't register or create any keys
// This assumes that the user knows what they are doing, and that the existing keys are
@@ -175,22 +160,22 @@ async fn setup_gateway(
config.get_base().get_validator_api_endpoints(),
user_chosen_gateway_id,
)
.await;
.await?;
log::debug!("Querying gateway gives: {}", gateway);
gateway.into()
Ok(gateway.into())
} else {
println!("Not registering gateway, will reuse existing config and keys");
match Config::load_from_file(Some(id)) {
Ok(existing_config) => existing_config.get_base().get_gateway_endpoint().clone(),
Err(err) => {
panic!(
"Unable to configure gateway: {err}. \n
Seems like the client was already initialized but it was not possible to read \
the existing configuration file. \n
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
removing the existing configuration and starting over."
)
}
}
let existing_config = Config::load_from_file(Some(id)).map_err(|err| {
log::error!(
"Unable to configure gateway: {err}. \n
Seems like the client was already initialized but it was not possible to read \
the existing configuration file. \n
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
removing the existing configuration and starting over."
);
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
})?;
Ok(existing_config.get_base().get_gateway_endpoint().clone())
}
}
+35 -50
View File
@@ -2,15 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use url::Url;
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
use completions::{fig_generate, ArgShell};
pub(crate) mod init;
pub(crate) mod run;
@@ -55,7 +50,7 @@ fn long_version_static() -> &'static str {
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(long)]
#[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
@@ -70,55 +65,61 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
pub(crate) struct OverrideConfig {
validators: Option<String>,
nymd_validators: Option<String>,
api_validators: Option<String>,
disable_socket: bool,
port: Option<u16>,
fastmode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Option<String>,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Option<String>,
}
pub(crate) async fn execute(args: &Cli) {
pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> {
let bin_name = "nym-native-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Run(m) => run::execute(m).await?,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
}
fn parse_validators(raw: &str) -> Vec<Url> {
raw.split(',')
.map(|raw_validator| {
raw_validator
.trim()
.parse()
.expect("one of the provided validator api urls is invalid")
})
.collect()
Ok(())
}
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
if let Some(raw_validators) = args.validators {
if let Some(raw_validators) = args.nymd_validators {
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
.set_custom_validators(config::parse_validators(&raw_validators));
} else if std::env::var(network_defaults::var_names::CONFIGURED).is_ok() {
let raw_validators = std::env::var(network_defaults::var_names::NYMD_VALIDATOR)
.expect("nymd validator not set");
config
.get_base_mut()
.set_custom_validators(config::parse_validators(&raw_validators));
}
if let Some(raw_validators) = args.api_validators {
config
.get_base_mut()
.set_custom_validator_apis(config::parse_validators(&raw_validators));
} else if std::env::var(network_defaults::var_names::CONFIGURED).is_ok() {
let raw_validators = std::env::var(network_defaults::var_names::API_VALIDATOR)
.expect("api validator not set");
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
.set_custom_validator_apis(config::parse_validators(&raw_validators));
}
if args.disable_socket {
@@ -129,27 +130,11 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config = config.with_port(port);
}
#[cfg(all(not(feature = "eth"), not(feature = "coconut")))]
{
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT.to_string());
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
}
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[cfg(feature = "coconut")]
{
if args.enabled_credentials_mode {
config.get_base_mut().with_disabled_credentials(false)
}
if let Some(eth_endpoint) = args.eth_endpoint {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
if let Some(eth_private_key) = args.eth_private_key {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
}
if args.fastmode {
+19 -33
View File
@@ -2,8 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{
client::{config::Config, NymClient},
client::{config::Config, SocketClient},
commands::{override_config, OverrideConfig},
error::ClientError,
};
use clap::Args;
@@ -17,9 +18,13 @@ pub(crate) struct Run {
#[clap(long)]
id: String,
/// Comma separated list of rest endpoints of the validators
/// Comma separated list of rest endpoints of the nymd validators
#[clap(long)]
validators: Option<String>,
nymd_validators: Option<String>,
/// Comma separated list of rest endpoints of the API validators
#[clap(long)]
api_validators: Option<String>,
/// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
/// ensure prior registration happened
@@ -35,41 +40,22 @@ pub(crate) struct Run {
port: Option<u16>,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
/// --eth-private-key don't need to be set.
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_endpoint: Option<String>,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_private_key: Option<String>,
}
impl From<Run> for OverrideConfig {
fn from(run_config: Run) -> Self {
OverrideConfig {
validators: run_config.validators,
nymd_validators: run_config.nymd_validators,
api_validators: run_config.api_validators,
disable_socket: run_config.disable_socket,
port: run_config.port,
fastmode: false,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: run_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: run_config.eth_private_key,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: run_config.eth_endpoint,
}
}
}
@@ -82,7 +68,7 @@ fn version_check(cfg: &Config) -> bool {
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);
warn!("The native-client binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
if is_minor_version_compatible(binary_version, config_version) {
info!("but they are still semver compatible. However, consider running the `upgrade` command");
true
@@ -93,14 +79,14 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) {
pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
return;
return Err(ClientError::FailedToLoadConfig(id.to_string()));
}
};
@@ -109,8 +95,8 @@ pub(crate) async fn execute(args: &Run) {
if !version_check(&config) {
error!("failed the local version check");
return;
return Err(ClientError::FailedLocalVersionCheck);
}
NymClient::new(config).run_forever().await;
SocketClient::new(config).run_socket_forever().await
}
+2 -2
View File
@@ -103,7 +103,7 @@ fn minor_0_12_upgrade(
Version::new(0, 12, 0)
};
print_start_upgrade(&config_version, &to_version);
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
@@ -111,7 +111,7 @@ fn minor_0_12_upgrade(
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&config_version, &to_version);
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
+15
View File
@@ -0,0 +1,15 @@
use client_core::error::ClientCoreError;
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
#[error("Failed local version check, client and config mismatch")]
FailedLocalVersionCheck,
#[error("Attempted to start the client in invalid socket mode")]
InvalidSocketMode,
}
+1
View File
@@ -2,4 +2,5 @@
// SPDX-License-Identifier: Apache-2.0
pub mod client;
pub mod error;
pub mod websocket;
+5 -24
View File
@@ -2,20 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use error::ClientError;
use logging::setup_logging;
use network_defaults::setup_env;
pub mod client;
pub mod commands;
pub mod error;
pub mod websocket;
#[tokio::main]
async fn main() {
async fn main() -> Result<(), ClientError> {
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
commands::execute(&args).await
}
fn banner() -> String {
@@ -34,25 +37,3 @@ fn banner() -> String {
crate_version!()
)
}
fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
log_builder.parse_filters(&s);
} else {
// default to 'Info'
log_builder.filter(None, log::LevelFilter::Info);
}
log_builder
.filter_module("hyper", log::LevelFilter::Warn)
.filter_module("tokio_reactor", log::LevelFilter::Warn)
.filter_module("reqwest", log::LevelFilter::Warn)
.filter_module("mio", log::LevelFilter::Warn)
.filter_module("want", log::LevelFilter::Warn)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.filter_module("handlebars", log::LevelFilter::Warn)
.filter_module("sled", log::LevelFilter::Warn)
.init();
}
+126 -50
View File
@@ -1,6 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_connections::{
ConnectionCommand, ConnectionCommandSender, LaneQueueLengths, TransmissionLane,
};
use client_core::client::{
inbound_messages::{InputMessage, InputMessageSender},
received_buffer::{
@@ -34,10 +37,12 @@ impl Default for ReceivedResponseType {
pub(crate) struct Handler {
msg_input: InputMessageSender,
client_connection_tx: ConnectionCommandSender,
buffer_requester: ReceivedBufferRequestSender,
self_full_address: Recipient,
socket: Option<WebSocketStream<TcpStream>>,
received_response_type: ReceivedResponseType,
lane_queue_lengths: LaneQueueLengths,
}
// clone is used to use handler on a new connection, which initially is `None`
@@ -45,10 +50,12 @@ impl Clone for Handler {
fn clone(&self) -> Self {
Handler {
msg_input: self.msg_input.clone(),
client_connection_tx: self.client_connection_tx.clone(),
buffer_requester: self.buffer_requester.clone(),
self_full_address: self.self_full_address,
socket: None,
received_response_type: Default::default(),
lane_queue_lengths: self.lane_queue_lengths.clone(),
}
}
}
@@ -64,38 +71,85 @@ impl Drop for Handler {
impl Handler {
pub(crate) fn new(
msg_input: InputMessageSender,
client_connection_tx: ConnectionCommandSender,
buffer_requester: ReceivedBufferRequestSender,
self_full_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
) -> Self {
Handler {
msg_input,
client_connection_tx,
buffer_requester,
self_full_address,
socket: None,
received_response_type: Default::default(),
lane_queue_lengths,
}
}
fn handle_send(
async fn handle_send(
&mut self,
recipient: Recipient,
recipient: &Recipient,
message: Vec<u8>,
with_reply_surb: bool,
connection_id: Option<u64>,
) -> Option<ServerResponse> {
// the ack control is now responsible for chunking, etc.
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb);
self.msg_input.unbounded_send(input_msg).unwrap();
// We map the absence of a connection id as going into the general lane.
let lane = connection_id.map_or(TransmissionLane::General, |id| {
TransmissionLane::ConnectionId(id)
});
None
// the ack control is now responsible for chunking, etc.
let input_msg = InputMessage::new_fresh(*recipient, message, with_reply_surb, lane);
self.msg_input
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
let connection_id = match lane {
TransmissionLane::General
| TransmissionLane::Reply
| TransmissionLane::Retransmission
| TransmissionLane::Control => return None,
TransmissionLane::ConnectionId(id) => id,
};
// on receiving a send, we reply back the current lane queue length for that connection id.
// Note that this does _NOT_ take into account the packets that have been received but not
// yet reach `OutQueueControl`, so it might be a tad low.
let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() else {
log::warn!(
"Failed to get the lane queue length lock, \
not responding back with the current queue length"
);
return None;
};
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
Some(ServerResponse::LaneQueueLength(connection_id, queue_length))
}
fn handle_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) -> Option<ServerResponse> {
async fn handle_reply(
&mut self,
reply_surb: ReplySurb,
message: Vec<u8>,
) -> Option<ServerResponse> {
if message.len() > ReplySurb::max_msg_len(Default::default()) {
return Some(ServerResponse::new_error(format!("too long message to put inside a reply SURB. Received: {} bytes and maximum is {} bytes", message.len(), ReplySurb::max_msg_len(Default::default()))));
return Some(
ServerResponse::new_error(
format!(
"too long message to put inside a reply SURB. Received: {} bytes and maximum is {} bytes",
message.len(), ReplySurb::max_msg_len(Default::default()))
)
);
}
let input_msg = InputMessage::new_reply(reply_surb, message);
self.msg_input.unbounded_send(input_msg).unwrap();
self.msg_input
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
None
}
@@ -104,22 +158,48 @@ impl Handler {
ServerResponse::SelfAddress(self.self_full_address)
}
fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
fn handle_closed_connection(&self, connection_id: u64) -> Option<ServerResponse> {
self.client_connection_tx
.unbounded_send(ConnectionCommand::Close(connection_id))
.unwrap();
None
}
fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option<ServerResponse> {
let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() else {
log::warn!(
"Failed to get the lane queue length lock, not responding back with the current queue length"
);
return None;
};
let lane = TransmissionLane::ConnectionId(connection_id);
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
Some(ServerResponse::LaneQueueLength(connection_id, queue_length))
}
async fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
match request {
ClientRequest::Send {
recipient,
message,
with_reply_surb,
} => self.handle_send(recipient, message, with_reply_surb),
connection_id,
} => {
self.handle_send(&recipient, message, with_reply_surb, connection_id)
.await
}
ClientRequest::Reply {
message,
reply_surb,
} => self.handle_reply(reply_surb, message),
} => self.handle_reply(reply_surb, message).await,
ClientRequest::SelfAddress => Some(self.handle_self_address()),
ClientRequest::ClosedConnection(id) => self.handle_closed_connection(id),
ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id),
}
}
fn handle_text_message(&mut self, msg: String) -> Option<WsMessage> {
async fn handle_text_message(&mut self, msg: String) -> Option<WsMessage> {
debug!("Handling text message request");
trace!("Content: {:?}", msg);
@@ -128,13 +208,13 @@ impl Handler {
let response = match client_request {
Err(err) => Some(ServerResponse::Error(err)),
Ok(req) => self.handle_request(req),
Ok(req) => self.handle_request(req).await,
};
response.map(|resp| WsMessage::text(resp.into_text()))
}
fn handle_binary_message(&mut self, msg: Vec<u8>) -> Option<WsMessage> {
async fn handle_binary_message(&mut self, msg: &[u8]) -> Option<WsMessage> {
debug!("Handling binary message request");
self.received_response_type = ReceivedResponseType::Binary;
@@ -142,49 +222,23 @@ impl Handler {
let response = match client_request {
Err(err) => Some(ServerResponse::Error(err)),
Ok(req) => self.handle_request(req),
Ok(req) => self.handle_request(req).await,
};
response.map(|resp| WsMessage::Binary(resp.into_binary()))
}
fn handle_ws_request(&mut self, raw_request: WsMessage) -> Option<WsMessage> {
async fn handle_ws_request(&mut self, raw_request: WsMessage) -> Option<WsMessage> {
// apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore
// them and let's test that claim. If that's not the case, just copy code from
// old version of this file.
match raw_request {
WsMessage::Text(text_message) => self.handle_text_message(text_message),
WsMessage::Binary(binary_message) => self.handle_binary_message(binary_message),
WsMessage::Text(text_message) => self.handle_text_message(text_message).await,
WsMessage::Binary(binary_message) => self.handle_binary_message(&binary_message).await,
_ => None,
}
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_binary(
&self,
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Binary(resp.into_binary())))
.collect()
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_text(
&self,
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Text(resp.into_text())))
.collect()
}
async fn push_websocket_received_plaintexts(
&mut self,
reconstructed_messages: Vec<ReconstructedMessage>,
@@ -193,10 +247,8 @@ impl Handler {
// if it's text or binary, but for time being we use the naive assumption that if
// client is sending Message::Text it expects text back. Same for Message::Binary
let response_messages = match self.received_response_type {
ReceivedResponseType::Binary => {
self.prepare_reconstructed_binary(reconstructed_messages)
}
ReceivedResponseType::Text => self.prepare_reconstructed_text(reconstructed_messages),
ReceivedResponseType::Binary => prepare_reconstructed_binary(reconstructed_messages),
ReceivedResponseType::Text => prepare_reconstructed_text(reconstructed_messages),
};
let mut send_stream = futures::stream::iter(response_messages);
@@ -244,7 +296,7 @@ impl Handler {
break;
}
if let Some(response) = self.handle_ws_request(socket_msg) {
if let Some(response) = self.handle_ws_request(socket_msg).await {
if let Err(err) = self.send_websocket_response(response).await {
warn!(
"Failed to send message over websocket: {}. Assuming the connection is dead.",
@@ -291,3 +343,27 @@ impl Handler {
self.listen_for_requests(reconstructed_receiver).await;
}
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_binary(
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Binary(resp.into_binary())))
.collect()
}
// I'm still not entirely sure why `send_all` requires `TryStream` rather than `Stream`, but
// let's just play along for now
fn prepare_reconstructed_text(
reconstructed_messages: Vec<ReconstructedMessage>,
) -> Vec<Result<WsMessage, WsError>> {
reconstructed_messages
.into_iter()
.map(ServerResponse::Received)
.map(|resp| Ok(WsMessage::Text(resp.into_text())))
.collect()
}
+121 -15
View File
@@ -20,6 +20,12 @@ pub const REPLY_REQUEST_TAG: u8 = 0x01;
/// Value tag representing [`SelfAddress`] variant of the [`ClientRequest`]
pub const SELF_ADDRESS_REQUEST_TAG: u8 = 0x02;
/// Value tag representing [`ClosedConnection`] variant of the [`ClientRequest`]
pub const CLOSED_CONNECTION_REQUEST_TAG: u8 = 0x03;
/// Value tag representing [`GetLaneQueueLength`] variant of the [`ClientRequest`]
pub const GET_LANE_QUEUE_LENGHT_TAG: u8 = 0x04;
#[allow(non_snake_case)]
#[derive(Debug)]
pub enum ClientRequest {
@@ -28,32 +34,42 @@ pub enum ClientRequest {
message: Vec<u8>,
// Perhaps we could change it to a number to indicate how many reply_SURBs we want to include?
with_reply_surb: bool,
connection_id: Option<u64>,
},
Reply {
message: Vec<u8>,
reply_surb: ReplySurb,
},
SelfAddress,
ClosedConnection(u64),
GetLaneQueueLength(u64),
}
// we could have been parsing it directly TryFrom<WsMessage>, but we want to retain
// information about whether it came from binary or text to send appropriate response back
impl ClientRequest {
// SEND_REQUEST_TAG || with_surb || recipient || data_len || data
fn serialize_send(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Vec<u8> {
// SEND_REQUEST_TAG || with_surb || recipient || conn_id || data_len || data
fn serialize_send(
recipient: Recipient,
data: Vec<u8>,
with_reply_surb: bool,
connection_id: Option<u64>,
) -> Vec<u8> {
let data_len_bytes = (data.len() as u64).to_be_bytes();
let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes();
std::iter::once(SEND_REQUEST_TAG)
.chain(std::iter::once(with_reply_surb as u8))
.chain(recipient.to_bytes().iter().cloned()) // will not be length prefixed because the length is constant
.chain(conn_id_bytes.iter().cloned())
.chain(data_len_bytes.iter().cloned())
.chain(data.into_iter())
.collect()
}
// SEND_REQUEST_TAG || with_reply || recipient || data_len || data
// SEND_REQUEST_TAG || with_reply || recipient || conn_id || data_len || data
fn deserialize_send(b: &[u8]) -> Result<Self, error::Error> {
// we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + sizeof<u64> bytes
if b.len() < 2 + Recipient::LEN + size_of::<u64>() {
// we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + 2*sizeof<u64> bytes
if b.len() < 2 + Recipient::LEN + 2 * size_of::<u64>() {
return Err(error::Error::new(
ErrorKind::TooShortRequest,
"not enough data provided to recover 'send'".to_string(),
@@ -86,9 +102,20 @@ impl ClientRequest {
}
};
let data_len_bytes = &b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::<u64>()];
let mut connection_id_bytes = [0u8; size_of::<u64>()];
connection_id_bytes
.copy_from_slice(&b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::<u64>()]);
let connection_id = u64::from_be_bytes(connection_id_bytes);
let connection_id = if connection_id == 0 {
None
} else {
Some(connection_id)
};
let data_len_bytes =
&b[2 + Recipient::LEN + size_of::<u64>()..2 + Recipient::LEN + 2 * size_of::<u64>()];
let data_len = u64::from_be_bytes(data_len_bytes.try_into().unwrap());
let data = &b[2 + Recipient::LEN + size_of::<u64>()..];
let data = &b[2 + Recipient::LEN + 2 * size_of::<u64>()..];
if data.len() as u64 != data_len {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
@@ -104,11 +131,12 @@ impl ClientRequest {
with_reply_surb,
recipient,
message: data.to_vec(),
connection_id,
})
}
// REPLY_REQUEST_TAG || surb_len || surb || message_len || message
fn serialize_reply(message: Vec<u8>, reply_surb: ReplySurb) -> Vec<u8> {
fn serialize_reply(message: Vec<u8>, reply_surb: &ReplySurb) -> Vec<u8> {
let reply_surb_bytes = reply_surb.to_bytes();
let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_bytes();
let message_len_bytes = (message.len() as u64).to_be_bytes();
@@ -202,20 +230,79 @@ impl ClientRequest {
ClientRequest::SelfAddress
}
// CLOSED_CONNECTION_REQUEST_TAG
fn serialize_closed_connection(connection_id: u64) -> Vec<u8> {
let conn_id_bytes = connection_id.to_be_bytes();
std::iter::once(CLOSED_CONNECTION_REQUEST_TAG)
.chain(conn_id_bytes.iter().copied())
.collect()
}
// CLOSED_CONNECTION_REQUEST_TAG
fn deserialize_closed_connection(b: &[u8]) -> Result<Self, error::Error> {
if b.len() != 1 + size_of::<u64>() {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
"the received closed connection has invalid length".to_string(),
));
}
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], CLOSED_CONNECTION_REQUEST_TAG);
let mut connection_id_bytes = [0u8; size_of::<u64>()];
connection_id_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
let connection_id = u64::from_be_bytes(connection_id_bytes);
Ok(ClientRequest::ClosedConnection(connection_id))
}
// GET_LANE_QUEUE_LENGHT_TAG
fn serialize_get_lane_queue_lengths(connection_id: u64) -> Vec<u8> {
let conn_id_bytes = connection_id.to_be_bytes();
std::iter::once(GET_LANE_QUEUE_LENGHT_TAG)
.chain(conn_id_bytes.iter().copied())
.collect()
}
// GET_LANE_QUEUE_LENGHT_TAG
fn deserialize_get_lane_queue_length(b: &[u8]) -> Result<Self, error::Error> {
if b.len() != 1 + size_of::<u64>() {
return Err(error::Error::new(
ErrorKind::MalformedRequest,
"the received get lane queue length has invalid length".to_string(),
));
}
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], GET_LANE_QUEUE_LENGHT_TAG);
let mut connection_id_bytes = [0u8; size_of::<u64>()];
connection_id_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
let connection_id = u64::from_be_bytes(connection_id_bytes);
Ok(ClientRequest::GetLaneQueueLength(connection_id))
}
pub fn serialize(self) -> Vec<u8> {
match self {
ClientRequest::Send {
recipient,
message,
with_reply_surb,
} => Self::serialize_send(recipient, message, with_reply_surb),
connection_id,
} => Self::serialize_send(recipient, message, with_reply_surb, connection_id),
ClientRequest::Reply {
message,
reply_surb,
} => Self::serialize_reply(message, reply_surb),
} => Self::serialize_reply(message, &reply_surb),
ClientRequest::SelfAddress => Self::serialize_self_address(),
ClientRequest::ClosedConnection(id) => Self::serialize_closed_connection(id),
ClientRequest::GetLaneQueueLength(id) => Self::serialize_get_lane_queue_lengths(id),
}
}
@@ -245,15 +332,17 @@ impl ClientRequest {
SEND_REQUEST_TAG => Self::deserialize_send(b),
REPLY_REQUEST_TAG => Self::deserialize_reply(b),
SELF_ADDRESS_REQUEST_TAG => Ok(Self::deserialize_self_address(b)),
CLOSED_CONNECTION_REQUEST_TAG => Self::deserialize_closed_connection(b),
GET_LANE_QUEUE_LENGHT_TAG => Self::deserialize_get_lane_queue_length(b),
n => Err(error::Error::new(
ErrorKind::UnknownRequest,
format!("type {}", n),
format!("type {n}"),
)),
}
}
pub fn try_from_binary(raw_req: Vec<u8>) -> Result<Self, error::Error> {
Self::deserialize(&raw_req)
pub fn try_from_binary(raw_req: &[u8]) -> Result<Self, error::Error> {
Self::deserialize(raw_req)
}
pub fn try_from_text(raw_req: String) -> Result<Self, error::Error> {
@@ -280,6 +369,7 @@ mod tests {
recipient,
message: b"foomp".to_vec(),
with_reply_surb: false,
connection_id: Some(42),
};
let bytes = send_request_no_surb.serialize();
@@ -289,10 +379,12 @@ mod tests {
recipient,
message,
with_reply_surb,
connection_id,
} => {
assert_eq!(recipient.to_string(), recipient_string);
assert_eq!(message, b"foomp".to_vec());
assert!(!with_reply_surb)
assert!(!with_reply_surb);
assert_eq!(connection_id, Some(42))
}
_ => unreachable!(),
}
@@ -301,6 +393,7 @@ mod tests {
recipient,
message: b"foomp".to_vec(),
with_reply_surb: true,
connection_id: None,
};
let bytes = send_request_surb.serialize();
@@ -310,10 +403,12 @@ mod tests {
recipient,
message,
with_reply_surb,
connection_id,
} => {
assert_eq!(recipient.to_string(), recipient_string);
assert_eq!(message, b"foomp".to_vec());
assert!(with_reply_surb)
assert!(with_reply_surb);
assert_eq!(connection_id, None)
}
_ => unreachable!(),
}
@@ -352,4 +447,15 @@ mod tests {
_ => unreachable!(),
}
}
#[test]
fn close_connection_request_serialization_works() {
let close_connection_request = ClientRequest::ClosedConnection(42);
let bytes = close_connection_request.serialize();
let recovered = ClientRequest::deserialize(&bytes).unwrap();
match recovered {
ClientRequest::ClosedConnection(id) => assert_eq!(id, 42),
_ => unreachable!(),
}
}
}
@@ -23,10 +23,14 @@ pub const RECEIVED_RESPONSE_TAG: u8 = 0x01;
/// Value tag representing [`SelfAddress`] variant of the [`ServerResponse`]
pub const SELF_ADDRESS_RESPONSE_TAG: u8 = 0x02;
/// Value tag representing [`LaneQueueLength`] variant of the [`ServerResponse`]
pub const LANE_QUEUE_LENGTH_RESPONSE_TAG: u8 = 0x03;
#[derive(Debug)]
pub enum ServerResponse {
Received(ReconstructedMessage),
SelfAddress(Recipient),
LaneQueueLength(u64, usize),
Error(error::Error),
}
@@ -193,6 +197,31 @@ impl ServerResponse {
Ok(ServerResponse::SelfAddress(recipient))
}
// LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length
fn serialize_lane_queue_length(lane: u64, queue_length: usize) -> Vec<u8> {
std::iter::once(LANE_QUEUE_LENGTH_RESPONSE_TAG)
.chain(lane.to_be_bytes().iter().cloned())
.chain(queue_length.to_be_bytes().iter().cloned())
.collect()
}
// LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length
fn deserialize_lane_queue_length(b: &[u8]) -> Result<Self, error::Error> {
// this MUST match because it was called by 'deserialize'
debug_assert_eq!(b[0], LANE_QUEUE_LENGTH_RESPONSE_TAG);
let mut lane_bytes = [0u8; size_of::<u64>()];
lane_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
let lane = u64::from_be_bytes(lane_bytes);
let mut queue_length_bytes = [0u8; size_of::<usize>()];
queue_length_bytes
.copy_from_slice(&b[1 + size_of::<u64>()..1 + size_of::<u64>() + size_of::<usize>()]);
let queue_length = usize::from_be_bytes(queue_length_bytes);
Ok(ServerResponse::LaneQueueLength(lane, queue_length))
}
// ERROR_RESPONSE_TAG || err_code || msg_len || msg
fn serialize_error(error: error::Error) -> Vec<u8> {
let message_len_bytes = (error.message.len() as u64).to_be_bytes();
@@ -272,6 +301,9 @@ impl ServerResponse {
Self::serialize_received(reconstructed_message)
}
ServerResponse::SelfAddress(address) => Self::serialize_self_address(address),
ServerResponse::LaneQueueLength(lane, queue_length) => {
Self::serialize_lane_queue_length(lane, queue_length)
}
ServerResponse::Error(err) => Self::serialize_error(err),
}
}
@@ -302,6 +334,7 @@ impl ServerResponse {
match response_tag {
RECEIVED_RESPONSE_TAG => Self::deserialize_received(b),
SELF_ADDRESS_RESPONSE_TAG => Self::deserialize_self_address(b),
LANE_QUEUE_LENGTH_RESPONSE_TAG => Self::deserialize_lane_queue_length(b),
ERROR_RESPONSE_TAG => Self::deserialize_error(b),
n => Err(error::Error::new(
ErrorKind::UnknownResponse,
@@ -378,6 +411,20 @@ mod tests {
}
}
#[test]
fn lane_queue_length_response_serialization_works() {
let lane_queue_length_response = ServerResponse::LaneQueueLength(13, 42);
let bytes = lane_queue_length_response.serialize();
let recovered = ServerResponse::deserialize(&bytes).unwrap();
match recovered {
ServerResponse::LaneQueueLength(lane, queue_length) => {
assert_eq!(lane, 13);
assert_eq!(queue_length, 42)
}
_ => unreachable!(),
}
}
#[test]
fn error_response_serialization_works() {
let dummy_error = error::Error::new(ErrorKind::UnknownRequest, "foomp message".to_string());
@@ -20,6 +20,7 @@ pub(super) enum ClientRequestText {
message: String,
recipient: String,
with_reply_surb: bool,
connection_id: Option<u64>,
},
SelfAddress,
#[serde(rename_all = "camelCase")]
@@ -46,6 +47,7 @@ impl TryInto<ClientRequest> for ClientRequestText {
message,
recipient,
with_reply_surb,
connection_id,
} => {
let message_bytes = message.into_bytes();
let recipient = Recipient::try_from_base58_string(recipient).map_err(|err| {
@@ -56,6 +58,7 @@ impl TryInto<ClientRequest> for ClientRequestText {
message: message_bytes,
recipient,
with_reply_surb,
connection_id,
})
}
ClientRequestText::SelfAddress => Ok(ClientRequest::SelfAddress),
@@ -91,6 +94,10 @@ pub(super) enum ServerResponseText {
SelfAddress {
address: String,
},
LaneQueueLength {
lane: u64,
queue_length: usize,
},
Error {
message: String,
},
@@ -132,6 +139,9 @@ impl From<ServerResponse> for ServerResponseText {
ServerResponse::SelfAddress(recipient) => ServerResponseText::SelfAddress {
address: recipient.to_string(),
},
ServerResponse::LaneQueueLength(lane, queue_length) => {
ServerResponseText::LaneQueueLength { lane, queue_length }
}
ServerResponse::Error(err) => ServerResponseText::Error {
message: err.to_string(),
},
+14 -8
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.0.1"
version = "1.1.1"
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"
@@ -11,7 +11,7 @@ name = "nym_socks5"
path = "src/lib.rs"
[dependencies]
clap = { version = "3.2.8", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
dirs = "4.0"
futures = "0.3"
log = "0.4"
@@ -20,27 +20,33 @@ pretty_env_logger = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization
snafu = "0.6"
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal"] }
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] }
url = "2.2"
# internal
client-core = { path = "../client-core" }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
credential-storage = { path = "../../common/credential-storage" }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
logging = { path = "../../common/logging"}
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
network-defaults = { path = "../../common/network-defaults" }
nymsphinx = { path = "../../common/nymsphinx" }
ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
socks5-requests = { path = "../../common/socks5/requests" }
topology = { path = "../../common/topology" }
pemstore = { path = "../../common/pemstore" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
socks5-requests = { path = "../../common/socks5/requests" }
task = { path = "../../common/task" }
topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path = "../../common/version-checker" }
network-defaults = { path = "../../common/network-defaults" }
tap = "1.0.1"
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"]
+4
View File
@@ -33,6 +33,10 @@ impl NymConfig for Config {
.join("socks5-clients")
}
fn try_default_root_directory() -> Option<PathBuf> {
dirs::home_dir().map(|path| path.join(".nym").join("socks5-clients"))
}
fn root_directory(&self) -> PathBuf {
self.base.get_nym_root_directory()
}
+7 -6
View File
@@ -23,6 +23,13 @@ id = '{{ client.id }}'
# to claim bandwidth without presenting bandwidth credentials.
disabled_credentials_mode = {{ client.disabled_credentials_mode }}
# Addresses to nymd validators via which the client can communicate with the chain.
validator_urls = [
{{#each client.validator_urls }}
'{{this}}',
{{/each}}
]
# Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [
{{#each client.validator_api_urls }}
@@ -49,12 +56,6 @@ reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to the database containing bandwidth credentials
database_path = '{{ client.database_path }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options #####
# A gateway specific, optional, base58 stringified shared key used for
+134 -280
View File
@@ -1,40 +1,22 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
};
use client_core::client::key_manager::KeyManager;
use client_core::client::mix_traffic::{
BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController,
};
use client_core::client::real_messages_control::RealMessagesController;
use client_core::client::received_buffer::{
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
};
use client_core::client::reply_key_storage::ReplyKeyStorage;
use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use futures::StreamExt;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use crate::client::config::Config;
use crate::error::Socks5ClientError;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use futures::channel::mpsc;
use futures::StreamExt;
use gateway_client::bandwidth::BandwidthController;
use log::*;
use nymsphinx::addressing::clients::Recipient;
use std::error::Error;
use task::{wait_for_signal_and_error, ShutdownListener, ShutdownNotifier};
pub mod config;
@@ -68,295 +50,167 @@ impl NymClient {
}
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
// TODO: below only works under assumption that gateway address == gateway id
// (which currently is true)
NodeIdentity::from_base58_string(self.config.get_base().get_gateway_id()).unwrap(),
)
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
) {
info!("Starting loop cover traffic stream...");
LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_average_packet_delay(),
self.config
.get_base()
.get_loop_cover_traffic_average_delay(),
mix_tx,
self.as_mix_recipient(),
topology_accessor,
)
.start();
}
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
) {
let controller_config = client_core::client::real_messages_control::Config::new(
self.key_manager.ack_key(),
self.config.get_base().get_ack_wait_multiplier(),
self.config.get_base().get_ack_wait_addition(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_message_sending_average_delay(),
self.config.get_base().get_average_packet_delay(),
self.as_mix_recipient(),
);
info!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
reply_key_storage,
)
.start();
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
reply_key_storage: ReplyKeyStorage,
) {
info!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
reply_key_storage,
)
.start()
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
) -> GatewayClient {
let gateway_id = self.config.get_base().get_gateway_id();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_owner = self.config.get_base().get_gateway_owner();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_address = self.config.get_base().get_gateway_listener();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
async fn create_bandwidth_controller(config: &Config) -> BandwidthController {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
self.config.get_base().get_validator_api_endpoints(),
);
let bandwidth_controller = {
let details = network_defaults::NymNetworkDetails::new_from_env();
let client_config = validator_client::Config::try_from_nym_network_details(&details)
.expect("failed to construct validator client config");
let client = validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
let coconut_api_clients =
validator_client::CoconutApiClient::all_coconut_api_clients(&client)
.await
.expect("Could not query api clients");
BandwidthController::new(
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
coconut_api_clients,
)
};
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
if self.config.get_base().get_disabled_credentials_mode() {
gateway_client.set_disabled_credentials_mode(true)
}
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
);
}
info!("Starting topology refresher...");
topology_refresher.start();
}
// controller for sending sphinx 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(
&mut self,
mix_rx: BatchMixMessageReceiver,
gateway_client: GatewayClient,
) {
info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client).start();
bandwidth_controller
}
fn start_socks5_listener(
&self,
buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender,
config: &Config,
client_input: ClientInput,
client_output: ClientOutput,
self_address: Recipient,
mut shutdown: ShutdownListener,
) {
info!("Starting socks5 listener...");
let auth_methods = vec![AuthenticationMethods::NoAuth as u8];
let allowed_users: Vec<User> = Vec::new();
let ClientInput {
shared_lane_queue_lengths,
connection_command_sender,
input_sender,
} = client_input;
let received_buffer_request_sender = client_output.received_buffer_request_sender;
let authenticator = Authenticator::new(auth_methods, allowed_users);
let mut sphinx_socks = SphinxSocksServer::new(
self.config.get_listening_port(),
config.get_listening_port(),
authenticator,
self.config.get_provider_mix_address(),
self.as_mix_recipient(),
config.get_provider_mix_address(),
self_address,
shared_lane_queue_lengths,
shutdown.clone(),
);
tokio::spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
tokio::spawn(async move {
// Ideally we should have a fully fledged task manager to check for errors in all
// tasks.
// However, pragmatically, we start out by at least reporting errors for some of the
// tasks that interact with the outside world and can fail in normal operation, such as
// network issues.
// TODO: replace this by a generic solution, such as a task manager that stores all
// JoinHandles of all spawned tasks.
if let Err(res) = sphinx_socks
.serve(
input_sender,
received_buffer_request_sender,
connection_command_sender,
)
.await
{
shutdown.send_we_stopped(Box::new(res));
}
});
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) {
self.start().await;
if let Err(e) = tokio::signal::ctrl_c().await {
error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e
);
}
pub async fn run_forever(self) -> Result<(), Box<dyn Error + Send>> {
let mut shutdown = self
.start()
.await
.map_err(|err| Box::new(err) as Box<dyn Error + Send>)?;
println!(
"Received SIGINT - the client will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)."
);
let res = wait_for_signal_and_error(&mut shutdown).await;
log::info!("Sending shutdown");
shutdown.signal_shutdown().ok();
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client");
res
}
// Variant of `run_forever` that listends for remote control messages
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
self.start().await;
tokio::select! {
pub async fn run_and_listen(
self,
mut receiver: Socks5ControlMessageReceiver,
) -> Result<(), Box<dyn Error + Send>> {
// Start the main task
let mut shutdown = self
.start()
.await
.map_err(|err| Box::new(err) as Box<dyn Error + Send>)?;
let res = tokio::select! {
biased;
message = receiver.next() => {
log::debug!("Received message: {:?}", message);
match message {
Some(Socks5ControlMessage::Stop) => {
log::info!("Shutting down");
log::info!("Graceful shutdown of tasks not yet implemented, you might see (harmless) panics until then");
log::info!("Received stop message");
}
None => {
log::info!("Channel closed, stopping");
}
None => log::debug!("None"),
}
Ok(())
}
}
Some(msg) = shutdown.wait_for_error() => {
log::info!("Task error: {:?}", msg);
Err(msg)
}
_ = tokio::signal::ctrl_c() => {
log::info!("Received SIGINT");
Ok(())
},
};
log::info!("Sending shutdown");
shutdown.signal_shutdown().ok();
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client");
res
}
pub async fn start(&mut self) {
info!("Starting nym 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
// and would allow anyone to clone the sender channel
// sphinx_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
// sphinx_message_receiver is the receiver used by MixTrafficController that sends the actual traffic
let (sphinx_message_sender, sphinx_message_receiver) = mpsc::unbounded();
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
let reply_key_storage =
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
.expect("Failed to load reply key storage!");
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_key_storage.clone(),
pub async fn start(self) -> Result<ShutdownNotifier, Socks5ClientError> {
let base_builder = BaseClientBuilder::new_from_base_config(
self.config.get_base(),
self.key_manager,
Some(Self::create_bandwidth_controller(&self.config).await),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
let self_address = base_builder.as_mix_recipient();
let mut started_client = base_builder.start_base().await?;
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
reply_key_storage,
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
Self::start_socks5_listener(
&self.config,
client_input,
client_output,
self_address,
started_client.shutdown_notifier.subscribe(),
);
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
self.start_socks5_listener(received_buffer_request_sender, input_sender);
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
info!("The address of this client is: {}", self_address);
Ok(started_client.shutdown_notifier)
}
}
+40 -57
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use clap::Args;
use client_core::config::GatewayEndpoint;
use client_core::{config::GatewayEndpointConfig, error::ClientCoreError};
use config::NymConfig;
use crate::{
@@ -10,9 +10,6 @@ use crate::{
commands::{override_config, OverrideConfig},
};
#[cfg(all(feature = "eth", not(feature = "coconut")))]
use crate::commands::{DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY};
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
@@ -32,9 +29,13 @@ pub(crate) struct Init {
#[clap(long)]
force_register_gateway: bool,
/// Comma separated list of rest endpoints of the validators
/// Comma separated list of rest endpoints of the nymd validators
#[clap(long)]
validators: Option<String>,
nymd_validators: Option<String>,
/// Comma separated list of rest endpoints of the API validators
#[clap(long)]
api_validators: Option<String>,
/// Port for the socket to listen on in all subsequent runs
#[clap(short, long)]
@@ -46,46 +47,21 @@ pub(crate) struct Init {
fastmode: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
/// --eth-private_key don't need to be set.
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_ENDPOINT))
)]
eth_endpoint: String,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_PRIVATE_KEY))
)]
eth_private_key: String,
}
impl From<Init> for OverrideConfig {
fn from(init_config: Init) -> Self {
OverrideConfig {
validators: init_config.validators,
nymd_validators: init_config.nymd_validators,
api_validators: init_config.api_validators,
port: init_config.port,
fastmode: init_config.fastmode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: init_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Some(init_config.eth_private_key),
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Some(init_config.eth_endpoint),
}
}
}
@@ -121,7 +97,12 @@ pub(crate) async fn execute(args: &Init) {
let override_config_fields = OverrideConfig::from(args.clone());
config = override_config(config, override_config_fields);
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await;
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config)
.await
.unwrap_or_else(|err| {
eprintln!("Failed to setup gateway\nError: {err}");
std::process::exit(1)
});
config.get_base_mut().with_gateway_endpoint(gateway);
let config_save_location = config.get_config_file_save_location();
@@ -139,7 +120,10 @@ pub(crate) async fn execute(args: &Init) {
);
println!("Client configuration completed.");
client_core::init::show_address(config.get_base());
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
eprintln!("Failed to show address\nError: {err}");
std::process::exit(1)
});
}
async fn setup_gateway(
@@ -147,7 +131,7 @@ async fn setup_gateway(
register: bool,
user_chosen_gateway_id: Option<&str>,
config: &Config,
) -> GatewayEndpoint {
) -> Result<GatewayEndpointConfig, ClientCoreError> {
if register {
// Get the gateway details by querying the validator-api. Either pick one at random or use
// the chosen one if it's among the available ones.
@@ -156,16 +140,16 @@ async fn setup_gateway(
config.get_base().get_validator_api_endpoints(),
user_chosen_gateway_id,
)
.await;
.await?;
log::debug!("Querying gateway gives: {}", gateway);
// Registering with gateway by setting up and writing shared keys to disk
log::trace!("Registering gateway");
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
.await;
.await?;
println!("Saved all generated keys");
gateway.into()
Ok(gateway.into())
} else if user_chosen_gateway_id.is_some() {
// Just set the config, don't register or create any keys
// This assumes that the user knows what they are doing, and that the existing keys are
@@ -175,22 +159,21 @@ async fn setup_gateway(
config.get_base().get_validator_api_endpoints(),
user_chosen_gateway_id,
)
.await;
.await?;
log::debug!("Querying gateway gives: {}", gateway);
gateway.into()
Ok(gateway.into())
} else {
println!("Not registering gateway, will reuse existing config and keys");
match Config::load_from_file(Some(id)) {
Ok(existing_config) => existing_config.get_base().get_gateway_endpoint().clone(),
Err(err) => {
panic!(
"Unable to configure gateway: {err}. \n
Seems like the client was already initialized but it was not possible to read \
the existing configuration file. \n
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
removing the existing configuration and starting over."
)
}
}
let existing_config = Config::load_from_file(Some(id)).map_err(|err| {
log::error!(
"Unable to configure gateway: {err}. \n
Seems like the client was already initialized but it was not possible to read \
the existing configuration file. \n
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
removing the existing configuration and starting over."
);
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
})?;
Ok(existing_config.get_base().get_gateway_endpoint().clone())
}
}
+35 -48
View File
@@ -1,21 +1,18 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::error::Error;
use crate::client::config::Config;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use url::Url;
use completions::{fig_generate, ArgShell};
use config::parse_validators;
pub mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
fn long_version() -> String {
format!(
r#"
@@ -55,7 +52,7 @@ fn long_version_static() -> &'static str {
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the client.
#[clap(long)]
#[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)]
@@ -66,49 +63,55 @@ pub(crate) struct Cli {
pub(crate) enum Commands {
/// Initialise a Nym client. Do this first!
Init(init::Init),
/// Run the Nym client with provided configuration client optionally overriding set parameters
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
pub(crate) struct OverrideConfig {
validators: Option<String>,
nymd_validators: Option<String>,
api_validators: Option<String>,
port: Option<u16>,
fastmode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Option<String>,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Option<String>,
}
pub(crate) async fn execute(args: &Cli) {
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send>> {
let bin_name = "nym-socks5-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Run(m) => run::execute(m).await?,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
}
pub fn parse_validators(raw: &str) -> Vec<Url> {
raw.split(',')
.map(|raw_validator| {
raw_validator
.trim()
.parse()
.expect("one of the provided validator api urls is invalid")
})
.collect()
Ok(())
}
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
if let Some(raw_validators) = args.validators {
if let Some(raw_validators) = args.nymd_validators {
config
.get_base_mut()
.set_custom_validators(parse_validators(&raw_validators));
} else if let Ok(raw_validators) = std::env::var(network_defaults::var_names::NYMD_VALIDATOR) {
config
.get_base_mut()
.set_custom_validators(parse_validators(&raw_validators));
}
if let Some(raw_validators) = args.api_validators {
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators));
@@ -122,27 +125,11 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config = config.with_port(port);
}
#[cfg(all(not(feature = "eth"), not(feature = "coconut")))]
{
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT.to_string());
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
}
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[cfg(feature = "coconut")]
{
if args.enabled_credentials_mode {
config.get_base_mut().with_disabled_credentials(false)
}
if let Some(eth_endpoint) = args.eth_endpoint {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
if let Some(eth_private_key) = args.eth_private_key {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
}
if args.fastmode {
+19 -30
View File
@@ -4,6 +4,7 @@
use crate::{
client::{config::Config, NymClient},
commands::{override_config, OverrideConfig},
error::Socks5ClientError,
};
use clap::Args;
@@ -30,49 +31,35 @@ pub(crate) struct Run {
#[clap(long)]
gateway: Option<String>,
/// Comma separated list of rest endpoints of the validators
/// Comma separated list of rest endpoints of the nymd validators
#[clap(long)]
validators: Option<String>,
nymd_validators: Option<String>,
/// Comma separated list of rest endpoints of the API validators
#[clap(long)]
api_validators: Option<String>,
/// Port for the socket to listen on
#[clap(short, long)]
port: Option<u16>,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
/// --eth-private-key don't need to be set.
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_endpoint: Option<String>,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_private_key: Option<String>,
}
impl From<Run> for OverrideConfig {
fn from(run_config: Run) -> Self {
OverrideConfig {
validators: run_config.validators,
nymd_validators: run_config.nymd_validators,
api_validators: run_config.api_validators,
port: run_config.port,
fastmode: false,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[cfg(feature = "coconut")]
enabled_credentials_mode: run_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: run_config.eth_private_key,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: run_config.eth_endpoint,
}
}
}
@@ -99,14 +86,16 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) {
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send>> {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
return;
return Err(Box::new(Socks5ClientError::FailedToLoadConfig(
id.to_string(),
)));
}
};
@@ -115,8 +104,8 @@ pub(crate) async fn execute(args: &Run) {
if !version_check(&config) {
error!("failed the local version check");
return;
return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck));
}
NymClient::new(config).run_forever().await;
NymClient::new(config).run_forever().await
}
+2 -2
View File
@@ -102,7 +102,7 @@ fn minor_0_12_upgrade(
Version::new(0, 12, 0)
};
print_start_upgrade(&config_version, &to_version);
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
@@ -110,7 +110,7 @@ fn minor_0_12_upgrade(
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&config_version, &to_version);
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
+21
View File
@@ -0,0 +1,21 @@
use client_core::error::ClientCoreError;
use crate::socks::types::SocksProxyError;
#[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("SOCKS proxy error")]
SocksProxyError(SocksProxyError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
#[error("Failed local version check, client and config mismatch")]
FailedLocalVersionCheck,
#[error("Fail to bind address")]
FailToBindAddress,
}
+1 -5
View File
@@ -2,9 +2,5 @@
// SPDX-License-Identifier: Apache-2.0
pub mod client;
// This is only used as we reach into the init functions in nym-connect. We need to refactor the
// init functions so that nym-connect can just call the same init function as the regular socks5
// client.
#[allow(unused)]
pub mod commands;
pub mod error;
pub mod socks;
+6 -22
View File
@@ -1,21 +1,25 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::error::Error;
use clap::{crate_version, Parser};
use logging::setup_logging;
use network_defaults::setup_env;
pub mod client;
mod commands;
pub mod error;
pub mod socks;
#[tokio::main]
async fn main() {
async fn main() -> Result<(), Box<dyn Error + Send>> {
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
commands::execute(&args).await
}
fn banner() -> String {
@@ -34,23 +38,3 @@ fn banner() -> String {
crate_version!()
)
}
fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
log_builder.parse_filters(&s);
} else {
// default to 'Info'
log_builder.filter(None, log::LevelFilter::Info);
}
log_builder
.filter_module("hyper", log::LevelFilter::Warn)
.filter_module("tokio_reactor", log::LevelFilter::Warn)
.filter_module("reqwest", log::LevelFilter::Warn)
.filter_module("mio", log::LevelFilter::Warn)
.filter_module("want", log::LevelFilter::Warn)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.init();
}
+138 -47
View File
@@ -2,10 +2,10 @@
use super::authentication::{AuthenticationMethods, Authenticator, User};
use super::request::{SocksCommand, SocksRequest};
use super::types::{ResponseCode, SocksProxyError};
use super::{RESERVED, SOCKS_VERSION};
use client_core::client::inbound_messages::InputMessage;
use client_core::client::inbound_messages::InputMessageSender;
use super::types::{ResponseCodeV4, ResponseCodeV5, SocksProxyError};
use super::{SocksVersion, RESERVED, SOCKS4_VERSION, SOCKS5_VERSION};
use client_connections::{LaneQueueLengths, TransmissionLane};
use client_core::client::inbound_messages::{InputMessage, InputMessageSender};
use futures::channel::mpsc;
use futures::task::{Context, Poll};
use log::*;
@@ -20,6 +20,7 @@ use socks5_requests::{ConnectionId, Message, RemoteAddress, Request};
use std::io;
use std::net::SocketAddr;
use std::pin::Pin;
use task::ShutdownListener;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
use tokio::{self, net::TcpStream};
@@ -128,18 +129,20 @@ impl AsyncWrite for StreamState {
/// A client connecting to the Socks proxy server, because
/// it wants to make a Nym-protected outbound request. Typically, this is
/// something like e.g. a wallet app running on your laptop connecting to
/// SphinxSocksServer.
/// `SphinxSocksServer`.
pub(crate) struct SocksClient {
controller_sender: ControllerSender,
stream: StreamState,
auth_nmethods: u8,
authenticator: Authenticator,
socks_version: u8,
socks_version: Option<SocksVersion>,
input_sender: InputMessageSender,
connection_id: ConnectionId,
service_provider: Recipient,
self_address: Recipient,
started_proxy: bool,
lane_queue_lengths: LaneQueueLengths,
shutdown_listener: ShutdownListener,
}
impl Drop for SocksClient {
@@ -155,27 +158,35 @@ impl Drop for SocksClient {
}
impl SocksClient {
/// Create a new SOCKClient
#[allow(clippy::too_many_arguments)]
pub fn new(
stream: TcpStream,
authenticator: Authenticator,
input_sender: InputMessageSender,
service_provider: Recipient,
service_provider: &Recipient,
controller_sender: ControllerSender,
self_address: Recipient,
self_address: &Recipient,
lane_queue_lengths: LaneQueueLengths,
mut shutdown_listener: ShutdownListener,
) -> Self {
// If this task fails and exits, we don't want to send shutdown signal
shutdown_listener.mark_as_success();
let connection_id = Self::generate_random();
SocksClient {
controller_sender,
connection_id,
stream: StreamState::Available(stream),
auth_nmethods: 0,
socks_version: 0,
socks_version: None,
authenticator,
input_sender,
service_provider,
self_address,
service_provider: *service_provider,
self_address: *self_address,
started_proxy: false,
lane_queue_lengths,
shutdown_listener,
}
}
@@ -184,16 +195,49 @@ impl SocksClient {
rng.next_u64()
}
pub async fn send_error(&mut self, err: SocksProxyError) -> Result<(), SocksProxyError> {
let error_text = format!("{}", err);
let Some(ref version) = self.socks_version else {
log::error!("Trying to send error without knowing the version");
return Ok(());
};
match version {
SocksVersion::V4 => {
let response = ResponseCodeV4::RequestRejected;
self.send_error_v4(response).await
}
SocksVersion::V5 => {
let response = if error_text.contains("Host") {
ResponseCodeV5::HostUnreachable
} else if error_text.contains("Network") {
ResponseCodeV5::NetworkUnreachable
} else if error_text.contains("ttl") {
ResponseCodeV5::TtlExpired
} else {
ResponseCodeV5::Failure
};
self.send_error_v5(response).await
}
}
}
// Send an error back to the client
pub async fn error(&mut self, r: ResponseCode) -> Result<(), SocksProxyError> {
self.stream.write_all(&[5, r as u8]).await?;
pub async fn send_error_v4(&mut self, r: ResponseCodeV4) -> Result<(), SocksProxyError> {
self.stream.write_all(&[SOCKS4_VERSION, r as u8]).await?;
Ok(())
}
/// Shutdown the TcpStream to the client and end the session
pub async fn send_error_v5(&mut self, r: ResponseCodeV5) -> Result<(), SocksProxyError> {
self.stream.write_all(&[SOCKS5_VERSION, r as u8]).await?;
Ok(())
}
/// Shutdown the `TcpStream` to the client and end the session
pub async fn shutdown(&mut self) -> Result<(), SocksProxyError> {
info!("client is shutting down its TCP stream");
self.stream.shutdown().await?;
self.shutdown_listener.mark_as_success();
Ok(())
}
@@ -201,33 +245,43 @@ impl SocksClient {
/// is in use and that the client is authenticated, then runs the request.
pub async fn run(&mut self) -> Result<(), SocksProxyError> {
debug!("New connection from: {}", self.stream.peer_addr()?.ip());
let mut header = [0u8; 2];
// Read a byte from the stream and determine the version being requested
let mut header = [0u8];
self.stream.read_exact(&mut header).await?;
self.socks_version = header[0];
self.auth_nmethods = header[1];
self.socks_version = match SocksVersion::try_from(header[0]) {
Ok(version) => Some(version),
Err(_err) => {
warn!("Init: Unsupported version: SOCKS{}", header[0]);
return self.shutdown().await;
}
};
// Handle SOCKS4 requests
if header[0] != SOCKS_VERSION {
warn!("Init: Unsupported version: SOCKS{}", self.socks_version);
self.shutdown().await
}
// Valid SOCKS5
else {
// Authenticate w/ client
self.authenticate().await?;
// Handle requests
self.handle_request().await
if self.socks_version == Some(SocksVersion::V5) {
let mut auth = [0u8];
self.stream.read_exact(&mut auth).await?;
self.auth_nmethods = auth[0];
self.authenticate_socks5().await?;
}
self.handle_request().await
}
async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
let req = Request::new_connect(self.connection_id, remote_address, self.self_address);
let msg = Message::Request(req);
let input_message = InputMessage::new_fresh(self.service_provider, msg.into_bytes(), false);
self.input_sender.unbounded_send(input_message).unwrap();
let input_message = InputMessage::new_fresh(
self.service_provider,
msg.into_bytes(),
false,
TransmissionLane::ConnectionId(self.connection_id),
);
self.input_sender
.send(input_message)
.await
.expect("InputMessageReceiver has stopped receiving!");
}
async fn run_proxy(&mut self, conn_receiver: ConnectionReceiver, remote_proxy_target: String) {
@@ -235,10 +289,15 @@ impl SocksClient {
.await;
let stream = self.stream.run_proxy();
let local_stream_remote = stream
.peer_addr()
.expect("failed to extract peer address")
.to_string();
let peer_addr = match stream.peer_addr() {
Ok(peer_addr) => peer_addr,
Err(err) => {
log::error!("Unable to extract the remote peer address: {err}");
return;
}
};
let local_stream_remote = peer_addr.to_string();
let connection_id = self.connection_id;
let input_sender = self.input_sender.clone();
@@ -250,11 +309,14 @@ impl SocksClient {
conn_receiver,
input_sender,
connection_id,
Some(self.lane_queue_lengths.clone()),
self.shutdown_listener.clone(),
)
.run(move |conn_id, read_data, socket_closed| {
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
let provider_message = Message::Request(provider_request);
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false)
let lane = TransmissionLane::ConnectionId(conn_id);
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false, lane)
})
.await
.into_inner();
@@ -266,8 +328,17 @@ impl SocksClient {
async fn handle_request(&mut self) -> Result<(), SocksProxyError> {
debug!("Handling CONNECT Command");
let request = SocksRequest::from_stream(&mut self.stream).await?;
let remote_address = request.to_string();
let version = self
.socks_version
.as_ref()
.expect("Must read version before parsing request");
let request = match version {
SocksVersion::V4 => SocksRequest::from_stream_socks4(&mut self.stream).await?,
SocksVersion::V5 => SocksRequest::from_stream_socks5(&mut self.stream).await?,
};
let remote_address = request.address_string();
// setup for receiving from the mixnet
let (mix_sender, mix_receiver) = mpsc::unbounded();
@@ -276,7 +347,10 @@ impl SocksClient {
// Use the Proxy to connect to the specified addr/port
SocksCommand::Connect => {
trace!("Connecting to: {:?}", remote_address.clone());
self.acknowledge_socks5().await;
match version {
SocksVersion::V4 => self.acknowledge_socks4().await,
SocksVersion::V5 => self.acknowledge_socks5().await,
}
self.started_proxy = true;
self.controller_sender
@@ -307,8 +381,8 @@ impl SocksClient {
async fn acknowledge_socks5(&mut self) {
self.stream
.write_all(&[
SOCKS_VERSION,
ResponseCode::Success as u8,
SOCKS5_VERSION,
ResponseCodeV5::Success as u8,
RESERVED,
1,
127,
@@ -322,13 +396,30 @@ impl SocksClient {
.unwrap();
}
/// Writes a Socks4 header back to the requesting client's TCP stream,
async fn acknowledge_socks4(&mut self) {
self.stream
.write_all(&[
0, //SOCKS4_VERSION,
ResponseCodeV4::Granted as u8,
0,
0,
127,
0,
0,
1,
])
.await
.unwrap();
}
/// Authenticate the incoming request. Each request is checked for its
/// authentication method. A user/password request will extract the
/// username and password from the stream, then check with the Authenticator
/// to see if the resulting user is allowed.
///
/// A lot of this could probably be put into the `SocksRequest::from_stream()`
/// constructor, and/or cleaned up with tokio::codec. It's mostly just
/// constructor, and/or cleaned up with `tokio::codec`. It's mostly just
/// read-a-byte-or-two. The bytes being extracted look like this:
///
/// +----+------+----------+------+------------+
@@ -340,7 +431,7 @@ impl SocksClient {
/// Pulling out the stream code into its own home, and moving the if/else logic
/// into the Authenticator (where it'll be more easily testable)
/// would be a good next step.
async fn authenticate(&mut self) -> Result<(), SocksProxyError> {
async fn authenticate_socks5(&mut self) -> Result<(), SocksProxyError> {
debug!("Authenticating w/ {}", self.stream.peer_addr()?.ip());
// Get valid auth methods
let methods = self.get_available_methods().await?;
@@ -349,7 +440,7 @@ impl SocksClient {
let mut response = [0u8; 2];
// Set the version in the response
response[0] = SOCKS_VERSION;
response[0] = SOCKS5_VERSION;
if methods.contains(&(AuthenticationMethods::UserPass as u8)) {
// Set the default auth method (NO AUTH)
response[1] = AuthenticationMethods::UserPass as u8;
@@ -385,11 +476,11 @@ impl SocksClient {
// Authenticate passwords
if self.authenticator.is_allowed(&user) {
debug!("Access Granted. User: {}", user.username);
let response = [1, ResponseCode::Success as u8];
let response = [1, ResponseCodeV5::Success as u8];
self.stream.write_all(&response).await?;
} else {
debug!("Access Denied. User: {}", user.username);
let response = [1, ResponseCode::Failure as u8];
let response = [1, ResponseCodeV5::Failure as u8];
self.stream.write_all(&response).await?;
// Shutdown
@@ -408,7 +499,7 @@ impl SocksClient {
response[1] = AuthenticationMethods::NoMethods as u8;
self.stream.write_all(&response).await?;
self.shutdown().await?;
Err(ResponseCode::Failure.into())
Err(ResponseCodeV5::Failure.into())
}
}
+46 -8
View File
@@ -1,23 +1,35 @@
use client_core::client::received_buffer::ReconstructedMessagesReceiver;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender};
use std::time::Duration;
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use client_core::client::received_buffer::ReconstructedMessagesReceiver;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender};
use nymsphinx::receiver::ReconstructedMessage;
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
use socks5_requests::Message;
use task::ShutdownListener;
pub(crate) struct MixnetResponseListener {
buffer_requester: ReceivedBufferRequestSender,
mix_response_receiver: ReconstructedMessagesReceiver,
controller_sender: ControllerSender,
shutdown: ShutdownListener,
}
impl Drop for MixnetResponseListener {
fn drop(&mut self) {
self.buffer_requester
if let Err(err) = self
.buffer_requester
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
.expect("the buffer request failed!")
{
if self.shutdown.is_shutdown_poll() {
log::debug!("The buffer request failed: {}", err);
} else {
log::error!("The buffer request failed: {}", err);
}
}
}
}
@@ -25,6 +37,7 @@ impl MixnetResponseListener {
pub(crate) fn new(
buffer_requester: ReceivedBufferRequestSender,
controller_sender: ControllerSender,
shutdown: ShutdownListener,
) -> Self {
let (mix_response_sender, mix_response_receiver) = mpsc::unbounded();
buffer_requester
@@ -35,6 +48,7 @@ impl MixnetResponseListener {
buffer_requester,
mix_response_receiver,
controller_sender,
shutdown,
}
}
@@ -54,6 +68,13 @@ impl MixnetResponseListener {
return;
}
Ok(Message::Response(data)) => data,
Ok(Message::NetworkRequesterResponse(r)) => {
error!(
"Network requester failed on connection id {} with error: {}",
r.connection_id, r.network_requester_error
);
return;
}
};
self.controller_sender
@@ -66,11 +87,28 @@ impl MixnetResponseListener {
}
pub(crate) async fn run(&mut self) {
while let Some(received_responses) = self.mix_response_receiver.next().await {
for reconstructed_message in received_responses {
self.on_message(reconstructed_message).await;
while !self.shutdown.is_shutdown() {
tokio::select! {
received_responses = self.mix_response_receiver.next() => match received_responses {
Some(received_responses) => {
for reconstructed_message in received_responses {
self.on_message(reconstructed_message).await;
}
},
None => {
log::trace!("MixnetResponseListener: Stopping since channel closed");
break;
}
},
_ = self.shutdown.recv() => {
log::trace!("MixnetResponseListener: Received shutdown");
}
}
}
error!("We should never see this message");
#[cfg(not(target_arch = "wasm32"))]
tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("MixnetResponseListener: Exiting");
}
}
+26 -1
View File
@@ -1,5 +1,9 @@
#![forbid(unsafe_code)]
use std::convert::TryFrom;
use self::types::SocksProxyError;
pub mod authentication;
mod client;
pub(crate) mod mixnet_responses;
@@ -9,6 +13,27 @@ pub mod types;
pub mod utils;
/// Version of socks
const SOCKS_VERSION: u8 = 0x05;
const SOCKS4_VERSION: u8 = 0x04;
const SOCKS5_VERSION: u8 = 0x05;
const RESERVED: u8 = 0x00;
#[derive(Clone, PartialEq, Eq)]
pub enum SocksVersion {
V4 = 0x04,
V5 = 0x05,
}
pub struct InvalidSocksVersion;
impl TryFrom<u8> for SocksVersion {
type Error = SocksProxyError;
fn try_from(version: u8) -> Result<Self, Self::Error> {
match version {
SOCKS4_VERSION => Ok(Self::V4),
SOCKS5_VERSION => Ok(Self::V5),
_ => Err(SocksProxyError::UnsupportedProxyVersion(version)),
}
}
}
+109 -49
View File
@@ -1,5 +1,7 @@
use super::types::{AddrType, ResponseCode, SocksProxyError};
use super::{utils as socks_utils, SOCKS_VERSION};
use crate::socks::SOCKS4_VERSION;
use super::types::{AddrType, ResponseCodeV5, SocksProxyError};
use super::{utils as socks_utils, SOCKS5_VERSION};
use log::*;
use std::fmt::{self, Display};
use tokio::io::{AsyncRead, AsyncReadExt};
@@ -15,80 +17,114 @@ pub(crate) struct SocksRequest {
}
impl SocksRequest {
/// Parse a SOCKS5 request from a TcpStream
pub async fn from_stream<R>(stream: &mut R) -> Result<Self, SocksProxyError>
/// Parse a SOCKS4 request from a `TcpStream`
/// From documents at:
/// - SOCKS4: https://www.openssh.com/txt/socks4.protocol
/// - SOCKS4a: https://www.openssh.com/txt/socks4a.protocol
pub async fn from_stream_socks4<R>(stream: &mut R) -> Result<Self, SocksProxyError>
where
R: AsyncRead + Unpin,
{
log::trace!("read from stream socks4");
let mut packet = [0u8; 3];
stream.read_exact(&mut packet).await?;
// CD (command)
let Some(command) = SocksCommand::from(packet[0] as usize) else {
log::warn!("Invalid Command");
return Err(ResponseCodeV5::CommandNotSupported.into());
};
// DSTPORT
let mut port = [0u8; 2];
port.copy_from_slice(&packet[1..]);
let port = merge_u8_into_u16(port[0], port[1]);
// DSTIP
let mut ip = [0u8; 4];
stream.read_exact(&mut ip).await?;
// USERID
let _userid = read_until_zero(stream).await;
// SOCKS4a extension
// https://www.openssh.com/txt/socks4a.protocol
// If the IP is 0.0.0.x with x nonzero, read the domain name
let (addr, addr_type) = if ip[..3] == [0, 0, 0] && ip[3] != 0 {
(read_until_zero(stream).await?, AddrType::Domain)
} else {
(ip.to_vec(), AddrType::V4)
};
// Return parsed request
Ok(SocksRequest {
version: SOCKS4_VERSION,
command,
addr_type,
addr,
port,
})
}
/// Parse a SOCKS5 request from a `TcpStream`
/// From: https://www.rfc-editor.org/rfc/rfc1928
pub async fn from_stream_socks5<R>(stream: &mut R) -> Result<Self, SocksProxyError>
where
R: AsyncRead + Unpin,
{
log::info!("read from stream socks5");
let mut packet = [0u8; 4];
// Read a byte from the stream and determine the version being requested
stream.read_exact(&mut packet).await?;
if packet[0] != SOCKS_VERSION {
warn!("from_stream Unsupported version: SOCKS{}", packet[0]);
// VER
if packet[0] != SOCKS5_VERSION {
warn!("Unsupported version: SOCKS{}", packet[0]);
return Err(SocksProxyError::UnsupportedProxyVersion(packet[0]));
}
// Get command
let mut command: SocksCommand = SocksCommand::Connect;
match SocksCommand::from(packet[1] as usize) {
Some(com) => {
command = com;
Ok(())
}
None => {
warn!("Invalid Command");
Err(ResponseCode::CommandNotSupported)
}
}?;
// CMD
let Some(command) = SocksCommand::from(packet[1] as usize) else {
warn!("Invalid Command");
return Err(ResponseCodeV5::CommandNotSupported.into());
};
// DST.address
// RSV
// packet[2] is reserved
let mut addr_type: AddrType = AddrType::V6;
match AddrType::from(packet[3] as usize) {
Some(addr) => {
addr_type = addr;
Ok(())
}
None => {
error!("No Addr");
Err(ResponseCode::AddrTypeNotSupported)
}
}?;
// ATYP
let Some(addr_type) = AddrType::from(packet[3] as usize) else {
error!("No Addr");
return Err(ResponseCodeV5::AddrTypeNotSupported.into())
};
trace!("Getting Addr");
// Get Addr from addr_type and stream
let addr: Result<Vec<u8>, SocksProxyError> = match addr_type {
// DST.ADDR
let addr = match addr_type {
AddrType::Domain => {
let mut domain_length = [0u8; 1];
let mut domain_length = [0u8];
stream.read_exact(&mut domain_length).await?;
let mut domain = vec![0u8; domain_length[0] as usize];
stream.read_exact(&mut domain).await?;
Ok(domain)
domain
}
AddrType::V4 => {
let mut addr = [0u8; 4];
stream.read_exact(&mut addr).await?;
Ok(addr.to_vec())
addr.to_vec()
}
AddrType::V6 => {
let mut addr = [0u8; 16];
stream.read_exact(&mut addr).await?;
Ok(addr.to_vec())
addr.to_vec()
}
};
let addr = addr?;
// read DST.port
// DST.PORT
let mut port = [0u8; 2];
stream.read_exact(&mut port).await?;
// Merge two u8s into u16
let port = (u16::from(port[0]) << 8) | u16::from(port[1]);
let port = merge_u8_into_u16(port[0], port[1]);
// Return parsed request
Ok(SocksRequest {
version: packet[0],
command,
@@ -97,14 +133,18 @@ impl SocksRequest {
port,
})
}
/// Print out the address and port to a String.
/// This might return domain:port, ipv6:port, or ipv4:port.
pub fn address_string(&self) -> String {
let address = socks_utils::pretty_print_addr(&self.addr_type, &self.addr);
format!("{}:{}", address, self.port)
}
}
impl Display for SocksRequest {
/// Print out the address and port to a String.
/// This might return domain:port, ipv6:port, or ipv4:port.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let address = socks_utils::pretty_print_addr(&self.addr_type, &self.addr);
write!(f, "{}:{}", address, self.port)
write!(f, "{}", self.address_string())
}
}
@@ -127,3 +167,23 @@ impl SocksCommand {
}
}
}
fn merge_u8_into_u16(a: u8, b: u8) -> u16 {
(u16::from(a) << 8) | u16::from(b)
}
async fn read_until_zero<R>(stream: &mut R) -> Result<Vec<u8>, SocksProxyError>
where
R: AsyncRead + Unpin,
{
let mut result = Vec::new();
let mut char = [0u8];
loop {
stream.read_exact(&mut char).await?;
if char[0] == 0 {
break;
}
result.push(char[0]);
}
Ok(result)
}
+57 -52
View File
@@ -1,16 +1,18 @@
use super::authentication::Authenticator;
use super::client::SocksClient;
use crate::error::Socks5ClientError;
use super::{
mixnet_responses::MixnetResponseListener,
types::{ResponseCode, SocksProxyError},
authentication::Authenticator, client::SocksClient, mixnet_responses::MixnetResponseListener,
};
use client_connections::{ConnectionCommandSender, LaneQueueLengths};
use client_core::client::{
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
};
use log::*;
use nymsphinx::addressing::clients::Recipient;
use proxy_helpers::connection_controller::Controller;
use proxy_helpers::connection_controller::{BroadcastActiveConnections, Controller};
use std::net::SocketAddr;
use tap::TapFallible;
use task::ShutdownListener;
use tokio::net::TcpListener;
/// A Socks5 server that listens for connections.
@@ -19,6 +21,8 @@ pub struct SphinxSocksServer {
listening_address: SocketAddr,
service_provider: Recipient,
self_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener,
}
impl SphinxSocksServer {
@@ -28,6 +32,8 @@ impl SphinxSocksServer {
authenticator: Authenticator,
service_provider: Recipient,
self_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener,
) -> Self {
// hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can
// just modify the config
@@ -38,6 +44,8 @@ impl SphinxSocksServer {
listening_address: format!("{}:{}", ip, port).parse().unwrap(),
service_provider,
self_address,
lane_queue_lengths,
shutdown,
}
}
@@ -47,67 +55,64 @@ impl SphinxSocksServer {
&mut self,
input_sender: InputMessageSender,
buffer_requester: ReceivedBufferRequestSender,
) -> Result<(), SocksProxyError> {
let listener = TcpListener::bind(self.listening_address).await.unwrap();
client_connection_tx: ConnectionCommandSender,
) -> Result<(), Socks5ClientError> {
let listener = TcpListener::bind(self.listening_address)
.await
.tap_err(|err| log::error!("Failed to bind to address: {err}"))?;
info!("Serving Connections...");
// controller for managing all active connections
let (mut active_streams_controller, controller_sender) = Controller::new();
let (mut active_streams_controller, controller_sender) = Controller::new(
client_connection_tx,
BroadcastActiveConnections::Off,
self.shutdown.clone(),
);
tokio::spawn(async move {
active_streams_controller.run().await;
});
// listener for mix messages
let mut mixnet_response_listener =
MixnetResponseListener::new(buffer_requester, controller_sender.clone());
let mut mixnet_response_listener = MixnetResponseListener::new(
buffer_requester,
controller_sender.clone(),
self.shutdown.clone(),
);
tokio::spawn(async move {
mixnet_response_listener.run().await;
});
loop {
if let Ok((stream, _remote)) = listener.accept().await {
// TODO Optimize this
let mut client = SocksClient::new(
stream,
self.authenticator.clone(),
input_sender.clone(),
self.service_provider,
controller_sender.clone(),
self.self_address,
);
tokio::select! {
Ok((stream, _remote)) = listener.accept() => {
let mut client = SocksClient::new(
stream,
self.authenticator.clone(),
input_sender.clone(),
&self.service_provider,
controller_sender.clone(),
&self.self_address,
self.lane_queue_lengths.clone(),
self.shutdown.clone(),
);
tokio::spawn(async move {
{
match client.run().await {
Ok(_) => {}
Err(error) => {
error!("Error! {}", error);
let error_text = format!("{}", error);
let response: ResponseCode;
if error_text.contains("Host") {
response = ResponseCode::HostUnreachable;
} else if error_text.contains("Network") {
response = ResponseCode::NetworkUnreachable;
} else if error_text.contains("ttl") {
response = ResponseCode::TtlExpired
} else {
response = ResponseCode::Failure
}
if client.error(response).await.is_err() {
warn!("Failed to send error code");
};
if client.shutdown().await.is_err() {
warn!("Failed to shutdown TcpStream");
};
}
};
// client gets dropped here
}
});
tokio::spawn(async move {
if let Err(err) = client.run().await {
error!("Error! {}", err);
if client.send_error(err).await.is_err() {
warn!("Failed to send error code");
};
if client.shutdown().await.is_err() {
warn!("Failed to shutdown TcpStream");
};
}
});
},
_ = self.shutdown.recv() => {
log::trace!("SphinxSocksServer: Received shutdown");
log::debug!("SphinxSocksServer: Exiting");
return Ok(());
}
}
}
}
+13 -3
View File
@@ -1,7 +1,17 @@
use snafu::Snafu;
#[derive(Debug, Snafu)]
/// SOCKS4 Response codes
#[allow(dead_code)]
pub(crate) enum ResponseCodeV4 {
Granted = 0x5a,
RequestRejected = 0x5b,
CannotConnectToIdent = 0x5c,
DifferentUserId = 0x5d,
}
/// Possible SOCKS5 Response Codes
pub(crate) enum ResponseCode {
#[derive(Debug, Snafu)]
pub(crate) enum ResponseCodeV5 {
Success = 0x00,
#[snafu(display("SOCKS5 Server Failure"))]
Failure = 0x01,
@@ -48,7 +58,7 @@ where
}
/// DST.addr variant types
#[derive(PartialEq)]
#[derive(Debug, PartialEq)]
pub(crate) enum AddrType {
V4 = 0x01,
Domain = 0x03,
@@ -4,8 +4,8 @@ import expect from 'expect';
describe('Query: balances', () => {
it('can query for an account balance', async () => {
const client = await ValidatorClient.connectForQuery(
'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
'https://rpc.nymtech.net/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
}).timeout(5000);
})
})
+14 -8
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.0.1"
version = "1.1.0"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -19,23 +19,27 @@ coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
[dependencies]
futures = "0.3"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = { version = "=0.2.78", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
tokio = { version = "1.21.2", features = ["sync"] }
url = "2.2"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
# internal
client-core = { path = "../client-core", default-features = false, features = ["wasm"] }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
nymsphinx = { path = "../../common/nymsphinx" }
topology = { path = "../../common/topology" }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" }
task = { path = "../../common/task" }
# 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
@@ -53,6 +57,8 @@ wee_alloc = { version = "0.4", optional = true }
wasm-bindgen-test = "0.3"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
wasm-opt = true
[profile.release]
lto = true
opt-level = 'z'
+5
View File
@@ -0,0 +1,5 @@
clippy:
cargo clippy --all-features --target wasm32-unknown-unknown -- -D warnings
test:
wasm-pack test --node
+4 -3
View File
@@ -11,6 +11,7 @@ This client is part of the [Nym](https://nymtech.net/docs) project. It's written
## Security Status
From a security point of view, this module is not yet complete. Key missing features include, but are not limited to: cover traffic, sending packets with delay according to Poisson distribution.
They should be implemented soon. You can build your applications, but don't rely on it for strong anonymity yet if your application needs cover traffic.
## Using it
@@ -37,6 +38,6 @@ To be clear, this is not something that most JS developers need to worry about,
If you're a Nym platform developer who's made changes to the Rust (or JS) files and wants to re-publish the package to NPM, here's how you do it:
1. `wasm-pack build --scope nymproject` builds the wasm binaries into the `pkg` directory (not in source control)
2. bump version numbers as necessary for SemVer
3. `wasm-pack publish --access=public` will publish your changed package to NPM
1. bump version numbers as necessary for SemVer
2. `wasm-pack build --scope nymproject` builds the wasm binaries into the `pkg` directory (not in source control)
3. `cd pkg && npm publish --access=public` will publish your changed package to NPM
+11 -1
View File
@@ -4,6 +4,16 @@ This example application demonstrates how to use WebAssembly to create Sphinx pa
## 🚴 Usage
Build the WASM package for bundling:
```
wasm-pack build --scope nymproject --target no-modules
```
in the `clients/webassembly` directory (one up).
Start the webpack dev server:
```
npm install # set up dependencies
npm run start # starts a web server at http://localhost:8001
@@ -15,4 +25,4 @@ Check your dev console for output.
Install `wasm-pack`. Instruction are at the [Rust WASM tutorial](https://rustwasm.github.io/docs/book/game-of-life/hello-world.html).
`wasm-pack build` in the `clients/webassembly` directory (one up) will rebuild the wasm package if you make changes to the Rust source. That will be automatically picked up (and reloaded, if need be) by the npm dev server.
`wasm-pack build --scope nymproject --target no-modules` in the `clients/webassembly` directory (one up) will rebuild the wasm package if you make changes to the Rust source. That will be automatically picked up (and reloaded, if need be) by the npm dev server.
+2 -2
View File
@@ -1,5 +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));
import('./index.js')
.catch(e => console.error('Error importing `index.js`:', e));
+68 -56
View File
@@ -1,4 +1,4 @@
// Copyright 2020 Nym Technologies SA
// Copyright 2020-2022 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.
@@ -12,55 +12,67 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {
NymClient,
set_panic_hook
} from "@nymproject/nym-client-wasm"
class WebWorkerClient {
worker = null;
// current limitation of rust-wasm for async stuff : (
let client = 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 } = ev.data.args;
displayReceived(message);
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,
},
});
};
}
let client = null;
async function main() {
// sets up better stack traces in case of in-rust panics
set_panic_hook();
client = new WebWorkerClient();
// validator server we will use to get topology from
const validator = "https://sandbox-validator.nymtech.net/api"; //"http://localhost:8081";
client = new NymClient(validator);
const on_message = (msg) => displayReceived(msg);
const on_connect = () => console.log("Established (and authenticated) gateway connection!");
client.set_on_message(on_message);
client.set_on_gateway_connect(on_connect);
// this is current limitation of wasm in rust - for async methods you can't take self my reference...
// I'm trying to figure out if I can somehow hack my way around it, but for time being you have to re-assign
// the object (it's the same one)
client = await client.initial_setup();
const self_address = client.self_address();
displaySenderAddress(self_address);
const sendButton = document.querySelector('#send-button');
sendButton.onclick = function () {
sendMessageTo();
}
const sendButton = document.querySelector('#send-button');
sendButton.onclick = function() {
sendMessageTo();
};
}
/**
* 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.
*
* @param {Client} nymClient the nym client to use for message sending
*/
async function sendMessageTo() {
const message = document.getElementById("message").value;
const recipient = document.getElementById("recipient").value;
client = await client.send_message(message, recipient);
displaySend(message);
const message = document.getElementById('message').value;
const recipient = document.getElementById('recipient').value;
await client.sendMessage(message, recipient);
displaySend(message);
}
/**
@@ -69,16 +81,16 @@ async function sendMessageTo() {
* @param {string} message
*/
function displaySend(message) {
let timestamp = new Date().toISOString().substr(11, 12);
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)
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)
sendDiv.appendChild(paragraph);
document.getElementById('output').appendChild(sendDiv);
}
/**
@@ -87,17 +99,17 @@ function displaySend(message) {
* @param {string} message
*/
function displayReceived(message) {
const content = message.message
const replySurb = message.replySurb
const content = message;
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 >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)"))
paragraph.appendChild(paragraphContent)
receivedDiv.appendChild(paragraph)
document.getElementById("output").appendChild(receivedDiv)
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 >>> ' + content);
// let paragraphContent = document.createTextNode(timestamp + " received >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)"))
paragraph.appendChild(paragraphContent);
receivedDiv.appendChild(paragraph);
document.getElementById('output').appendChild(receivedDiv);
}
@@ -107,7 +119,7 @@ function displayReceived(message) {
* @param {Client} nymClient
*/
function displaySenderAddress(address) {
document.getElementById("sender").value = address;
document.getElementById('sender').value = address;
}
// Let's get started!
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -36,4 +36,4 @@
"dependencies": {
"@nymproject/nym-client-wasm": "file:../pkg"
}
}
}
@@ -1,15 +1,33 @@
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
entry: "./bootstrap.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bootstrap.js",
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
mode: "development",
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']})
new CopyWebpackPlugin({
patterns: [
'index.html',
{
from: 'node_modules/@nymproject/nym-client-wasm/*.(js|wasm)',
to: '[name][ext]',
},
],
}),
],
experiments: { asyncWebAssembly: true }
experiments: { syncWebAssembly: true },
};
+125
View File
@@ -0,0 +1,125 @@
// Copyright 2020-2022 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 { default_debug, get_gateway, NymClient, set_panic_hook, Config } = wasm_bindgen;
class ClientWrapper {
constructor(config, onMessageHandler) {
this.rustClient = new NymClient(config);
this.rustClient.set_on_message(onMessageHandler);
this.rustClient.set_on_gateway_connect(this.onConnect);
}
selfAddress = () => {
return this.rustClient.self_address();
};
onConnect = () => {
console.log('Established (and authenticated) gateway connection!');
};
start = async () => {
// this is current limitation of wasm in rust - for async methods you can't take self by reference...
// I'm trying to figure out if I can somehow hack my way around it, but for time being you have to re-assign
// the object (it's the same one)
this.rustClient = await this.rustClient.start();
};
sendMessage = async (recipient, message) => {
this.rustClient = await this.rustClient.send_message(recipient, message);
};
sendBinaryMessage = async (recipient, message) => {
this.rustClient = await this.rustClient.send_binary_message(recipient, message);
};
}
let client = null;
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();
// validator server we will use to get topology from
const validator = 'https://validator.nymtech.net/api'; //"http://localhost:8081";
const preferredGateway = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
const gatewayEndpoint = await get_gateway(validator, preferredGateway);
gatewayEndpoint.gateway_listener = "wss://gateway1.nymtech.net:443"; // this is needed if we want it to work on the web. However this gateway is a v1 gateway, we will need to change for v2 once we get there
// 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 = true;
// 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 config = new Config('my-awesome-wasm-client', validator, gatewayEndpoint, debug);
const onMessageHandler = (message) => {
self.postMessage({
kind: 'ReceiveMessage',
args: {
message,
},
});
};
console.log('Instantiating WASM client...');
client = new ClientWrapper(config, onMessageHandler);
console.log('Web worker creating WASM client...');
await client.start();
console.log('WASM client running!');
const selfAddress = client.rustClient.self_address();
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 => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'SendMessage': {
const { message, recipient } = event.data.args;
await client.sendMessage(message, recipient);
}
}
}
};
}
// Let's get started!
main();
-11
View File
@@ -1,11 +0,0 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
}
}
}
@@ -0,0 +1,217 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
#[wasm_bindgen(typescript_custom_section)]
const TS_DEFS: &'static str = r#"
export interface BinaryMessage {
kind: number,
payload: Uint8Array;
headers: string,
}
export interface StringMessage {
kind: number,
payload: string;
}
"#;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "BinaryMessage")]
pub type IBinaryMessage;
#[wasm_bindgen(typescript_type = "StringMessage")]
pub type IStringMessage;
}
#[derive(Serialize, Deserialize)]
pub struct BinaryMessage {
pub kind: u8,
pub payload: Vec<u8>,
pub headers: String,
}
#[derive(Serialize, Deserialize)]
pub struct StringMessage {
pub kind: u8,
pub payload: String,
}
/// Create a new binary message with a user-specified `kind`.
#[wasm_bindgen]
pub fn create_binary_message(kind: u8, payload: Vec<u8>) -> Vec<u8> {
create_binary_message_with_headers(kind, payload, "".to_string())
}
/// Create a new message with a UTF-8 encoded string `payload` and a user-specified `kind`.
#[wasm_bindgen]
pub fn create_binary_message_from_string(kind: u8, payload: String) -> Vec<u8> {
create_binary_message_with_headers(kind, payload.as_bytes().to_vec(), "".to_string())
}
/// Create a new binary message with a user-specified `kind`, and `headers` as a string.
#[wasm_bindgen]
pub fn create_binary_message_with_headers(kind: u8, payload: Vec<u8>, headers: String) -> Vec<u8> {
let headers = headers.as_bytes().to_vec();
let size = (headers.len() as u64).to_be_bytes().to_vec();
vec![vec![kind], size, headers, payload].concat()
}
/// Parse the `kind` and byte array `payload` from a byte array
#[wasm_bindgen]
pub async fn parse_binary_message(message: Vec<u8>) -> Result<IBinaryMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let (kind, _headers, payload) = parse_binary_payload(&message);
Ok(serde_wasm_bindgen::to_value(&BinaryMessage {
kind,
payload: payload.to_vec(),
headers: "".to_string(),
})
.unwrap()
.unchecked_into::<IBinaryMessage>())
}
/// Parse the `kind` and byte array `payload` from a byte array with headers
#[wasm_bindgen]
pub async fn parse_binary_message_with_headers(
message: Vec<u8>,
) -> Result<IBinaryMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let (kind, headers, payload) = parse_binary_payload(&message);
Ok(serde_wasm_bindgen::to_value(&BinaryMessage {
kind,
payload: payload.to_vec(),
headers,
})
.unwrap()
.unchecked_into::<IBinaryMessage>())
}
/// Parse the `kind` and UTF-8 string `payload` from a byte array with headers
#[wasm_bindgen]
pub async fn parse_string_message_with_headers(
message: Vec<u8>,
) -> Result<IStringMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let (kind, _headers, payload) = parse_binary_payload(&message);
let payload = String::from_utf8_lossy(payload).into_owned();
Ok(
serde_wasm_bindgen::to_value(&StringMessage { kind, payload })
.unwrap()
.unchecked_into::<IStringMessage>(),
)
}
pub(crate) fn parse_binary_payload(message: &[u8]) -> (u8, String, &[u8]) {
// 1st byte is the kind
let kind = message[0];
// then the size as u64 big endian
let mut size = [0u8; 8];
size.clone_from_slice(&message[1..9]);
let size = u64::from_be_bytes(size) as usize;
// then the headers
let headers = String::from_utf8_lossy(&message[9..9 + size]).into_owned();
// finally the payload
let payload = &message[9 + size..];
(kind, headers, payload)
}
/// Parse the `kind` and UTF-8 string `payload` from a byte array
#[wasm_bindgen]
pub async fn parse_string_message(message: Vec<u8>) -> Result<IStringMessage, JsError> {
if message.len() < 2 {
return Err(JsError::new(
"Could not parse message, as less than 2 bytes long",
));
}
let kind = message[0];
let payload = String::from_utf8_lossy(&message[1..]).into_owned();
Ok(
serde_wasm_bindgen::to_value(&StringMessage { kind, payload })
.unwrap()
.unchecked_into::<IStringMessage>(),
)
}
#[cfg(test)]
mod tests {
use super::{create_binary_message_with_headers, parse_binary_payload};
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn test_binary_with_headers() {
let message_as_bytes = create_binary_message_with_headers(
42u8,
vec![0u8, 1u8, 2u8],
"test headers".to_string(),
);
// calculate header size
let headers = "test headers".as_bytes().to_vec();
let size = headers.len();
// the expected size
let expected_size = 12;
assert_eq!(size, expected_size);
assert_eq!(message_as_bytes[0], 42u8);
assert_eq!(message_as_bytes[1..9], 12u64.to_be_bytes());
assert_eq!(
message_as_bytes[9 + expected_size..9 + expected_size + 3],
vec![0u8, 1u8, 2u8]
);
let res = parse_binary_payload(&message_as_bytes);
assert_eq!(res.0, 42u8);
assert_eq!(res.1, "test headers".to_string());
assert_eq!(res.2, vec![0u8, 1u8, 2u8]);
}
#[wasm_bindgen_test]
fn test_binary_with_empty_headers() {
let message_as_bytes =
create_binary_message_with_headers(42u8, vec![0u8, 1u8, 2u8], "".to_string());
let expected_size = 0;
assert_eq!(message_as_bytes[0], 42u8);
assert_eq!(message_as_bytes[1..9], 0u64.to_be_bytes());
assert_eq!(
message_as_bytes[9 + expected_size..9 + expected_size + 3],
vec![0u8, 1u8, 2u8]
);
let res = parse_binary_payload(&message_as_bytes);
assert_eq!(res.0, 42u8);
assert_eq!(res.1, "".to_string());
assert_eq!(res.2, vec![0u8, 1u8, 2u8]);
}
}
+167
View File
@@ -0,0 +1,167 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
#![allow(clippy::drop_non_drop)]
use client_core::config::{DebugConfig as ConfigDebug, ExtendedPacketSize, GatewayEndpointConfig};
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) validator_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,
}
#[wasm_bindgen]
impl Config {
#[wasm_bindgen(constructor)]
pub fn new(
id: String,
validator_server: String,
gateway_endpoint: GatewayEndpointConfig,
debug: Option<Debug>,
) -> Self {
Config {
id,
validator_api_url: validator_server
.parse()
.expect("provided url was malformed"),
disabled_credentials_mode: true,
gateway_endpoint,
debug: debug.map(Into::into).unwrap_or_default(),
}
}
}
// just a helper structure to more easily pass through the JS boundary
#[wasm_bindgen]
pub struct Debug {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
pub average_packet_delay_ms: u64,
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
pub average_ack_delay_ms: u64,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 1.
pub ack_wait_multiplier: f64,
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
pub ack_wait_addition_ms: u64,
/// 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,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
pub message_sending_average_delay_ms: u64,
/// 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,
/// 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,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
pub topology_resolution_timeout_ms: u64,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay_ms])
pub disable_loop_cover_traffic_stream: bool,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
pub use_extended_packet_size: bool,
}
impl From<Debug> for ConfigDebug {
fn from(debug: Debug) -> Self {
// For now we just always use the (older) 32kb extended size
let use_extended_packet_size = debug
.use_extended_packet_size
.then(|| ExtendedPacketSize::Extended32);
ConfigDebug {
average_packet_delay: Duration::from_millis(debug.average_packet_delay_ms),
average_ack_delay: Duration::from_millis(debug.average_ack_delay_ms),
ack_wait_multiplier: debug.ack_wait_multiplier,
ack_wait_addition: Duration::from_millis(debug.ack_wait_addition_ms),
loop_cover_traffic_average_delay: Duration::from_millis(
debug.loop_cover_traffic_average_delay_ms,
),
message_sending_average_delay: Duration::from_millis(
debug.message_sending_average_delay_ms,
),
gateway_response_timeout: Duration::from_millis(debug.gateway_response_timeout_ms),
topology_refresh_rate: Duration::from_millis(debug.topology_refresh_rate_ms),
topology_resolution_timeout: Duration::from_millis(
debug.topology_resolution_timeout_ms,
),
disable_loop_cover_traffic_stream: debug.disable_loop_cover_traffic_stream,
disable_main_poisson_packet_distribution: debug
.disable_main_poisson_packet_distribution,
use_extended_packet_size,
}
}
}
impl From<ConfigDebug> for Debug {
fn from(debug: ConfigDebug) -> Self {
Debug {
average_packet_delay_ms: debug.average_packet_delay.as_millis() as u64,
average_ack_delay_ms: debug.average_ack_delay.as_millis() as u64,
ack_wait_multiplier: debug.ack_wait_multiplier,
ack_wait_addition_ms: debug.ack_wait_addition.as_millis() as u64,
loop_cover_traffic_average_delay_ms: debug.loop_cover_traffic_average_delay.as_millis()
as u64,
message_sending_average_delay_ms: debug.message_sending_average_delay.as_millis()
as u64,
gateway_response_timeout_ms: debug.gateway_response_timeout.as_millis() as u64,
topology_refresh_rate_ms: debug.topology_refresh_rate.as_millis() as u64,
topology_resolution_timeout_ms: debug.topology_resolution_timeout.as_millis() as u64,
disable_loop_cover_traffic_stream: debug.disable_loop_cover_traffic_stream,
disable_main_poisson_packet_distribution: debug
.disable_main_poisson_packet_distribution,
use_extended_packet_size: debug.use_extended_packet_size.is_some(),
}
}
}
#[wasm_bindgen]
pub fn default_debug() -> Debug {
ConfigDebug::default().into()
}
+123 -213
View File
@@ -1,148 +1,167 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crypto::asymmetric::{encryption, identity};
use futures::channel::mpsc;
use gateway_client::GatewayClient;
use nymsphinx::acknowledgements::AckKey;
use self::config::Config;
use crate::client::response_pusher::ResponsePusher;
use client_connections::TransmissionLane;
use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
use client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
use crypto::asymmetric::identity;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::preparer::MessagePreparer;
use rand::rngs::OsRng;
use received_processor::ReceivedMessagesProcessor;
use std::sync::Arc;
use std::time::Duration;
use topology::{gateway, nym_topology_from_bonds, NymTopology};
use url::Url;
use task::ShutdownNotifier;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::{console_log, console_warn};
use wasm_utils::{console_error, console_log};
pub(crate) mod received_processor;
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
pub mod config;
mod response_pusher;
#[wasm_bindgen]
pub struct NymClient {
validator_server: Url,
disabled_credentials_mode: bool,
config: Config,
// TODO: technically this doesn't need to be an Arc since wasm is run on a single thread
// however, once we eventually combine this code with the native-client's, it will make things
// easier.
identity: Arc<identity::KeyPair>,
encryption_keys: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
message_preparer: Option<MessagePreparer<OsRng>>,
// message_receiver: MessageReceiver,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
// due to disgusting workaround I had to wrap the key_manager in an Option
// so that the interface wouldn't change (i.e. both `start` and `new` would still return a `NymClient`)
key_manager: Option<KeyManager>,
self_address: Option<String>,
// TODO: this should be stored somewhere persistently
// received_keys: HashSet<SURBEncryptionKey>,
topology: Option<NymTopology>,
gateway_client: Option<GatewayClient>,
/// Channel used for transforming 'raw' messages into sphinx packets and sending them
/// through the mix network.
client_input: Option<ClientInput>,
// callbacks
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
on_gateway_connect: Option<js_sys::Function>,
// 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
_shutdown: Option<ShutdownNotifier>,
}
#[wasm_bindgen]
impl NymClient {
#[wasm_bindgen(constructor)]
pub fn new(validator_server: String) -> Self {
pub fn new(config: Config) -> Self {
Self {
config,
key_manager: Some(Self::setup_key_manager()),
on_message: None,
on_binary_message: None,
on_gateway_connect: None,
client_input: None,
self_address: None,
_shutdown: None,
}
}
// 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
// perhaps this should be public?
fn setup_key_manager() -> KeyManager {
let mut rng = OsRng;
// for time being generate new keys each time...
let identity = identity::KeyPair::new(&mut rng);
let encryption_keys = encryption::KeyPair::new(&mut rng);
let ack_key = AckKey::new(&mut rng);
Self {
identity: Arc::new(identity),
encryption_keys: Arc::new(encryption_keys),
ack_key: Arc::new(ack_key),
validator_server: validator_server
.parse()
.expect("malformed validator server url provided"),
message_preparer: None,
// received_keys: Default::default(),
topology: None,
gateway_client: None,
on_message: None,
on_gateway_connect: None,
disabled_credentials_mode: true,
}
console_log!("generated new set of keys");
KeyManager::new(&mut rng)
}
pub fn set_on_message(&mut self, on_message: js_sys::Function) {
self.on_message = Some(on_message);
}
pub fn set_on_binary_message(&mut self, on_binary_message: js_sys::Function) {
self.on_binary_message = Some(on_binary_message);
}
pub fn set_on_gateway_connect(&mut self, on_connect: js_sys::Function) {
console_log!("setting on connect...");
self.on_gateway_connect = Some(on_connect)
}
pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) {
console_log!(
"Setting disabled credentials mode to {}",
disabled_credentials_mode
);
self.disabled_credentials_mode = disabled_credentials_mode;
}
fn as_mix_recipient(&self) -> Recipient {
// another disgusting (and hopefully temporary) workaround
let key_manager_ref = self
.key_manager
.as_ref()
.expect("attempting to call 'as_mix_recipient' after 'start'");
fn self_recipient(&self) -> Recipient {
Recipient::new(
*self.identity.public_key(),
*self.encryption_keys.public_key(),
self.gateway_client
.as_ref()
.expect("gateway connection was not established!")
.gateway_identity(),
*key_manager_ref.identity_keypair().public_key(),
*key_manager_ref.encryption_keypair().public_key(),
identity::PublicKey::from_base58_string(&self.config.gateway_endpoint.gateway_id)
.expect("no gateway has been selected"),
)
}
pub fn self_address(&self) -> String {
self.self_recipient().to_string()
if let Some(address) = &self.self_address {
address.clone()
} else {
self.as_mix_recipient().to_string()
}
}
// Right now it's impossible to have async exported functions to take `&self` rather than self
pub async fn initial_setup(self) -> Self {
let disabled_credentials_mode = self.disabled_credentials_mode;
// Right now it's impossible to have async exported functions to take `&mut self` rather than mut self
// TODO: try Rc<RefCell<Self>> approach?
pub async fn send_message(self, message: String, recipient: String) -> Self {
console_log!("Sending {} to {}", message, recipient);
let bandwidth_controller = None;
let message_bytes = message.into_bytes();
self.send_binary_message(message_bytes, recipient).await
}
let mut client = self.get_and_update_topology().await;
let gateway = client.choose_gateway();
pub async fn send_binary_message(self, message: Vec<u8>, recipient: String) -> Self {
console_log!("Sending {} bytes to {}", message.len(), recipient);
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded();
let recipient = Recipient::try_from_base58_string(recipient).unwrap();
let lane = TransmissionLane::General;
let mut gateway_client = GatewayClient::new(
gateway.clients_address(),
Arc::clone(&client.identity),
gateway.identity_key,
gateway.owner.clone(),
let input_msg = InputMessage::new_fresh(recipient, message, false, lane);
self.client_input
.as_ref()
.expect("start method was not called before!")
.input_sender
.send(input_msg)
.await
.expect("InputMessageReceiver has stopped receiving!");
self
}
fn start_reconstructed_pusher(
client_output: ClientOutput,
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
) {
ResponsePusher::new(client_output, on_message, on_binary_message).start()
}
pub async fn start(mut self) -> NymClient {
console_log!("Starting the wasm client");
let base_builder = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&self.config.debug,
self.key_manager.take().unwrap(),
None,
mixnet_messages_sender,
ack_sender,
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
bandwidth_controller,
true,
vec![self.config.validator_api_url.clone()],
);
if disabled_credentials_mode {
gateway_client.set_disabled_credentials_mode(true)
}
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
client.gateway_client = Some(gateway_client);
match client.on_gateway_connect.as_ref() {
self.self_address = Some(base_builder.as_mix_recipient().to_string());
let mut started_client = match base_builder.start_base().await {
Ok(base_client) => base_client,
Err(err) => {
console_error!("failed to start base client components - {}", err);
// proper error handling is left here as an exercise for the reader (hi Mark : ))
panic!("failed to start base client components - {err}")
}
};
match self.on_gateway_connect.as_ref() {
Some(callback) => {
callback
.call0(&JsValue::null())
@@ -151,125 +170,16 @@ impl NymClient {
None => console_log!("Gateway connection established - no callback specified"),
};
let rng = rand::rngs::OsRng;
let message_preparer = MessagePreparer::new(
rng,
client.self_recipient(),
DEFAULT_AVERAGE_PACKET_DELAY,
DEFAULT_AVERAGE_ACK_DELAY,
);
// those should be moved to a completely different struct, but I don't want to break compatibility for now
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
let received_processor = ReceivedMessagesProcessor::new(
Arc::clone(&client.encryption_keys),
Arc::clone(&client.ack_key),
);
let on_message = self.on_message.take();
let on_binary_message = self.on_binary_message.take();
Self::start_reconstructed_pusher(client_output, on_message, on_binary_message);
self.client_input = Some(client_input);
self._shutdown = Some(started_client.shutdown_notifier);
client.message_preparer = Some(message_preparer);
spawn_local(received_processor.start_processing(
mixnet_messages_receiver,
ack_receiver,
client.on_message.take().expect("on_message was not set!"),
));
client
}
// Right now it's impossible to have async exported functions to take `&mut self` rather than mut self
// TODO: try Rc<RefCell<Self>> approach?
pub async fn send_message(mut self, message: String, recipient: String) -> Self {
console_log!("Sending {} to {}", message, recipient);
let message_bytes = message.into_bytes();
let recipient = Recipient::try_from_base58_string(recipient).unwrap();
let topology = self
.topology
.as_ref()
.expect("did not obtain topology before");
let message_preparer = self.message_preparer.as_mut().unwrap();
let (split_message, _reply_keys) = message_preparer
.prepare_and_split_message(message_bytes, false, topology)
.expect("failed to split the message");
let mut mix_packets = Vec::with_capacity(split_message.len());
for message_chunk in split_message {
// don't bother with acks etc. for time being
let prepared_fragment = message_preparer
.prepare_chunk_for_sending(message_chunk, topology, &self.ack_key, &recipient)
.await
.unwrap();
console_warn!("packet is going to have round trip time of {:?}, but we're not going to do anything for acks anyway ", prepared_fragment.total_delay);
mix_packets.push(prepared_fragment.mix_packet);
}
self.gateway_client
.as_mut()
.unwrap()
.batch_send_mix_packets(mix_packets)
.await
.unwrap();
self
}
pub(crate) fn choose_gateway(&self) -> &gateway::Node {
let topology = self
.topology
.as_ref()
.expect("did not obtain topology before");
// choose the first one available
assert!(!topology.gateways().is_empty());
topology.gateways().first().unwrap()
}
// Right now it's impossible to have async exported functions to take `&mut self` rather than mut self
// self: Rc<Self>
// or this: Rc<RefCell<Self>>
pub async fn get_and_update_topology(mut self) -> Self {
let new_topology = self.get_nym_topology().await;
self.update_topology(new_topology);
self
}
pub(crate) fn update_topology(&mut self, topology: NymTopology) {
self.topology = Some(topology)
}
// when updated to 0.10.0, to prevent headache later on, this function requires those two imports:
// use js_sys::Promise;
// use wasm_bindgen_futures::future_to_promise;
//
// pub fn get_full_topology_json(&self) -> Promise {
// let validator_client_config = validator_client::Config::new(
// vec![self.validator_server.clone()],
// &self.mixnet_contract_address,
// );
// let validator_client = validator_client::Client::new(validator_client_config);
//
// future_to_promise(async move {
// let topology = &validator_client.get_active_topology().await.unwrap();
// Ok(JsValue::from_serde(&topology).unwrap())
// })
// }
pub(crate) async fn get_nym_topology(&self) -> NymTopology {
let validator_client = validator_client::ApiClient::new(self.validator_server.clone());
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
Err(err) => panic!("{:?}", err),
Ok(mixes) => mixes,
};
let gateways = match validator_client.get_cached_gateways().await {
Err(err) => panic!("{}", err),
Ok(gateways) => gateways,
};
let topology = nym_topology_from_bonds(mixnodes, gateways);
let version = env!("CARGO_PKG_VERSION");
topology.filter_system_version(version)
}
}
@@ -1,183 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crypto::asymmetric::encryption;
use futures::StreamExt;
use gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
use nymsphinx::acknowledgements::identifier::recover_identifier;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::sync::Arc;
use wasm_bindgen::JsValue;
use wasm_utils::{console_error, console_log, console_warn};
#[derive(Serialize, Deserialize)]
pub struct ProcessedMessage {
pub message: String,
pub reply_surb: Option<String>,
}
impl From<ReconstructedMessage> for ProcessedMessage {
fn from(reconstructed: ReconstructedMessage) -> Self {
ProcessedMessage {
message: String::from_utf8_lossy(&reconstructed.message).into_owned(),
reply_surb: reconstructed
.reply_surb
.map(|reply_surb| reply_surb.to_base58_string()),
}
}
}
pub(crate) struct ReceivedMessagesProcessor {
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
message_receiver: MessageReceiver,
recently_reconstructed: HashSet<i32>,
}
impl ReceivedMessagesProcessor {
pub(crate) fn new(
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
) -> Self {
ReceivedMessagesProcessor {
local_encryption_keypair,
ack_key,
message_receiver: MessageReceiver::new(),
recently_reconstructed: HashSet::new(),
}
}
// TODO: duplicate code from received_buffer.rs in client-core....
fn process_received_fragment(&mut self, raw_fragment: Vec<u8>) -> Option<ProcessedMessage> {
let fragment_data = match self
.message_receiver
.recover_plaintext(self.local_encryption_keypair.private_key(), raw_fragment)
{
Err(e) => {
console_warn!("failed to recover fragment data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e);
return None;
}
Ok(frag_data) => frag_data,
};
if nymsphinx::cover::is_cover(&fragment_data) {
// currently won't be the case for a loooong time
console_log!("The message was a loop cover message! Skipping it");
return None;
}
let fragment = match self.message_receiver.recover_fragment(&fragment_data) {
Err(e) => {
console_warn!("failed to recover fragment from raw data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e);
return None;
}
Ok(frag) => frag,
};
if self.recently_reconstructed.contains(&fragment.id()) {
console_warn!("Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost", fragment.id());
return None;
}
// if we returned an error the underlying message is malformed in some way
match self.message_receiver.insert_new_fragment(fragment) {
Err(err) => match err {
MessageRecoveryError::MalformedReconstructedMessage(message_sets) => {
// TODO: should we really insert reconstructed sets? could this be abused for some attack?
for set_id in message_sets {
if !self.recently_reconstructed.insert(set_id) {
// or perhaps we should even panic at this point?
console_error!(
"Reconstructed another message containing already used set id!"
)
}
}
None
}
_ => unreachable!(
"no other error kind should have been returned here! If so, it's a bug!"
),
},
Ok(reconstruction_result) => match reconstruction_result {
Some((reconstructed_message, used_sets)) => {
for set_id in used_sets {
if !self.recently_reconstructed.insert(set_id) {
// or perhaps we should even panic at this point?
console_error!(
"Reconstructed another message containing already used set id!"
)
}
}
Some(reconstructed_message.into())
}
None => None,
},
}
}
// TODO: duplicate code from acknowledgement listener...
fn process_received_ack(&self, ack_content: Vec<u8>) {
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
.map(FragmentIdentifier::try_from_bytes)
{
Some(Ok(frag_id)) => frag_id,
_ => {
console_warn!("Received invalid ACK!"); // should we do anything else about that?
return;
}
};
// if we received an ack for cover message or a reply there will be nothing to remove,
// because nothing was inserted in the first place
if frag_id == COVER_FRAG_ID {
return;
} else if frag_id.is_reply() {
console_warn!("Received an ack for a reply message - no need to do anything! (don't know what to do!)");
// TODO: probably there will need to be some extra procedure here, something to notify
// user that his reply reached the recipient (since we got an ack)
return;
}
console_log!(
"Received an ack for fragment {:?} but can't do anything more about it just yet...",
frag_id
);
// here be ack handling...
}
// TODO: this needs to have a shutdown signal!
pub(crate) async fn start_processing(
mut self,
mixnet_messages_receiver: MixnetMessageReceiver,
ack_receiver: AcknowledgementReceiver,
on_message: js_sys::Function,
) {
let mut fused_mixnet_messages_receiver = mixnet_messages_receiver.fuse();
let mut fused_ack_receiver = ack_receiver.fuse();
let this = JsValue::null();
loop {
futures::select! {
mix_msgs = fused_mixnet_messages_receiver.next() => {
for mix_msg in mix_msgs.unwrap() {
if let Some(processed) = self.process_received_fragment(mix_msg) {
let arg1 = JsValue::from_serde(&processed).unwrap();
on_message.call1(&this, &arg1).expect("on message failed!");
}
}
}
acks = fused_ack_receiver.next() => {
for ack in acks.unwrap() {
self.process_received_ack(ack);
}
}
}
}
}
}
@@ -0,0 +1,71 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_core::client::base_client::ClientOutput;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
use futures::channel::mpsc;
use futures::StreamExt;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::console_log;
pub(crate) struct ResponsePusher {
reconstructed_receiver: ReconstructedMessagesReceiver,
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
}
impl ResponsePusher {
pub(crate) fn new(
client_output: ClientOutput,
on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
) -> Self {
if on_message.is_none() && on_binary_message.is_none() {
// exercise for the reader : )
panic!("neither 'on_message' nor 'on_binary_message' was set!")
}
// register our output
let (reconstructed_sender, reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
client_output
.received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
ResponsePusher {
reconstructed_receiver,
on_message,
on_binary_message,
}
}
pub(crate) fn start(mut self) {
spawn_local(async move {
let this = JsValue::null();
while let Some(reconstructed) = self.reconstructed_receiver.next().await {
for msg in reconstructed {
if let Some(ref callback_binary) = self.on_binary_message {
let arg1 = serde_wasm_bindgen::to_value(&msg.message).unwrap();
callback_binary
.call1(&this, &arg1)
.expect("on binary message failed!");
}
if let Some(ref callback) = self.on_message {
if msg.reply_surb.is_some() {
console_log!("the received message contained a reply-surb that we do not know how to handle (yet)")
}
let stringified = String::from_utf8_lossy(&msg.message).into_owned();
let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap();
callback.call1(&this, &arg1).expect("on message failed!");
}
}
}
})
}
}
@@ -0,0 +1,44 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_core::config::GatewayEndpointConfig;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpointConfig {
let validator_client = validator_client::client::ApiClient::new(api_server.parse().unwrap());
let gateways = match validator_client.get_cached_gateways().await {
Err(err) => panic!("failed to obtain list of all gateways - {err}"),
Ok(gateways) => gateways,
};
if let Some(preferred) = preferred {
if let Some(details) = gateways
.iter()
.find(|g| g.gateway.identity_key == preferred)
{
return GatewayEndpointConfig {
gateway_id: details.gateway.identity_key.clone(),
gateway_owner: details.owner.to_string(),
gateway_listener: format!(
"ws://{}:{}",
details.gateway.host, details.gateway.clients_port
),
};
}
}
let details = gateways
.first()
.expect("current topology holds no gateways");
GatewayEndpointConfig {
gateway_id: details.gateway.identity_key.clone(),
gateway_owner: details.owner.to_string(),
gateway_listener: format!(
"ws://{}:{}",
details.gateway.host, details.gateway.clients_port
),
}
}

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