Compare commits

...

192 Commits

Author SHA1 Message Date
Mark Sinclair c720b430b7 sdk: examples: UI to send and show sender client address 2022-11-29 14:15:47 +00:00
Mark Sinclair 934a8c3735 sdk: add headers to wasm client 2022-11-29 14:15:47 +00:00
Mark Sinclair ab1479f363 wasm-client: make headers Option<String> and add to all methods 2022-11-29 14:15:47 +00: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
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
Jędrzej Stuczyński fe6da046dc Fixed beta clippy warnings (#1736) 2022-11-03 10:13:16 +00: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
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 2d5f851252 Workaround for clippy #9612 issue 2022-10-27 10:47:13 +01: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
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 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
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 0fd178a304 Merge branch 'develop' into 327-nym-connect-colours 2022-09-22 10:37:06 +02: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 28cc772d7b various ui updates 2022-09-08 11:43:59 +02:00
406 changed files with 11868 additions and 8690 deletions
+30 -14
View File
@@ -1,36 +1,52 @@
name: Daily security audit name: Daily security audit
on: workflow_dispatch on:
schedule:
- cron: '5 9 * * *'
jobs: jobs:
security_audit: cargo-deny:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - name: Checkout repository code
- uses: actions-rs/audit-check@v1 uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} 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: notification:
if: ${{ failure() }} needs: cargo-deny
needs: security_audit
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v2 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 - name: Keybase - Node Install
run: npm install run: npm install
working-directory: .github/workflows/support-files working-directory: .github/workflows/support-files
- name: Keybase - Send Notification - name: Keybase - Send Notification
env: env:
NYM_NOTIFICATION_KIND: nightly NYM_NOTIFICATION_KIND: security
NYM_PROJECT_NAME: "Nym daily audit" NYM_PROJECT_NAME: "Daily security report"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBTECH_TEAM }}" KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "test" KEYBASE_NYM_CHANNEL: "security"
uses: docker://keybaseio/client:stable-node uses: docker://keybaseio/client:stable-node
with: with:
args: .github/workflows/support-files/notifications/entry_point.sh args: .github/workflows/support-files/notifications/entry_point.sh
+9 -7
View File
@@ -29,6 +29,12 @@ jobs:
override: true override: true
components: rustfmt, clippy components: rustfmt, clippy
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Build all binaries - name: Build all binaries
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
@@ -48,12 +54,6 @@ jobs:
command: test command: test
args: --workspace --all-features -- --ignored 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 - uses: actions-rs/clippy-check@v1
name: Clippy checks name: Clippy checks
with: with:
@@ -66,6 +66,8 @@ jobs:
command: clippy command: clippy
args: --workspace -- -D warnings args: --workspace -- -D warnings
# COCONUT stuff
- name: Build all binaries with coconut enabled - name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
@@ -82,4 +84,4 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: clippy 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 name: Build release of Nym smart contracts
on: on:
workflow_dispatch: workflow_dispatch:
release:
defaults: types: [created]
run:
working-directory: contracts
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - 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 - name: Install Rust stable
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -21,7 +26,7 @@ jobs:
components: rustfmt, clippy components: rustfmt, clippy
- name: Build release contracts - 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 - name: Upload Mixnet Contract Artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@@ -36,3 +41,11 @@ jobs:
name: vesting_contract.wasm name: vesting_contract.wasm
path: contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm path: contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
retention-days: 5 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
@@ -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: jobs:
matrix_prep: matrix_prep:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -8,25 +10,40 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }} matrix: ${{ steps.set-matrix.outputs.matrix }}
steps: steps:
# creates the matrix strategy from nightly_build_matrix_includes.json # creates the matrix strategy from nightly_build_matrix_includes.json
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- id: set-matrix - id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main uses: JoshuaTheMiller/conditional-build-matrix@main
with: 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`]' filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
build: get_release:
runs-on: ubuntu-latest
needs: matrix_prep 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: strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}} matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }} continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps: steps:
- name: Install Dependencies (Linux) - 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' if: matrix.os == 'ubuntu-latest'
- name: Check out repository code - name: Check out latest release branch
uses: actions/checkout@v2 uses: actions/checkout@v3
with:
ref: ${{needs.get_release.outputs.output1}}
- name: Install rust toolchain - name: Install rust toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@@ -42,6 +59,12 @@ jobs:
command: build command: build
args: --workspace 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 - name: Run all tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
@@ -99,6 +122,12 @@ jobs:
command: build command: build
args: --workspace --features=coconut 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 - name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
@@ -145,13 +174,13 @@ jobs:
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification: notification:
needs: build needs: [build,get_release]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Collect jobs status - name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v2 uses: technote-space/workflow-conclusion-action@v2
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Keybase - Node Install - name: Keybase - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure' if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install run: npm install
@@ -160,14 +189,14 @@ jobs:
if: env.WORKFLOW_CONCLUSION == 'failure' if: env.WORKFLOW_CONCLUSION == 'failure'
env: env:
NYM_NOTIFICATION_KIND: nightly NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build" NYM_PROJECT_NAME: "Nym nightly build on latest release"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" 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_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMTECH_TEAM }}" KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "${{ secrets.KEYBASE_CHANNEL_DEV_CORE_ID }}" KEYBASE_NYM_CHANNEL: "ci-nightly-release"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}" IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node uses: docker://keybaseio/client:stable-node
with: 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
+16 -16
View File
@@ -41,19 +41,19 @@ jobs:
- name: Keybase - Node Install - name: Keybase - Node Install
run: npm install run: npm install
working-directory: .github/workflows/support-files working-directory: .github/workflows/support-files
# - name: Keybase - Send Notification - name: Keybase - Send Notification
# env: env:
# NYM_NOTIFICATION_KIND: nym-connect NYM_NOTIFICATION_KIND: nym-connect
# NYM_PROJECT_NAME: "nym-connect" NYM_PROJECT_NAME: "nym-connect"
# NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}" NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
# NYM_CI_WWW_LOCATION: "nym-connect-${{ env.GITHUB_REF_SLUG }}" NYM_CI_WWW_LOCATION: "nym-connect-${{ env.GITHUB_REF_SLUG }}"
# GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
# GIT_BRANCH: "${GITHUB_REF##*/}" GIT_BRANCH: "${GITHUB_REF##*/}"
# KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
# KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
# KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
# KEYBASE_NYM_CHANNEL: "ci-nym-connect" KEYBASE_NYM_CHANNEL: "ci-nym-connect"
# IS_SUCCESS: "${{ job.status == 'success' }}" IS_SUCCESS: "${{ job.status == 'success' }}"
# uses: docker://keybaseio/client:stable-node uses: docker://keybaseio/client:stable-node
# with: with:
# args: .github/workflows/support-files/notifications/entry_point.sh args: .github/workflows/support-files/notifications/entry_point.sh
@@ -25,6 +25,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - 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-` - name: Check the release tag starts with `nym-binaries-`
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch' if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3 uses: actions/github-script@v3
@@ -3,7 +3,7 @@ require('dotenv').config();
const Bot = require('keybase-bot'); const Bot = require('keybase-bot');
let context = { let context = {
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect'], kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect','security'],
}; };
/** /**
@@ -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,
};
+62 -25
View File
@@ -6,34 +6,71 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
### Added ### Added
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577]) - binaries: add `-c` shortform for `--config-env-file`
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558]) - websocket-requests: add server response signalling current packet queue length in the client
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611)) - contracts: DKG contract that handles coconut key generation ([#1678][#1708][#1747])
- common/ledger: new library for communicating with a Ledger device ([#1640]) - validator-api: generate coconut keys interactively, using DKG and multisig contracts ([#1678][#1708][#1747])
- native-client/socks5-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
- native-client/socks5-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: `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])
- 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])
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to cmpute reward estimation endpoint
- vesting-contract: optional locked token pledge cap per account ([#1687]), defaults to 100_000 NYM
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
### Fixed
- 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])
- socks5-client: fix bug where in some cases packet reordering could trigger a connection being closed too early ([#1702],[#1724])
### Changed ### Changed
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541]) - clients: add concept of transmission lanes to better handle multiple data streams ([#1720])
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591]) - clients,validator-api: take coconut signers from the chain instead of specifying them via CLI ([#1747])
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585]) - multisig contract: add DKG contract to the list of addresses that can create proposals ([#1747])
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669]) - socks5-client: wait closing inbound connection until data is sent, and throttle incoming data in general ([#1783])
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681]) - nym-cli: improve error reporting/handling and changed `vesting-schedule` queries to use query client instead of signing client
- moved `Percent` struct to to `contracts-common`, change affects explorer-api
- clients: bound the sphinx packet channel and reduce sending rate if gateway can't keep up ([#1703],[#1725])
### 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 [#1541]: https://github.com/nymtech/nym/pull/1541
[#1558]: https://github.com/nymtech/nym/pull/1558 [#1558]: https://github.com/nymtech/nym/pull/1558
[#1577]: https://github.com/nymtech/nym/pull/1577 [#1577]: https://github.com/nymtech/nym/pull/1577
@@ -41,13 +78,13 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1591]: https://github.com/nymtech/nym/pull/1591 [#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640 [#1640]: https://github.com/nymtech/nym/pull/1640
[#1645]: https://github.com/nymtech/nym/pull/1645 [#1645]: https://github.com/nymtech/nym/pull/1645
[#1611]: https://github.com/nymtech/nym/pull/1611
[#1664]: https://github.com/nymtech/nym/pull/1664 [#1664]: https://github.com/nymtech/nym/pull/1664
[#1666]: https://github.com/nymtech/nym/pull/1645 [#1666]: https://github.com/nymtech/nym/pull/1645
[#1669]: https://github.com/nymtech/nym/pull/1669 [#1669]: https://github.com/nymtech/nym/pull/1669
[#1671]: https://github.com/nymtech/nym/pull/1671 [#1671]: https://github.com/nymtech/nym/pull/1671
[#1673]: https://github.com/nymtech/nym/pull/1673 [#1673]: https://github.com/nymtech/nym/pull/1673
[#1681]: https://github.com/nymtech/nym/pull/1681 [#1681]: https://github.com/nymtech/nym/pull/1681
[#1687]: https://github.com/nymtech/nym/pull/1687
[#1702]: https://github.com/nymtech/nym/pull/1702 [#1702]: https://github.com/nymtech/nym/pull/1702
[#1703]: https://github.com/nymtech/nym/pull/1703 [#1703]: https://github.com/nymtech/nym/pull/1703
[#1721]: https://github.com/nymtech/nym/pull/1721 [#1721]: https://github.com/nymtech/nym/pull/1721
Generated
+102 -31
View File
@@ -577,9 +577,18 @@ dependencies = [
] ]
[[package]] [[package]]
name = "client-core" name = "client-connections"
version = "1.0.1" version = "0.1.0"
dependencies = [ dependencies = [
"futures",
"log",
]
[[package]]
name = "client-core"
version = "1.1.0"
dependencies = [
"client-connections",
"config", "config",
"crypto", "crypto",
"dirs", "dirs",
@@ -627,6 +636,18 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "coconut-dkg-common"
version = "0.1.0"
dependencies = [
"contracts-common",
"cosmwasm-std",
"cw-utils",
"multisig-contract-common",
"schemars",
"serde",
]
[[package]] [[package]]
name = "coconut-interface" name = "coconut-interface"
version = "0.1.0" version = "0.1.0"
@@ -736,7 +757,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
name = "contracts-common" name = "contracts-common"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bs58",
"cosmwasm-std", "cosmwasm-std",
"dkg",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
@@ -850,9 +873,9 @@ dependencies = [
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.2" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -942,7 +965,6 @@ dependencies = [
"crypto", "crypto",
"rand 0.7.3", "rand 0.7.3",
"thiserror", "thiserror",
"url",
"validator-api-requests", "validator-api-requests",
"validator-client", "validator-client",
] ]
@@ -1221,9 +1243,9 @@ dependencies = [
[[package]] [[package]]
name = "cw-utils" name = "cw-utils"
version = "0.13.2" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e" checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b"
dependencies = [ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"schemars", "schemars",
@@ -1232,10 +1254,22 @@ dependencies = [
] ]
[[package]] [[package]]
name = "cw3" name = "cw2"
version = "0.13.2" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f871854338a54c7bb094d16ffe17212b93b146d9659dbce4c9402a9b77e240ef" checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"schemars",
"serde",
]
[[package]]
name = "cw3"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b"
dependencies = [ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"cw-utils", "cw-utils",
@@ -1244,10 +1278,26 @@ dependencies = [
] ]
[[package]] [[package]]
name = "cw4" name = "cw3-fixed-multisig"
version = "0.13.2" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4476d6a7c13c46ed9ff260bd0e1cf648dc37b13f483822e1ff2a431f0f6ee52" checksum = "df54aa54c13f405ec4ab36b6217538bc957d439eee58f89312db05a79caf6706"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"cw-utils",
"cw2",
"cw3",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw4"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd"
dependencies = [ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"cw-storage-plus", "cw-storage-plus",
@@ -1402,6 +1452,7 @@ dependencies = [
"ff 0.11.0", "ff 0.11.0",
"group 0.11.0", "group 0.11.0",
"lazy_static", "lazy_static",
"pemstore",
"rand 0.8.5", "rand 0.8.5",
"rand_chacha 0.3.1", "rand_chacha 0.3.1",
"rand_core 0.6.3", "rand_core 0.6.3",
@@ -1453,9 +1504,9 @@ dependencies = [
[[package]] [[package]]
name = "ed25519" name = "ed25519"
version = "1.4.1" version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369"
dependencies = [ dependencies = [
"serde", "serde",
"signature", "signature",
@@ -1582,7 +1633,7 @@ dependencies = [
[[package]] [[package]]
name = "explorer-api" name = "explorer-api"
version = "1.0.1" version = "1.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap 3.2.8", "clap 3.2.8",
@@ -2930,6 +2981,7 @@ dependencies = [
"cosmwasm-std", "cosmwasm-std",
"cw-utils", "cw-utils",
"cw3", "cw3",
"cw3-fixed-multisig",
"cw4", "cw4",
"schemars", "schemars",
"serde", "serde",
@@ -3070,7 +3122,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-cli" name = "nym-cli"
version = "1.0.0" version = "1.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@@ -3087,6 +3139,7 @@ dependencies = [
"pretty_env_logger", "pretty_env_logger",
"serde", "serde",
"serde_json", "serde_json",
"tap",
"tokio", "tokio",
"validator-client", "validator-client",
] ]
@@ -3112,6 +3165,7 @@ dependencies = [
"rand 0.6.5", "rand 0.6.5",
"serde", "serde",
"serde_json", "serde_json",
"tap",
"thiserror", "thiserror",
"time 0.3.14", "time 0.3.14",
"toml", "toml",
@@ -3122,9 +3176,10 @@ dependencies = [
[[package]] [[package]]
name = "nym-client" name = "nym-client"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"clap 3.2.8", "clap 3.2.8",
"client-connections",
"client-core", "client-core",
"coconut-interface", "coconut-interface",
"completions", "completions",
@@ -3146,7 +3201,9 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sled", "sled",
"tap",
"task", "task",
"thiserror",
"tokio", "tokio",
"tokio-tungstenite 0.14.0", "tokio-tungstenite 0.14.0",
"topology", "topology",
@@ -3159,7 +3216,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-gateway" name = "nym-gateway"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@@ -3206,7 +3263,7 @@ dependencies = [
[[package]] [[package]]
name = "nym-mixnode" name = "nym-mixnode"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bs58", "bs58",
@@ -3248,10 +3305,11 @@ dependencies = [
[[package]] [[package]]
name = "nym-network-requester" name = "nym-network-requester"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"clap 3.2.8", "clap 3.2.8",
"client-connections",
"completions", "completions",
"dirs", "dirs",
"futures", "futures",
@@ -3295,9 +3353,10 @@ dependencies = [
[[package]] [[package]]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"clap 3.2.8", "clap 3.2.8",
"client-connections",
"client-core", "client-core",
"coconut-interface", "coconut-interface",
"completions", "completions",
@@ -3322,7 +3381,9 @@ dependencies = [
"serde", "serde",
"snafu 0.6.10", "snafu 0.6.10",
"socks5-requests", "socks5-requests",
"tap",
"task", "task",
"thiserror",
"tokio", "tokio",
"topology", "topology",
"url", "url",
@@ -3359,13 +3420,15 @@ dependencies = [
[[package]] [[package]]
name = "nym-validator-api" name = "nym-validator-api"
version = "1.0.2" version = "1.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"bs58",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"clap 3.2.8", "clap 3.2.8",
"coconut-bandwidth-contract-common", "coconut-bandwidth-contract-common",
"coconut-dkg-common",
"coconut-interface", "coconut-interface",
"config", "config",
"console-subscriber", "console-subscriber",
@@ -3377,6 +3440,7 @@ dependencies = [
"cw-utils", "cw-utils",
"cw3", "cw3",
"dirs", "dirs",
"dkg",
"dotenv", "dotenv",
"futures", "futures",
"gateway-client", "gateway-client",
@@ -3390,6 +3454,7 @@ dependencies = [
"nymcoconut", "nymcoconut",
"nymsphinx", "nymsphinx",
"okapi", "okapi",
"pemstore",
"pin-project", "pin-project",
"pretty_env_logger", "pretty_env_logger",
"rand 0.7.3", "rand 0.7.3",
@@ -3442,16 +3507,19 @@ name = "nymcoconut"
version = "0.5.0" version = "0.5.0"
dependencies = [ dependencies = [
"bincode", "bincode",
"bls12_381 0.5.0", "bls12_381 0.6.0",
"bs58", "bs58",
"criterion", "criterion",
"digest 0.9.0", "digest 0.9.0",
"dkg",
"doc-comment", "doc-comment",
"ff 0.10.1", "ff 0.11.0",
"getrandom 0.2.6", "getrandom 0.2.6",
"group 0.10.0", "group 0.11.0",
"itertools", "itertools",
"pemstore",
"rand 0.8.5", "rand 0.8.5",
"rand_chacha 0.3.1",
"serde", "serde",
"serde_derive", "serde_derive",
"sha2 0.9.9", "sha2 0.9.9",
@@ -4121,6 +4189,7 @@ name = "proxy-helpers"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bytes", "bytes",
"client-connections",
"futures", "futures",
"log", "log",
"ordered-buffer", "ordered-buffer",
@@ -5626,7 +5695,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
name = "task" name = "task"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"futures",
"log", "log",
"thiserror",
"tokio", "tokio",
] ]
@@ -5766,18 +5837,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.35" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.35" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -6405,6 +6476,7 @@ dependencies = [
"base64", "base64",
"bip39", "bip39",
"coconut-bandwidth-contract-common", "coconut-bandwidth-contract-common",
"coconut-dkg-common",
"coconut-interface", "coconut-interface",
"colored", "colored",
"config", "config",
@@ -6514,7 +6586,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"contracts-common", "contracts-common",
"cosmwasm-std", "cosmwasm-std",
"log",
"mixnet-contract-common", "mixnet-contract-common",
"schemars", "schemars",
"serde", "serde",
+2
View File
@@ -26,10 +26,12 @@ members = [
"common/client-libs/gateway-client", "common/client-libs/gateway-client",
"common/client-libs/mixnet-client", "common/client-libs/mixnet-client",
"common/client-libs/validator-client", "common/client-libs/validator-client",
"common/client-connections",
"common/coconut-interface", "common/coconut-interface",
"common/commands", "common/commands",
"common/config", "common/config",
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract", "common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/coconut-dkg",
"common/cosmwasm-smart-contracts/contracts-common", "common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/mixnet-contract", "common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract", "common/cosmwasm-smart-contracts/multisig-contract",
+3
View File
@@ -92,6 +92,9 @@ build-wallet:
build-connect: build-connect:
cargo build --manifest-path nym-connect/Cargo.toml --workspace cargo build --manifest-path nym-connect/Cargo.toml --workspace
build-explorer-api:
cargo build --manifest-path explorer-api/Cargo.toml --workspace
build-wasm-client: build-wasm-client:
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
+5 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "client-core" name = "client-core"
version = "1.0.1" version = "1.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021" edition = "2021"
@@ -14,11 +14,13 @@ log = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] } rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sled = { version = "0.34", optional = true } sled = { version = "0.34", optional = true }
tap = "1.0.1"
thiserror = "1.0.34" thiserror = "1.0.34"
url = { version ="2.2", features = ["serde"] } url = { version ="2.2", features = ["serde"] }
# internal # internal
config = { path = "../../common/config" } config = { path = "../../common/config" }
client-connections = { path = "../../common/client-connections" }
crypto = { path = "../../common/crypto" } crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" } gateway-client = { path = "../../common/client-libs/gateway-client" }
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] } #gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
@@ -28,7 +30,6 @@ nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" } pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" } topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false } validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
tap = "1.0.1"
tokio = { version = "1.21.2", features = ["time", "macros"]} tokio = { version = "1.21.2", features = ["time", "macros"]}
@@ -56,4 +57,5 @@ tempfile = "3.1.0"
default = ["reply-surb"] default = ["reply-surb"]
wasm = ["gateway-client/wasm"] wasm = ["gateway-client/wasm"]
coconut = ["gateway-client/coconut", "gateway-requests/coconut"] coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
reply-surb = ["sled"] reply-surb = ["sled"]
@@ -178,6 +178,10 @@ impl LoopCoverTrafficStream<OsRng> {
// This isn't a problem, if the channel is full means we're already sending the // This isn't a problem, if the channel is full means we're already sending the
// max amount of messages downstream can handle. // max amount of messages downstream can handle.
log::debug!("Failed to send cover message - channel full"); 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(_) => { TrySendError::Closed(_) => {
log::warn!("Failed to send cover message - channel closed"); log::warn!("Failed to send cover message - channel closed");
@@ -224,7 +228,9 @@ impl LoopCoverTrafficStream<OsRng> {
} }
} }
} }
assert!(shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("LoopCoverTrafficStream: Exiting"); log::debug!("LoopCoverTrafficStream: Exiting");
}) })
} }
@@ -1,9 +1,9 @@
use futures::channel::mpsc; use client_connections::TransmissionLane;
use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::clients::Recipient;
use nymsphinx::anonymous_replies::ReplySurb; use nymsphinx::anonymous_replies::ReplySurb;
pub type InputMessageSender = mpsc::UnboundedSender<InputMessage>; pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
pub type InputMessageReceiver = mpsc::UnboundedReceiver<InputMessage>; pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
#[derive(Debug)] #[derive(Debug)]
pub enum InputMessage { pub enum InputMessage {
@@ -11,6 +11,7 @@ pub enum InputMessage {
recipient: Recipient, recipient: Recipient,
data: Vec<u8>, data: Vec<u8>,
with_reply_surb: bool, with_reply_surb: bool,
lane: TransmissionLane,
}, },
Reply { Reply {
reply_surb: ReplySurb, reply_surb: ReplySurb,
@@ -19,11 +20,17 @@ pub enum InputMessage {
} }
impl 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 { InputMessage::Fresh {
recipient, recipient,
data, data,
with_reply_surb, with_reply_surb,
lane,
} }
} }
@@ -69,6 +69,8 @@ impl MixTrafficController {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) { pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
spawn_future(async move { spawn_future(async move {
debug!("Started MixTrafficController with graceful shutdown support"); debug!("Started MixTrafficController with graceful shutdown support");
@@ -88,7 +90,9 @@ impl MixTrafficController {
} }
} }
} }
assert!(shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("MixTrafficController: Exiting"); log::debug!("MixTrafficController: Exiting");
}) })
} }
-9
View File
@@ -1,5 +1,3 @@
use std::sync::atomic::AtomicBool;
pub mod cover_traffic_stream; pub mod cover_traffic_stream;
pub mod inbound_messages; pub mod inbound_messages;
pub mod key_manager; pub mod key_manager;
@@ -9,10 +7,3 @@ pub mod received_buffer;
#[cfg(feature = "reply-surb")] #[cfg(feature = "reply-surb")]
pub mod reply_key_storage; pub mod reply_key_storage;
pub mod topology_control; pub mod topology_control;
// This is *NOT* used to signal shutdown.
// It's critical that we don't have any tasks finishing early, this is an additional safety check
// that tasks exiting are doing so because shutdown has been signalled, and no other reason.
// In particular for tasks that rely on their associated channel being closed to signal shutdown,
// and don't have access to a shutdown listener channel.
pub static SHUTDOWN_HAS_BEEN_SIGNALLED: AtomicBool = AtomicBool::new(false);
@@ -33,7 +33,7 @@ impl AcknowledgementListener {
} }
async fn on_ack(&mut self, ack_content: Vec<u8>) { 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) let frag_id = match recover_identifier(&self.ack_key, &ack_content)
.map(FragmentIdentifier::try_from_bytes) .map(FragmentIdentifier::try_from_bytes)
{ {
@@ -72,6 +72,8 @@ impl AcknowledgementListener {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
debug!("Started AcknowledgementListener with graceful shutdown support"); debug!("Started AcknowledgementListener with graceful shutdown support");
while !shutdown.is_shutdown() { while !shutdown.is_shutdown() {
@@ -88,7 +90,9 @@ impl AcknowledgementListener {
} }
} }
} }
assert!(shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("AcknowledgementListener: Exiting"); log::debug!("AcknowledgementListener: Exiting");
} }
@@ -272,7 +272,9 @@ impl ActionController {
} }
} }
} }
assert!(shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("ActionController: Exiting"); log::debug!("ActionController: Exiting");
} }
@@ -8,7 +8,7 @@ use crate::client::{
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage}, real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
topology_control::TopologyAccessor, topology_control::TopologyAccessor,
}; };
use futures::StreamExt; use client_connections::TransmissionLane;
use log::*; use log::*;
use nymsphinx::anonymous_replies::ReplySurb; use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::preparer::MessagePreparer; use nymsphinx::preparer::MessagePreparer;
@@ -104,6 +104,7 @@ where
content: Vec<u8>, content: Vec<u8>,
with_reply_surb: bool, with_reply_surb: bool,
) -> Option<Vec<RealMessage>> { ) -> Option<Vec<RealMessage>> {
log::trace!("handling msg size: {}", content.len());
let topology_permit = self.topology_access.get_read_permit().await; let topology_permit = self.topology_access.get_read_permit().await;
let topology = match topology_permit let topology = match topology_permit
.try_get_valid_topology_ref(&self.ack_recipient, Some(&recipient)) .try_get_valid_topology_ref(&self.ack_recipient, Some(&recipient))
@@ -164,37 +165,44 @@ where
} }
async fn on_input_message(&mut self, msg: InputMessage) { async fn on_input_message(&mut self, msg: InputMessage) {
let real_messages = match msg { let (real_messages, lane) = match msg {
InputMessage::Fresh { InputMessage::Fresh {
recipient, recipient,
data, data,
with_reply_surb, with_reply_surb,
} => { lane,
} => (
self.handle_fresh_message(recipient, data, with_reply_surb) self.handle_fresh_message(recipient, data, with_reply_surb)
.await,
lane,
),
InputMessage::Reply { reply_surb, data } => (
self.handle_reply(reply_surb, data)
.await .await
} .map(|message| vec![message]),
InputMessage::Reply { reply_surb, data } => self TransmissionLane::Reply,
.handle_reply(reply_surb, data) ),
.await
.map(|message| vec![message]),
}; };
// there's no point in trying to send nothing // there's no point in trying to send nothing
if let Some(real_messages) = real_messages { if let Some(real_messages) = real_messages {
// tells real message sender (with the poisson timer) to send this to the mix network // tells real message sender (with the poisson timer) to send this to the mix network
self.real_message_sender self.real_message_sender
.unbounded_send(real_messages) .send((real_messages, lane))
.unwrap(); .await
.expect("BatchRealMessageReceiver has stopped receiving!");
} }
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
debug!("Started InputMessageListener with graceful shutdown support"); debug!("Started InputMessageListener with graceful shutdown support");
while !shutdown.is_shutdown() { while !shutdown.is_shutdown() {
tokio::select! { tokio::select! {
input_msg = self.input_receiver.next() => match input_msg { input_msg = self.input_receiver.recv() => match input_msg {
Some(input_msg) => { Some(input_msg) => {
self.on_input_message(input_msg).await; self.on_input_message(input_msg).await;
}, },
@@ -208,14 +216,16 @@ where
} }
} }
} }
assert!(shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("InputMessageListener: Exiting"); log::debug!("InputMessageListener: Exiting");
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub(super) async fn run(&mut self) { pub(super) async fn run(&mut self) {
debug!("Started InputMessageListener without graceful shutdown support"); debug!("Started InputMessageListener without graceful shutdown support");
while let Some(input_msg) = self.input_receiver.next().await { while let Some(input_msg) = self.input_receiver.recv().await {
self.on_input_message(input_msg).await; self.on_input_message(input_msg).await;
} }
} }
@@ -1,17 +1,21 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use super::action_controller::{Action, ActionSender}; use super::{
use super::PendingAcknowledgement; action_controller::{Action, ActionSender},
use super::RetransmissionRequestReceiver; PendingAcknowledgement, RetransmissionRequestReceiver,
};
use crate::client::{ use crate::client::{
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage}, real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
topology_control::TopologyAccessor, topology_control::TopologyAccessor,
}; };
use client_connections::TransmissionLane;
use futures::StreamExt; use futures::StreamExt;
use log::*; use log::*;
use nymsphinx::preparer::MessagePreparer; use nymsphinx::{
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient}; acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer,
};
use rand::{CryptoRng, Rng}; use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
@@ -113,15 +117,18 @@ where
// send to `OutQueueControl` to eventually send to the mix network // send to `OutQueueControl` to eventually send to the mix network
self.real_message_sender self.real_message_sender
.unbounded_send(vec![RealMessage::new( .send((
prepared_fragment.mix_packet, vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
frag_id, TransmissionLane::Retransmission,
)]) ))
.unwrap(); .await
.expect("BatchRealMessageReceiver has stopped receiving!");
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
debug!("Started RetransmissionRequestListener with graceful shutdown support"); debug!("Started RetransmissionRequestListener with graceful shutdown support");
while !shutdown.is_shutdown() { while !shutdown.is_shutdown() {
@@ -138,7 +145,9 @@ where
} }
} }
} }
assert!(shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("RetransmissionRequestListener: Exiting"); log::debug!("RetransmissionRequestListener: Exiting");
} }
@@ -8,12 +8,15 @@
use self::{ use self::{
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl, acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
}; };
use crate::client::real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors; use crate::{
use crate::client::{ client::{
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender, inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
topology_control::TopologyAccessor, real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors,
topology_control::TopologyAccessor,
},
spawn_future,
}; };
use crate::spawn_future; use client_connections::{ConnectionCommandReceiver, LaneQueueLengths};
use futures::channel::mpsc; use futures::channel::mpsc;
use gateway_client::AcknowledgementReceiver; use gateway_client::AcknowledgementReceiver;
use log::*; use log::*;
@@ -103,6 +106,7 @@ where
// obviously when we finally make shared rng that is on 'higher' level, this should become // obviously when we finally make shared rng that is on 'higher' level, this should become
// generic `R` // generic `R`
impl RealMessagesController<OsRng> { impl RealMessagesController<OsRng> {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
config: Config, config: Config,
ack_receiver: AcknowledgementReceiver, ack_receiver: AcknowledgementReceiver,
@@ -110,10 +114,12 @@ impl RealMessagesController<OsRng> {
mix_sender: BatchMixMessageSender, mix_sender: BatchMixMessageSender,
topology_access: TopologyAccessor, topology_access: TopologyAccessor,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage, #[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
) -> Self { ) -> Self {
let rng = OsRng; 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 (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded();
let ack_controller_connectors = AcknowledgementControllerConnectors::new( let ack_controller_connectors = AcknowledgementControllerConnectors::new(
@@ -159,6 +165,8 @@ impl RealMessagesController<OsRng> {
rng, rng,
config.self_recipient, config.self_recipient,
topology_access, topology_access,
lane_queue_lengths,
client_connection_rx,
); );
RealMessagesController { RealMessagesController {
@@ -4,7 +4,9 @@
use crate::client::mix_traffic::BatchMixMessageSender; use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender; use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
use crate::client::topology_control::TopologyAccessor; 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::task::{Context, Poll};
use futures::{Future, Stream, StreamExt}; use futures::{Future, Stream, StreamExt};
use log::*; use log::*;
@@ -16,7 +18,6 @@ use nymsphinx::forwarding::packet::MixPacket;
use nymsphinx::params::PacketSize; use nymsphinx::params::PacketSize;
use nymsphinx::utils::sample_poisson_duration; use nymsphinx::utils::sample_poisson_duration;
use rand::{CryptoRng, Rng}; use rand::{CryptoRng, Rng};
use std::collections::VecDeque;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@@ -27,22 +28,22 @@ use tokio::time;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_timer; use wasm_timer;
// The minimum time between increasing the average delay between packets. If we hit the ceiling in use self::{
// the available buffer space we want to take somewhat swift action, but we still need to give a sending_delay_controller::SendingDelayController, transmission_buffer::TransmissionBuffer,
// 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 mod sending_delay_controller;
// to quickly to keep things somewhat stable. Also there are buffers downstreams meaning we need to mod transmission_buffer;
// wait a little to see the effect before we decrease further.
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 30; #[cfg(not(target_arch = "wasm32"))]
// If we enough time passes without any sign of backpressure in the channel, we can consider fn get_time_now() -> time::Instant {
// lowering the average delay. The goal is to keep somewhat stable, rather than maxing out time::Instant::now()
// bandwidth at all times. }
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 30;
// The maximum multiplier we apply to the base average Poisson delay. #[cfg(target_arch = "wasm32")]
const MAX_DELAY_MULTIPLIER: u32 = 6; fn get_time_now() -> wasm_timer::Instant {
// The minium multiplier we apply to the base average Poisson delay. wasm_timer::Instant::now()
const MIN_DELAY_MULTIPLIER: u32 = 1; }
/// Configurable parameters of the `OutQueueControl` /// Configurable parameters of the `OutQueueControl`
pub(crate) struct Config { pub(crate) struct Config {
@@ -85,101 +86,6 @@ impl Config {
} }
} }
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,
}
#[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()
}
impl SendingDelayController {
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,
}
}
fn current_multiplier(&self) -> u32 {
self.current_multiplier
}
fn increase_delay_multiplier(&mut self) {
self.current_multiplier =
(self.current_multiplier + 1).clamp(self.lower_bound, self.upper_bound);
self.time_when_changed = get_time_now();
log::debug!(
"Increasing sending delay multiplier to: {}",
self.current_multiplier
);
}
fn decrease_delay_multiplier(&mut self) {
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
);
}
fn record_backpressure_detected(&mut self) {
self.time_when_backpressure_detected = get_time_now();
}
fn not_increased_delay_recently(&self) -> bool {
get_time_now()
> self.time_when_changed + Duration::from_secs(INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
}
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
}
}
pub(crate) struct OutQueueControl<R> pub(crate) struct OutQueueControl<R>
where where
R: CryptoRng + Rng, R: CryptoRng + Rng,
@@ -203,7 +109,7 @@ where
// To make sure we don't overload the mix_tx channel, we limit the rate we are pushing // To make sure we don't overload the mix_tx channel, we limit the rate we are pushing
// messages. // messages.
sending_rate_controller: SendingDelayController, sending_delay_controller: SendingDelayController,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them /// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays. /// out to the network without any further delays.
@@ -222,10 +128,19 @@ where
/// Accessor to the common instance of network topology. /// Accessor to the common instance of network topology.
topology_access: TopologyAccessor, topology_access: TopologyAccessor,
/// Buffer containing all real messages received. It is first exhausted before more are pulled. /// Buffer containing all incoming real messages keyed by transmission lane, that we will send
received_buffer: VecDeque<RealMessage>, /// 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 { pub(crate) struct RealMessage {
mix_packet: MixPacket, mix_packet: MixPacket,
fragment_id: FragmentIdentifier, fragment_id: FragmentIdentifier,
@@ -242,8 +157,9 @@ impl RealMessage {
// messages are already prepared, etc. the real point of it is to forward it to mix_traffic // messages are already prepared, etc. the real point of it is to forward it to mix_traffic
// after sufficient delay // after sufficient delay
pub(crate) type BatchRealMessageSender = mpsc::UnboundedSender<Vec<RealMessage>>; pub(crate) type BatchRealMessageSender =
type BatchRealMessageReceiver = mpsc::UnboundedReceiver<Vec<RealMessage>>; tokio::sync::mpsc::Sender<(Vec<RealMessage>, TransmissionLane)>;
type BatchRealMessageReceiver = tokio::sync::mpsc::Receiver<(Vec<RealMessage>, TransmissionLane)>;
pub(crate) enum StreamMessage { pub(crate) enum StreamMessage {
Cover, Cover,
@@ -266,22 +182,23 @@ where
rng: R, rng: R,
our_full_destination: Recipient, our_full_destination: Recipient,
topology_access: TopologyAccessor, topology_access: TopologyAccessor,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
) -> Self { ) -> Self {
OutQueueControl { OutQueueControl {
config, config,
ack_key, ack_key,
sent_notifier, sent_notifier,
next_delay: None, next_delay: None,
sending_rate_controller: SendingDelayController::new( sending_delay_controller: Default::default(),
MIN_DELAY_MULTIPLIER,
MAX_DELAY_MULTIPLIER,
),
mix_tx, mix_tx,
real_receiver, real_receiver,
our_full_destination, our_full_destination,
rng, rng,
topology_access, 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,
} }
} }
@@ -296,7 +213,7 @@ where
async fn on_message(&mut self, next_message: StreamMessage) { async fn on_message(&mut self, next_message: StreamMessage) {
trace!("created new message"); trace!("created new message");
let next_message = match next_message { let (next_message, fragment_id) = match next_message {
StreamMessage::Cover => { StreamMessage::Cover => {
// TODO for way down the line: in very rare cases (during topology update) we might have // 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 // to wait a really tiny bit before actually obtaining the permit hence messing with our
@@ -315,27 +232,41 @@ where
} }
let topology_ref = topology_ref_option.unwrap(); let topology_ref = topology_ref_option.unwrap();
generate_loop_cover_packet( (
&mut self.rng, generate_loop_cover_packet(
topology_ref, &mut self.rng,
&self.ack_key, topology_ref,
&self.our_full_destination, &self.ack_key,
self.config.average_ack_delay, &self.our_full_destination,
self.config.average_packet_delay, self.config.average_ack_delay,
self.config.cover_packet_size, 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) => { StreamMessage::Real(real_message) => {
self.sent_notify(real_message.fragment_id); (real_message.mix_packet, Some(real_message.fragment_id))
real_message.mix_packet
} }
}; };
if let Err(err) = self.mix_tx.send(vec![next_message]).await { if let Err(err) = self.mix_tx.send(vec![next_message]).await {
log::error!("Failed to send - channel closed: {}", err); 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, // 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 // 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 // JS2: Basically it was the case that with high enough rate, the stream had already a next value
@@ -347,44 +278,71 @@ where
tokio::task::yield_now().await; tokio::task::yield_now().await;
} }
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 { fn current_average_message_sending_delay(&self) -> Duration {
self.config.average_message_sending_delay self.config.average_message_sending_delay
* self.sending_rate_controller.current_multiplier() * self.sending_delay_controller.current_multiplier()
} }
fn adjust_current_average_message_sending_delay(&mut self) { fn adjust_current_average_message_sending_delay(&mut self) {
let used_slots = self.mix_tx.max_capacity() - self.mix_tx.capacity(); let used_slots = self.mix_tx.max_capacity() - self.mix_tx.capacity();
log::trace!( log::trace!(
"used_slots: {used_slots}, current_multiplier: {}", "used_slots: {used_slots}, current_multiplier: {}",
self.sending_rate_controller.current_multiplier() self.sending_delay_controller.current_multiplier()
); );
// Even just a single used slot is enough to signal backpressure // Even just a single used slot is enough to signal backpressure
if used_slots > 0 { if used_slots > 0 {
log::trace!("Backpressure detected"); log::trace!("Backpressure detected");
self.sending_rate_controller.record_backpressure_detected(); self.sending_delay_controller.record_backpressure_detected();
} }
// If the buffer is running out, slow down the sending rate // If the buffer is running out, slow down the sending rate
if self.mix_tx.capacity() == 0 if self.mix_tx.capacity() == 0
&& self.sending_rate_controller.not_increased_delay_recently() && self.sending_delay_controller.not_increased_delay_recently()
{ {
self.sending_rate_controller.increase_delay_multiplier(); self.sending_delay_controller.increase_delay_multiplier();
} }
// Very carefully step up the sending rate in case it seems like we can solidly handle the // Very carefully step up the sending rate in case it seems like we can solidly handle the
// current rate. // current rate.
if self.sending_rate_controller.is_sending_reliable() { if self.sending_delay_controller.is_sending_reliable() {
self.sending_rate_controller.decrease_delay_multiplier(); 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>> { fn poll_poisson(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// The average delay could change depending on if backpressure in the downstream channel // The average delay could change depending on if backpressure in the downstream channel
// (mix_tx) was detected. // (mix_tx) was detected.
self.adjust_current_average_message_sending_delay(); self.adjust_current_average_message_sending_delay();
let avg_delay = self.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 { if let Some(ref mut next_delay) = &mut self.next_delay {
// it is not yet time to return a message // it is not yet time to return a message
if next_delay.as_mut().poll(cx).is_pending() { if next_delay.as_mut().poll(cx).is_pending() {
@@ -409,28 +367,32 @@ where
next_delay.as_mut().reset(next_poisson_delay); next_delay.as_mut().reset(next_poisson_delay);
} }
// check if we have anything immediately available // On every iteration we get new messages from upstream. Given that these come bunched
if let Some(real_available) = self.received_buffer.pop_front() { // in `Vec`, this ensures that on average we will fetch messages faster than we can
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available)))); // 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) {
// 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 // 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) // (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None), Poll::Ready(None) => Poll::Ready(None),
// if there are more messages available, return first one and store the rest Poll::Ready(Some((real_messages, conn_id))) => {
Poll::Ready(Some(real_messages)) => { log::trace!("handling real_messages: size: {}", real_messages.len());
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message self.transmission_buffer.store(&conn_id, real_messages);
Poll::Ready(Some(StreamMessage::Real(Box::new( let real_next = self.pop_next_message().expect("Just stored one");
self.received_buffer.pop_front().unwrap(),
)))) Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
} }
// otherwise construct a dummy one Poll::Pending => {
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)), 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 { } else {
// we never set an initial delay - let's do it now // we never set an initial delay - let's do it now
@@ -452,32 +414,36 @@ where
} }
fn poll_immediate(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> { fn poll_immediate(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// check if we have anything immediately available // Start by checking if we have any incoming messages about closed connections
if let Some(real_available) = self.received_buffer.pop_front() { if let Poll::Ready(Some(id)) = Pin::new(&mut self.client_connection_rx).poll_next(cx) {
// if there are more messages immediately available, notify the runtime match id {
// because we should be polled again ConnectionCommand::Close(id) => self.on_close_connection(id),
if !self.received_buffer.is_empty() { ConnectionCommand::ActiveConnections(_) => panic!(),
cx.waker().wake_by_ref()
} }
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
} }
match Pin::new(&mut self.real_receiver).poll_next(cx) { 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 // 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) // (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None), Poll::Ready(None) => Poll::Ready(None),
// if there are more messages available, return first one and store the rest Poll::Ready(Some((real_messages, conn_id))) => {
Poll::Ready(Some(real_messages)) => { log::trace!("handling real_messages: size: {}", real_messages.len());
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message // First store what we got for the given connection id
Poll::Ready(Some(StreamMessage::Real(Box::new( self.transmission_buffer.store(&conn_id, real_messages);
self.received_buffer.pop_front().unwrap(), let real_next = self.pop_next_message().expect("we just added one");
))))
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
} }
// if there's nothing, then there's nothing Poll::Pending => {
Poll::Pending => Poll::Pending, if let Some(real_next) = self.pop_next_message() {
Poll::Ready(Some(StreamMessage::Real(Box::new(real_next))))
} else {
Poll::Pending
}
}
} }
} }
@@ -492,28 +458,73 @@ where
} }
} }
#[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()
);
}
}
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started OutQueueControl with graceful shutdown support"); debug!("Started OutQueueControl with graceful shutdown support");
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() { while !shutdown.is_shutdown() {
tokio::select! { tokio::select! {
biased; biased;
_ = shutdown.recv() => { _ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown"); log::trace!("OutQueueControl: Received shutdown");
} }
next_message = self.next() => match next_message { _ = status_timer.tick() => {
Some(next_message) => { self.log_status();
self.on_message(next_message).await; }
}, _ = infrequent_status_timer.tick() => {
None => { self.log_status_infrequent();
log::trace!("OutQueueControl: Stopping since channel closed"); }
break; 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;
} }
} }
} }
assert!(shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("OutQueueControl: Exiting"); log::debug!("OutQueueControl: Exiting");
} }
@@ -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()
}
}
@@ -208,7 +208,7 @@ impl ReceivedMessagesBuffer {
} }
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) { async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
debug!( trace!(
"Processing {:?} new message that might get added to the buffer!", "Processing {:?} new message that might get added to the buffer!",
msgs.len() msgs.len()
); );
@@ -322,6 +322,8 @@ impl RequestReceiver {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
debug!("Started RequestReceiver with graceful shutdown support"); debug!("Started RequestReceiver with graceful shutdown support");
while !shutdown.is_shutdown() { while !shutdown.is_shutdown() {
tokio::select! { tokio::select! {
@@ -340,7 +342,9 @@ impl RequestReceiver {
}, },
} }
} }
assert!(shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("RequestReceiver: Exiting"); log::debug!("RequestReceiver: Exiting");
} }
@@ -372,6 +376,8 @@ impl FragmentedMessageReceiver {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
use std::time::Duration;
debug!("Started FragmentedMessageReceiver with graceful shutdown support"); debug!("Started FragmentedMessageReceiver with graceful shutdown support");
while !shutdown.is_shutdown() { while !shutdown.is_shutdown() {
tokio::select! { tokio::select! {
@@ -389,7 +395,9 @@ impl FragmentedMessageReceiver {
} }
} }
} }
assert!(shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("FragmentedMessageReceiver: Exiting"); log::debug!("FragmentedMessageReceiver: Exiting");
} }
@@ -8,10 +8,13 @@ use nymsphinx::anonymous_replies::{
}; };
use std::path::Path; use std::path::Path;
#[derive(Debug)] #[derive(Debug, thiserror::Error)]
pub enum ReplyKeyStorageError { pub enum ReplyKeyStorageError {
#[error("DB Read Error: {0}")]
DbReadError(sled::Error), DbReadError(sled::Error),
#[error("DB Write Error: {0}")]
DbWriteError(sled::Error), DbWriteError(sled::Error),
#[error("DB Open Error: {0}")]
DbOpenError(sled::Error), DbOpenError(sled::Error),
} }
@@ -57,24 +57,15 @@ impl<'a> TopologyReadPermit<'a> {
) -> Option<&'a NymTopology> { ) -> Option<&'a NymTopology> {
// Note: implicit deref with Deref for TopologyReadPermit is happening here // Note: implicit deref with Deref for TopologyReadPermit is happening here
let topology_ref_option = self.permit.as_ref(); let topology_ref_option = self.permit.as_ref();
match topology_ref_option { topology_ref_option.as_ref().filter(|topology_ref| {
None => None, !(!topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
Some(topology_ref) => { || !topology_ref.gateway_exists(ack_recipient.gateway())
// see if it's possible to route the packet to both gateways || if let Some(packet_recipient) = packet_recipient {
if !topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS) !topology_ref.gateway_exists(packet_recipient.gateway())
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|| if let Some(packet_recipient) = packet_recipient {
!topology_ref.gateway_exists(packet_recipient.gateway())
} else {
false
}
{
None
} else { } else {
Some(topology_ref) false
} })
} })
}
} }
} }
@@ -147,7 +138,7 @@ impl TopologyRefresherConfig {
} }
pub struct TopologyRefresher { pub struct TopologyRefresher {
validator_client: validator_client::ApiClient, validator_client: validator_client::client::ApiClient,
client_version: String, client_version: String,
validator_api_urls: Vec<Url>, validator_api_urls: Vec<Url>,
@@ -163,7 +154,9 @@ impl TopologyRefresher {
cfg.validator_api_urls.shuffle(&mut thread_rng()); cfg.validator_api_urls.shuffle(&mut thread_rng());
TopologyRefresher { 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, client_version: cfg.client_version,
validator_api_urls: cfg.validator_api_urls, validator_api_urls: cfg.validator_api_urls,
topology_accessor, topology_accessor,
@@ -318,7 +311,9 @@ impl TopologyRefresher {
}, },
} }
} }
assert!(shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("TopologyRefresher: Exiting"); log::debug!("TopologyRefresher: Exiting");
}) })
} }
+13
View File
@@ -125,6 +125,10 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_endpoint.gateway_id = id.into(); self.client.gateway_endpoint.gateway_id = id.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>) { pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec<Url>) {
self.client.validator_api_urls = validator_api_urls; self.client.validator_api_urls = validator_api_urls;
} }
@@ -179,6 +183,10 @@ impl<T: NymConfig> Config<T> {
self.client.ack_key_file.clone() 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> { pub fn get_validator_api_endpoints(&self) -> Vec<Url> {
self.client.validator_api_urls.clone() self.client.validator_api_urls.clone()
} }
@@ -306,6 +314,10 @@ pub struct Client<T> {
#[serde(default)] #[serde(default)]
disabled_credentials_mode: bool, 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. /// Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls: Vec<Url>, validator_api_urls: Vec<Url>,
@@ -354,6 +366,7 @@ impl<T: NymConfig> Default for Client<T> {
version: env!("CARGO_PKG_VERSION").to_string(), version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(), id: "".to_string(),
disabled_credentials_mode: true, disabled_credentials_mode: true,
validator_urls: vec![],
validator_api_urls: vec![], validator_api_urls: vec![],
private_identity_key_file: Default::default(), private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(), public_identity_key_file: Default::default(),
+5
View File
@@ -24,4 +24,9 @@ pub enum ClientCoreError {
ListOfValidatorApisIsEmpty, ListOfValidatorApisIsEmpty,
#[error("Could not load existing gateway configuration: {0}")] #[error("Could not load existing gateway configuration: {0}")]
CouldNotLoadExistingGatewayConfiguration(std::io::Error), CouldNotLoadExistingGatewayConfiguration(std::io::Error),
#[error("The current network topology seem to be insufficient to route any packets through")]
InsufficientNetworkTopology,
#[error("Unexpected exit")]
UnexpectedExit,
} }
+1 -1
View File
@@ -31,7 +31,7 @@ pub async fn query_gateway_details(
let validator_api = validator_servers let validator_api = validator_servers
.choose(&mut thread_rng()) .choose(&mut thread_rng())
.ok_or(ClientCoreError::ListOfValidatorApisIsEmpty)?; .ok_or(ClientCoreError::ListOfValidatorApisIsEmpty)?;
let validator_client = validator_client::ApiClient::new(validator_api.clone()); let validator_client = validator_client::client::ApiClient::new(validator_api.clone());
log::trace!("Fetching list of gateways from: {}", validator_api); log::trace!("Fetching list of gateways from: {}", validator_api);
let gateways = validator_client.get_cached_gateways().await?; let gateways = validator_client.get_cached_gateways().await?;
+14 -7
View File
@@ -14,8 +14,9 @@ use credential_storage::PersistentStorage;
use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES}; use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use credentials::coconut::utils::obtain_aggregate_signature; use credentials::coconut::utils::obtain_aggregate_signature;
use crypto::asymmetric::{encryption, identity}; 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::nymd::tx::Hash;
use validator_client::{CoconutApiClient, Config};
use crate::client::Client; use crate::client::Client;
use crate::error::{CredentialClientError, Result}; use crate::error::{CredentialClientError, Result};
@@ -107,10 +108,9 @@ pub(crate) struct GetCredential {
/// The hash of a successful deposit transaction /// The hash of a successful deposit transaction
#[clap(long)] #[clap(long)]
tx_hash: String, tx_hash: String,
/// The URLs to the validator-api endpoints the are run as coconut signer authorities, separated /// The nymd URL that should be used
/// by comma (,)
#[clap(long)] #[clap(long)]
signer_authorities: String, nymd_url: String,
/// If we want to get the signature without attaching a blind sign request; it is expected that /// 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 /// there is already a signature stored on the signer
#[clap(long, parse(from_flag))] #[clap(long, parse(from_flag))]
@@ -124,7 +124,10 @@ impl Execute for GetCredential {
.get::<State>(&self.tx_hash) .get::<State>(&self.tx_hash)
.ok_or(CredentialClientError::NoDeposit)?; .ok_or(CredentialClientError::NoDeposit)?;
let urls = config::parse_validators(&self.signer_authorities); 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 params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let bandwidth_credential_attributes = if self.__no_request { let bandwidth_credential_attributes = if self.__no_request {
@@ -178,8 +181,12 @@ impl Execute for GetCredential {
)?); )?);
db.set(&self.tx_hash, &state).unwrap(); db.set(&self.tx_hash, &state).unwrap();
let signature = let signature = obtain_aggregate_signature(
obtain_aggregate_signature(&params, &bandwidth_credential_attributes, &urls).await?; &params,
&bandwidth_credential_attributes,
&coconut_api_clients,
)
.await?;
shared_storage shared_storage
.insert_coconut_credential( .insert_coconut_credential(
state.amount.to_string(), 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::encryption::KeyRecoveryError;
use crypto::asymmetric::identity::Ed25519RecoveryError; use crypto::asymmetric::identity::Ed25519RecoveryError;
use validator_client::nymd::error::NymdError; use validator_client::nymd::error::NymdError;
use validator_client::ValidatorClientError;
pub type Result<T> = std::result::Result<T, CredentialClientError>; pub type Result<T> = std::result::Result<T, CredentialClientError>;
@@ -16,6 +17,9 @@ pub enum CredentialClientError {
#[error("Nymd error: {0}")] #[error("Nymd error: {0}")]
Nymd(#[from] NymdError), Nymd(#[from] NymdError),
#[error("Validator client error: {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("Credential error: {0}")] #[error("Credential error: {0}")]
Credential(#[from] CredentialError), Credential(#[from] CredentialError),
+1 -1
View File
@@ -22,7 +22,7 @@ cfg_if::cfg_if! {
#[clap(author = "Nymtech", version, about)] #[clap(author = "Nymtech", version, about)]
struct Cli { struct Cli {
/// Path pointing to an env file that configures the client. /// Path pointing to an env file that configures the client.
#[clap(long)] #[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>, pub(crate) config_env_file: Option<std::path::PathBuf>,
/// Path where the sqlite credental database will be located. /// Path where the sqlite credental database will be located.
+5 -2
View File
@@ -1,10 +1,10 @@
[package] [package]
name = "nym-client" name = "nym-client"
version = "1.0.2" version = "1.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client" description = "Implementation of the Nym Client"
edition = "2021" edition = "2021"
rust-version = "1.56" rust-version = "1.65"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -27,11 +27,13 @@ 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 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 serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
sled = "0.34" # for storage of replySURB decryption keys sled = "0.34" # for storage of replySURB decryption keys
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime
tokio-tungstenite = "0.14" # websocket tokio-tungstenite = "0.14" # websocket
## internal ## internal
client-core = { path = "../client-core" } client-core = { path = "../client-core" }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true } coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" } config = { path = "../../common/config" }
completions = { path = "../../common/completions" } completions = { path = "../../common/completions" }
@@ -49,6 +51,7 @@ topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] } validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path = "../../common/version-checker" } version-checker = { path = "../../common/version-checker" }
websocket-requests = { path = "websocket-requests" } websocket-requests = { path = "websocket-requests" }
tap = "1.0.1"
[features] [features]
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"] coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
@@ -43,6 +43,7 @@ async fn send_file_with_reply() {
recipient, recipient,
message: read_data, message: read_data,
with_reply_surb: true, with_reply_surb: true,
connection_id: Some(0),
}; };
println!("sending content of 'dummy_file' over the mix network..."); println!("sending content of 'dummy_file' over the mix network...");
@@ -91,6 +92,7 @@ async fn send_file_without_reply() {
recipient, recipient,
message: read_data, message: read_data,
with_reply_surb: false, with_reply_surb: false,
connection_id: Some(0),
}; };
println!("sending content of 'dummy_file' over the mix network..."); println!("sending content of 'dummy_file' over the mix network...");
@@ -23,6 +23,13 @@ id = '{{ client.id }}'
# to claim bandwidth without presenting bandwidth credentials. # to claim bandwidth without presenting bandwidth credentials.
disabled_credentials_mode = {{ client.disabled_credentials_mode }} 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. # Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [ validator_api_urls = [
{{#each client.validator_api_urls }} {{#each client.validator_api_urls }}
+83 -27
View File
@@ -1,6 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use client_connections::{
ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths, TransmissionLane,
};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream; use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{ use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender, InputMessage, InputMessageReceiver, InputMessageSender,
@@ -18,6 +21,7 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig, TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
}; };
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity; use crypto::asymmetric::identity;
use futures::channel::mpsc; use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController; use gateway_client::bandwidth::BandwidthController;
@@ -30,9 +34,11 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity; use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::anonymous_replies::ReplySurb; use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage; use nymsphinx::receiver::ReconstructedMessage;
use tap::TapFallible;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier}; use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
use crate::client::config::{Config, SocketType}; use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use crate::websocket; use crate::websocket;
pub(crate) mod config; pub(crate) mod config;
@@ -108,6 +114,7 @@ impl NymClient {
stream.start_with_shutdown(shutdown); stream.start_with_shutdown(shutdown);
} }
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller( fn start_real_traffic_controller(
&self, &self,
topology_accessor: TopologyAccessor, topology_accessor: TopologyAccessor,
@@ -115,6 +122,8 @@ impl NymClient {
ack_receiver: AcknowledgementReceiver, ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver, input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender, mix_sender: BatchMixMessageSender,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
shutdown: ShutdownListener, shutdown: ShutdownListener,
) { ) {
let mut controller_config = real_messages_control::Config::new( let mut controller_config = real_messages_control::Config::new(
@@ -144,6 +153,8 @@ impl NymClient {
mix_sender, mix_sender,
topology_accessor, topology_accessor,
reply_key_storage, reply_key_storage,
lane_queue_lengths,
client_connection_rx,
) )
.start_with_shutdown(shutdown); .start_with_shutdown(shutdown);
} }
@@ -190,11 +201,22 @@ impl NymClient {
.expect("provided gateway id is invalid!"); .expect("provided gateway id is invalid!");
#[cfg(feature = "coconut")] #[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new( let bandwidth_controller = {
credential_storage::initialise_storage(self.config.get_base().get_database_path()) let details = network_defaults::NymNetworkDetails::new_from_env();
.await, let client_config = validator_client::Config::try_from_nym_network_details(&details)
self.config.get_base().get_validator_api_endpoints(), .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(self.config.get_base().get_database_path())
.await,
coconut_api_clients,
)
};
#[cfg(not(feature = "coconut"))] #[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new( let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path()) credential_storage::initialise_storage(self.config.get_base().get_database_path())
@@ -232,7 +254,7 @@ impl NymClient {
&mut self, &mut self,
topology_accessor: TopologyAccessor, topology_accessor: TopologyAccessor,
shutdown: ShutdownListener, shutdown: ShutdownListener,
) { ) -> Result<(), ClientError> {
let topology_refresher_config = TopologyRefresherConfig::new( let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(), self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(), self.config.get_base().get_topology_refresh_rate(),
@@ -247,14 +269,16 @@ impl NymClient {
// TODO: a slightly more graceful termination here // TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await { if !topology_refresher.is_topology_routable().await {
panic!( log::error!(
"The current network topology seem to be insufficient to route any packets through\ "The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online" - check if enough nodes and a gateway are online"
); );
return Err(ClientCoreError::InsufficientNetworkTopology.into());
} }
info!("Starting topology refresher..."); info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown); topology_refresher.start_with_shutdown(shutdown);
Ok(())
} }
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic) // controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -275,11 +299,18 @@ impl NymClient {
&self, &self,
buffer_requester: ReceivedBufferRequestSender, buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender, msg_input: InputMessageSender,
shared_lane_queue_lengths: LaneQueueLengths,
client_connection_tx: ConnectionCommandSender,
) { ) {
info!("Starting websocket listener..."); info!("Starting websocket listener...");
let websocket_handler = let websocket_handler = websocket::Handler::new(
websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient()); msg_input,
client_connection_tx,
buffer_requester,
&self.as_mix_recipient(),
shared_lane_queue_lengths,
);
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler); websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
} }
@@ -287,27 +318,35 @@ impl NymClient {
/// EXPERIMENTAL DIRECT RUST API /// EXPERIMENTAL DIRECT RUST API
/// It's untested and there are absolutely no guarantees about it (but seems to have worked /// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests) /// well enough in local tests)
pub fn send_message(&mut self, recipient: Recipient, message: Vec<u8>, with_reply_surb: bool) { pub async fn send_message(
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb); &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.input_tx self.input_tx
.as_ref() .as_ref()
.expect("start method was not called before!") .expect("start method was not called before!")
.unbounded_send(input_msg) .send(input_msg)
.unwrap(); .await
.expect("InputMessageReceiver has stopped receiving!");
} }
/// EXPERIMENTAL DIRECT RUST API /// EXPERIMENTAL DIRECT RUST API
/// It's untested and there are absolutely no guarantees about it (but seems to have worked /// It's untested and there are absolutely no guarantees about it (but seems to have worked
/// well enough in local tests) /// 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); let input_msg = InputMessage::new_reply(reply_surb, message);
self.input_tx self.input_tx
.as_ref() .as_ref()
.expect("start method was not called before!") .expect("start method was not called before!")
.unbounded_send(input_msg) .send(input_msg)
.unwrap(); .await
.expect("InputMessageReceiver has stopped receiving!");
} }
/// EXPERIMENTAL DIRECT RUST API /// EXPERIMENTAL DIRECT RUST API
@@ -328,8 +367,8 @@ impl NymClient {
} }
/// blocking version of `start` method. Will run forever (or until SIGINT is sent) /// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) { pub async fn run_forever(&mut self) -> Result<(), ClientError> {
let shutdown = self.start().await; let shutdown = self.start().await?;
wait_for_signal().await; wait_for_signal().await;
println!( println!(
@@ -346,9 +385,10 @@ impl NymClient {
//shutdown.wait_for_shutdown().await; //shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-client"); log::info!("Stopping nym-client");
Ok(())
} }
pub async fn start(&mut self) -> ShutdownNotifier { pub async fn start(&mut self) -> Result<ShutdownNotifier, ClientError> {
info!("Starting nym client"); info!("Starting nym client");
// channels for inter-component communication // channels for inter-component communication
// TODO: make the channels be internally created by the relevant components // TODO: make the channels be internally created by the relevant components
@@ -363,7 +403,7 @@ impl NymClient {
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded(); let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages // channels responsible for controlling real messages
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>(); let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages // channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded(); let (ack_sender, ack_receiver) = mpsc::unbounded();
@@ -371,7 +411,10 @@ impl NymClient {
let reply_key_storage = let reply_key_storage =
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path()) ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
.expect("Failed to load reply key storage!"); .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 // Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default(); let shutdown = ShutdownNotifier::default();
@@ -379,7 +422,7 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing, // the components are started in very specific order. Unless you know what you are doing,
// do not change that. // do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe()) self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await; .await?;
self.start_received_messages_buffer_controller( self.start_received_messages_buffer_controller(
received_buffer_request_receiver, received_buffer_request_receiver,
mixnet_messages_receiver, mixnet_messages_receiver,
@@ -398,12 +441,22 @@ impl NymClient {
let sphinx_message_sender = let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe()); 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( self.start_real_traffic_controller(
shared_topology_accessor.clone(), shared_topology_accessor.clone(),
reply_key_storage, reply_key_storage,
ack_receiver, ack_receiver,
input_receiver, input_receiver,
sphinx_message_sender.clone(), sphinx_message_sender.clone(),
shared_lane_queue_lengths.clone(),
client_connection_rx,
shutdown.subscribe(), shutdown.subscribe(),
); );
@@ -420,9 +473,12 @@ impl NymClient {
} }
match self.config.get_socket_type() { match self.config.get_socket_type() {
SocketType::WebSocket => { SocketType::WebSocket => self.start_websocket_listener(
self.start_websocket_listener(received_buffer_request_sender, input_sender) received_buffer_request_sender,
} input_sender,
shared_lane_queue_lengths,
client_connection_tx,
),
SocketType::None => { SocketType::None => {
// if we did not start the socket, it means we're running (supposedly) in the native mode // 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 // and hence we should announce 'ourselves' to the buffer
@@ -443,6 +499,6 @@ impl NymClient {
info!("Client startup finished!"); info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient()); info!("The address of this client is: {}", self.as_mix_recipient());
shutdown Ok(shutdown)
} }
} }
+8 -3
View File
@@ -25,9 +25,13 @@ pub(crate) struct Init {
#[clap(long)] #[clap(long)]
force_register_gateway: bool, 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)] #[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 /// Whether to not start the websocket
#[clap(long)] #[clap(long)]
@@ -52,7 +56,8 @@ pub(crate) struct Init {
impl From<Init> for OverrideConfig { impl From<Init> for OverrideConfig {
fn from(init_config: Init) -> Self { fn from(init_config: Init) -> Self {
OverrideConfig { OverrideConfig {
validators: init_config.validators, nymd_validators: init_config.nymd_validators,
api_validators: init_config.api_validators,
disable_socket: init_config.disable_socket, disable_socket: init_config.disable_socket,
port: init_config.port, port: init_config.port,
fastmode: init_config.fastmode, fastmode: init_config.fastmode,
+19 -5
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType}; use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use clap::CommandFactory; use clap::CommandFactory;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell}; use completions::{fig_generate, ArgShell};
@@ -49,7 +50,7 @@ fn long_version_static() -> &'static str {
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)] #[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli { pub(crate) struct Cli {
/// Path pointing to an env file that configures the client. /// Path pointing to an env file that configures the client.
#[clap(long)] #[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>, pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)] #[clap(subcommand)]
@@ -74,7 +75,8 @@ pub(crate) enum Commands {
// Configuration that can be overridden. // Configuration that can be overridden.
pub(crate) struct OverrideConfig { pub(crate) struct OverrideConfig {
validators: Option<String>, nymd_validators: Option<String>,
api_validators: Option<String>,
disable_socket: bool, disable_socket: bool,
port: Option<u16>, port: Option<u16>,
fastmode: bool, fastmode: bool,
@@ -83,20 +85,32 @@ pub(crate) struct OverrideConfig {
enabled_credentials_mode: bool, enabled_credentials_mode: bool,
} }
pub(crate) async fn execute(args: &Cli) { pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> {
let bin_name = "nym-native-client"; let bin_name = "nym-native-client";
match &args.command { match &args.command {
Commands::Init(m) => init::execute(m).await, 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::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name), Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
} }
Ok(())
} }
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config { 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(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 config
.get_base_mut() .get_base_mut()
.set_custom_validator_apis(config::parse_validators(&raw_validators)); .set_custom_validator_apis(config::parse_validators(&raw_validators));
+13 -7
View File
@@ -4,6 +4,7 @@
use crate::{ use crate::{
client::{config::Config, NymClient}, client::{config::Config, NymClient},
commands::{override_config, OverrideConfig}, commands::{override_config, OverrideConfig},
error::ClientError,
}; };
use clap::Args; use clap::Args;
@@ -17,9 +18,13 @@ pub(crate) struct Run {
#[clap(long)] #[clap(long)]
id: String, id: String,
/// Comma separated list of rest endpoints of the validators /// Comma separated list of rest endpoints of the nymd validators
#[clap(long)] #[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 /// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
/// ensure prior registration happened /// ensure prior registration happened
@@ -44,7 +49,8 @@ pub(crate) struct Run {
impl From<Run> for OverrideConfig { impl From<Run> for OverrideConfig {
fn from(run_config: Run) -> Self { fn from(run_config: Run) -> Self {
OverrideConfig { OverrideConfig {
validators: run_config.validators, nymd_validators: run_config.nymd_validators,
api_validators: run_config.api_validators,
disable_socket: run_config.disable_socket, disable_socket: run_config.disable_socket,
port: run_config.port, port: run_config.port,
fastmode: false, fastmode: false,
@@ -73,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 id = &args.id;
let mut config = match Config::load_from_file(Some(id)) { let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(err) => { Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, 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()));
} }
}; };
@@ -89,8 +95,8 @@ pub(crate) async fn execute(args: &Run) {
if !version_check(&config) { if !version_check(&config) {
error!("failed the local version check"); error!("failed the local version check");
return; return Err(ClientError::FailedLocalVersionCheck);
} }
NymClient::new(config).run_forever().await; NymClient::new(config).run_forever().await
} }
+25
View File
@@ -0,0 +1,25 @@
use client_core::{client::reply_key_storage::ReplyKeyStorageError, error::ClientCoreError};
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
#[derive(thiserror::Error, Debug)]
pub enum ClientError {
#[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),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Reply key storage error: {0}")]
ReplyKeyStorageError(#[from] ReplyKeyStorageError),
#[error("Failed to load config for: {0}")]
FailedToLoadConfig(String),
#[error("Failed local version check, client and config mismatch")]
FailedLocalVersionCheck,
}
+1
View File
@@ -2,4 +2,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
pub mod client; pub mod client;
pub mod error;
pub mod websocket; pub mod websocket;
+4 -2
View File
@@ -2,21 +2,23 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser}; use clap::{crate_version, Parser};
use error::ClientError;
use logging::setup_logging; use logging::setup_logging;
use network_defaults::setup_env; use network_defaults::setup_env;
pub mod client; pub mod client;
pub mod commands; pub mod commands;
pub mod error;
pub mod websocket; pub mod websocket;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> Result<(), ClientError> {
setup_logging(); setup_logging();
println!("{}", banner()); println!("{}", banner());
let args = commands::Cli::parse(); let args = commands::Cli::parse();
setup_env(args.config_env_file.clone()); setup_env(args.config_env_file.clone());
commands::execute(&args).await; commands::execute(&args).await
} }
fn banner() -> String { fn banner() -> String {
+128 -52
View File
@@ -1,6 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use client_connections::{
ConnectionCommand, ConnectionCommandSender, LaneQueueLengths, TransmissionLane,
};
use client_core::client::{ use client_core::client::{
inbound_messages::{InputMessage, InputMessageSender}, inbound_messages::{InputMessage, InputMessageSender},
received_buffer::{ received_buffer::{
@@ -34,10 +37,12 @@ impl Default for ReceivedResponseType {
pub(crate) struct Handler { pub(crate) struct Handler {
msg_input: InputMessageSender, msg_input: InputMessageSender,
client_connection_tx: ConnectionCommandSender,
buffer_requester: ReceivedBufferRequestSender, buffer_requester: ReceivedBufferRequestSender,
self_full_address: Recipient, self_full_address: Recipient,
socket: Option<WebSocketStream<TcpStream>>, socket: Option<WebSocketStream<TcpStream>>,
received_response_type: ReceivedResponseType, received_response_type: ReceivedResponseType,
lane_queue_lengths: LaneQueueLengths,
} }
// clone is used to use handler on a new connection, which initially is `None` // 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 { fn clone(&self) -> Self {
Handler { Handler {
msg_input: self.msg_input.clone(), msg_input: self.msg_input.clone(),
client_connection_tx: self.client_connection_tx.clone(),
buffer_requester: self.buffer_requester.clone(), buffer_requester: self.buffer_requester.clone(),
self_full_address: self.self_full_address, self_full_address: self.self_full_address,
socket: None, socket: None,
received_response_type: Default::default(), received_response_type: Default::default(),
lane_queue_lengths: self.lane_queue_lengths.clone(),
} }
} }
} }
@@ -64,38 +71,85 @@ impl Drop for Handler {
impl Handler { impl Handler {
pub(crate) fn new( pub(crate) fn new(
msg_input: InputMessageSender, msg_input: InputMessageSender,
client_connection_tx: ConnectionCommandSender,
buffer_requester: ReceivedBufferRequestSender, buffer_requester: ReceivedBufferRequestSender,
self_full_address: Recipient, self_full_address: &Recipient,
lane_queue_lengths: LaneQueueLengths,
) -> Self { ) -> Self {
Handler { Handler {
msg_input, msg_input,
client_connection_tx,
buffer_requester, buffer_requester,
self_full_address, self_full_address: *self_full_address,
socket: None, socket: None,
received_response_type: Default::default(), received_response_type: Default::default(),
lane_queue_lengths,
} }
} }
fn handle_send( async fn handle_send(
&mut self, &mut self,
recipient: Recipient, recipient: &Recipient,
message: Vec<u8>, message: Vec<u8>,
with_reply_surb: bool, with_reply_surb: bool,
connection_id: Option<u64>,
) -> Option<ServerResponse> { ) -> Option<ServerResponse> {
// the ack control is now responsible for chunking, etc. // We map the absence of a connection id as going into the general lane.
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb); let lane = connection_id.map_or(TransmissionLane::General, |id| {
self.msg_input.unbounded_send(input_msg).unwrap(); 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()) { 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); 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 None
} }
@@ -104,22 +158,48 @@ impl Handler {
ServerResponse::SelfAddress(self.self_full_address) 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 { match request {
ClientRequest::Send { ClientRequest::Send {
recipient, recipient,
message, message,
with_reply_surb, 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 { ClientRequest::Reply {
message, message,
reply_surb, reply_surb,
} => self.handle_reply(reply_surb, message), } => self.handle_reply(reply_surb, message).await,
ClientRequest::SelfAddress => Some(self.handle_self_address()), 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"); debug!("Handling text message request");
trace!("Content: {:?}", msg); trace!("Content: {:?}", msg);
@@ -128,13 +208,13 @@ impl Handler {
let response = match client_request { let response = match client_request {
Err(err) => Some(ServerResponse::Error(err)), 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())) 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"); debug!("Handling binary message request");
self.received_response_type = ReceivedResponseType::Binary; self.received_response_type = ReceivedResponseType::Binary;
@@ -142,49 +222,23 @@ impl Handler {
let response = match client_request { let response = match client_request {
Err(err) => Some(ServerResponse::Error(err)), 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())) 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 // 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 // them and let's test that claim. If that's not the case, just copy code from
// old version of this file. // old version of this file.
match raw_request { match raw_request {
WsMessage::Text(text_message) => self.handle_text_message(text_message), WsMessage::Text(text_message) => self.handle_text_message(text_message).await,
WsMessage::Binary(binary_message) => self.handle_binary_message(binary_message), WsMessage::Binary(binary_message) => self.handle_binary_message(&binary_message).await,
_ => None, _ => 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( async fn push_websocket_received_plaintexts(
&mut self, &mut self,
reconstructed_messages: Vec<ReconstructedMessage>, 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 // 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 // client is sending Message::Text it expects text back. Same for Message::Binary
let response_messages = match self.received_response_type { let response_messages = match self.received_response_type {
ReceivedResponseType::Binary => { ReceivedResponseType::Binary => prepare_reconstructed_binary(reconstructed_messages),
self.prepare_reconstructed_binary(reconstructed_messages) ReceivedResponseType::Text => prepare_reconstructed_text(reconstructed_messages),
}
ReceivedResponseType::Text => self.prepare_reconstructed_text(reconstructed_messages),
}; };
let mut send_stream = futures::stream::iter(response_messages); let mut send_stream = futures::stream::iter(response_messages);
@@ -244,7 +296,7 @@ impl Handler {
break; 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 { if let Err(err) = self.send_websocket_response(response).await {
warn!( warn!(
"Failed to send message over websocket: {}. Assuming the connection is dead.", "Failed to send message over websocket: {}. Assuming the connection is dead.",
@@ -291,3 +343,27 @@ impl Handler {
self.listen_for_requests(reconstructed_receiver).await; 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`] /// Value tag representing [`SelfAddress`] variant of the [`ClientRequest`]
pub const SELF_ADDRESS_REQUEST_TAG: u8 = 0x02; 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)] #[allow(non_snake_case)]
#[derive(Debug)] #[derive(Debug)]
pub enum ClientRequest { pub enum ClientRequest {
@@ -28,32 +34,42 @@ pub enum ClientRequest {
message: Vec<u8>, message: Vec<u8>,
// Perhaps we could change it to a number to indicate how many reply_SURBs we want to include? // Perhaps we could change it to a number to indicate how many reply_SURBs we want to include?
with_reply_surb: bool, with_reply_surb: bool,
connection_id: Option<u64>,
}, },
Reply { Reply {
message: Vec<u8>, message: Vec<u8>,
reply_surb: ReplySurb, reply_surb: ReplySurb,
}, },
SelfAddress, SelfAddress,
ClosedConnection(u64),
GetLaneQueueLength(u64),
} }
// we could have been parsing it directly TryFrom<WsMessage>, but we want to retain // 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 // information about whether it came from binary or text to send appropriate response back
impl ClientRequest { impl ClientRequest {
// SEND_REQUEST_TAG || with_surb || recipient || data_len || data // SEND_REQUEST_TAG || with_surb || recipient || conn_id || data_len || data
fn serialize_send(recipient: Recipient, data: Vec<u8>, with_reply_surb: bool) -> Vec<u8> { 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 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) std::iter::once(SEND_REQUEST_TAG)
.chain(std::iter::once(with_reply_surb as u8)) .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(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_len_bytes.iter().cloned())
.chain(data.into_iter()) .chain(data.into_iter())
.collect() .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> { 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 // we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + 2*sizeof<u64> bytes
if b.len() < 2 + Recipient::LEN + size_of::<u64>() { if b.len() < 2 + Recipient::LEN + 2 * size_of::<u64>() {
return Err(error::Error::new( return Err(error::Error::new(
ErrorKind::TooShortRequest, ErrorKind::TooShortRequest,
"not enough data provided to recover 'send'".to_string(), "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_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 { if data.len() as u64 != data_len {
return Err(error::Error::new( return Err(error::Error::new(
ErrorKind::MalformedRequest, ErrorKind::MalformedRequest,
@@ -104,11 +131,12 @@ impl ClientRequest {
with_reply_surb, with_reply_surb,
recipient, recipient,
message: data.to_vec(), message: data.to_vec(),
connection_id,
}) })
} }
// REPLY_REQUEST_TAG || surb_len || surb || message_len || message // 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 reply_surb_bytes = reply_surb.to_bytes();
let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_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(); let message_len_bytes = (message.len() as u64).to_be_bytes();
@@ -202,20 +230,79 @@ impl ClientRequest {
ClientRequest::SelfAddress 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> { pub fn serialize(self) -> Vec<u8> {
match self { match self {
ClientRequest::Send { ClientRequest::Send {
recipient, recipient,
message, message,
with_reply_surb, 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 { ClientRequest::Reply {
message, message,
reply_surb, reply_surb,
} => Self::serialize_reply(message, reply_surb), } => Self::serialize_reply(message, &reply_surb),
ClientRequest::SelfAddress => Self::serialize_self_address(), 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), SEND_REQUEST_TAG => Self::deserialize_send(b),
REPLY_REQUEST_TAG => Self::deserialize_reply(b), REPLY_REQUEST_TAG => Self::deserialize_reply(b),
SELF_ADDRESS_REQUEST_TAG => Ok(Self::deserialize_self_address(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( n => Err(error::Error::new(
ErrorKind::UnknownRequest, ErrorKind::UnknownRequest,
format!("type {}", n), format!("type {n}"),
)), )),
} }
} }
pub fn try_from_binary(raw_req: Vec<u8>) -> Result<Self, error::Error> { pub fn try_from_binary(raw_req: &[u8]) -> Result<Self, error::Error> {
Self::deserialize(&raw_req) Self::deserialize(raw_req)
} }
pub fn try_from_text(raw_req: String) -> Result<Self, error::Error> { pub fn try_from_text(raw_req: String) -> Result<Self, error::Error> {
@@ -280,6 +369,7 @@ mod tests {
recipient, recipient,
message: b"foomp".to_vec(), message: b"foomp".to_vec(),
with_reply_surb: false, with_reply_surb: false,
connection_id: Some(42),
}; };
let bytes = send_request_no_surb.serialize(); let bytes = send_request_no_surb.serialize();
@@ -289,10 +379,12 @@ mod tests {
recipient, recipient,
message, message,
with_reply_surb, with_reply_surb,
connection_id,
} => { } => {
assert_eq!(recipient.to_string(), recipient_string); assert_eq!(recipient.to_string(), recipient_string);
assert_eq!(message, b"foomp".to_vec()); assert_eq!(message, b"foomp".to_vec());
assert!(!with_reply_surb) assert!(!with_reply_surb);
assert_eq!(connection_id, Some(42))
} }
_ => unreachable!(), _ => unreachable!(),
} }
@@ -301,6 +393,7 @@ mod tests {
recipient, recipient,
message: b"foomp".to_vec(), message: b"foomp".to_vec(),
with_reply_surb: true, with_reply_surb: true,
connection_id: None,
}; };
let bytes = send_request_surb.serialize(); let bytes = send_request_surb.serialize();
@@ -310,10 +403,12 @@ mod tests {
recipient, recipient,
message, message,
with_reply_surb, with_reply_surb,
connection_id,
} => { } => {
assert_eq!(recipient.to_string(), recipient_string); assert_eq!(recipient.to_string(), recipient_string);
assert_eq!(message, b"foomp".to_vec()); assert_eq!(message, b"foomp".to_vec());
assert!(with_reply_surb) assert!(with_reply_surb);
assert_eq!(connection_id, None)
} }
_ => unreachable!(), _ => unreachable!(),
} }
@@ -352,4 +447,15 @@ mod tests {
_ => unreachable!(), _ => 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`] /// Value tag representing [`SelfAddress`] variant of the [`ServerResponse`]
pub const SELF_ADDRESS_RESPONSE_TAG: u8 = 0x02; 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)] #[derive(Debug)]
pub enum ServerResponse { pub enum ServerResponse {
Received(ReconstructedMessage), Received(ReconstructedMessage),
SelfAddress(Recipient), SelfAddress(Recipient),
LaneQueueLength(u64, usize),
Error(error::Error), Error(error::Error),
} }
@@ -193,6 +197,31 @@ impl ServerResponse {
Ok(ServerResponse::SelfAddress(recipient)) 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 // ERROR_RESPONSE_TAG || err_code || msg_len || msg
fn serialize_error(error: error::Error) -> Vec<u8> { fn serialize_error(error: error::Error) -> Vec<u8> {
let message_len_bytes = (error.message.len() as u64).to_be_bytes(); let message_len_bytes = (error.message.len() as u64).to_be_bytes();
@@ -272,6 +301,9 @@ impl ServerResponse {
Self::serialize_received(reconstructed_message) Self::serialize_received(reconstructed_message)
} }
ServerResponse::SelfAddress(address) => Self::serialize_self_address(address), 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), ServerResponse::Error(err) => Self::serialize_error(err),
} }
} }
@@ -302,6 +334,7 @@ impl ServerResponse {
match response_tag { match response_tag {
RECEIVED_RESPONSE_TAG => Self::deserialize_received(b), RECEIVED_RESPONSE_TAG => Self::deserialize_received(b),
SELF_ADDRESS_RESPONSE_TAG => Self::deserialize_self_address(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), ERROR_RESPONSE_TAG => Self::deserialize_error(b),
n => Err(error::Error::new( n => Err(error::Error::new(
ErrorKind::UnknownResponse, 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] #[test]
fn error_response_serialization_works() { fn error_response_serialization_works() {
let dummy_error = error::Error::new(ErrorKind::UnknownRequest, "foomp message".to_string()); let dummy_error = error::Error::new(ErrorKind::UnknownRequest, "foomp message".to_string());
@@ -20,6 +20,7 @@ pub(super) enum ClientRequestText {
message: String, message: String,
recipient: String, recipient: String,
with_reply_surb: bool, with_reply_surb: bool,
connection_id: Option<u64>,
}, },
SelfAddress, SelfAddress,
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@@ -46,6 +47,7 @@ impl TryInto<ClientRequest> for ClientRequestText {
message, message,
recipient, recipient,
with_reply_surb, with_reply_surb,
connection_id,
} => { } => {
let message_bytes = message.into_bytes(); let message_bytes = message.into_bytes();
let recipient = Recipient::try_from_base58_string(recipient).map_err(|err| { let recipient = Recipient::try_from_base58_string(recipient).map_err(|err| {
@@ -56,6 +58,7 @@ impl TryInto<ClientRequest> for ClientRequestText {
message: message_bytes, message: message_bytes,
recipient, recipient,
with_reply_surb, with_reply_surb,
connection_id,
}) })
} }
ClientRequestText::SelfAddress => Ok(ClientRequest::SelfAddress), ClientRequestText::SelfAddress => Ok(ClientRequest::SelfAddress),
@@ -91,6 +94,10 @@ pub(super) enum ServerResponseText {
SelfAddress { SelfAddress {
address: String, address: String,
}, },
LaneQueueLength {
lane: u64,
queue_length: usize,
},
Error { Error {
message: String, message: String,
}, },
@@ -132,6 +139,9 @@ impl From<ServerResponse> for ServerResponseText {
ServerResponse::SelfAddress(recipient) => ServerResponseText::SelfAddress { ServerResponse::SelfAddress(recipient) => ServerResponseText::SelfAddress {
address: recipient.to_string(), address: recipient.to_string(),
}, },
ServerResponse::LaneQueueLength(lane, queue_length) => {
ServerResponseText::LaneQueueLength { lane, queue_length }
}
ServerResponse::Error(err) => ServerResponseText::Error { ServerResponse::Error(err) => ServerResponseText::Error {
message: err.to_string(), message: err.to_string(),
}, },
+4 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "nym-socks5-client" name = "nym-socks5-client"
version = "1.0.2" version = "1.1.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021" edition = "2021"
@@ -20,11 +20,13 @@ pretty_env_logger = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] } rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization
snafu = "0.6" snafu = "0.6"
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] }
url = "2.2" url = "2.2"
# internal # internal
client-core = { path = "../client-core" } client-core = { path = "../client-core" }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true } coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" } config = { path = "../../common/config" }
completions = { path = "../../common/completions" } completions = { path = "../../common/completions" }
@@ -44,6 +46,7 @@ task = { path = "../../common/task" }
topology = { path = "../../common/topology" } topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] } validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path = "../../common/version-checker" } version-checker = { path = "../../common/version-checker" }
tap = "1.0.1"
[features] [features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"] coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"]
@@ -23,6 +23,13 @@ id = '{{ client.id }}'
# to claim bandwidth without presenting bandwidth credentials. # to claim bandwidth without presenting bandwidth credentials.
disabled_credentials_mode = {{ client.disabled_credentials_mode }} 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. # Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [ validator_api_urls = [
{{#each client.validator_api_urls }} {{#each client.validator_api_urls }}
+100 -27
View File
@@ -1,13 +1,15 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use std::sync::atomic::Ordering; use std::error::Error;
use crate::client::config::Config; use crate::client::config::Config;
use crate::error::Socks5ClientError;
use crate::socks::{ use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User}, authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer, server::SphinxSocksServer,
}; };
use client_connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream; use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{ use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender, InputMessage, InputMessageReceiver, InputMessageSender,
@@ -23,6 +25,7 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig, TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
}; };
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity; use crypto::asymmetric::identity;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::StreamExt; use futures::StreamExt;
@@ -34,7 +37,8 @@ use gateway_client::{
use log::*; use log::*;
use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity; use nymsphinx::addressing::nodes::NodeIdentity;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier}; use tap::TapFallible;
use task::{wait_for_signal_and_error, ShutdownListener, ShutdownNotifier};
pub mod config; pub mod config;
@@ -108,6 +112,7 @@ impl NymClient {
stream.start_with_shutdown(shutdown); stream.start_with_shutdown(shutdown);
} }
#[allow(clippy::too_many_arguments)]
fn start_real_traffic_controller( fn start_real_traffic_controller(
&self, &self,
topology_accessor: TopologyAccessor, topology_accessor: TopologyAccessor,
@@ -115,6 +120,8 @@ impl NymClient {
ack_receiver: AcknowledgementReceiver, ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver, input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender, mix_sender: BatchMixMessageSender,
client_connection_rx: ConnectionCommandReceiver,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener, shutdown: ShutdownListener,
) { ) {
let mut controller_config = client_core::client::real_messages_control::Config::new( let mut controller_config = client_core::client::real_messages_control::Config::new(
@@ -144,6 +151,8 @@ impl NymClient {
mix_sender, mix_sender,
topology_accessor, topology_accessor,
reply_key_storage, reply_key_storage,
lane_queue_lengths,
client_connection_rx,
) )
.start_with_shutdown(shutdown); .start_with_shutdown(shutdown);
} }
@@ -190,11 +199,22 @@ impl NymClient {
.expect("provided gateway id is invalid!"); .expect("provided gateway id is invalid!");
#[cfg(feature = "coconut")] #[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new( let bandwidth_controller = {
credential_storage::initialise_storage(self.config.get_base().get_database_path()) let details = network_defaults::NymNetworkDetails::new_from_env();
.await, let client_config = validator_client::Config::try_from_nym_network_details(&details)
self.config.get_base().get_validator_api_endpoints(), .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(self.config.get_base().get_database_path())
.await,
coconut_api_clients,
)
};
#[cfg(not(feature = "coconut"))] #[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new( let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path()) credential_storage::initialise_storage(self.config.get_base().get_database_path())
@@ -232,7 +252,7 @@ impl NymClient {
&mut self, &mut self,
topology_accessor: TopologyAccessor, topology_accessor: TopologyAccessor,
shutdown: ShutdownListener, shutdown: ShutdownListener,
) { ) -> Result<(), Socks5ClientError> {
let topology_refresher_config = TopologyRefresherConfig::new( let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(), self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(), self.config.get_base().get_topology_refresh_rate(),
@@ -247,14 +267,16 @@ impl NymClient {
// TODO: a slightly more graceful termination here // TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await { if !topology_refresher.is_topology_routable().await {
panic!( log::error!(
"The current network topology seem to be insufficient to route any packets through\ "The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online" - check if enough nodes and a gateway are online"
); );
return Err(ClientCoreError::InsufficientNetworkTopology.into());
} }
info!("Starting topology refresher..."); info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown); topology_refresher.start_with_shutdown(shutdown);
Ok(())
} }
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic) // controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -275,7 +297,9 @@ impl NymClient {
&self, &self,
buffer_requester: ReceivedBufferRequestSender, buffer_requester: ReceivedBufferRequestSender,
msg_input: InputMessageSender, msg_input: InputMessageSender,
shutdown: ShutdownListener, client_connection_tx: ConnectionCommandSender,
lane_queue_lengths: LaneQueueLengths,
mut shutdown: ShutdownListener,
) { ) {
info!("Starting socks5 listener..."); info!("Starting socks5 listener...");
let auth_methods = vec![AuthenticationMethods::NoAuth as u8]; let auth_methods = vec![AuthenticationMethods::NoAuth as u8];
@@ -287,30 +311,58 @@ impl NymClient {
authenticator, authenticator,
self.config.get_provider_mix_address(), self.config.get_provider_mix_address(),
self.as_mix_recipient(), self.as_mix_recipient(),
shutdown, 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(msg_input, buffer_requester, client_connection_tx)
.await
{
shutdown.send_we_stopped(Box::new(res));
}
});
} }
/// blocking version of `start` method. Will run forever (or until SIGINT is sent) /// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) { pub async fn run_forever(&mut self) -> Result<(), Box<dyn Error + Send>> {
let mut shutdown = self.start().await; let mut shutdown = self
wait_for_signal().await; .start()
.await
.map_err(|err| Box::new(err) as Box<dyn Error + Send>)?;
let res = wait_for_signal_and_error(&mut shutdown).await;
log::info!("Sending shutdown"); log::info!("Sending shutdown");
client_core::client::SHUTDOWN_HAS_BEEN_SIGNALLED.store(true, Ordering::Relaxed);
shutdown.signal_shutdown().ok(); shutdown.signal_shutdown().ok();
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)"); log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
shutdown.wait_for_shutdown().await; shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client"); log::info!("Stopping nym-socks5-client");
res
} }
// Variant of `run_forever` that listends for remote control messages // Variant of `run_forever` that listends for remote control messages
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) { pub async fn run_and_listen(
let mut shutdown = self.start().await; &mut self,
tokio::select! { 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() => { message = receiver.next() => {
log::debug!("Received message: {:?}", message); log::debug!("Received message: {:?}", message);
match message { match message {
@@ -321,23 +373,29 @@ impl NymClient {
log::info!("Channel closed, stopping"); log::info!("Channel closed, stopping");
} }
} }
Ok(())
}
Some(msg) = shutdown.wait_for_error() => {
log::info!("Task error: {:?}", msg);
Err(msg)
} }
_ = tokio::signal::ctrl_c() => { _ = tokio::signal::ctrl_c() => {
log::info!("Received SIGINT"); log::info!("Received SIGINT");
Ok(())
}, },
} };
log::info!("Sending shutdown"); log::info!("Sending shutdown");
client_core::client::SHUTDOWN_HAS_BEEN_SIGNALLED.store(true, Ordering::Relaxed);
shutdown.signal_shutdown().ok(); shutdown.signal_shutdown().ok();
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)"); log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
shutdown.wait_for_shutdown().await; shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client"); log::info!("Stopping nym-socks5-client");
res
} }
pub async fn start(&mut self) -> ShutdownNotifier { pub async fn start(&mut self) -> Result<ShutdownNotifier, Socks5ClientError> {
info!("Starting nym client"); info!("Starting nym client");
// channels for inter-component communication // channels for inter-component communication
// TODO: make the channels be internally created by the relevant components // TODO: make the channels be internally created by the relevant components
@@ -352,7 +410,7 @@ impl NymClient {
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded(); let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages // channels responsible for controlling real messages
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>(); let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages // channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded(); let (ack_sender, ack_receiver) = mpsc::unbounded();
@@ -360,7 +418,10 @@ impl NymClient {
let reply_key_storage = let reply_key_storage =
ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path()) ReplyKeyStorage::load(self.config.get_base().get_reply_encryption_key_store_path())
.expect("Failed to load reply key storage!"); .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 // Shutdown notifier for signalling tasks to stop
let shutdown = ShutdownNotifier::default(); let shutdown = ShutdownNotifier::default();
@@ -368,7 +429,7 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing, // the components are started in very specific order. Unless you know what you are doing,
// do not change that. // do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe()) self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await; .await?;
self.start_received_messages_buffer_controller( self.start_received_messages_buffer_controller(
received_buffer_request_receiver, received_buffer_request_receiver,
mixnet_messages_receiver, mixnet_messages_receiver,
@@ -387,12 +448,22 @@ impl NymClient {
let sphinx_message_sender = let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe()); Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
// Channel for announcing closed (socks5) connections by the controller.
// This will be forwarded to `OutQueueControl`
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
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller( self.start_real_traffic_controller(
shared_topology_accessor.clone(), shared_topology_accessor.clone(),
reply_key_storage, reply_key_storage,
ack_receiver, ack_receiver,
input_receiver, input_receiver,
sphinx_message_sender.clone(), sphinx_message_sender.clone(),
client_connection_rx,
shared_lane_queue_lengths.clone(),
shutdown.subscribe(), shutdown.subscribe(),
); );
@@ -411,12 +482,14 @@ impl NymClient {
self.start_socks5_listener( self.start_socks5_listener(
received_buffer_request_sender, received_buffer_request_sender,
input_sender, input_sender,
client_connection_tx,
shared_lane_queue_lengths,
shutdown.subscribe(), shutdown.subscribe(),
); );
info!("Client startup finished!"); info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient()); info!("The address of this client is: {}", self.as_mix_recipient());
shutdown Ok(shutdown)
} }
} }
+8 -3
View File
@@ -29,9 +29,13 @@ pub(crate) struct Init {
#[clap(long)] #[clap(long)]
force_register_gateway: bool, 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)] #[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 /// Port for the socket to listen on in all subsequent runs
#[clap(short, long)] #[clap(short, long)]
@@ -52,7 +56,8 @@ pub(crate) struct Init {
impl From<Init> for OverrideConfig { impl From<Init> for OverrideConfig {
fn from(init_config: Init) -> Self { fn from(init_config: Init) -> Self {
OverrideConfig { OverrideConfig {
validators: init_config.validators, nymd_validators: init_config.nymd_validators,
api_validators: init_config.api_validators,
port: init_config.port, port: init_config.port,
fastmode: init_config.fastmode, fastmode: init_config.fastmode,
#[cfg(feature = "coconut")] #[cfg(feature = "coconut")]
+20 -5
View File
@@ -1,6 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use std::error::Error;
use crate::client::config::Config; use crate::client::config::Config;
use clap::CommandFactory; use clap::CommandFactory;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
@@ -50,7 +52,7 @@ fn long_version_static() -> &'static str {
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)] #[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli { pub(crate) struct Cli {
/// Path pointing to an env file that configures the client. /// Path pointing to an env file that configures the client.
#[clap(long)] #[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>, pub(crate) config_env_file: Option<std::path::PathBuf>,
#[clap(subcommand)] #[clap(subcommand)]
@@ -61,8 +63,10 @@ pub(crate) struct Cli {
pub(crate) enum Commands { pub(crate) enum Commands {
/// Initialise a Nym client. Do this first! /// Initialise a Nym client. Do this first!
Init(init::Init), Init(init::Init),
/// Run the Nym client with provided configuration client optionally overriding set parameters /// Run the Nym client with provided configuration client optionally overriding set parameters
Run(run::Run), Run(run::Run),
/// Try to upgrade the client /// Try to upgrade the client
Upgrade(upgrade::Upgrade), Upgrade(upgrade::Upgrade),
@@ -75,7 +79,8 @@ pub(crate) enum Commands {
// Configuration that can be overridden. // Configuration that can be overridden.
pub(crate) struct OverrideConfig { pub(crate) struct OverrideConfig {
validators: Option<String>, nymd_validators: Option<String>,
api_validators: Option<String>,
port: Option<u16>, port: Option<u16>,
fastmode: bool, fastmode: bool,
@@ -83,20 +88,30 @@ pub(crate) struct OverrideConfig {
enabled_credentials_mode: bool, enabled_credentials_mode: bool,
} }
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"; let bin_name = "nym-socks5-client";
match &args.command { match &args.command {
Commands::Init(m) => init::execute(m).await, 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::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name), Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
} }
Ok(())
} }
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config { 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 config
.get_base_mut() .get_base_mut()
.set_custom_validator_apis(parse_validators(&raw_validators)); .set_custom_validator_apis(parse_validators(&raw_validators));
+15 -7
View File
@@ -4,6 +4,7 @@
use crate::{ use crate::{
client::{config::Config, NymClient}, client::{config::Config, NymClient},
commands::{override_config, OverrideConfig}, commands::{override_config, OverrideConfig},
error::Socks5ClientError,
}; };
use clap::Args; use clap::Args;
@@ -30,9 +31,13 @@ pub(crate) struct Run {
#[clap(long)] #[clap(long)]
gateway: Option<String>, gateway: Option<String>,
/// Comma separated list of rest endpoints of the validators /// Comma separated list of rest endpoints of the nymd validators
#[clap(long)] #[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 /// Port for the socket to listen on
#[clap(short, long)] #[clap(short, long)]
@@ -48,7 +53,8 @@ pub(crate) struct Run {
impl From<Run> for OverrideConfig { impl From<Run> for OverrideConfig {
fn from(run_config: Run) -> Self { fn from(run_config: Run) -> Self {
OverrideConfig { OverrideConfig {
validators: run_config.validators, nymd_validators: run_config.nymd_validators,
api_validators: run_config.api_validators,
port: run_config.port, port: run_config.port,
fastmode: false, fastmode: false,
@@ -80,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 id = &args.id;
let mut config = match Config::load_from_file(Some(id)) { let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(err) => { Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, 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(),
)));
} }
}; };
@@ -96,8 +104,8 @@ pub(crate) async fn execute(args: &Run) {
if !version_check(&config) { if !version_check(&config) {
error!("failed the local version check"); 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
} }
+32
View File
@@ -0,0 +1,32 @@
use client_core::{client::reply_key_storage::ReplyKeyStorageError, error::ClientCoreError};
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
use crate::socks::types::SocksProxyError;
#[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError {
#[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),
#[error("client-core error: {0}")]
ClientCoreError(#[from] ClientCoreError),
#[error("Reply key storage error: {0}")]
ReplyKeyStorageError(#[from] ReplyKeyStorageError),
#[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
View File
@@ -2,4 +2,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
pub mod client; pub mod client;
pub mod error;
pub mod socks; pub mod socks;
+5 -2
View File
@@ -1,22 +1,25 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use std::error::Error;
use clap::{crate_version, Parser}; use clap::{crate_version, Parser};
use logging::setup_logging; use logging::setup_logging;
use network_defaults::setup_env; use network_defaults::setup_env;
pub mod client; pub mod client;
mod commands; mod commands;
pub mod error;
pub mod socks; pub mod socks;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> Result<(), Box<dyn Error + Send>> {
setup_logging(); setup_logging();
println!("{}", banner()); println!("{}", banner());
let args = commands::Cli::parse(); let args = commands::Cli::parse();
setup_env(args.config_env_file.clone()); setup_env(args.config_env_file.clone());
commands::execute(&args).await; commands::execute(&args).await
} }
fn banner() -> String { fn banner() -> String {
+21 -5
View File
@@ -4,8 +4,8 @@ use super::authentication::{AuthenticationMethods, Authenticator, User};
use super::request::{SocksCommand, SocksRequest}; use super::request::{SocksCommand, SocksRequest};
use super::types::{ResponseCode, SocksProxyError}; use super::types::{ResponseCode, SocksProxyError};
use super::{RESERVED, SOCKS_VERSION}; use super::{RESERVED, SOCKS_VERSION};
use client_core::client::inbound_messages::InputMessage; use client_connections::{LaneQueueLengths, TransmissionLane};
use client_core::client::inbound_messages::InputMessageSender; use client_core::client::inbound_messages::{InputMessage, InputMessageSender};
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::task::{Context, Poll}; use futures::task::{Context, Poll};
use log::*; use log::*;
@@ -141,6 +141,7 @@ pub(crate) struct SocksClient {
service_provider: Recipient, service_provider: Recipient,
self_address: Recipient, self_address: Recipient,
started_proxy: bool, started_proxy: bool,
lane_queue_lengths: LaneQueueLengths,
shutdown_listener: ShutdownListener, shutdown_listener: ShutdownListener,
} }
@@ -158,6 +159,7 @@ impl Drop for SocksClient {
impl SocksClient { impl SocksClient {
/// Create a new SOCKClient /// Create a new SOCKClient
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
stream: TcpStream, stream: TcpStream,
authenticator: Authenticator, authenticator: Authenticator,
@@ -165,6 +167,7 @@ impl SocksClient {
service_provider: Recipient, service_provider: Recipient,
controller_sender: ControllerSender, controller_sender: ControllerSender,
self_address: Recipient, self_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
shutdown_listener: ShutdownListener, shutdown_listener: ShutdownListener,
) -> Self { ) -> Self {
let connection_id = Self::generate_random(); let connection_id = Self::generate_random();
@@ -179,6 +182,7 @@ impl SocksClient {
service_provider, service_provider,
self_address, self_address,
started_proxy: false, started_proxy: false,
lane_queue_lengths,
shutdown_listener, shutdown_listener,
} }
} }
@@ -198,6 +202,7 @@ impl SocksClient {
pub async fn shutdown(&mut self) -> Result<(), SocksProxyError> { pub async fn shutdown(&mut self) -> Result<(), SocksProxyError> {
info!("client is shutting down its TCP stream"); info!("client is shutting down its TCP stream");
self.stream.shutdown().await?; self.stream.shutdown().await?;
self.shutdown_listener.mark_as_success();
Ok(()) Ok(())
} }
@@ -230,8 +235,16 @@ impl SocksClient {
let req = Request::new_connect(self.connection_id, remote_address, self.self_address); let req = Request::new_connect(self.connection_id, remote_address, self.self_address);
let msg = Message::Request(req); let msg = Message::Request(req);
let input_message = InputMessage::new_fresh(self.service_provider, msg.into_bytes(), false); let input_message = InputMessage::new_fresh(
self.input_sender.unbounded_send(input_message).unwrap(); 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) { async fn run_proxy(&mut self, conn_receiver: ConnectionReceiver, remote_proxy_target: String) {
@@ -254,12 +267,14 @@ impl SocksClient {
conn_receiver, conn_receiver,
input_sender, input_sender,
connection_id, connection_id,
Some(self.lane_queue_lengths.clone()),
self.shutdown_listener.clone(), self.shutdown_listener.clone(),
) )
.run(move |conn_id, read_data, socket_closed| { .run(move |conn_id, read_data, socket_closed| {
let provider_request = Request::new_send(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); 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 .await
.into_inner(); .into_inner();
@@ -304,6 +319,7 @@ impl SocksClient {
SocksCommand::UdpAssociate => unimplemented!(), // not handled SocksCommand::UdpAssociate => unimplemented!(), // not handled
}; };
self.shutdown_listener.mark_as_success();
Ok(()) Ok(())
} }
+14 -3
View File
@@ -1,3 +1,5 @@
use std::time::Duration;
use futures::channel::mpsc; use futures::channel::mpsc;
use futures::StreamExt; use futures::StreamExt;
use log::*; use log::*;
@@ -18,9 +20,16 @@ pub(crate) struct MixnetResponseListener {
impl Drop for MixnetResponseListener { impl Drop for MixnetResponseListener {
fn drop(&mut self) { fn drop(&mut self) {
self.buffer_requester if let Err(err) = self
.buffer_requester
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect) .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);
}
}
} }
} }
@@ -96,7 +105,9 @@ impl MixnetResponseListener {
} }
} }
} }
assert!(self.shutdown.is_shutdown_poll()); tokio::time::timeout(Duration::from_secs(5), self.shutdown.recv())
.await
.expect("Task stopped without shutdown called");
log::debug!("MixnetResponseListener: Exiting"); log::debug!("MixnetResponseListener: Exiting");
} }
} }
+20 -9
View File
@@ -1,16 +1,17 @@
use crate::error::Socks5ClientError;
use super::authentication::Authenticator; use super::authentication::Authenticator;
use super::client::SocksClient; use super::client::SocksClient;
use super::{ use super::{mixnet_responses::MixnetResponseListener, types::ResponseCode};
mixnet_responses::MixnetResponseListener, use client_connections::{ConnectionCommandSender, LaneQueueLengths};
types::{ResponseCode, SocksProxyError},
};
use client_core::client::{ use client_core::client::{
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender, inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
}; };
use log::*; use log::*;
use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::clients::Recipient;
use proxy_helpers::connection_controller::Controller; use proxy_helpers::connection_controller::{BroadcastActiveConnections, Controller};
use std::net::SocketAddr; use std::net::SocketAddr;
use tap::TapFallible;
use task::ShutdownListener; use task::ShutdownListener;
use tokio::net::TcpListener; use tokio::net::TcpListener;
@@ -20,6 +21,7 @@ pub struct SphinxSocksServer {
listening_address: SocketAddr, listening_address: SocketAddr,
service_provider: Recipient, service_provider: Recipient,
self_address: Recipient, self_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener, shutdown: ShutdownListener,
} }
@@ -30,6 +32,7 @@ impl SphinxSocksServer {
authenticator: Authenticator, authenticator: Authenticator,
service_provider: Recipient, service_provider: Recipient,
self_address: Recipient, self_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
shutdown: ShutdownListener, shutdown: ShutdownListener,
) -> Self { ) -> Self {
// hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can // hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can
@@ -41,6 +44,7 @@ impl SphinxSocksServer {
listening_address: format!("{}:{}", ip, port).parse().unwrap(), listening_address: format!("{}:{}", ip, port).parse().unwrap(),
service_provider, service_provider,
self_address, self_address,
lane_queue_lengths,
shutdown, shutdown,
} }
} }
@@ -51,13 +55,19 @@ impl SphinxSocksServer {
&mut self, &mut self,
input_sender: InputMessageSender, input_sender: InputMessageSender,
buffer_requester: ReceivedBufferRequestSender, buffer_requester: ReceivedBufferRequestSender,
) -> Result<(), SocksProxyError> { client_connection_tx: ConnectionCommandSender,
let listener = TcpListener::bind(self.listening_address).await.unwrap(); ) -> Result<(), Socks5ClientError> {
let listener = TcpListener::bind(self.listening_address)
.await
.tap_err(|err| log::error!("Failed to bind to address: {err}"))?;
info!("Serving Connections..."); info!("Serving Connections...");
// controller for managing all active connections // controller for managing all active connections
let (mut active_streams_controller, controller_sender) = let (mut active_streams_controller, controller_sender) = Controller::new(
Controller::new(self.shutdown.clone()); client_connection_tx,
BroadcastActiveConnections::Off,
self.shutdown.clone(),
);
tokio::spawn(async move { tokio::spawn(async move {
active_streams_controller.run().await; active_streams_controller.run().await;
}); });
@@ -83,6 +93,7 @@ impl SphinxSocksServer {
self.service_provider, self.service_provider,
controller_sender.clone(), controller_sender.clone(),
self.self_address, self.self_address,
self.lane_queue_lengths.clone(),
self.shutdown.clone(), self.shutdown.clone(),
); );
+11 -7
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "nym-client-wasm" name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"] authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.0.0" version = "1.1.0"
edition = "2021" edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"] keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0" license = "Apache-2.0"
@@ -19,16 +19,18 @@ coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
[dependencies] [dependencies]
futures = "0.3" futures = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
js-sys = "0.3" js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] } 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" url = "2.2"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
# internal # internal
client-core = { path = "../client-core", default-features = false, features = ["wasm"] } client-core = { path = "../client-core", default-features = false, features = ["wasm"] }
client-connections = { path = "../../common/client-connections" }
coconut-interface = { path = "../../common/coconut-interface", optional = true } coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true } credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" } crypto = { path = "../../common/crypto" }
@@ -55,6 +57,8 @@ wee_alloc = { version = "0.4", optional = true }
wasm-bindgen-test = "0.3" wasm-bindgen-test = "0.3"
[package.metadata.wasm-pack.profile.release] [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
@@ -2,6 +2,11 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path'); const path = require('path');
module.exports = { module.exports = {
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
entry: { entry: {
bootstrap: './bootstrap.js', bootstrap: './bootstrap.js',
worker: './worker.js', worker: './worker.js',
@@ -22,6 +27,7 @@ module.exports = {
}, },
], ],
}), }),
], ],
experiments: { syncWebAssembly: true }, experiments: { syncWebAssembly: true },
}; };
+1
View File
@@ -73,6 +73,7 @@ async function main() {
// const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM'; // const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM';
const gatewayEndpoint = await get_gateway(validator, preferredGateway); 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 // only really useful if you want to adjust some settings like traffic rate
// (if not needed you can just pass a null) // (if not needed you can just pass a null)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,226 @@
// 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>, headers: Option<String>) -> Vec<u8> {
create_binary_message_with_headers(kind, payload, headers)
}
/// 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,
headers: Option<String>,
) -> Vec<u8> {
create_binary_message_with_headers(kind, payload.as_bytes().to_vec(), headers)
}
/// 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: Option<String>,
) -> Vec<u8> {
let headers = headers.unwrap_or_else(|| "".to_string());
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],
Some("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], Some("".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]);
}
}
+37 -11
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use self::config::Config; use self::config::Config;
use client_connections::{ConnectionCommandReceiver, LaneQueueLengths, TransmissionLane};
use client_core::client::{ use client_core::client::{
cover_traffic_stream::LoopCoverTrafficStream, cover_traffic_stream::LoopCoverTrafficStream,
inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender}, inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender},
@@ -25,7 +26,7 @@ use nymsphinx::addressing::clients::Recipient;
use rand::rngs::OsRng; use rand::rngs::OsRng;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local; use wasm_bindgen_futures::spawn_local;
use wasm_utils::{console_log, console_warn}; use wasm_utils::console_log;
pub mod config; pub mod config;
@@ -44,6 +45,7 @@ pub struct NymClient {
// callbacks // callbacks
on_message: Option<js_sys::Function>, on_message: Option<js_sys::Function>,
on_binary_message: Option<js_sys::Function>,
on_gateway_connect: Option<js_sys::Function>, on_gateway_connect: Option<js_sys::Function>,
} }
@@ -55,6 +57,7 @@ impl NymClient {
config, config,
key_manager: Self::setup_key_manager(), key_manager: Self::setup_key_manager(),
on_message: None, on_message: None,
on_binary_message: None,
on_gateway_connect: None, on_gateway_connect: None,
input_tx: None, input_tx: None,
} }
@@ -72,8 +75,11 @@ impl NymClient {
self.on_message = Some(on_message); 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) { 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) self.on_gateway_connect = Some(on_connect)
} }
@@ -122,6 +128,8 @@ impl NymClient {
ack_receiver: AcknowledgementReceiver, ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver, input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender, mix_sender: BatchMixMessageSender,
client_connection_rx: ConnectionCommandReceiver,
lane_queue_lengths: LaneQueueLengths,
) { ) {
let mut controller_config = real_messages_control::Config::new( let mut controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(), self.key_manager.ack_key(),
@@ -146,6 +154,8 @@ impl NymClient {
input_receiver, input_receiver,
mix_sender, mix_sender,
topology_accessor, topology_accessor,
lane_queue_lengths,
client_connection_rx,
) )
.start(); .start();
} }
@@ -266,6 +276,7 @@ impl NymClient {
received_buffer_request_sender: ReceivedBufferRequestSender, received_buffer_request_sender: ReceivedBufferRequestSender,
) { ) {
let on_message = self.on_message.take(); let on_message = self.on_message.take();
let on_binary_message = self.on_binary_message.take();
spawn_local(async move { spawn_local(async move {
let (reconstructed_sender, mut reconstructed_receiver) = mpsc::unbounded(); let (reconstructed_sender, mut reconstructed_receiver) = mpsc::unbounded();
@@ -280,8 +291,14 @@ impl NymClient {
let this = JsValue::null(); let this = JsValue::null();
while let Some(reconstructed) = reconstructed_receiver.next().await { while let Some(reconstructed) = reconstructed_receiver.next().await {
if let Some(ref callback) = on_message { for msg in reconstructed {
for msg in reconstructed { if let Some(ref callback_binary) = 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) = on_message {
if msg.reply_surb.is_some() { if msg.reply_surb.is_some() {
console_log!("the received message contained a reply-surb that we do not know how to handle (yet)") console_log!("the received message contained a reply-surb that we do not know how to handle (yet)")
} }
@@ -289,9 +306,6 @@ impl NymClient {
let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap(); let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap();
callback.call1(&this, &arg1).expect("on message failed!"); callback.call1(&this, &arg1).expect("on message failed!");
} }
} else {
console_warn!("no on_message callback was specified. the received message content is getting dropped");
console_log!("the raw messages: {:?}", reconstructed)
} }
} }
}); });
@@ -312,12 +326,16 @@ impl NymClient {
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded(); let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages // channels responsible for controlling real messages
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>(); let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for controlling ack messages // channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded(); let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new(); let shared_topology_accessor = TopologyAccessor::new();
// Channel that the real traffix controller can listed to for closing connections.
// Currently unused in the wasm client.
let (_client_connection_tx, client_connection_rx) = mpsc::unbounded();
// the components are started in very specific order. Unless you know what you are doing, // the components are started in very specific order. Unless you know what you are doing,
// do not change that. // do not change that.
self.start_topology_refresher(shared_topology_accessor.clone()) self.start_topology_refresher(shared_topology_accessor.clone())
@@ -337,11 +355,17 @@ impl NymClient {
// The MixTrafficController then sends the actual traffic // The MixTrafficController then sends the actual traffic
let sphinx_message_sender = Self::start_mix_traffic_controller(gateway_client); let sphinx_message_sender = Self::start_mix_traffic_controller(gateway_client);
// Shared queue length data. Published by the `OutQueueController` in the client, and used
// primarily to throttle incoming connections
let shared_lane_queue_lengths = LaneQueueLengths::new();
self.start_real_traffic_controller( self.start_real_traffic_controller(
shared_topology_accessor.clone(), shared_topology_accessor.clone(),
ack_receiver, ack_receiver,
input_receiver, input_receiver,
sphinx_message_sender.clone(), sphinx_message_sender.clone(),
client_connection_rx,
shared_lane_queue_lengths,
); );
if !self.config.debug.disable_loop_cover_traffic_stream { if !self.config.debug.disable_loop_cover_traffic_stream {
@@ -367,14 +391,16 @@ impl NymClient {
console_log!("Sending {} bytes to {}", message.len(), recipient); console_log!("Sending {} bytes to {}", message.len(), recipient);
let recipient = Recipient::try_from_base58_string(recipient).unwrap(); let recipient = Recipient::try_from_base58_string(recipient).unwrap();
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_fresh(recipient, message, false); let input_msg = InputMessage::new_fresh(recipient, message, false, lane);
self.input_tx self.input_tx
.as_ref() .as_ref()
.expect("start method was not called before!") .expect("start method was not called before!")
.unbounded_send(input_msg) .send(input_msg)
.unwrap(); .await
.expect("InputMessageReceiver has stopped receiving!");
self self
} }
+1 -1
View File
@@ -6,7 +6,7 @@ use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpoint { pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpoint {
let validator_client = validator_client::ApiClient::new(api_server.parse().unwrap()); let validator_client = validator_client::client::ApiClient::new(api_server.parse().unwrap());
let gateways = match validator_client.get_cached_gateways().await { let gateways = match validator_client.get_cached_gateways().await {
Err(err) => panic!("failed to obtain list of all gateways - {}", err), Err(err) => panic!("failed to obtain list of all gateways - {}", err),
+2
View File
@@ -3,6 +3,8 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
pub mod binary_message_helper;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
mod client; mod client;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
+2
View File
@@ -0,0 +1,2 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "client-connections"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = "0.3"
log = "0.4.17"
+111
View File
@@ -0,0 +1,111 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::collections::HashMap;
use futures::channel::mpsc;
pub type ConnectionId = u64;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum TransmissionLane {
General,
Reply,
Retransmission,
Control,
ConnectionId(ConnectionId),
}
/// Used by the connection controller to report current state for client connections.
pub type ConnectionCommandSender = mpsc::UnboundedSender<ConnectionCommand>;
pub type ConnectionCommandReceiver = mpsc::UnboundedReceiver<ConnectionCommand>;
pub enum ConnectionCommand {
// Announce that at a connection was closed. E.g the `OutQueueControl` uses this to discard
// transmission lanes.
Close(ConnectionId),
// In the network requester for example, we usually want to broadcast active connections
// regularly, so we know what connections we need to request lane queue lengths for from the
// client.
// In the socks5-client, this is not needed since have direct access to the lane queue lengths.
ActiveConnections(Vec<ConnectionId>),
}
// The `OutQueueControl` publishes the backlog per lane, primarily so that upstream can slow down
// if needed.
#[derive(Clone, Debug)]
pub struct LaneQueueLengths(std::sync::Arc<std::sync::Mutex<LaneQueueLengthsInner>>);
impl LaneQueueLengths {
pub fn new() -> Self {
LaneQueueLengths(std::sync::Arc::new(std::sync::Mutex::new(
LaneQueueLengthsInner {
map: HashMap::new(),
},
)))
}
pub fn set(&mut self, lane: &TransmissionLane, lane_length: Option<usize>) {
match self.0.lock() {
Ok(mut inner) => {
if let Some(length) = lane_length {
inner
.map
.entry(*lane)
.and_modify(|e| *e = length)
.or_insert(length);
} else {
inner.map.remove(lane);
}
}
Err(err) => log::warn!("Failed to set lane queue length: {err}"),
}
}
pub fn get(&self, lane: &TransmissionLane) -> Option<usize> {
match self.0.lock() {
Ok(inner) => inner.get(lane),
Err(err) => {
log::warn!("Failed to get lane queue length: {err}");
None
}
}
}
}
impl Default for LaneQueueLengths {
fn default() -> Self {
Self::new()
}
}
impl std::ops::Deref for LaneQueueLengths {
type Target = std::sync::Arc<std::sync::Mutex<LaneQueueLengthsInner>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub struct LaneQueueLengthsInner {
pub map: HashMap<TransmissionLane, usize>,
}
impl LaneQueueLengthsInner {
pub fn get(&self, lane: &TransmissionLane) -> Option<usize> {
self.map.get(lane).copied()
}
pub fn values(&self) -> impl Iterator<Item = &usize> {
self.map.values()
}
pub fn modify<F>(&mut self, lane: &TransmissionLane, f: F)
where
F: FnOnce(&mut usize),
{
self.map.entry(*lane).and_modify(f);
}
}
@@ -17,6 +17,8 @@ use credential_storage::error::StorageError;
#[cfg(feature = "coconut")] #[cfg(feature = "coconut")]
use std::str::FromStr; use std::str::FromStr;
#[cfg(feature = "coconut")] #[cfg(feature = "coconut")]
use validator_client::client::CoconutApiClient;
#[cfg(feature = "coconut")]
use { use {
coconut_interface::Base58, coconut_interface::Base58,
credentials::coconut::{ credentials::coconut::{
@@ -29,7 +31,7 @@ pub struct BandwidthController<St: Storage> {
#[allow(dead_code)] #[allow(dead_code)]
storage: St, storage: St,
#[cfg(feature = "coconut")] #[cfg(feature = "coconut")]
validator_endpoints: Vec<url::Url>, coconut_api_clients: Vec<CoconutApiClient>,
} }
impl<St> BandwidthController<St> impl<St> BandwidthController<St>
@@ -37,10 +39,10 @@ where
St: Storage + Clone + 'static, St: Storage + Clone + 'static,
{ {
#[cfg(feature = "coconut")] #[cfg(feature = "coconut")]
pub fn new(storage: St, validator_endpoints: Vec<url::Url>) -> Self { pub fn new(storage: St, coconut_api_clients: Vec<CoconutApiClient>) -> Self {
BandwidthController { BandwidthController {
storage, storage,
validator_endpoints, coconut_api_clients,
} }
} }
@@ -53,7 +55,7 @@ where
pub async fn prepare_coconut_credential( pub async fn prepare_coconut_credential(
&self, &self,
) -> Result<coconut_interface::Credential, GatewayClientError> { ) -> Result<coconut_interface::Credential, GatewayClientError> {
let verification_key = obtain_aggregate_verification_key(&self.validator_endpoints).await?; let verification_key = obtain_aggregate_verification_key(&self.coconut_api_clients).await?;
let bandwidth_credential = self.storage.get_next_coconut_credential().await?; let bandwidth_credential = self.storage.get_next_coconut_credential().await?;
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value) let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
.map_err(|_| StorageError::InconsistentData)?; .map_err(|_| StorageError::InconsistentData)?;
+65 -16
View File
@@ -2,25 +2,19 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::bandwidth::BandwidthController; use crate::bandwidth::BandwidthController;
use crate::cleanup_socket_message;
use crate::error::GatewayClientError; use crate::error::GatewayClientError;
use crate::packet_router::PacketRouter; use crate::packet_router::PacketRouter;
pub use crate::packet_router::{ pub use crate::packet_router::{
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender, AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
}; };
use crate::socket_state::{PartiallyDelegated, SocketState}; use crate::socket_state::{PartiallyDelegated, SocketState};
#[cfg(target_arch = "wasm32")] use crate::{cleanup_socket_message, try_decrypt_binary_message};
use crate::wasm_storage::PersistentStorage;
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::PersistentStorage;
use crypto::asymmetric::identity; use crypto::asymmetric::identity;
use futures::{FutureExt, SinkExt, StreamExt}; use futures::{FutureExt, SinkExt, StreamExt};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use gateway_requests::iv::IV; use gateway_requests::iv::IV;
use gateway_requests::registration::handshake::{client_handshake, SharedKeys}; use gateway_requests::registration::handshake::{client_handshake, SharedKeys};
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse}; use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, PROTOCOL_VERSION};
use log::*; use log::*;
use network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN}; use network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
use nymsphinx::forwarding::packet::MixPacket; use nymsphinx::forwarding::packet::MixPacket;
@@ -28,13 +22,20 @@ use rand::rngs::OsRng;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
#[cfg(not(target_arch = "wasm32"))]
use task::ShutdownListener;
use tungstenite::protocol::Message; use tungstenite::protocol::Message;
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
use task::ShutdownListener;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async; use tokio_tungstenite::connect_async;
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::PersistentStorage;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_timer; use wasm_timer;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@@ -306,6 +307,8 @@ impl GatewayClient {
let m_shutdown = self.shutdown.clone(); let m_shutdown = self.shutdown.clone();
async { async {
if let Some(mut s) = m_shutdown { if let Some(mut s) = m_shutdown {
// TODO: fix this by marking as success _after_ the select
s.mark_as_success();
s.recv().await s.recv().await
} else { } else {
std::future::pending::<()>().await std::future::pending::<()>().await
@@ -336,7 +339,15 @@ impl GatewayClient {
}; };
match ws_msg { match ws_msg {
Message::Binary(bin_msg) => { Message::Binary(bin_msg) => {
if let Err(err) = self.packet_router.route_received(vec![bin_msg]) { // if we have established the shared key already, attempt to use it for decryption
// otherwise there's not much we can do apart from just routing what we have on hand
if let Some(shared_keys) = &self.shared_key {
if let Some(plaintext) = try_decrypt_binary_message(bin_msg, shared_keys) {
if let Err(err) = self.packet_router.route_received(vec![plaintext]) {
log::warn!("Route received failed: {:?}", err);
}
}
} else if let Err(err) = self.packet_router.route_received(vec![bin_msg]) {
log::warn!("Route received failed: {:?}", err); log::warn!("Route received failed: {:?}", err);
} }
} }
@@ -436,6 +447,33 @@ impl GatewayClient {
} }
} }
fn check_gateway_protocol(
&self,
gateway_protocol: Option<u8>,
) -> Result<(), GatewayClientError> {
// right now there are no failure cases here, but this might change in the future
match gateway_protocol {
None => {
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
// note: in 1.2.0 we will have to return a hard error here
Ok(())
}
Some(v) if v != PROTOCOL_VERSION => {
let err = GatewayClientError::IncompatibleProtocol {
gateway: Some(v),
current: PROTOCOL_VERSION,
};
error!("{err}");
Err(err)
}
Some(_) => {
info!("the gateway is using exactly the same protocol version as we are. We're good to continue!");
Ok(())
}
}
}
async fn register(&mut self) -> Result<(), GatewayClientError> { async fn register(&mut self) -> Result<(), GatewayClientError> {
if !self.connection.is_established() { if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished); return Err(GatewayClientError::ConnectionNotEstablished);
@@ -458,11 +496,20 @@ impl GatewayClient {
.map_err(GatewayClientError::RegistrationFailure), .map_err(GatewayClientError::RegistrationFailure),
_ => unreachable!(), _ => unreachable!(),
}?; }?;
self.authenticated = match self.read_control_response().await? { let (authentication_status, gateway_protocol) = match self.read_control_response().await? {
ServerResponse::Register { status } => Ok(status), ServerResponse::Register {
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), protocol_version,
_ => Err(GatewayClientError::UnexpectedResponse), status,
}?; } => (status, protocol_version),
ServerResponse::Error { message } => {
return Err(GatewayClientError::GatewayError(message))
}
_ => return Err(GatewayClientError::UnexpectedResponse),
};
self.check_gateway_protocol(gateway_protocol)?;
self.authenticated = authentication_status;
if self.authenticated { if self.authenticated {
self.shared_key = Some(Arc::new(shared_key)); self.shared_key = Some(Arc::new(shared_key));
} }
@@ -501,9 +548,11 @@ impl GatewayClient {
match self.send_websocket_message(msg).await? { match self.send_websocket_message(msg).await? {
ServerResponse::Authenticate { ServerResponse::Authenticate {
protocol_version,
status, status,
bandwidth_remaining, bandwidth_remaining,
} => { } => {
self.check_gateway_protocol(protocol_version)?;
self.authenticated = status; self.authenticated = status;
self.bandwidth_remaining = bandwidth_remaining; self.bandwidth_remaining = bandwidth_remaining;
Ok(()) Ok(())
@@ -85,6 +85,9 @@ pub enum GatewayClientError {
#[error("Failed to send mixnet message")] #[error("Failed to send mixnet message")]
MixnetMsgSenderFailedToSend, MixnetMsgSenderFailedToSend,
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway:?}")]
IncompatibleProtocol { gateway: Option<u8>, current: u8 },
} }
impl GatewayClientError { impl GatewayClientError {
@@ -3,6 +3,9 @@
use crate::error::GatewayClientError; use crate::error::GatewayClientError;
pub use client::GatewayClient; pub use client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
use gateway_requests::BinaryResponse;
use log::warn;
pub use packet_router::{ pub use packet_router::{
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender, AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
}; };
@@ -37,3 +40,21 @@ pub(crate) fn cleanup_socket_messages(
None => Err(GatewayClientError::ConnectionAbruptlyClosed), None => Err(GatewayClientError::ConnectionAbruptlyClosed),
} }
} }
pub(crate) fn try_decrypt_binary_message(
bin_msg: Vec<u8>,
shared_keys: &SharedKeys,
) -> Option<Vec<u8>> {
match BinaryResponse::try_from_encrypted_tagged_bytes(bin_msg, shared_keys) {
Ok(bin_response) => match bin_response {
BinaryResponse::PushedMixMessage(plaintext) => Some(plaintext),
},
Err(err) => {
warn!(
"message received from the gateway was malformed! - {:?}",
err
);
None
}
}
}
@@ -1,14 +1,13 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net> // Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::cleanup_socket_messages;
use crate::error::GatewayClientError; use crate::error::GatewayClientError;
use crate::packet_router::PacketRouter; use crate::packet_router::PacketRouter;
use crate::{cleanup_socket_messages, try_decrypt_binary_message};
use futures::channel::oneshot; use futures::channel::oneshot;
use futures::stream::{SplitSink, SplitStream}; use futures::stream::{SplitSink, SplitStream};
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};
use gateway_requests::registration::handshake::SharedKeys; use gateway_requests::registration::handshake::SharedKeys;
use gateway_requests::BinaryResponse;
use log::*; use log::*;
use std::sync::Arc; use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@@ -50,21 +49,9 @@ impl PartiallyDelegated {
match ws_msg { match ws_msg {
Message::Binary(bin_msg) => { Message::Binary(bin_msg) => {
// this function decrypts the request and checks the MAC // this function decrypts the request and checks the MAC
let plaintext = match BinaryResponse::try_from_encrypted_tagged_bytes( if let Some(plaintext) = try_decrypt_binary_message(bin_msg, shared_key) {
bin_msg, shared_key, plaintexts.push(plaintext)
) { }
Ok(bin_response) => match bin_response {
BinaryResponse::PushedMixMessage(plaintext) => plaintext,
},
Err(err) => {
warn!(
"message received from the gateway was malformed! - {:?}",
err
);
continue;
}
};
plaintexts.push(plaintext)
} }
// I think that in the future we should perhaps have some sequence number system, i.e. // I think that in the future we should perhaps have some sequence number system, i.e.
// so each request/response pair can be easily identified, so that if messages are // so each request/response pair can be easily identified, so that if messages are
@@ -10,10 +10,11 @@ rust-version = "1.56"
[dependencies] [dependencies]
base64 = "0.13" base64 = "0.13"
colored = "2.0" colored = "2.0"
cw3 = "0.13.1"
coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" }
contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" } mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" } vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" } coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
vesting-contract = { path = "../../../contracts/vesting" } vesting-contract = { path = "../../../contracts/vesting" }
@@ -37,6 +38,7 @@ async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true } bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true } config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true} cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
cw3 = { version = "0.13.4", optional = true }
prost = { version = "0.10", default-features = false, optional = true } prost = { version = "0.10", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true } flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true } sha2 = { version = "0.9.5", optional = true }
@@ -53,6 +55,7 @@ nymd-client = [
"bip39", "bip39",
"config", "config",
"cosmrs", "cosmrs",
"cw3",
"prost", "prost",
"flate2", "flate2",
"sha2", "sha2",
+202 -18
View File
@@ -2,13 +2,21 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::{validator_api, ValidatorClientError}; use crate::{validator_api, ValidatorClientError};
use coconut_dkg_common::types::NodeIndex;
#[cfg(feature = "nymd-client")]
use coconut_dkg_common::{
dealer::ContractDealing, types::DealerDetails, verification_key::ContractVKShare,
};
#[cfg(feature = "nymd-client")]
use coconut_interface::Base58;
use coconut_interface::VerificationKey;
use mixnet_contract_common::mixnode::MixNodeDetails; use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::MixId; use mixnet_contract_common::MixId;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef}; use mixnet_contract_common::{GatewayBond, IdentityKeyRef};
use url::Url; #[cfg(feature = "nymd-client")]
use std::str::FromStr;
use validator_api_requests::coconut::{ use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse, BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
VerifyCredentialBody, VerifyCredentialResponse,
}; };
use validator_api_requests::models::{ use validator_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse, GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
@@ -16,10 +24,12 @@ use validator_api_requests::models::{
}; };
#[cfg(feature = "nymd-client")] #[cfg(feature = "nymd-client")]
use crate::nymd::traits::MixnetQueryClient; use crate::nymd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient};
#[cfg(feature = "nymd-client")] #[cfg(feature = "nymd-client")]
use crate::nymd::{self, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient}; use crate::nymd::{self, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient};
#[cfg(feature = "nymd-client")] #[cfg(feature = "nymd-client")]
use cw3::ProposalResponse;
#[cfg(feature = "nymd-client")]
use mixnet_contract_common::{ use mixnet_contract_common::{
mixnode::MixNodeBond, mixnode::MixNodeBond,
pending_events::{PendingEpochEvent, PendingIntervalEvent}, pending_events::{PendingEpochEvent, PendingIntervalEvent},
@@ -27,6 +37,7 @@ use mixnet_contract_common::{
}; };
#[cfg(feature = "nymd-client")] #[cfg(feature = "nymd-client")]
use network_defaults::NymNetworkDetails; use network_defaults::NymNetworkDetails;
use url::Url;
#[cfg(feature = "nymd-client")] #[cfg(feature = "nymd-client")]
use validator_api_requests::models::MixNodeBondAnnotated; use validator_api_requests::models::MixNodeBondAnnotated;
@@ -43,6 +54,9 @@ pub struct Config {
gateway_page_limit: Option<u32>, gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>, mixnode_delegations_page_limit: Option<u32>,
rewarded_set_page_limit: Option<u32>, rewarded_set_page_limit: Option<u32>,
dealers_page_limit: Option<u32>,
verification_key_page_limit: Option<u32>,
proposals_page_limit: Option<u32>,
} }
#[cfg(feature = "nymd-client")] #[cfg(feature = "nymd-client")]
@@ -72,6 +86,9 @@ impl Config {
gateway_page_limit: None, gateway_page_limit: None,
mixnode_delegations_page_limit: None, mixnode_delegations_page_limit: None,
rewarded_set_page_limit: None, rewarded_set_page_limit: None,
dealers_page_limit: None,
verification_key_page_limit: None,
proposals_page_limit: None,
}) })
} }
@@ -119,6 +136,9 @@ pub struct Client<C> {
gateway_page_limit: Option<u32>, gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>, mixnode_delegations_page_limit: Option<u32>,
rewarded_set_page_limit: Option<u32>, rewarded_set_page_limit: Option<u32>,
dealers_page_limit: Option<u32>,
verification_key_page_limit: Option<u32>,
proposals_page_limit: Option<u32>,
// ideally they would have been read-only, but unfortunately rust doesn't have such features // ideally they would have been read-only, but unfortunately rust doesn't have such features
pub validator_api: validator_api::Client, pub validator_api: validator_api::Client,
@@ -145,6 +165,9 @@ impl Client<SigningNymdClient> {
gateway_page_limit: config.gateway_page_limit, gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
rewarded_set_page_limit: config.rewarded_set_page_limit, rewarded_set_page_limit: config.rewarded_set_page_limit,
dealers_page_limit: config.dealers_page_limit,
verification_key_page_limit: config.verification_key_page_limit,
proposals_page_limit: config.proposals_page_limit,
validator_api: validator_api_client, validator_api: validator_api_client,
nymd: nymd_client, nymd: nymd_client,
}) })
@@ -178,6 +201,9 @@ impl Client<QueryNymdClient> {
gateway_page_limit: config.gateway_page_limit, gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
rewarded_set_page_limit: config.rewarded_set_page_limit, rewarded_set_page_limit: config.rewarded_set_page_limit,
dealers_page_limit: config.dealers_page_limit,
verification_key_page_limit: config.verification_key_page_limit,
proposals_page_limit: config.proposals_page_limit,
validator_api: validator_api_client, validator_api: validator_api_client,
nymd: nymd_client, nymd: nymd_client,
}) })
@@ -519,6 +545,135 @@ impl<C> Client<C> {
Ok(events) Ok(events)
} }
pub async fn get_all_nymd_current_dealers(
&self,
) -> Result<Vec<DealerDetails>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut dealers = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_current_dealers_paged(start_after.take(), self.dealers_page_limit)
.await?;
dealers.append(&mut paged_response.dealers);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(dealers)
}
pub async fn get_all_nymd_past_dealers(
&self,
) -> Result<Vec<DealerDetails>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut dealers = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_past_dealers_paged(start_after.take(), self.dealers_page_limit)
.await?;
dealers.append(&mut paged_response.dealers);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(dealers)
}
pub async fn get_all_nymd_epoch_dealings(
&self,
idx: usize,
) -> Result<Vec<ContractDealing>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut dealings = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_dealings_paged(idx, start_after.take(), self.dealers_page_limit)
.await?;
dealings.append(&mut paged_response.dealings);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(dealings)
}
pub async fn get_all_nymd_verification_key_shares(
&self,
) -> Result<Vec<ContractVKShare>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut shares = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_vk_shares_paged(start_after.take(), self.verification_key_page_limit)
.await?;
shares.append(&mut paged_response.shares);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(shares)
}
pub async fn get_all_nymd_proposals(
&self,
) -> Result<Vec<ProposalResponse>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut proposals = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.list_proposals(start_after.take(), self.proposals_page_limit)
.await?;
let last_id = paged_response.proposals.last().map(|prop| prop.id);
proposals.append(&mut paged_response.proposals);
if let Some(start_after_res) = last_id {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(proposals)
}
} }
// validator-api wrappers // validator-api wrappers
@@ -572,14 +727,53 @@ impl<C> Client<C> {
) -> Result<BlindedSignatureResponse, ValidatorClientError> { ) -> Result<BlindedSignatureResponse, ValidatorClientError> {
Ok(self.validator_api.blind_sign(request_body).await?) Ok(self.validator_api.blind_sign(request_body).await?)
} }
}
pub async fn get_coconut_verification_key( #[derive(Clone)]
&self, pub struct CoconutApiClient {
) -> Result<VerificationKeyResponse, ValidatorClientError> { pub api_client: ApiClient,
Ok(self.validator_api.get_coconut_verification_key().await?) pub verification_key: VerificationKey,
pub node_id: NodeIndex,
#[cfg(feature = "nymd-client")]
pub cosmos_address: cosmrs::AccountId,
}
#[cfg(feature = "nymd-client")]
impl CoconutApiClient {
pub async fn all_coconut_api_clients<C>(
nymd_client: &Client<C>,
) -> Result<Vec<Self>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
Ok(nymd_client
.get_all_nymd_verification_key_shares()
.await?
.into_iter()
.filter_map(Self::try_from)
.collect())
}
fn try_from(share: ContractVKShare) -> Option<Self> {
if share.verified {
if let Ok(url_address) = Url::parse(&share.announce_address) {
if let Ok(verification_key) = VerificationKey::try_from_bs58(&share.share) {
if let Ok(cosmos_address) = cosmrs::AccountId::from_str(share.owner.as_str()) {
return Some(CoconutApiClient {
api_client: ApiClient::new(url_address),
verification_key,
node_id: share.node_index,
cosmos_address,
});
}
}
}
}
None
} }
} }
#[derive(Clone)]
pub struct ApiClient { pub struct ApiClient {
pub validator_api: validator_api::Client, pub validator_api: validator_api::Client,
// TODO: perhaps if we really need it at some (currently I don't see any reasons for it) // TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
@@ -685,16 +879,6 @@ impl ApiClient {
.await?) .await?)
} }
pub async fn get_coconut_verification_key(
&self,
) -> Result<VerificationKeyResponse, ValidatorClientError> {
Ok(self.validator_api.get_coconut_verification_key().await?)
}
pub async fn get_cosmos_address(&self) -> Result<CosmosAddressResponse, ValidatorClientError> {
Ok(self.validator_api.get_cosmos_address().await?)
}
pub async fn verify_bandwidth_credential( pub async fn verify_bandwidth_credential(
&self, &self,
request_body: &VerifyCredentialBody, request_body: &VerifyCredentialBody,
@@ -9,7 +9,8 @@ mod error;
pub mod nymd; pub mod nymd;
pub mod validator_api; pub mod validator_api;
pub use crate::client::ApiClient; #[cfg(feature = "nymd-client")]
pub use crate::client::{ApiClient, CoconutApiClient};
pub use crate::error::ValidatorClientError; pub use crate::error::ValidatorClientError;
pub use validator_api_requests::*; pub use validator_api_requests::*;
@@ -6,6 +6,9 @@ use cosmrs::tendermint::abci;
use itertools::Itertools; use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub use coconut_bandwidth_contract_common::event_attributes::*;
pub use coconut_dkg_common::event_attributes::*;
// it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response) // it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response)
// as theirs logs // as theirs logs
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@@ -65,6 +65,7 @@ pub struct Config {
pub(crate) bandwidth_claim_contract_address: Option<AccountId>, pub(crate) bandwidth_claim_contract_address: Option<AccountId>,
pub(crate) coconut_bandwidth_contract_address: Option<AccountId>, pub(crate) coconut_bandwidth_contract_address: Option<AccountId>,
pub(crate) multisig_contract_address: Option<AccountId>, pub(crate) multisig_contract_address: Option<AccountId>,
pub(crate) coconut_dkg_contract_address: Option<AccountId>,
// TODO: add this in later commits // TODO: add this in later commits
// pub(crate) gas_price: GasPrice, // pub(crate) gas_price: GasPrice,
} }
@@ -118,6 +119,10 @@ impl Config {
details.contracts.multisig_contract_address.as_ref(), details.contracts.multisig_contract_address.as_ref(),
prefix, prefix,
)?, )?,
coconut_dkg_contract_address: Self::parse_optional_account(
details.contracts.coconut_dkg_contract_address.as_ref(),
prefix,
)?,
}) })
} }
} }
@@ -275,6 +280,14 @@ impl<C> NymdClient<C> {
self.config.multisig_contract_address.as_ref().unwrap() self.config.multisig_contract_address.as_ref().unwrap()
} }
// TODO: this should get changed into Result<&AccountId, NymdError> (or Option<&AccountId> in future commits
// note: what unwrap is doing here is just moving a failure that would have normally
// occurred in `connect` when attempting to parse an empty address,
// so it's not introducing new source of failure (just moves it)
pub fn coconut_dkg_contract_address(&self) -> &AccountId {
self.config.coconut_dkg_contract_address.as_ref().unwrap()
}
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) { pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.simulated_gas_multiplier = multiplier; self.simulated_gas_multiplier = multiplier;
} }
@@ -0,0 +1,126 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::error::NymdError;
use crate::nymd::{CosmWasmClient, NymdClient};
use async_trait::async_trait;
use coconut_dkg_common::dealer::{
DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse,
};
use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use coconut_dkg_common::types::EpochState;
use coconut_dkg_common::verification_key::PagedVKSharesResponse;
use cosmrs::AccountId;
#[async_trait]
pub trait DkgQueryClient {
async fn get_current_epoch_state(&self) -> Result<EpochState, NymdError>;
async fn get_dealer_details(
&self,
address: &AccountId,
) -> Result<DealerDetailsResponse, NymdError>;
async fn get_current_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError>;
async fn get_past_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError>;
async fn get_dealings_paged(
&self,
idx: usize,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealingsResponse, NymdError>;
async fn get_vk_shares_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedVKSharesResponse, NymdError>;
}
#[async_trait]
impl<C> DkgQueryClient for NymdClient<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn get_current_epoch_state(&self) -> Result<EpochState, NymdError> {
let request = DkgQueryMsg::GetCurrentEpochState {};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_dealer_details(
&self,
address: &AccountId,
) -> Result<DealerDetailsResponse, NymdError> {
let request = DkgQueryMsg::GetDealerDetails {
dealer_address: address.to_string(),
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_current_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError> {
let request = DkgQueryMsg::GetCurrentDealers {
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_past_dealers_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealerResponse, NymdError> {
let request = DkgQueryMsg::GetPastDealers {
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_dealings_paged(
&self,
idx: usize,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedDealingsResponse, NymdError> {
let request = DkgQueryMsg::GetDealing {
idx: idx as u64,
limit: page_limit,
start_after,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
async fn get_vk_shares_paged(
&self,
start_after: Option<String>,
page_limit: Option<u32>,
) -> Result<PagedVKSharesResponse, NymdError> {
let request = DkgQueryMsg::GetVerificationKeys {
limit: page_limit,
start_after,
};
self.client
.query_contract_smart(self.coconut_dkg_contract_address(), &request)
.await
}
}
@@ -0,0 +1,100 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Fee, NymdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg;
use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof;
use coconut_dkg_common::verification_key::VerificationKeyShare;
use contracts_common::dealings::ContractSafeBytes;
#[async_trait]
pub trait DkgSigningClient {
async fn register_dealer(
&self,
bte_key: EncodedBTEPublicKeyWithProof,
announce_address: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn submit_dealing_bytes(
&self,
commitment: ContractSafeBytes,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn submit_verification_key_share(
&self,
share: VerificationKeyShare,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C> DkgSigningClient for NymdClient<C>
where
C: SigningCosmWasmClient + Send + Sync,
{
async fn register_dealer(
&self,
bte_key: EncodedBTEPublicKeyWithProof,
announce_address: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let req = DkgExecuteMsg::RegisterDealer {
bte_key_with_proof: bte_key,
announce_address,
};
self.client
.execute(
self.address(),
self.coconut_dkg_contract_address(),
&req,
fee.unwrap_or_default(),
format!("registering {} as a dealer", self.address()),
vec![],
)
.await
}
async fn submit_dealing_bytes(
&self,
dealing_bytes: ContractSafeBytes,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let req = DkgExecuteMsg::CommitDealing { dealing_bytes };
self.client
.execute(
self.address(),
self.coconut_dkg_contract_address(),
&req,
fee.unwrap_or_default(),
"dealing commitment",
vec![],
)
.await
}
async fn submit_verification_key_share(
&self,
share: VerificationKeyShare,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let req = DkgExecuteMsg::CommitVerificationKeyShare { share };
self.client
.execute(
self.address(),
self.coconut_dkg_contract_address(),
&req,
fee.unwrap_or_default(),
"verification key share commitment",
vec![],
)
.await
}
}
@@ -3,6 +3,8 @@
mod coconut_bandwidth_query_client; mod coconut_bandwidth_query_client;
mod coconut_bandwidth_signing_client; mod coconut_bandwidth_signing_client;
mod dkg_query_client;
mod dkg_signing_client;
mod mixnet_query_client; mod mixnet_query_client;
mod mixnet_signing_client; mod mixnet_signing_client;
mod multisig_query_client; mod multisig_query_client;
@@ -12,6 +14,8 @@ mod vesting_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient; pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient; pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use dkg_query_client::DkgQueryClient;
pub use dkg_signing_client::DkgSigningClient;
pub use mixnet_query_client::MixnetQueryClient; pub use mixnet_query_client::MixnetQueryClient;
pub use mixnet_signing_client::MixnetSigningClient; pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_query_client::MultisigQueryClient; pub use multisig_query_client::MultisigQueryClient;
@@ -4,13 +4,19 @@
use crate::nymd::error::NymdError; use crate::nymd::error::NymdError;
use crate::nymd::{CosmWasmClient, NymdClient}; use crate::nymd::{CosmWasmClient, NymdClient};
use multisig_contract_common::msg::{ProposalResponse, QueryMsg}; use cw3::{ProposalListResponse, ProposalResponse};
use multisig_contract_common::msg::QueryMsg;
use async_trait::async_trait; use async_trait::async_trait;
#[async_trait] #[async_trait]
pub trait MultisigQueryClient { pub trait MultisigQueryClient {
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NymdError>; async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NymdError>;
async fn list_proposals(
&self,
start_after: Option<u64>,
limit: Option<u32>,
) -> Result<ProposalListResponse, NymdError>;
} }
#[async_trait] #[async_trait]
@@ -21,4 +27,15 @@ impl<C: CosmWasmClient + Sync + Send> MultisigQueryClient for NymdClient<C> {
.query_contract_smart(self.multisig_contract_address(), &request) .query_contract_smart(self.multisig_contract_address(), &request)
.await .await
} }
async fn list_proposals(
&self,
start_after: Option<u64>,
limit: Option<u32>,
) -> Result<ProposalListResponse, NymdError> {
let request = QueryMsg::ListProposals { start_after, limit };
self.client
.query_contract_smart(self.multisig_contract_address(), &request)
.await
}
} }
@@ -7,11 +7,11 @@ use crate::nymd::error::NymdError;
use crate::nymd::{Fee, NymdClient}; use crate::nymd::{Fee, NymdClient};
use coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg; use coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg;
use cw3::Vote;
use multisig_contract_common::msg::ExecuteMsg; use multisig_contract_common::msg::ExecuteMsg;
use async_trait::async_trait; use async_trait::async_trait;
use cosmwasm_std::{to_binary, Coin, CosmosMsg, WasmMsg}; use cosmwasm_std::{to_binary, Coin, CosmosMsg, WasmMsg};
use cw3::Vote;
#[async_trait] #[async_trait]
pub trait MultisigSigningClient { pub trait MultisigSigningClient {
@@ -9,8 +9,7 @@ use reqwest::Response;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
use validator_api_requests::coconut::{ use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse, BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
VerifyCredentialBody, VerifyCredentialResponse,
}; };
use validator_api_requests::models::{ use validator_api_requests::models::{
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
@@ -27,6 +26,7 @@ type Params<'a, K, V> = &'a [(K, V)];
const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[]; const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
#[derive(Clone)]
pub struct Client { pub struct Client {
url: Url, url: Url,
reqwest_client: reqwest::Client, reqwest_client: reqwest::Client,
@@ -443,34 +443,6 @@ impl Client {
.await .await
} }
pub async fn get_coconut_verification_key(
&self,
) -> Result<VerificationKeyResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_VERIFICATION_KEY,
],
NO_PARAMS,
)
.await
}
pub async fn get_cosmos_address(&self) -> Result<CosmosAddressResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_COSMOS_ADDRESS,
],
NO_PARAMS,
)
.await
}
pub async fn verify_bandwidth_credential( pub async fn verify_bandwidth_credential(
&self, &self,
request_body: &VerifyCredentialBody, request_body: &VerifyCredentialBody,
@@ -15,8 +15,6 @@ pub const BANDWIDTH: &str = "bandwidth";
pub const COCONUT_BLIND_SIGN: &str = "blind-sign"; pub const COCONUT_BLIND_SIGN: &str = "blind-sign";
pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-credential"; pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-credential";
pub const COCONUT_VERIFICATION_KEY: &str = "verification-key";
pub const COCONUT_COSMOS_ADDRESS: &str = "cosmos-address";
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential"; pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
pub const STATUS_ROUTES: &str = "status"; pub const STATUS_ROUTES: &str = "status";
+1
View File
@@ -22,6 +22,7 @@ thiserror = "1"
time = { version = "0.3.6", features = ["parsing", "formatting"] } time = { version = "0.3.6", features = ["parsing", "formatting"] }
toml = "0.5.6" toml = "0.5.6"
url = "2.2" url = "2.2"
tap = "1"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" } cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
cosmwasm-std = { version = "1.0.0" } cosmwasm-std = { version = "1.0.0" }
+6
View File
@@ -15,4 +15,10 @@ pub enum ContextError {
// TODO: improve this to return known errors // TODO: improve this to return known errors
#[error("failed to create client - {0}")] #[error("failed to create client - {0}")]
NymdError(String), NymdError(String),
#[error("{0}")]
NymdErrorPassthrough(#[from] validator_client::nymd::error::NymdError),
#[error("{0}")]
ValidatorClientError(#[from] validator_client::ValidatorClientError),
} }
+5 -4
View File
@@ -6,6 +6,7 @@ use network_defaults::{
var_names::{API_VALIDATOR, MIXNET_CONTRACT_ADDRESS, NYMD_VALIDATOR, VESTING_CONTRACT_ADDRESS}, var_names::{API_VALIDATOR, MIXNET_CONTRACT_ADDRESS, NYMD_VALIDATOR, VESTING_CONTRACT_ADDRESS},
NymNetworkDetails, NymNetworkDetails,
}; };
use tap::prelude::*;
use validator_client::nymd::{self, AccountId, NymdClient, QueryNymdClient, SigningNymdClient}; use validator_client::nymd::{self, AccountId, NymdClient, QueryNymdClient, SigningNymdClient};
pub use validator_client::validator_api::Client as ValidatorApiClient; pub use validator_client::validator_api::Client as ValidatorApiClient;
@@ -58,7 +59,7 @@ pub fn create_signing_client(
network_details: &NymNetworkDetails, network_details: &NymNetworkDetails,
) -> Result<SigningClient, ContextError> { ) -> Result<SigningClient, ContextError> {
let client_config = nymd::Config::try_from_nym_network_details(network_details) let client_config = nymd::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network"); .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
// get mnemonic // get mnemonic
let mnemonic = match std::env::var("MNEMONIC") { let mnemonic = match std::env::var("MNEMONIC") {
@@ -87,7 +88,7 @@ pub fn create_query_client(
network_details: &NymNetworkDetails, network_details: &NymNetworkDetails,
) -> Result<QueryClient, ContextError> { ) -> Result<QueryClient, ContextError> {
let client_config = nymd::Config::try_from_nym_network_details(network_details) let client_config = nymd::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network"); .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
let nymd_url = network_details let nymd_url = network_details
.endpoints .endpoints
@@ -107,7 +108,7 @@ pub fn create_signing_client_with_validator_api(
network_details: &NymNetworkDetails, network_details: &NymNetworkDetails,
) -> Result<SigningClientWithValidatorAPI, ContextError> { ) -> Result<SigningClientWithValidatorAPI, ContextError> {
let client_config = validator_client::Config::try_from_nym_network_details(network_details) let client_config = validator_client::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network"); .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
// get mnemonic // get mnemonic
let mnemonic = match std::env::var("MNEMONIC") { let mnemonic = match std::env::var("MNEMONIC") {
@@ -129,7 +130,7 @@ pub fn create_query_client_with_validator_api(
network_details: &NymNetworkDetails, network_details: &NymNetworkDetails,
) -> Result<QueryClientWithValidatorAPI, ContextError> { ) -> Result<QueryClientWithValidatorAPI, ContextError> {
let client_config = validator_client::Config::try_from_nym_network_details(network_details) let client_config = validator_client::Config::try_from_nym_network_details(network_details)
.expect("failed to construct valid validator client config with the provided network"); .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?;
match validator_client::client::Client::new_query(client_config) { match validator_client::client::Client::new_query(client_config) {
Ok(client) => Ok(client), Ok(client) => Ok(client),
@@ -13,7 +13,7 @@ use validator_client::nymd::wallet::DirectSecp256k1HdWallet;
pub struct SignatureOutputJson { pub struct SignatureOutputJson {
pub account_id: String, pub account_id: String,
pub public_key: PublicKey, pub public_key: PublicKey,
pub signature: String, pub signature_as_hex: String,
} }
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
@@ -46,7 +46,7 @@ pub fn sign(args: Args, prefix: &str, mnemonic: Option<bip39::Mnemonic>) {
let output = SignatureOutputJson { let output = SignatureOutputJson {
account_id: account.address().to_string(), account_id: account.address().to_string(),
public_key: account.public_key(), public_key: account.public_key(),
signature: signature.to_string(), signature_as_hex: signature.to_string(),
}; };
println!("{}", json!(output)); println!("{}", json!(output));
} }
@@ -3,11 +3,11 @@
use clap::Parser; use clap::Parser;
use cosmrs::AccountId; use cosmrs::AccountId;
use log::info; use log::{error, info};
use validator_client::nymd::{Coin, VestingQueryClient}; use validator_client::nymd::{Coin, VestingQueryClient};
use crate::context::SigningClient; use crate::context::QueryClient;
use crate::utils::show_error; use crate::utils::show_error;
use crate::utils::{pretty_coin, pretty_cosmwasm_coin}; use crate::utils::{pretty_coin, pretty_cosmwasm_coin};
@@ -18,8 +18,16 @@ pub struct Args {
pub address: Option<AccountId>, pub address: Option<AccountId>,
} }
pub async fn balance(args: Args, client: SigningClient) { pub async fn balance(args: Args, client: QueryClient, address_from_mnemonic: Option<AccountId>) {
let account_id = args.address.unwrap_or_else(|| client.address().clone()); if args.address.is_none() && address_from_mnemonic.is_none() {
error!("Please specify an account address or a mnemonic to get the balance for");
return;
}
let account_id = args
.address
.unwrap_or_else(|| address_from_mnemonic.expect("please provide a mnemonic"));
let vesting_address = account_id.to_string(); let vesting_address = account_id.to_string();
let denom = client.current_chain_details().mix_denom.base.as_str(); let denom = client.current_chain_details().mix_denom.base.as_str();
@@ -4,11 +4,11 @@
use clap::Parser; use clap::Parser;
use cosmrs::AccountId; use cosmrs::AccountId;
use cosmwasm_std::Coin as CosmWasmCoin; use cosmwasm_std::Coin as CosmWasmCoin;
use log::info; use log::{error, info};
use validator_client::nymd::{Coin, VestingQueryClient}; use validator_client::nymd::{Coin, VestingQueryClient};
use crate::context::SigningClient; use crate::context::QueryClient;
use crate::utils::show_error; use crate::utils::show_error;
use crate::utils::{pretty_coin, pretty_cosmwasm_coin}; use crate::utils::{pretty_coin, pretty_cosmwasm_coin};
@@ -19,8 +19,18 @@ pub struct Args {
pub address: Option<AccountId>, pub address: Option<AccountId>,
} }
pub async fn query(args: Args, client: SigningClient) { pub async fn query(args: Args, client: QueryClient, address_from_mnemonic: Option<AccountId>) {
let account_id = args.address.unwrap_or_else(|| client.address().clone()); if args.address.is_none() && address_from_mnemonic.is_none() {
error!("Please specify an account address or a mnemonic to get the balance for");
return;
}
let account_id = args
.address
.unwrap_or_else(|| address_from_mnemonic.expect("please provide a mnemonic"));
info!("Checking account {} for a vesting schedule...", account_id);
let vesting_address = account_id.to_string(); let vesting_address = account_id.to_string();
let denom = client.current_chain_details().mix_denom.base.as_str(); let denom = client.current_chain_details().mix_denom.base.as_str();
@@ -0,0 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub const BANDWIDTH_PROPOSAL_ID: &str = "proposal_id";

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