Compare commits

...

247 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacșu 3a0fbb4b77 Validator used logs 2022-11-14 14:14:06 +02:00
Bogdan-Ștefan Neacșu 0c67e6cca9 Some logging on cred verification 2022-11-14 13:42:52 +02: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
Jon Häggblad 9b14e00653 Fix merge error 2022-11-07 13:55:36 +01:00
Jon Häggblad ec8b5e6e9d Merge remote-tracking branch 'origin/release/v1.1.0' into develop 2022-11-07 10:13:33 +01:00
Raphaël Walther d4584c305a Set cron 2022-11-04 15:50:08 +01:00
Raphaël Walther afc53d4379 Added audit notification 2022-11-04 15:12:29 +01:00
Raphaël Walther 4278e88d3c Added audit notification 2022-11-04 14:55:47 +01:00
Raphaël Walther e12a34ce6b Added audit notification 2022-11-04 11:58:23 +01:00
Raphaël Walther 1de64f7b52 Added audit notification 2022-11-04 11:42:31 +01:00
Raphaël Walther 66dbe09e66 Added audit notification 2022-11-04 11:22:54 +01:00
Raphaël Walther dcce269921 Added audit notification 2022-11-04 09:56:00 +01:00
Jędrzej Stuczyński c043f0096a Notify about sent packet after actually pushing it through mix_tx (#1735) 2022-11-03 15:11:58 +00:00
Fouad a7cd7a58f2 Bugfix/delegations sort by no bonded node (#1737)
* use sorting function

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

* show tooltip on delegation with unbonded node

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

* add additional state to check for unbonding event

* disable actions when pending unbond event

* add request and type guard for pending unbond event

* add mixnode_is_unbonding to delegation item type

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

* use display percentage function

* fix profit margin display

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

* Fixed import paths

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

* Using legacy mode by default in mixnodes and gateways

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

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

* typos

* Using the same  underlying timer for uptime updater

* Updating uptimes at 23:00 UTC each day

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

* clients: dynamically adjust sending delay in steps

* rustfmt

* wasm-client: update channel

* client: introduce SendingDelayController

* client-core: downgrade two debug statements to trace

* sending delay controller: tweak parameters

* wasm-client: add tokio dependency

* client-core: rework delay controller

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

This reverts commit e0a7772fafac7bff0e4a2c50ba25e94b52b794e6.

* Remove outdated comment

* Remove WIP comments

* changelog: add note

* out queue controller: simplify with just send

* client-core: document constants

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

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

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

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

* socks5: fix typo in patch

* socks5: fix tests

* socks5: add type for returned data and index

* socks5: make closed_at_index an Option

* changelog: add note

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

* adding uptime chart

* adding loading state for gateways

* adding link style

* fixing gateways pagination

* remove gateway name and desc

* adding correct toolpit text and cleaning

* fix build

* PR requested changes

* fix build

* requested changes

* fix build a rever console utility addition

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

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

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

* client: handle two more error cases

* changelog: add note

* socks5: add error type and start handle run errors

* network-requester: add some error types

* rustfmt

* changelog: update note

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

* clients: dynamically adjust sending delay in steps

* rustfmt

* wasm-client: update channel

* client: introduce SendingDelayController

* client-core: downgrade two debug statements to trace

* sending delay controller: tweak parameters

* wasm-client: add tokio dependency

* client-core: rework delay controller

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

This reverts commit e0a7772fafac7bff0e4a2c50ba25e94b52b794e6.

* Remove outdated comment

* Remove WIP comments

* changelog: add note

* out queue controller: simplify with just send

* client-core: document constants

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

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

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

* socks5: fix typo in patch

* socks5: fix tests

* socks5: add type for returned data and index

* socks5: make closed_at_index an Option

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

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

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

* Add the three remaining validator-client functions

* display gatways routing scores

* handle undefined gateway report

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

* Add two more extended packet sizes

* Update config handling for new packet sizes

* Update wasm-client

* Changelog: update

* wasm-client: fix ref

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

* Removed dodgy unwraps from the mixnet contract

* Removed dodgy unwraps from the vesting contract

* Removed unwraps/expects from common contracts crate

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

* Address PR comments

* Update CHANGELOG

* No cap if no locked

* Fail account creation if taking account already exists

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

* feat(explorer-api): read dotenv file

* fix(explorer-api): gitignore

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

* Fixed existing unit tests

* Emitting source height for resolved pending events

* Removed unused attribute keys

* Emitting initial total unit reward at time of delegation

* Updated ts types

* regenerated corresponding typescript types

* missed changes

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

* get current interval

* display next interval and epoch times

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

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

* Mixnet contract test fixes

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

* change uptime label to routing score

* update validation for operator cost

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

* fix(wallet): bonding context

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

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

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

* fix(wallet-bonding): post godzilla merge

fix: post rebase

feature(wallet-bonding): wip

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

feat(wallet-bonding): add node stats component

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

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

fix(wallet-bonding): post godzilla merge

* feat(wallet): fetch active set probabilities

* feat(wallet): operation mixnode avg uptime

* fix: typo

* fix(wallet): theme colors

fix(wallet): theme colors

* fix(wallet): destructuring

* feat(wallet): request total reward data

* refactor(wallet): clean code

* fix(wallet): typo, better error logging

* fix(wallet): some nym decimal values

* fix(wallet): deal with unym nym values

* fix(wallet): routing score chart

* feat(wallet): new node stats design

* feat(wallet): new node stats design

* fix(wallet): increase component width

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

* changelog: add note

* rustfmt

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

* wip

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

* Removed unused module

* Removed hardcoded constants

* Easier way of sending binary messages

* WIP cleanup before machine switch

* Upgrade wasm-bindgen to 0.2.83

* Fixed compilation warnings for wasm client

* all clients compiling without warnings

* disabling topology refresh in wasm

* Added a config option to disable loop cover traffic stream

* config changes

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

* Restored topology refreshing

* correctly polling items in the wasm delay_queue

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

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

* Importing tokio::select in wasm32 target

* Updated changelog

* missing imports

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

* Introduced disable_main_poisson_packet_distribution to force real_traffic_stream to disable poisson sending

* Updated changelog

* Adjusting default settings

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

* local adjustments

* Removed warning associated with receiving extended packets

* Minimal v2-required changes

* Updated changelog

* explicitly allowing clippy drop_non_drop

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

* cargo fmt

* Removed warning associated with receiving extended packets

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

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

* Importing tokio::select in wasm32 target

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

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

* Updated mixnode and gateway configs with similar priority adjustments

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

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

* Renamed 'node_id' in Delegation to 'mix_id'

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

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

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

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

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

* Fixup after rebase

* Lock file update

* storybook update

* update storybook

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

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

* Update changelog

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

* Queries for currently pending events

* Rewarded set query

* Moved ContractState to common types

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

* Cleaend up storage initialisation

* started restoring unit tests

* Removed attached 1ucoin for cross-contract execute msgs

* wip

* query for rewarding details of a mix node

* Changes for mixnodes and gateways

* Furher progress on v2 changelog(-ish) description

* wip

* first version of the description

* mixnode bonding queries tests and fixes

* ibid for storage

* MixnodeEventType enum + created events for missing mixnode txs

* tests for adding new mixnode

* Additional mixnode-related tests + bug fixes

* Display for Percent

* Bunch of tests for try_reward_mixnode

* More tests and fixes

* ibid

* tests for updating rewarding params + important bug fix

* Started removing unused imports

* rewarding queries tests + undelegation bugfix

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

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

* Delegation tests + fixes

* Emiting events by top level interval txs + incorporating limit

* question

* Missing events emissions

* removed some code duplication

* wip

* pending delegation tests

* Vesting contract update

* More tests (and fixes) for pending events txs

* Restored gateway tx tests

* Another cleanup iteration

* removed redundant comment

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

* Unit tests for helper functions

* Interval queries unit tests

* Test for correct contract initialisation

* Another round of cleanup

* Work on mixnet_query_client trait

* mixnet_signing_client trait

* Removed redundant methods

* Slowly restoring validator client functionality

* Added deprecated query for mix details by identity

* wip restoration of validator-api

* Work on deprecating validator API routes

* Further validator-api routes

* Restored rest of status api routes

* Resolved all todos in ValidatorApiStorage

There's still bunch left in StorageManager though

* Changed NodeId from u64 to u32

* Updating sql code

* Network monitor internals

* Changed behaviour of full_epoch_id and updated epoch operations

* Fixed sql queries

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

* Post rebasing fixes

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

* Changed cache to allow for non-string keys

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

* Updated validator-api client routes

* Updated routes to use mix-id indexing

* Introduction of deprecated routes callable by identity key

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

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

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

* Updated constructor for ValidatorCacheInner

* Fixed wasm client topology construction

* Run cargo fmt on the entire codebase

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

* Updated mixnode-related ts types

* Updated nym-wallet-types

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

* 'update_contract_settings'

* 'bond_gateway'

* unbond_gateway'

* Utility commands for the transition period

* 'bond_mixnode'

* 'unbond_mixnode'

* Ability to update mixnode cost paramaters

* Mixnode config update

* Updated mixnode_bond_details

It also returns a different underlying type now

* Updated 'gateway_bond_details'

* Obtaining pending operator rewards

* Improved way of obtaining number of mixnode delegators

* simplified error handling in 'fetch_mix_node_description'

* mixnode and gateway ownership queries

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

* mixnode delegation

* undelegating

* Obtaining pending delegator rewards

* Command for obtaining current interval details

* Queries to handle paging for pending events

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

* Wallet compatible pending event types

* Commands fo obtaining pending events

* Re-implemented pending delegation events

* Further work on delegation

* Removed unused imports

* Commands for withdrawing rewards

* Admin-related simulations

* mixnet-related simulation commands

* Validator-api related routes

* Bond-related vesting operations

* Vesting simulations

* Vesting handler for UpdateMixnodeCostParams

* Vesting reward claiming

* Vesting queries

* claim_locked_and_unlocked_delegator_reward

* The massive delegation query

* cleanup

* updated typescript requests

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

* Regenerated typescript types

* temporarily ignoring unreachable code in vesting migration

* Updated missed test fixture

* Fixed missing coconut-specific import

* cargo fmt

* Exporting reward-related types

* utility to convert stringified decimal to cosmjs Decimal

* deriving Eq alongside PartialEq

* wip - typescript fixes

* using default operating cost when bonding mixnode

* Using default operating cost when updating mixnode cost params

* most delegation fixes

* Wrapping delegation with node identity

* Added MultiIndex on owner and identity key to unbonded mixnodes

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

* Cargo fmt + ts types update

* feature locking unused imports

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

* post-rebase fixes

* Changed storage key for new delegations map in vesting contract

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

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

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

* Changed default_mixnode_cost_params to allow accepting f32 instead

* Revert "Changed default_mixnode_cost_params to allow accepting f32 instead"

This reverts commit fb62a0014f.

* Fixed APY calculation for 0 pledge value

* Don't send rewarding transactions for empty rewarded set

* Fixed mixnode rewarding in validator API

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

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

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

* Updated typescript side of things

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

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

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

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

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

* Updated typescript side of things

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

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

* Updated typescript side of things

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

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

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

* Updated typescript side of things

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

* fix(nym-wallet): fix clippy

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

* Updated vesting contract migration to update the mixnet contract address

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

* Post rebasing fixes

* commented out all code

* Bunch of work in progres, but working simulator

* Removing redundant fields + increased precision to 9decimal places

* deserialization of Percent with value validation

* wip

* Further moving things around + mixnode bonding

* Mixnode unbonding

* Starting restoration on contract state

* Revamping interval

* More work on epoch/interval

* progress on mixnode rewarding

* Moved MixNodeRewarding to rewards storage

* wip on delegations

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

* more wip delegation

* Full delegation flow

* mixnode config updates

* Mixnode cost function updates

* Work on moving mixnode unbonding to post-epoch actions

* Unbonding

* Processing undelegation

* changing cost params

* Uncommented existing gateways features

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

* ExecuteMsg cleanup

* unit tests for withdrawing rewards against known values

* Transactions for withdrawing rewards

* Transactions for updating various parameters

* First round of post-tx cleanup

* Moved all storage keys to constants.rs

* Using correct initial gateway pledge amount

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

* Starting with contract queries

* Keeping minimal details of unbonded mixnoces

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

* Mixnode-related queries

* Gateway-related queries

* Query for paged unbonded mixnodes

* Delegations queries

* Query for current interval details

* Removed 'fixed' dependency from the mixnet common

* wip on implementing rewards-related queries

* Pending rewards queries

* Query for node stake saturation

* Queries for currently pending events

* Rewarded set query

* Moved ContractState to common types

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

* Cleaend up storage initialisation

* started restoring unit tests

* Removed attached 1ucoin for cross-contract execute msgs

* wip

* query for rewarding details of a mix node

* Changes for mixnodes and gateways

* Furher progress on v2 changelog(-ish) description

* wip

* first version of the description

* mixnode bonding queries tests and fixes

* ibid for storage

* MixnodeEventType enum + created events for missing mixnode txs

* tests for adding new mixnode

* Additional mixnode-related tests + bug fixes

* Display for Percent

* Bunch of tests for try_reward_mixnode

* More tests and fixes

* ibid

* tests for updating rewarding params + important bug fix

* Started removing unused imports

* rewarding queries tests + undelegation bugfix

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

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

* Delegation tests + fixes

* Emiting events by top level interval txs + incorporating limit

* question

* Missing events emissions

* removed some code duplication

* wip

* pending delegation tests

* Vesting contract update

* More tests (and fixes) for pending events txs

* Restored gateway tx tests

* Another cleanup iteration

* removed redundant comment

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

* Unit tests for helper functions

* Interval queries unit tests

* Test for correct contract initialisation

* Another round of cleanup

* Work on mixnet_query_client trait

* mixnet_signing_client trait

* Removed redundant methods

* Slowly restoring validator client functionality

* Added deprecated query for mix details by identity

* wip restoration of validator-api

* Work on deprecating validator API routes

* Further validator-api routes

* Restored rest of status api routes

* Resolved all todos in ValidatorApiStorage

There's still bunch left in StorageManager though

* Changed NodeId from u64 to u32

* Updating sql code

* Network monitor internals

* Changed behaviour of full_epoch_id and updated epoch operations

* Fixed sql queries

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

* Post rebasing fixes

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

* Changed cache to allow for non-string keys

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

* Updated validator-api client routes

* Updated routes to use mix-id indexing

* Introduction of deprecated routes callable by identity key

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

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

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

* Updated constructor for ValidatorCacheInner

* Fixed wasm client topology construction

* Run cargo fmt on the entire codebase

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

* Updated mixnode-related ts types

* Updated nym-wallet-types

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

* 'update_contract_settings'

* 'bond_gateway'

* unbond_gateway'

* Utility commands for the transition period

* 'bond_mixnode'

* 'unbond_mixnode'

* Ability to update mixnode cost paramaters

* Mixnode config update

* Updated mixnode_bond_details

It also returns a different underlying type now

* Updated 'gateway_bond_details'

* Obtaining pending operator rewards

* Improved way of obtaining number of mixnode delegators

* simplified error handling in 'fetch_mix_node_description'

* mixnode and gateway ownership queries

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

* mixnode delegation

* undelegating

* Obtaining pending delegator rewards

* Command for obtaining current interval details

* Queries to handle paging for pending events

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

* Wallet compatible pending event types

* Commands fo obtaining pending events

* Re-implemented pending delegation events

* Further work on delegation

* Removed unused imports

* Commands for withdrawing rewards

* Admin-related simulations

* mixnet-related simulation commands

* Validator-api related routes

* Bond-related vesting operations

* Vesting simulations

* Vesting handler for UpdateMixnodeCostParams

* Vesting reward claiming

* Vesting queries

* claim_locked_and_unlocked_delegator_reward

* The massive delegation query

* cleanup

* updated typescript requests

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

* Regenerated typescript types

* temporarily ignoring unreachable code in vesting migration

* Updated missed test fixture

* Fixed missing coconut-specific import

* cargo fmt

* Exporting reward-related types

* utility to convert stringified decimal to cosmjs Decimal

* deriving Eq alongside PartialEq

* wip - typescript fixes

* using default operating cost when bonding mixnode

* Using default operating cost when updating mixnode cost params

* most delegation fixes

* Wrapping delegation with node identity

* Added MultiIndex on owner and identity key to unbonded mixnodes

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

* Cargo fmt + ts types update

* feature locking unused imports

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

* post-rebase fixes

* Changed storage key for new delegations map in vesting contract

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

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

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

* Changed default_mixnode_cost_params to allow accepting f32 instead

* Revert "Changed default_mixnode_cost_params to allow accepting f32 instead"

This reverts commit fb62a0014f.

* Fixed APY calculation for 0 pledge value

* Don't send rewarding transactions for empty rewarded set

* Fixed mixnode rewarding in validator API

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

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

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

* Updated typescript side of things

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

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

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

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

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

* Updated typescript side of things

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

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

* Updated typescript side of things

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

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

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

* Updated typescript side of things

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

* fix(nym-wallet): fix clippy

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

* Updated vesting contract migration to update the mixnet contract address

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

* Post rebasing fixes

* Removed deprecation on GetMixnodeDetailsByIdentity

* Fixed nym-cli

* Removed needless borrow

* Updated colorMap and textMap

Co-authored-by: durch <durch@users.noreply.github.com>
Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
Co-authored-by: Pierre Dommerc <dommerc.pierre@gmail.com>
2022-09-20 14:44:57 +01:00
Jon Häggblad ecbf5296a5 validator-api: guard against funny numbers in apy calc (#1636) 2022-09-19 14:37:09 +02:00
Jon Häggblad f3454409f8 Upgrade nym-connect to tauri 1.1.1 (#1635) 2022-09-19 11:34:33 +02:00
Gala 7d2e90b69f Merge pull request #1625 from nymtech/407-ne-filters-granularity
NE: 407 filters granularity
2022-09-19 11:28:26 +02:00
Gala bb2732bcc6 Merge pull request #1619 from nymtech/413-ne-content
NE: Display raw bond instead of self %
2022-09-19 11:16:16 +02:00
Gala d196993993 fix build 2022-09-19 11:08:16 +02:00
Gala b4fe8af890 refactor from CR 2022-09-19 11:04:26 +02:00
Gala 1c8ab2395d Merge branch 'develop' into 409-ne-filters-info 2022-09-19 10:24:18 +02:00
Gala 02e75ea5cd display different content when user doesn't have delegations 2022-09-16 12:24:32 +02:00
Gala ebb3f6eebb fixing the build 2022-09-15 16:06:14 +02:00
Gala 2c15d22e1e Merge branch 'develop' into 409-ne-filters-info 2022-09-15 15:55:58 +02:00
Gala f1dca2c9a8 styling the mobile view 2022-09-15 14:50:33 +02:00
Gala d039c25b55 create a custom label for stake sat 2022-09-15 14:27:52 +02:00
Gala 057b3456a7 more granulatity on filters 2022-09-15 12:49:27 +02:00
Gala dee2c50b50 ne toolbar changes 2022-09-14 16:04:39 +02:00
Gala a394c9b59a PR requested changes 2022-09-14 14:48:30 +02:00
Gala 17768bab0b Merge branch 'develop' into 317-forms-menus-titles-ui 2022-09-14 14:11:10 +02:00
Gala 7816b4c839 some refactor 2022-09-14 13:58:04 +02:00
Gala e7ed48e55e trying to fix the build 2022-09-14 13:35:22 +02:00
Gala 63855f6ca4 display pledge_amount instead of self on mix nodes page 2022-09-14 13:13:08 +02:00
Gala 27d566dd47 some styling 2022-09-14 11:46:06 +02:00
Gala 8924f9642f wip 2022-09-14 11:35:22 +02:00
Gala 58541defad Merge branch 'develop' into 309-figma-diff-mnemonic 2022-09-14 11:29:55 +02:00
Gala 1af1370f23 display raw bond instead of self % 2022-09-13 17:00:37 +02:00
Gala ade15d3c60 change mnemonic textfield 2022-09-13 14:53:04 +02:00
Gala 1241a81514 changing alert behaviour 2022-09-13 13:49:42 +02:00
Gala 08a190c1cb Merge branch 'develop' into 348-bonding-settings 2022-09-13 12:44:11 +02:00
Gala 81f36e8da7 some refactor 2022-09-08 13:36:44 +02:00
Gala f230229ce9 change save button label 2022-09-08 12:18:09 +02:00
Gala 912fb4ab38 Merge branch 'develop' into 348-bonding-settings 2022-09-08 12:05:34 +02:00
Gala 99ceabb0b0 using the wallet Tab component 2022-09-06 13:18:43 +02:00
Gala 25df7bcd4d Merge branch 'develop' into 348-bonding-settings 2022-09-02 09:39:21 +02:00
Gala 1cdca7bec3 unbond modal verification step 2022-09-01 16:57:48 +02:00
Gala c809c7733d logic refactor 2022-09-01 15:06:23 +02:00
Gala 7b53003edb wip verification modal 2022-08-31 18:49:47 +02:00
Gala 831d9d2bf8 update alert text 2022-08-31 18:20:40 +02:00
Gala cb7c51ba12 remove node settings modal trigger 2022-08-31 17:37:00 +02:00
Gala 0310f0a8a9 some refactor 2022-08-31 17:23:53 +02:00
Gala bb79d08f6d dynamic values and remove hard coded code 2022-08-31 16:58:53 +02:00
Gala 414c86b500 fix button width 2022-08-31 12:13:40 +02:00
Gala 4304ffcf3c adding notification span 2022-08-31 11:44:23 +02:00
Gala 309b23e18a adding confirmation modals 2022-08-31 10:55:13 +02:00
Gala 52703583f0 adding validation to parameters settings 2022-08-31 10:10:37 +02:00
Gala 6473ef13c6 validate info fields 2022-08-30 18:51:39 +02:00
Gala 6de7d060e3 keep same margin on between pages 2022-08-30 17:00:48 +02:00
Gala 9a45f15ba4 wip 2022-08-30 16:49:07 +02:00
Gala 66b5eb13b0 fixing nymcard title size and margin in delegations page 2022-08-30 14:32:15 +02:00
Gala 4b37d4f050 Merge branch 'develop' into 317-forms-menus-titles-ui 2022-08-30 12:50:11 +02:00
Gala 746795b7ce mook bonded node 2022-08-30 12:49:50 +02:00
Gala 8b81247044 Merge branch 'develop' into 348-bonding-settings 2022-08-30 11:08:19 +02:00
Gala c6cd787950 adding unbonding modal 2022-08-19 18:06:04 +02:00
Gala f9ab20b10f more styling in node
settings page
2022-08-18 17:27:28 +02:00
Gala acffd496ed nav styles 2022-08-18 17:07:59 +02:00
Gala 466ac1a1e0 settings general page 2022-08-18 16:39:05 +02:00
Gala d53adcd17e nodesettings page and logic to browse 2022-08-17 18:55:58 +02:00
Gala 36e82e831f Merge branch 'develop' into 348-bonding-settings 2022-08-17 13:55:06 +02:00
Gala cbe0115f01 wip 2022-08-17 11:10:10 +02:00
Gala 1dae3c3fc2 remove vAxis label 2022-08-16 14:08:00 +02:00
Gala 574e5cf10a change legend 2022-08-16 13:42:48 +02:00
Gala b3fcbb6726 remove log 2022-08-16 12:28:09 +02:00
Gala f96a60b6a2 log in staging 2022-08-16 12:10:17 +02:00
Gala 37d501f16d fix delegate more focus state 2022-08-11 16:20:05 +02:00
Gala 1e76169178 fixing spacing in modals 2022-08-11 14:57:37 +02:00
Gala 7406eeff14 ui top bar 2022-08-11 14:11:57 +02:00
Gala bdabe31fc9 side navigation 2022-08-11 14:03:02 +02:00
Gala f7b979825b Merge branch 'develop' into 340-ne-content 2022-08-10 16:44:02 +02:00
Gala 6ac1259f7a changing content 2022-08-10 11:24:02 +02:00
577 changed files with 36033 additions and 22310 deletions
+18
View File
@@ -3,3 +3,21 @@
RUST_LOG=info
RUST_BACKTRACE=1
#########################################
# geoipupdate (needed for explorer-api) #
#########################################
# MaxMind account ID (change it to a valid account ID)
GEOIPUPDATE_ACCOUNT_ID=xxx
# MaxMind license key (change it to a valid license key)
GEOIPUPDATE_LICENSE_KEY=xxx
# List of space-separated database edition IDs. Edition IDs may
# consist of letters, digits, and dashes. For example, GeoIP2-City
# would download the GeoIP2 City database (GeoIP2-City).
GEOIPUPDATE_EDITION_IDS=GeoLite2-Country
# The number of hours between geoipupdate runs. If this is not set
# or is set to 0, geoipupdate will run once and exit.
GEOIPUPDATE_FREQUENCY=72
# The path to the directory where geoipupdate will download the
# database.
GEOIP_DB_DIRECTORY=./explorer-api/geo_ip
+27 -14
View File
@@ -1,36 +1,49 @@
name: Daily security audit
on: workflow_dispatch
on:
schedule:
- cron: '5 9 * * *'
jobs:
security_audit:
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions-rs/audit-check@v1
- name: Checkout repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
toolchain: stable
- name: Install cargo deny
run: cargo install --locked cargo-deny
- name: Run cargo deny
run: cargo deny check advisories --hide-inclusion-graph &> .github/workflows/support-files/notifications/deny.message
- uses: actions/upload-artifact@v3
with:
name: report
path: .github/workflows/support-files/notifications/deny.message
notification:
if: ${{ failure() }}
needs: security_audit
needs: cargo-deny
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v2
- name: Download report from previous job
uses: actions/download-artifact@v3
with:
name: report
path: .github/workflows/support-files/notifications
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym daily audit"
NYM_NOTIFICATION_KIND: security
NYM_PROJECT_NAME: "Daily security report"
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_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBTECH_TEAM }}"
KEYBASE_NYM_CHANNEL: "test"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "security"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
- name: Check out repository code
uses: actions/checkout@v2
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code
+11
View File
@@ -2,6 +2,12 @@ name: Publish Nym binaries
on:
workflow_dispatch:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
release:
types: [created]
@@ -25,6 +31,11 @@ jobs:
with:
script: |
core.setFailed('Release tag did not start with nym-binaries-...')
- name: Sets env vars for tokio if set in manual dispatch inputs
run: |
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
@@ -3,7 +3,7 @@ require('dotenv').config();
const Bot = require('keybase-bot');
let context = {
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect'],
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect','security'],
};
/**
@@ -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,
};
+34
View File
@@ -9,18 +9,52 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
- common/ledger: new library for communicating with a Ledger device ([#1640])
- 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
- native-client/socks5-client/network-requester: improve handling error cases ([#1713])
- 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
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
- 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])
[#1541]: https://github.com/nymtech/nym/pull/1541
[#1558]: https://github.com/nymtech/nym/pull/1558
[#1577]: https://github.com/nymtech/nym/pull/1577
[#1585]: https://github.com/nymtech/nym/pull/1585
[#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640
[#1645]: https://github.com/nymtech/nym/pull/1645
[#1664]: https://github.com/nymtech/nym/pull/1664
[#1666]: https://github.com/nymtech/nym/pull/1645
[#1669]: https://github.com/nymtech/nym/pull/1669
[#1671]: https://github.com/nymtech/nym/pull/1671
[#1673]: https://github.com/nymtech/nym/pull/1673
[#1681]: https://github.com/nymtech/nym/pull/1681
[#1687]: https://github.com/nymtech/nym/pull/1687
[#1702]: https://github.com/nymtech/nym/pull/1702
[#1703]: https://github.com/nymtech/nym/pull/1703
[#1713]: https://github.com/nymtech/nym/pull/1713
[#1721]: https://github.com/nymtech/nym/pull/1721
[#1724]: https://github.com/nymtech/nym/pull/1724
[#1725]: https://github.com/nymtech/nym/pull/1725
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
Generated
+797 -1012
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -40,6 +40,8 @@ members = [
"common/crypto/dkg",
"common/execute",
"common/inclusion-probability",
"common/ledger",
"common/logging",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
@@ -62,9 +64,11 @@ members = [
"common/topology",
"common/types",
"common/wasm-utils",
"common/completions",
"explorer-api",
"gateway",
"gateway/gateway-requests",
"integrations/bity",
"mixnode",
"service-providers/network-requester",
"service-providers/network-statistics",
+19 -4
View File
@@ -2,12 +2,12 @@ test: clippy-all cargo-test wasm fmt
test-all: test cargo-test-expensive
no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-main clippy-coconut clippy-all-contracts clippy-all-wallet clippy-all-connect
clippy-all: clippy-main clippy-coconut clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-wasm-client
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect
cargo-test: test-main test-contracts test-wallet test-connect test-coconut
cargo-test: test-main test-contracts test-wallet test-connect test-coconut test-wasm-client
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive test-coconut-expensive
build: build-contracts build-wallet build-main build-connect
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect
build: build-contracts build-wallet build-main build-connect build-wasm-client
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-wasm-client
clippy-happy-main:
cargo clippy
@@ -40,6 +40,9 @@ clippy-all-wallet:
clippy-all-connect:
cargo clippy --workspace --manifest-path nym-connect/Cargo.toml --all-features -- -D warnings
clippy-all-wasm-client:
cargo clippy --workspace --manifest-path clients/webassembly/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
test-main:
cargo test --workspace
@@ -68,6 +71,9 @@ test-wallet:
test-wallet-expensive:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features -- --ignored
test-wasm-client:
cargo test --workspace --manifest-path clients/webassembly/Cargo.toml --all-features
test-connect:
cargo test --manifest-path nym-connect/Cargo.toml --all-features
@@ -86,6 +92,9 @@ build-wallet:
build-connect:
cargo build --manifest-path nym-connect/Cargo.toml --workspace
build-wasm-client:
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
build-nym-cli:
cargo build --release --manifest-path tools/nym-cli/Cargo.toml
@@ -101,9 +110,15 @@ fmt-wallet:
fmt-connect:
cargo fmt --manifest-path nym-connect/Cargo.toml --all
fmt-wasm-client:
cargo fmt --manifest-path clients/webassembly/Cargo.toml --all
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
mixnet-opt: wasm
cd contracts/mixnet && make opt
generate-typescript:
cd tools/ts-rs-cli && cargo run && cd ../..
yarn types:lint:fix
+25 -4
View File
@@ -13,26 +13,47 @@ humantime-serde = "1.0"
log = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
sled = "0.34"
sled = { version = "0.34", optional = true }
thiserror = "1.0.34"
tokio = { version = "1.19.1", features = ["macros"] }
url = { version ="2.2", features = ["serde"] }
# internal
config = { path = "../../common/config" }
crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
gateway-requests = { path = "../../gateway/gateway-requests" }
nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" }
nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
task = { path = "../../common/task" }
topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client" }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
tap = "1.0.1"
tokio = { version = "1.21.2", features = ["time", "macros"]}
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
version = "0.4"
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2.83"
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-timer]
git = "https://github.com/mmsinclair/wasm-timer"
rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"
[target."cfg(target_arch = \"wasm32\")".dependencies.gloo-timers]
version = "0.2.4"
features = ["futures"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.task]
path = "../../common/task"
[dev-dependencies]
tempfile = "3.1.0"
[features]
default = ["reply-surb"]
wasm = ["gateway-client/wasm"]
coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
reply-surb = ["sled"]
@@ -3,20 +3,27 @@
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::topology_control::TopologyAccessor;
use crate::spawn_future;
use futures::task::{Context, Poll};
use futures::{Future, Stream, StreamExt};
use log::*;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::cover::generate_loop_cover_packet;
use nymsphinx::params::PacketSize;
use nymsphinx::utils::sample_poisson_duration;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::pin::Pin;
use std::sync::Arc;
use task::ShutdownListener;
use tokio::task::JoinHandle;
use std::time::Duration;
use tokio::sync::mpsc::error::TrySendError;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
#[cfg(target_arch = "wasm32")]
use wasm_timer;
pub struct LoopCoverTrafficStream<R>
where
R: CryptoRng + Rng,
@@ -25,18 +32,22 @@ where
ack_key: Arc<AckKey>,
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
average_ack_delay: time::Duration,
average_ack_delay: Duration,
/// Average delay a data packet is going to get delay at a single mixnode.
average_packet_delay: time::Duration,
average_packet_delay: Duration,
/// Average delay between sending subsequent cover packets.
average_cover_message_sending_delay: time::Duration,
average_cover_message_sending_delay: Duration,
/// Internal state, determined by `average_message_sending_delay`,
/// used to keep track of when a next packet should be sent out.
#[cfg(not(target_arch = "wasm32"))]
next_delay: Pin<Box<time::Sleep>>,
#[cfg(target_arch = "wasm32")]
next_delay: Pin<Box<wasm_timer::Delay>>,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: BatchMixMessageSender,
@@ -50,8 +61,8 @@ where
/// Accessor to the common instance of network topology.
topology_access: TopologyAccessor,
/// Listen to shutdown signals.
shutdown: ShutdownListener,
/// Predefined packet size used for the loop cover messages.
packet_size: PacketSize,
}
impl<R> Stream for LoopCoverTrafficStream<R>
@@ -73,13 +84,21 @@ where
// we know it's time to send a message, so let's prepare delay for the next one
// Get the `now` by looking at the current `delay` deadline
let avg_delay = self.average_cover_message_sending_delay;
let now = self.next_delay.deadline();
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
// The next interval value is `next_poisson_delay` after the one that just
// yielded.
let next = now + next_poisson_delay;
self.next_delay.as_mut().reset(next);
#[cfg(not(target_arch = "wasm32"))]
{
let now = self.next_delay.deadline();
let next = now + next_poisson_delay;
self.next_delay.as_mut().reset(next);
}
#[cfg(target_arch = "wasm32")]
{
self.next_delay.as_mut().reset(next_poisson_delay);
}
Poll::Ready(Some(()))
}
@@ -91,30 +110,39 @@ impl LoopCoverTrafficStream<OsRng> {
#[allow(clippy::too_many_arguments)]
pub fn new(
ack_key: Arc<AckKey>,
average_ack_delay: time::Duration,
average_packet_delay: time::Duration,
average_cover_message_sending_delay: time::Duration,
average_ack_delay: Duration,
average_packet_delay: Duration,
average_cover_message_sending_delay: Duration,
mix_tx: BatchMixMessageSender,
our_full_destination: Recipient,
topology_access: TopologyAccessor,
shutdown: ShutdownListener,
) -> Self {
let rng = OsRng;
#[cfg(not(target_arch = "wasm32"))]
let next_delay = Box::pin(time::sleep(Default::default()));
#[cfg(target_arch = "wasm32")]
let next_delay = Box::pin(wasm_timer::Delay::new(Default::default()));
LoopCoverTrafficStream {
ack_key,
average_ack_delay,
average_packet_delay,
average_cover_message_sending_delay,
next_delay: Box::pin(time::sleep(Default::default())),
next_delay,
mix_tx,
our_full_destination,
rng,
topology_access,
shutdown,
packet_size: Default::default(),
}
}
pub fn set_custom_packet_size(&mut self, packet_size: PacketSize) {
self.packet_size = packet_size;
}
async fn on_new_message(&mut self) {
trace!("next cover message!");
@@ -136,18 +164,26 @@ impl LoopCoverTrafficStream<OsRng> {
let cover_message = generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&*self.ack_key,
&self.ack_key,
&self.our_full_destination,
self.average_ack_delay,
self.average_packet_delay,
self.packet_size,
)
.expect("Somehow failed to generate a loop cover message with a valid topology");
// if this one fails, there's no retrying because it means that either:
// - we run out of memory
// - the receiver channel is closed
// in either case there's no recovery and we can only panic
self.mix_tx.unbounded_send(vec![cover_message]).unwrap();
if let Err(err) = self.mix_tx.try_send(vec![cover_message]) {
match err {
TrySendError::Full(_) => {
// This isn't a problem, if the channel is full means we're already sending the
// max amount of messages downstream can handle.
log::debug!("Failed to send cover message - channel full");
}
TrySendError::Closed(_) => {
log::warn!("Failed to send cover message - channel closed");
}
}
}
// TODO: I'm not entirely sure whether this is really required, because I'm not 100%
// sure how `yield_now()` works - whether it just notifies the scheduler or whether it
@@ -156,40 +192,56 @@ impl LoopCoverTrafficStream<OsRng> {
// JS: due to identical logical structure to OutQueueControl::on_message(), this is also
// presumably required to prevent bugs in the future. Exact reason is still unknown to me.
// TODO: temporary and BAD workaround for wasm (we should find a way to yield here in wasm)
#[cfg(not(target_arch = "wasm32"))]
tokio::task::yield_now().await;
}
async fn run(&mut self) {
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
// we should set initial delay only when we actually start the stream
self.next_delay = Box::pin(time::sleep(sample_poisson_duration(
&mut self.rng,
self.average_cover_message_sending_delay,
)));
let sampled =
sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay);
self.next_delay = Box::pin(time::sleep(sampled));
let mut shutdown = self.shutdown.clone();
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("LoopCoverTrafficStream: Received shutdown");
}
next = self.next() => {
if next.is_some() {
self.on_new_message().await;
} else {
log::trace!("LoopCoverTrafficStream: Stopping since channel closed");
break;
spawn_future(async move {
debug!("Started LoopCoverTrafficStream with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("LoopCoverTrafficStream: Received shutdown");
}
next = self.next() => {
if next.is_some() {
self.on_new_message().await;
} else {
log::trace!("LoopCoverTrafficStream: Stopping since channel closed");
break;
}
}
}
}
}
assert!(self.shutdown.is_shutdown_poll());
log::debug!("LoopCoverTrafficStream: Exiting");
assert!(shutdown.is_shutdown_poll());
log::debug!("LoopCoverTrafficStream: Exiting");
})
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
self.run().await;
#[cfg(target_arch = "wasm32")]
pub fn start(mut self) {
// we should set initial delay only when we actually start the stream
let sampled =
sample_poisson_duration(&mut self.rng, self.average_cover_message_sending_delay);
self.next_delay = Box::pin(wasm_timer::Delay::new(sampled));
spawn_future(async move {
debug!("Started LoopCoverTrafficStream without graceful shutdown support");
while self.next().await.is_some() {
self.on_new_message().await;
}
})
}
}
+44 -36
View File
@@ -1,17 +1,16 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc;
use futures::StreamExt;
use crate::spawn_future;
use gateway_client::GatewayClient;
use log::*;
use nymsphinx::forwarding::packet::MixPacket;
use task::ShutdownListener;
use tokio::task::JoinHandle;
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
pub type BatchMixMessageReceiver = mpsc::UnboundedReceiver<Vec<MixPacket>>;
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
// We remind ourselves that 32 x 32kb = 1024kb, a reasonable size for a network buffer.
pub const MIX_MESSAGE_RECEIVER_BUFFER_SIZE: usize = 32;
const MAX_FAILURE_COUNT: usize = 100;
pub struct MixTrafficController {
@@ -19,7 +18,6 @@ pub struct MixTrafficController {
// later on gateway_client will need to be accessible by other entities
gateway_client: GatewayClient,
mix_rx: BatchMixMessageReceiver,
shutdown: ShutdownListener,
// TODO: this is temporary work-around.
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
@@ -27,17 +25,17 @@ pub struct MixTrafficController {
}
impl MixTrafficController {
pub fn new(
mix_rx: BatchMixMessageReceiver,
gateway_client: GatewayClient,
shutdown: ShutdownListener,
) -> MixTrafficController {
MixTrafficController {
gateway_client,
mix_rx,
shutdown,
consecutive_gateway_failure_count: 0,
}
pub fn new(gateway_client: GatewayClient) -> (MixTrafficController, BatchMixMessageSender) {
let (sphinx_message_sender, sphinx_message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
(
MixTrafficController {
gateway_client,
mix_rx: sphinx_message_receiver,
consecutive_gateway_failure_count: 0,
},
sphinx_message_sender,
)
}
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
@@ -69,30 +67,40 @@ impl MixTrafficController {
}
}
pub async fn run(&mut self) {
while !self.shutdown.is_shutdown() {
tokio::select! {
mix_packets = self.mix_rx.next() => match mix_packets {
Some(mix_packets) => {
self.on_messages(mix_packets).await;
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
spawn_future(async move {
debug!("Started MixTrafficController with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
mix_packets = self.mix_rx.recv() => match mix_packets {
Some(mix_packets) => {
self.on_messages(mix_packets).await;
},
None => {
log::trace!("MixTrafficController: Stopping since channel closed");
break;
}
},
None => {
log::trace!("MixTrafficController: Stopping since channel closed");
break;
_ = shutdown.recv() => {
log::trace!("MixTrafficController: Received shutdown");
}
},
_ = self.shutdown.recv() => {
log::trace!("MixTrafficController: Received shutdown");
}
}
}
assert!(self.shutdown.is_shutdown_poll());
log::debug!("MixTrafficController: Exiting");
assert!(shutdown.is_shutdown_poll());
log::debug!("MixTrafficController: Exiting");
})
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
self.run().await;
#[cfg(target_arch = "wasm32")]
pub fn start(mut self) {
spawn_future(async move {
debug!("Started MixTrafficController without graceful shutdown support");
while let Some(mix_packets) = self.mix_rx.recv().await {
self.on_messages(mix_packets).await;
}
})
}
}
+1
View File
@@ -6,6 +6,7 @@ pub mod key_manager;
pub mod mix_traffic;
pub mod real_messages_control;
pub mod received_buffer;
#[cfg(feature = "reply-surb")]
pub mod reply_key_storage;
pub mod topology_control;
@@ -10,7 +10,6 @@ use nymsphinx::{
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
};
use std::sync::Arc;
use task::ShutdownListener;
/// Module responsible for listening for any data resembling acknowledgements from the network
/// and firing actions to remove them from the 'Pending' state.
@@ -18,7 +17,6 @@ pub(super) struct AcknowledgementListener {
ack_key: Arc<AckKey>,
ack_receiver: AcknowledgementReceiver,
action_sender: ActionSender,
shutdown: ShutdownListener,
}
impl AcknowledgementListener {
@@ -26,13 +24,11 @@ impl AcknowledgementListener {
ack_key: Arc<AckKey>,
ack_receiver: AcknowledgementReceiver,
action_sender: ActionSender,
shutdown: ShutdownListener,
) -> Self {
AcknowledgementListener {
ack_key,
ack_receiver,
action_sender,
shutdown,
}
}
@@ -67,28 +63,41 @@ impl AcknowledgementListener {
.unwrap();
}
pub(super) async fn run(&mut self) {
debug!("Started AcknowledgementListener");
while !self.shutdown.is_shutdown() {
async fn handle_ack_receiver_item(&mut self, item: Vec<Vec<u8>>) {
// realistically we would only be getting one ack at the time
for ack in item {
self.on_ack(ack).await;
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started AcknowledgementListener with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
acks = self.ack_receiver.next() => match acks {
Some(acks) => {
// realistically we would only be getting one ack at the time
for ack in acks {
self.on_ack(ack).await;
}
},
Some(acks) => self.handle_ack_receiver_item(acks).await,
None => {
log::trace!("AcknowledgementListener: Stopping since channel closed");
break;
}
},
_ = self.shutdown.recv() => {
_ = shutdown.recv() => {
log::trace!("AcknowledgementListener: Received shutdown");
}
}
}
assert!(self.shutdown.is_shutdown_poll());
assert!(shutdown.is_shutdown_poll());
log::debug!("AcknowledgementListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
pub(super) async fn run(&mut self) {
debug!("Started AcknowledgementListener without graceful shutdown support");
while let Some(acks) = self.ack_receiver.next().await {
self.handle_ack_receiver_item(acks).await
}
}
}
@@ -12,7 +12,6 @@ use nymsphinx::Delay as SphinxDelay;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use task::ShutdownListener;
pub(crate) type ActionSender = UnboundedSender<Action>;
@@ -100,16 +99,12 @@ pub(super) struct ActionController {
/// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements.
retransmission_sender: RetransmissionRequestSender,
/// Listen for shutdown notifications
shutdown: ShutdownListener,
}
impl ActionController {
pub(super) fn new(
config: Config,
retransmission_sender: RetransmissionRequestSender,
shutdown: ShutdownListener,
) -> (Self, ActionSender) {
let (sender, receiver) = mpsc::unbounded();
(
@@ -119,7 +114,6 @@ impl ActionController {
pending_acks_timers: NonExhaustiveDelayQueue::new(),
incoming_actions: receiver,
retransmission_sender,
shutdown,
},
sender,
)
@@ -251,8 +245,11 @@ impl ActionController {
}
}
pub(super) async fn run(&mut self) {
while !self.shutdown.is_shutdown() {
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started ActionController with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
action = self.incoming_actions.next() => match action {
Some(action) => self.process_action(action),
@@ -270,12 +267,24 @@ impl ActionController {
break;
}
},
_ = self.shutdown.recv() => {
_ = shutdown.recv() => {
log::trace!("ActionController: Received shutdown");
}
}
}
assert!(self.shutdown.is_shutdown_poll());
assert!(shutdown.is_shutdown_poll());
log::debug!("ActionController: Exiting");
}
#[cfg(target_arch = "wasm32")]
pub(super) async fn run(&mut self) {
debug!("Started ActionController without graceful shutdown support");
loop {
tokio::select! {
action = self.incoming_actions.next() => self.process_action(action.unwrap()),
expired_ack = self.pending_acks_timers.next() => self.handle_expired_ack_timer(expired_ack.unwrap())
}
}
}
}
@@ -3,7 +3,6 @@
use super::action_controller::{Action, ActionSender};
use super::PendingAcknowledgement;
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::client::{
inbound_messages::{InputMessage, InputMessageReceiver},
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
@@ -16,7 +15,9 @@ use nymsphinx::preparer::MessagePreparer;
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
use rand::{CryptoRng, Rng};
use std::sync::Arc;
use task::ShutdownListener;
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorage;
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
/// putting everything into sphinx packets, etc.
@@ -32,8 +33,8 @@ where
action_sender: ActionSender,
real_message_sender: BatchRealMessageSender,
topology_access: TopologyAccessor,
#[cfg(feature = "reply-surb")]
reply_key_storage: ReplyKeyStorage,
shutdown: ShutdownListener,
}
impl<R> InputMessageListener<R>
@@ -51,8 +52,7 @@ where
action_sender: ActionSender,
real_message_sender: BatchRealMessageSender,
topology_access: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
shutdown: ShutdownListener,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
InputMessageListener {
ack_key,
@@ -62,8 +62,8 @@ where
action_sender,
real_message_sender,
topology_access,
#[cfg(feature = "reply-surb")]
reply_key_storage,
shutdown,
}
}
@@ -121,12 +121,16 @@ where
.prepare_and_split_message(content, with_reply_surb, topology)
.expect("somehow the topology was invalid after all!");
#[cfg(feature = "reply-surb")]
if let Some(reply_key) = reply_key {
self.reply_key_storage
.insert_encryption_key(reply_key)
.expect("Failed to insert surb reply key to the store!")
}
#[cfg(not(feature = "reply-surb"))]
let _reply_key = reply_key;
// encrypt chunks, put them inside sphinx packets and generate acks
let mut pending_acks = Vec::with_capacity(split_message.len());
let mut real_messages = Vec::with_capacity(split_message.len());
@@ -184,9 +188,11 @@ where
}
}
pub(super) async fn run(&mut self) {
debug!("Started InputMessageListener");
while !self.shutdown.is_shutdown() {
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started InputMessageListener with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
input_msg = self.input_receiver.next() => match input_msg {
Some(input_msg) => {
@@ -197,12 +203,20 @@ where
break;
}
},
_ = self.shutdown.recv() => {
_ = shutdown.recv() => {
log::trace!("InputMessageListener: Received shutdown");
}
}
}
assert!(self.shutdown.is_shutdown_poll());
assert!(shutdown.is_shutdown_poll());
log::debug!("InputMessageListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
pub(super) async fn run(&mut self) {
debug!("Started InputMessageListener without graceful shutdown support");
while let Some(input_msg) = self.input_receiver.next().await {
self.on_input_message(input_msg).await;
}
}
}
@@ -8,11 +8,12 @@ use self::{
sent_notification_listener::SentNotificationListener,
};
use super::real_traffic_stream::BatchRealMessageSender;
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::client::{inbound_messages::InputMessageReceiver, topology_control::TopologyAccessor};
use crate::spawn_future;
use futures::channel::mpsc;
use gateway_client::AcknowledgementReceiver;
use log::*;
use nymsphinx::params::PacketSize;
use nymsphinx::{
acknowledgements::AckKey,
addressing::clients::Recipient,
@@ -25,8 +26,9 @@ use std::{
sync::{Arc, Weak},
time::Duration,
};
use task::ShutdownListener;
use tokio::task::JoinHandle;
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorage;
mod acknowledgement_listener;
mod action_controller;
@@ -120,6 +122,9 @@ pub(super) struct Config {
/// Average delay a data packet is going to get delayed at a single mixnode.
average_packet_delay: Duration,
/// Predefined packet size used for the encapsulated messages.
packet_size: PacketSize,
}
impl Config {
@@ -134,19 +139,25 @@ impl Config {
ack_wait_multiplier,
average_ack_delay,
average_packet_delay,
packet_size: Default::default(),
}
}
pub fn with_custom_packet_size(mut self, packet_size: PacketSize) -> Self {
self.packet_size = packet_size;
self
}
}
pub(super) struct AcknowledgementController<R>
where
R: CryptoRng + Rng,
{
acknowledgement_listener: Option<AcknowledgementListener>,
input_message_listener: Option<InputMessageListener<R>>,
retransmission_request_listener: Option<RetransmissionRequestListener<R>>,
sent_notification_listener: Option<SentNotificationListener>,
action_controller: Option<ActionController>,
acknowledgement_listener: AcknowledgementListener,
input_message_listener: InputMessageListener<R>,
retransmission_request_listener: RetransmissionRequestListener<R>,
sent_notification_listener: SentNotificationListener,
action_controller: ActionController,
}
impl<R> AcknowledgementController<R>
@@ -160,30 +171,29 @@ where
topology_access: TopologyAccessor,
ack_key: Arc<AckKey>,
ack_recipient: Recipient,
reply_key_storage: ReplyKeyStorage,
connectors: AcknowledgementControllerConnectors,
shutdown: ShutdownListener,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
let action_config =
action_controller::Config::new(config.ack_wait_addition, config.ack_wait_multiplier);
let (action_controller, action_sender) =
ActionController::new(action_config, retransmission_tx, shutdown.clone());
ActionController::new(action_config, retransmission_tx);
let message_preparer = MessagePreparer::new(
rng,
ack_recipient,
config.average_packet_delay,
config.average_ack_delay,
);
)
.with_custom_real_message_packet_size(config.packet_size);
// will listen for any acks coming from the network
let acknowledgement_listener = AcknowledgementListener::new(
Arc::clone(&ack_key),
connectors.ack_receiver,
action_sender.clone(),
shutdown.clone(),
);
// will listen for any new messages from the client
@@ -195,8 +205,8 @@ where
action_sender.clone(),
connectors.real_message_sender.clone(),
topology_access.clone(),
#[cfg(feature = "reply-surb")]
reply_key_storage,
shutdown.clone(),
);
// will listen for any ack timeouts and trigger retransmission
@@ -208,75 +218,95 @@ where
connectors.real_message_sender,
retransmission_rx,
topology_access,
shutdown.clone(),
);
// will listen for events indicating the packet was sent through the network so that
// the retransmission timer should be started.
let sent_notification_listener =
SentNotificationListener::new(connectors.sent_notifier, action_sender, shutdown);
SentNotificationListener::new(connectors.sent_notifier, action_sender);
AcknowledgementController {
acknowledgement_listener: Some(acknowledgement_listener),
input_message_listener: Some(input_message_listener),
retransmission_request_listener: Some(retransmission_request_listener),
sent_notification_listener: Some(sent_notification_listener),
action_controller: Some(action_controller),
acknowledgement_listener,
input_message_listener,
retransmission_request_listener,
sent_notification_listener,
action_controller,
}
}
pub(super) async fn run(&mut self) {
let mut acknowledgement_listener = self.acknowledgement_listener.take().unwrap();
let mut input_message_listener = self.input_message_listener.take().unwrap();
let mut retransmission_request_listener =
self.retransmission_request_listener.take().unwrap();
let mut sent_notification_listener = self.sent_notification_listener.take().unwrap();
let mut action_controller = self.action_controller.take().unwrap();
#[cfg(not(target_arch = "wasm32"))]
pub(super) fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut acknowledgement_listener = self.acknowledgement_listener;
let mut input_message_listener = self.input_message_listener;
let mut retransmission_request_listener = self.retransmission_request_listener;
let mut sent_notification_listener = self.sent_notification_listener;
let mut action_controller = self.action_controller;
// the below are log messages are errors as at the current stage we do not expect any of
// the task to ever finish. This will of course change once we introduce
// graceful shutdowns.
let ack_listener_fut = tokio::spawn(async move {
acknowledgement_listener.run().await;
debug!("The acknowledgement listener has finished execution!");
let shutdown_handle = shutdown.clone();
spawn_future(async move {
acknowledgement_listener
});
let input_listener_fut = tokio::spawn(async move {
input_message_listener.run().await;
debug!("The input listener has finished execution!");
input_message_listener
});
let retransmission_req_fut = tokio::spawn(async move {
retransmission_request_listener.run().await;
debug!("The retransmission request listener has finished execution!");
retransmission_request_listener
});
let sent_notification_fut = tokio::spawn(async move {
sent_notification_listener.run().await;
debug!("The sent notification listener has finished execution!");
sent_notification_listener
});
let action_controller_fut = tokio::spawn(async move {
action_controller.run().await;
debug!("The controller has finished execution!");
action_controller
.run_with_shutdown(shutdown_handle)
.await;
debug!("The acknowledgement listener has finished execution!");
});
// technically we don't have to bring `AcknowledgementController` back to a valid state
// but we can do it, so why not? Perhaps it might be useful if we wanted to allow
// for restarts of certain modules without killing the entire process.
self.acknowledgement_listener = Some(ack_listener_fut.await.unwrap());
self.input_message_listener = Some(input_listener_fut.await.unwrap());
self.retransmission_request_listener = Some(retransmission_req_fut.await.unwrap());
self.sent_notification_listener = Some(sent_notification_fut.await.unwrap());
self.action_controller = Some(action_controller_fut.await.unwrap());
let shutdown_handle = shutdown.clone();
spawn_future(async move {
input_message_listener
.run_with_shutdown(shutdown_handle)
.await;
debug!("The input listener has finished execution!");
});
let shutdown_handle = shutdown.clone();
spawn_future(async move {
retransmission_request_listener
.run_with_shutdown(shutdown_handle)
.await;
debug!("The retransmission request listener has finished execution!");
});
let shutdown_handle = shutdown.clone();
spawn_future(async move {
sent_notification_listener
.run_with_shutdown(shutdown_handle)
.await;
debug!("The sent notification listener has finished execution!");
});
spawn_future(async move {
action_controller.run_with_shutdown(shutdown).await;
debug!("The controller has finished execution!");
});
}
#[allow(dead_code)]
pub(super) fn start(mut self) -> JoinHandle<Self> {
tokio::spawn(async move {
self.run().await;
self
})
#[cfg(target_arch = "wasm32")]
pub(super) fn start(self) {
let mut acknowledgement_listener = self.acknowledgement_listener;
let mut input_message_listener = self.input_message_listener;
let mut retransmission_request_listener = self.retransmission_request_listener;
let mut sent_notification_listener = self.sent_notification_listener;
let mut action_controller = self.action_controller;
spawn_future(async move {
acknowledgement_listener.run().await;
error!("The acknowledgement listener has finished execution!");
});
spawn_future(async move {
input_message_listener.run().await;
error!("The input listener has finished execution!");
});
spawn_future(async move {
retransmission_request_listener.run().await;
error!("The retransmission request listener has finished execution!");
});
spawn_future(async move {
sent_notification_listener.run().await;
error!("The sent notification listener has finished execution!");
});
spawn_future(async move {
action_controller.run().await;
error!("The controller has finished execution!");
});
}
}
@@ -14,7 +14,6 @@ use nymsphinx::preparer::MessagePreparer;
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak};
use task::ShutdownListener;
// responsible for packet retransmission upon fired timer
pub(super) struct RetransmissionRequestListener<R>
@@ -28,7 +27,6 @@ where
real_message_sender: BatchRealMessageSender,
request_receiver: RetransmissionRequestReceiver,
topology_access: TopologyAccessor,
shutdown: ShutdownListener,
}
impl<R> RetransmissionRequestListener<R>
@@ -44,7 +42,6 @@ where
real_message_sender: BatchRealMessageSender,
request_receiver: RetransmissionRequestReceiver,
topology_access: TopologyAccessor,
shutdown: ShutdownListener,
) -> Self {
RetransmissionRequestListener {
ack_key,
@@ -54,7 +51,6 @@ where
real_message_sender,
request_receiver,
topology_access,
shutdown,
}
}
@@ -124,10 +120,11 @@ where
.unwrap();
}
pub(super) async fn run(&mut self) {
debug!("Started RetransmissionRequestListener");
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started RetransmissionRequestListener with graceful shutdown support");
while !self.shutdown.is_shutdown() {
while !shutdown.is_shutdown() {
tokio::select! {
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack).await,
@@ -136,12 +133,21 @@ where
break;
}
},
_ = self.shutdown.recv() => {
_ = shutdown.recv() => {
log::trace!("RetransmissionRequestListener: Received shutdown");
}
}
}
assert!(self.shutdown.is_shutdown_poll());
assert!(shutdown.is_shutdown_poll());
log::debug!("RetransmissionRequestListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
pub(super) async fn run(&mut self) {
debug!("Started RetransmissionRequestListener without graceful shutdown support");
while let Some(timed_out_ack) = self.request_receiver.next().await {
self.on_retransmission_request(timed_out_ack).await;
}
}
}
@@ -6,7 +6,6 @@ use super::SentPacketNotificationReceiver;
use futures::StreamExt;
use log::*;
use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
use task::ShutdownListener;
/// Module responsible for starting up retransmission timers.
/// It is required because when we send our packet to the `real traffic stream` controlled
@@ -15,19 +14,16 @@ use task::ShutdownListener;
pub(super) struct SentNotificationListener {
sent_notifier: SentPacketNotificationReceiver,
action_sender: ActionSender,
shutdown: ShutdownListener,
}
impl SentNotificationListener {
pub(super) fn new(
sent_notifier: SentPacketNotificationReceiver,
action_sender: ActionSender,
shutdown: ShutdownListener,
) -> Self {
SentNotificationListener {
sent_notifier,
action_sender,
shutdown,
}
}
@@ -46,9 +42,11 @@ impl SentNotificationListener {
.unwrap();
}
pub(super) async fn run(&mut self) {
debug!("Started SentNotificationListener");
while !self.shutdown.is_shutdown() {
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started SentNotificationListener with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
frag_id = self.sent_notifier.next() => match frag_id {
Some(frag_id) => {
@@ -59,12 +57,21 @@ impl SentNotificationListener {
break;
}
},
_ = self.shutdown.recv() => {
_ = shutdown.recv() => {
log::trace!("SentNotificationListener: Received shutdown");
}
}
}
assert!(self.shutdown.is_shutdown_poll());
assert!(shutdown.is_shutdown_poll());
log::debug!("SentNotificationListener: Exiting");
}
#[cfg(target_arch = "wasm32")]
pub(super) async fn run(&mut self) {
debug!("Started SentNotificationListener without graceful shutdown support");
while let Some(frag_id) = self.sent_notifier.next().await {
self.on_sent_message(frag_id).await;
}
}
}
@@ -9,21 +9,23 @@ use self::{
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
};
use crate::client::real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors;
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::client::{
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
topology_control::TopologyAccessor,
};
use crate::spawn_future;
use futures::channel::mpsc;
use gateway_client::AcknowledgementReceiver;
use log::*;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::params::PacketSize;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
use std::time::Duration;
use task::ShutdownListener;
use tokio::task::JoinHandle;
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorage;
mod acknowledgement_control;
mod real_traffic_stream;
@@ -50,9 +52,18 @@ pub struct Config {
/// Average delay an acknowledgement packet is going to get delayed at a single mixnode.
average_ack_delay_duration: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
disable_main_poisson_packet_distribution: bool,
/// Predefined packet size used for the encapsulated messages.
packet_size: PacketSize,
}
impl Config {
// TODO: change the config into a builder
#[allow(clippy::too_many_arguments)]
pub fn new(
ack_key: Arc<AckKey>,
ack_wait_multiplier: f64,
@@ -60,6 +71,7 @@ impl Config {
average_ack_delay_duration: Duration,
average_message_sending_delay: Duration,
average_packet_delay_duration: Duration,
disable_main_poisson_packet_distribution: bool,
self_recipient: Recipient,
) -> Self {
Config {
@@ -70,16 +82,22 @@ impl Config {
average_message_sending_delay,
average_packet_delay_duration,
average_ack_delay_duration,
disable_main_poisson_packet_distribution,
packet_size: Default::default(),
}
}
pub fn set_custom_packet_size(&mut self, packet_size: PacketSize) {
self.packet_size = packet_size;
}
}
pub struct RealMessagesController<R>
where
R: CryptoRng + Rng,
{
out_queue_control: Option<OutQueueControl<R>>,
ack_control: Option<AcknowledgementController<R>>,
out_queue_control: OutQueueControl<R>,
ack_control: AcknowledgementController<R>,
}
// obviously when we finally make shared rng that is on 'higher' level, this should become
@@ -91,8 +109,7 @@ impl RealMessagesController<OsRng> {
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
topology_access: TopologyAccessor,
reply_key_storage: ReplyKeyStorage,
shutdown: ShutdownListener,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
let rng = OsRng;
@@ -111,7 +128,8 @@ impl RealMessagesController<OsRng> {
config.ack_wait_multiplier,
config.average_ack_delay_duration,
config.average_packet_delay_duration,
);
)
.with_custom_packet_size(config.packet_size);
let ack_control = AcknowledgementController::new(
ack_control_config,
@@ -119,16 +137,18 @@ impl RealMessagesController<OsRng> {
topology_access.clone(),
Arc::clone(&config.ack_key),
config.self_recipient,
reply_key_storage,
ack_controller_connectors,
shutdown.clone(),
#[cfg(feature = "reply-surb")]
reply_key_storage,
);
let out_queue_config = real_traffic_stream::Config::new(
config.average_ack_delay_duration,
config.average_packet_delay_duration,
config.average_message_sending_delay,
);
config.disable_main_poisson_packet_distribution,
)
.with_custom_cover_packet_size(config.packet_size);
let out_queue_control = OutQueueControl::new(
out_queue_config,
@@ -139,44 +159,36 @@ impl RealMessagesController<OsRng> {
rng,
config.self_recipient,
topology_access,
shutdown,
);
RealMessagesController {
out_queue_control: Some(out_queue_control),
ack_control: Some(ack_control),
out_queue_control,
ack_control,
}
}
pub(super) async fn run(&mut self) {
let mut out_queue_control = self.out_queue_control.take().unwrap();
let mut ack_control = self.ack_control.take().unwrap();
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut out_queue_control = self.out_queue_control;
let ack_control = self.ack_control;
// the below are log messages are errors as at the current stage we do not expect any of
// the task to ever finish. This will of course change once we introduce
// graceful shutdowns.
let out_queue_control_fut = tokio::spawn(async move {
out_queue_control.run_out_queue_control().await;
let shutdown_handle = shutdown.clone();
spawn_future(async move {
out_queue_control.run_with_shutdown(shutdown_handle).await;
debug!("The out queue controller has finished execution!");
out_queue_control
});
let ack_control_fut = tokio::spawn(async move {
ack_control.run().await;
debug!("The acknowledgement controller has finished execution!");
ack_control
});
// technically we don't have to bring `RealMessagesController` back to a valid state
// but we can do it, so why not? Perhaps it might be useful if we wanted to allow
// for restarts of certain modules without killing the entire process.
self.out_queue_control = Some(out_queue_control_fut.await.unwrap());
self.ack_control = Some(ack_control_fut.await.unwrap());
ack_control.start_with_shutdown(shutdown);
}
pub fn start(mut self) -> JoinHandle<Self> {
tokio::spawn(async move {
self.run().await;
self
})
#[cfg(target_arch = "wasm32")]
pub fn start(self) {
let mut out_queue_control = self.out_queue_control;
let ack_control = self.ack_control;
spawn_future(async move {
out_queue_control.run().await;
debug!("The out queue controller has finished execution!");
});
ack_control.start();
}
}
@@ -13,15 +13,37 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::chunking::fragment::FragmentIdentifier;
use nymsphinx::cover::generate_loop_cover_packet;
use nymsphinx::forwarding::packet::MixPacket;
use nymsphinx::params::PacketSize;
use nymsphinx::utils::sample_poisson_duration;
use rand::{CryptoRng, Rng};
use std::collections::VecDeque;
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use task::ShutdownListener;
#[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;
/// Configurable parameters of the `OutQueueControl`
pub(crate) struct Config {
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
@@ -32,6 +54,13 @@ pub(crate) struct Config {
/// Average delay between sending subsequent packets.
average_message_sending_delay: Duration,
/// Controls whether the stream constantly produces packets according to the predefined
/// poisson distribution.
disable_poisson_packet_distribution: bool,
/// Predefined packet size used for the loop cover messages.
cover_packet_size: PacketSize,
}
impl Config {
@@ -39,13 +68,116 @@ impl Config {
average_ack_delay: Duration,
average_packet_delay: Duration,
average_message_sending_delay: Duration,
disable_poisson_packet_distribution: bool,
) -> Self {
Config {
average_ack_delay,
average_packet_delay,
average_message_sending_delay,
disable_poisson_packet_distribution,
cover_packet_size: Default::default(),
}
}
pub fn with_custom_cover_packet_size(mut self, packet_size: PacketSize) -> Self {
self.cover_packet_size = packet_size;
self
}
}
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>
@@ -63,7 +195,15 @@ where
/// Internal state, determined by `average_message_sending_delay`,
/// used to keep track of when a next packet should be sent out.
next_delay: Pin<Box<time::Sleep>>,
#[cfg(not(target_arch = "wasm32"))]
next_delay: Option<Pin<Box<time::Sleep>>>,
#[cfg(target_arch = "wasm32")]
next_delay: Option<Pin<Box<wasm_timer::Delay>>>,
// To make sure we don't overload the mix_tx channel, we limit the rate we are pushing
// messages.
sending_rate_controller: SendingDelayController,
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
@@ -84,9 +224,6 @@ where
/// Buffer containing all real messages received. It is first exhausted before more are pulled.
received_buffer: VecDeque<RealMessage>,
/// Listens for shutdown signals
shutdown: ShutdownListener,
}
pub(crate) struct RealMessage {
@@ -113,55 +250,6 @@ pub(crate) enum StreamMessage {
Real(Box<RealMessage>),
}
impl<R> Stream for OutQueueControl<R>
where
R: CryptoRng + Rng + Unpin,
{
type Item = StreamMessage;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
// it is not yet time to return a message
if self.next_delay.as_mut().poll(cx).is_pending() {
return Poll::Pending;
};
// we know it's time to send a message, so let's prepare delay for the next one
// Get the `now` by looking at the current `delay` deadline
let avg_delay = self.config.average_message_sending_delay;
let now = self.next_delay.deadline();
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
// The next interval value is `next_poisson_delay` after the one that just
// yielded.
let next = now + next_poisson_delay;
self.next_delay.as_mut().reset(next);
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
}
// decide what kind of message to send
match Pin::new(&mut self.real_receiver).poll_next(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(Box::new(
self.received_buffer.pop_front().unwrap(),
))))
}
// otherwise construct a dummy one
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
}
}
}
impl<R> OutQueueControl<R>
where
R: CryptoRng + Rng + Unpin,
@@ -178,20 +266,22 @@ where
rng: R,
our_full_destination: Recipient,
topology_access: TopologyAccessor,
shutdown: ShutdownListener,
) -> Self {
OutQueueControl {
config,
ack_key,
sent_notifier,
next_delay: Box::pin(time::sleep(Default::default())),
next_delay: None,
sending_rate_controller: SendingDelayController::new(
MIN_DELAY_MULTIPLIER,
MAX_DELAY_MULTIPLIER,
),
mix_tx,
real_receiver,
our_full_destination,
rng,
topology_access,
received_buffer: VecDeque::with_capacity(0), // we won't be putting any data into this guy directly
shutdown,
}
}
@@ -206,7 +296,7 @@ where
async fn on_message(&mut self, next_message: StreamMessage) {
trace!("created new message");
let next_message = match next_message {
let (next_message, fragment_id) = match next_message {
StreamMessage::Cover => {
// TODO for way down the line: in very rare cases (during topology update) we might have
// to wait a really tiny bit before actually obtaining the permit hence messing with our
@@ -225,34 +315,35 @@ where
}
let topology_ref = topology_ref_option.unwrap();
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&*self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
(
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
self.config.cover_packet_size,
)
.expect(
"Somehow failed to generate a loop cover message with a valid topology",
),
None,
)
.expect("Somehow failed to generate a loop cover message with a valid topology")
}
StreamMessage::Real(real_message) => {
self.sent_notify(real_message.fragment_id);
real_message.mix_packet
(real_message.mix_packet, Some(real_message.fragment_id))
}
};
// if this one fails, there's no retrying because it means that either:
// - we run out of memory
// - the receiver channel is closed
// in either case there's no recovery and we can only panic
if let Err(err) = self.mix_tx.unbounded_send(vec![next_message]) {
if self.shutdown.is_shutdown_poll() {
log::info!("Failed to send (shutdown detected)");
} else {
// We don't try to limp along, panic to avoid continuing in a potentially
// inconsistent state
panic!("{err}");
}
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
log::error!("Failed to send - channel closed: {}", 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);
}
// JS: Not entirely sure why or how it fixes stuff, but without the yield call,
@@ -260,18 +351,161 @@ where
// JS2: Basically it was the case that with high enough rate, the stream had already a next value
// ready and hence was immediately re-scheduled causing other tasks to be starved;
// yield makes it go back the scheduling queue regardless of its value availability
// TODO: temporary and BAD workaround for wasm (we should find a way to yield here in wasm)
#[cfg(not(target_arch = "wasm32"))]
tokio::task::yield_now().await;
}
// Send messages at certain rate and if no real traffic is available, send cover message.
async fn run_normal_out_queue(&mut self) {
// we should set initial delay only when we actually start the stream
self.next_delay = Box::pin(time::sleep(sample_poisson_duration(
&mut self.rng,
self.config.average_message_sending_delay,
)));
fn current_average_message_sending_delay(&self) -> Duration {
self.config.average_message_sending_delay
* self.sending_rate_controller.current_multiplier()
}
fn adjust_current_average_message_sending_delay(&mut self) {
let used_slots = self.mix_tx.max_capacity() - self.mix_tx.capacity();
log::trace!(
"used_slots: {used_slots}, current_multiplier: {}",
self.sending_rate_controller.current_multiplier()
);
// Even just a single used slot is enough to signal backpressure
if used_slots > 0 {
log::trace!("Backpressure detected");
self.sending_rate_controller.record_backpressure_detected();
}
// If the buffer is running out, slow down the sending rate
if self.mix_tx.capacity() == 0
&& self.sending_rate_controller.not_increased_delay_recently()
{
self.sending_rate_controller.increase_delay_multiplier();
}
// Very carefully step up the sending rate in case it seems like we can solidly handle the
// current rate.
if self.sending_rate_controller.is_sending_reliable() {
self.sending_rate_controller.decrease_delay_multiplier();
}
}
fn poll_poisson(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// The average delay could change depending on if backpressure in the downstream channel
// (mix_tx) was detected.
self.adjust_current_average_message_sending_delay();
let avg_delay = self.current_average_message_sending_delay();
if let Some(ref mut next_delay) = &mut self.next_delay {
// it is not yet time to return a message
if next_delay.as_mut().poll(cx).is_pending() {
return Poll::Pending;
};
// we know it's time to send a message, so let's prepare delay for the next one
// Get the `now` by looking at the current `delay` deadline
let next_poisson_delay = sample_poisson_duration(&mut self.rng, avg_delay);
// The next interval value is `next_poisson_delay` after the one that just
// yielded.
#[cfg(not(target_arch = "wasm32"))]
{
let now = next_delay.deadline();
let next = now + next_poisson_delay;
next_delay.as_mut().reset(next);
}
#[cfg(target_arch = "wasm32")]
{
next_delay.as_mut().reset(next_poisson_delay);
}
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
return Poll::Ready(Some(StreamMessage::Real(Box::new(real_available))));
}
// decide what kind of message to send
match Pin::new(&mut self.real_receiver).poll_next(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(Box::new(
self.received_buffer.pop_front().unwrap(),
))))
}
// otherwise construct a dummy one
Poll::Pending => Poll::Ready(Some(StreamMessage::Cover)),
}
} else {
// we never set an initial delay - let's do it now
cx.waker().wake_by_ref();
let sampled =
sample_poisson_duration(&mut self.rng, self.config.average_message_sending_delay);
#[cfg(not(target_arch = "wasm32"))]
let next_delay = Box::pin(time::sleep(sampled));
#[cfg(target_arch = "wasm32")]
let next_delay = Box::pin(wasm_timer::Delay::new(sampled));
self.next_delay = Some(next_delay);
Poll::Pending
}
}
fn poll_immediate(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// check if we have anything immediately available
if let Some(real_available) = self.received_buffer.pop_front() {
// if there are more messages immediately available, notify the runtime
// because we should be polled again
if !self.received_buffer.is_empty() {
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) {
// in the case our real message channel stream was closed, we should also indicate we are closed
// (and whoever is using the stream should panic)
Poll::Ready(None) => Poll::Ready(None),
// if there are more messages available, return first one and store the rest
Poll::Ready(Some(real_messages)) => {
self.received_buffer = real_messages.into();
// we MUST HAVE received at least ONE message
Poll::Ready(Some(StreamMessage::Real(Box::new(
self.received_buffer.pop_front().unwrap(),
))))
}
// if there's nothing, then there's nothing
Poll::Pending => Poll::Pending,
}
}
fn poll_next_message(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<StreamMessage>> {
if self.config.disable_poisson_packet_distribution {
self.poll_immediate(cx)
} else {
self.poll_poisson(cx)
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started OutQueueControl with graceful shutdown support");
let mut shutdown = self.shutdown.clone();
while !shutdown.is_shutdown() {
tokio::select! {
biased;
@@ -293,8 +527,23 @@ where
log::debug!("OutQueueControl: Exiting");
}
pub(crate) async fn run_out_queue_control(&mut self) {
debug!("Starting out queue controller...");
self.run_normal_out_queue().await
#[cfg(target_arch = "wasm32")]
pub(super) async fn run(&mut self) {
debug!("Started OutQueueControl without graceful shutdown support");
while let Some(next_message) = self.next().await {
self.on_message(next_message).await;
}
}
}
impl<R> Stream for OutQueueControl<R>
where
R: CryptoRng + Rng + Unpin,
{
type Item = StreamMessage;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.poll_next_message(cx)
}
}
+148 -84
View File
@@ -1,24 +1,25 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::reply_key_storage::ReplyKeyStorage;
use crate::client::SHUTDOWN_HAS_BEEN_SIGNALLED;
use crate::spawn_future;
use crypto::asymmetric::encryption;
use crypto::symmetric::stream_cipher;
use crypto::Digest;
use futures::channel::mpsc;
use futures::lock::Mutex;
use futures::StreamExt;
use gateway_client::MixnetMessageReceiver;
use log::*;
use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey};
use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm};
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
use std::collections::HashSet;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use task::ShutdownListener;
use tokio::task::JoinHandle;
#[cfg(feature = "reply-surb")]
use crate::client::reply_key_storage::ReplyKeyStorage;
#[cfg(feature = "reply-surb")]
use crypto::{symmetric::stream_cipher, Digest};
#[cfg(feature = "reply-surb")]
use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey};
#[cfg(feature = "reply-surb")]
use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorithm};
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
// or to say "hey, I'm going offline, don't send anything more to me. Just buffer them instead"
@@ -116,13 +117,14 @@ struct ReceivedMessagesBuffer {
/// Storage containing keys to all [`ReplySURB`]s ever sent out that we did not receive back.
// There's no need to put it behind a Mutex since it's already properly concurrent
#[cfg(feature = "reply-surb")]
reply_key_storage: ReplyKeyStorage,
}
impl ReceivedMessagesBuffer {
fn new(
local_encryption_keypair: Arc<encryption::KeyPair>,
reply_key_storage: ReplyKeyStorage,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
ReceivedMessagesBuffer {
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
@@ -132,6 +134,7 @@ impl ReceivedMessagesBuffer {
message_sender: None,
recently_reconstructed: HashSet::new(),
})),
#[cfg(feature = "reply-surb")]
reply_key_storage,
}
}
@@ -180,6 +183,7 @@ impl ReceivedMessagesBuffer {
self.inner.lock().await.messages.extend(msgs)
}
#[cfg(feature = "reply-surb")]
fn process_received_reply(
reply_ciphertext: &[u8],
reply_key: SurbEncryptionKey,
@@ -212,38 +216,50 @@ impl ReceivedMessagesBuffer {
let mut completed_messages = Vec::new();
let mut inner_guard = self.inner.lock().await;
let reply_surb_digest_size = ReplySurbKeyDigestAlgorithm::output_size();
// first check if this is a reply or a chunked message
// TODO: verify with @AP if this way of doing it is safe or whether it could
// cause some attacks due to, I don't know, stupid edge case collisions?
// Update: this DOES introduce a possible leakage: https://github.com/nymtech/nym/issues/296
for msg in msgs {
let possible_key_digest =
EncryptionKeyDigest::clone_from_slice(&msg[..reply_surb_digest_size]);
// TODO:
// 1. make it nicer
// 2. make it not feature-locked
// check first `HasherOutputSize` bytes if they correspond to known encryption key
// if yes - this is a reply message
// TODO: this might be a bottleneck - since the keys are stored on disk we, presumably,
// are doing a disk operation every single received fragment
if let Some(reply_encryption_key) = self
.reply_key_storage
.get_and_remove_encryption_key(possible_key_digest)
.expect("storage operation failed!")
#[cfg(feature = "reply-surb")]
{
if let Some(completed_message) = Self::process_received_reply(
&msg[reply_surb_digest_size..],
reply_encryption_key,
) {
completed_messages.push(completed_message)
}
} else {
// otherwise - it's a 'normal' message
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
completed_messages.push(completed_message)
let reply_surb_digest_size = ReplySurbKeyDigestAlgorithm::output_size();
let possible_key_digest =
EncryptionKeyDigest::clone_from_slice(&msg[..reply_surb_digest_size]);
// check first `HasherOutputSize` bytes if they correspond to known encryption key
// if yes - this is a reply message
// TODO: this might be a bottleneck - since the keys are stored on disk we, presumably,
// are doing a disk operation every single received fragment
if let Some(reply_encryption_key) = self
.reply_key_storage
.get_and_remove_encryption_key(possible_key_digest)
.expect("storage operation failed!")
{
if let Some(completed_message) = Self::process_received_reply(
&msg[reply_surb_digest_size..],
reply_encryption_key,
) {
completed_messages.push(completed_message)
}
} else {
// otherwise - it's a 'normal' message
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
completed_messages.push(completed_message)
}
}
}
#[cfg(not(feature = "reply-surb"))]
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
completed_messages.push(completed_message)
}
}
if !completed_messages.is_empty() {
@@ -293,72 +309,97 @@ impl RequestReceiver {
}
}
fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
loop {
tokio::select! {
request = self.query_receiver.next() => {
match request {
Some(ReceivedBufferMessage::ReceiverAnnounce(sender)) => {
self.received_buffer.connect_sender(sender).await;
}
Some(ReceivedBufferMessage::ReceiverDisconnect) => {
self.received_buffer.disconnect_sender().await
}
None => {
log::trace!("RequestReceiver: Stopping since channel closed");
break;
},
}
},
};
async fn handle_message(&mut self, message: ReceivedBufferMessage) {
match message {
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
self.received_buffer.connect_sender(sender).await;
}
ReceivedBufferMessage::ReceiverDisconnect => {
self.received_buffer.disconnect_sender().await
}
}
}
assert!(SHUTDOWN_HAS_BEEN_SIGNALLED.load(Ordering::Relaxed));
log::debug!("RequestReceiver: Exiting");
})
#[cfg(not(target_arch = "wasm32"))]
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started RequestReceiver with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
biased;
_ = shutdown.recv() => {
log::trace!("RequestReceiver: Received shutdown");
}
request = self.query_receiver.next() => {
match request {
Some(message) => self.handle_message(message).await,
None => {
log::trace!("RequestReceiver: Stopping since channel closed");
break;
},
}
},
}
}
assert!(shutdown.is_shutdown_poll());
log::debug!("RequestReceiver: Exiting");
}
#[cfg(target_arch = "wasm32")]
async fn run(&mut self) {
debug!("Started RequestReceiver without graceful shutdown support");
while let Some(message) = self.query_receiver.next().await {
self.handle_message(message).await
}
}
}
struct FragmentedMessageReceiver {
received_buffer: ReceivedMessagesBuffer,
mixnet_packet_receiver: MixnetMessageReceiver,
shutdown: ShutdownListener,
}
impl FragmentedMessageReceiver {
fn new(
received_buffer: ReceivedMessagesBuffer,
mixnet_packet_receiver: MixnetMessageReceiver,
shutdown: ShutdownListener,
) -> Self {
FragmentedMessageReceiver {
received_buffer,
mixnet_packet_receiver,
shutdown,
}
}
fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
while !self.shutdown.is_shutdown() {
tokio::select! {
new_messages = self.mixnet_packet_receiver.next() => match new_messages {
Some(new_messages) => {
self.received_buffer.handle_new_received(new_messages).await;
}
None => {
log::trace!("FragmentedMessageReceiver: Stopping since channel closed");
break;
}
},
_ = self.shutdown.recv() => {
log::trace!("FragmentedMessageReceiver: Received shutdown");
#[cfg(not(target_arch = "wasm32"))]
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
new_messages = self.mixnet_packet_receiver.next() => match new_messages {
Some(new_messages) => {
self.received_buffer.handle_new_received(new_messages).await;
}
None => {
log::trace!("FragmentedMessageReceiver: Stopping since channel closed");
break;
}
},
_ = shutdown.recv() => {
log::trace!("FragmentedMessageReceiver: Received shutdown");
}
}
assert!(self.shutdown.is_shutdown_poll());
log::debug!("FragmentedMessageReceiver: Exiting");
})
}
assert!(shutdown.is_shutdown_poll());
log::debug!("FragmentedMessageReceiver: Exiting");
}
#[cfg(target_arch = "wasm32")]
async fn run(&mut self) {
debug!("Started FragmentedMessageReceiver without graceful shutdown support");
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
self.received_buffer.handle_new_received(new_messages).await;
}
}
}
@@ -372,25 +413,48 @@ impl ReceivedMessagesBufferController {
local_encryption_keypair: Arc<encryption::KeyPair>,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_packet_receiver: MixnetMessageReceiver,
reply_key_storage: ReplyKeyStorage,
shutdown: ShutdownListener,
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
) -> Self {
let received_buffer =
ReceivedMessagesBuffer::new(local_encryption_keypair, reply_key_storage);
let received_buffer = ReceivedMessagesBuffer::new(
local_encryption_keypair,
#[cfg(feature = "reply-surb")]
reply_key_storage,
);
ReceivedMessagesBufferController {
fragmented_message_receiver: FragmentedMessageReceiver::new(
received_buffer.clone(),
mixnet_packet_receiver,
shutdown,
),
request_receiver: RequestReceiver::new(received_buffer, query_receiver),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
let mut fragmented_message_receiver = self.fragmented_message_receiver;
let mut request_receiver = self.request_receiver;
let shutdown_handle = shutdown.clone();
spawn_future(async move {
fragmented_message_receiver
.run_with_shutdown(shutdown_handle)
.await;
});
spawn_future(async move {
request_receiver.run_with_shutdown(shutdown).await;
});
}
#[cfg(target_arch = "wasm32")]
pub fn start(self) {
// TODO: should we do anything with JoinHandle(s) returned by start methods?
self.fragmented_message_receiver.start();
self.request_receiver.start();
let mut fragmented_message_receiver = self.fragmented_message_receiver;
let mut request_receiver = self.request_receiver;
spawn_future(async move {
fragmented_message_receiver.run().await;
});
spawn_future(async move {
request_receiver.run().await;
});
}
}
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::params::DEFAULT_NUM_MIX_HOPS;
@@ -10,10 +11,8 @@ use std::ops::Deref;
use std::sync::Arc;
use std::time;
use std::time::Duration;
use task::ShutdownListener;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use topology::{nym_topology_from_bonds, NymTopology};
use topology::{nym_topology_from_detailed, NymTopology};
use url::Url;
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
@@ -58,24 +57,15 @@ impl<'a> TopologyReadPermit<'a> {
) -> Option<&'a NymTopology> {
// Note: implicit deref with Deref for TopologyReadPermit is happening here
let topology_ref_option = self.permit.as_ref();
match topology_ref_option {
None => None,
Some(topology_ref) => {
// see if it's possible to route the packet to both gateways
if !topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|| if let Some(packet_recipient) = packet_recipient {
!topology_ref.gateway_exists(packet_recipient.gateway())
} else {
false
}
{
None
topology_ref_option.as_ref().filter(|topology_ref| {
!(!topology_ref.can_construct_path_through(DEFAULT_NUM_MIX_HOPS)
|| !topology_ref.gateway_exists(ack_recipient.gateway())
|| if let Some(packet_recipient) = packet_recipient {
!topology_ref.gateway_exists(packet_recipient.gateway())
} else {
Some(topology_ref)
}
}
}
false
})
})
}
}
@@ -266,8 +256,8 @@ impl TopologyRefresher {
};
let mixnodes_count = mixnodes.len();
let topology =
nym_topology_from_bonds(mixnodes, gateways).filter_system_version(&self.client_version);
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
if !self.check_layer_distribution(&topology, mixnodes_count) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
@@ -304,8 +294,11 @@ impl TopologyRefresher {
self.topology_accessor.is_routable().await
}
pub fn start(mut self, mut shutdown: ShutdownListener) -> JoinHandle<()> {
tokio::spawn(async move {
#[cfg(not(target_arch = "wasm32"))]
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
spawn_future(async move {
debug!("Started TopologyRefresher with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
_ = tokio::time::sleep(self.refresh_rate) => {
@@ -320,4 +313,17 @@ impl TopologyRefresher {
log::debug!("TopologyRefresher: Exiting");
})
}
#[cfg(target_arch = "wasm32")]
pub fn start(mut self) {
use futures::StreamExt;
spawn_future(async move {
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while let Some(_) = interval.next().await {
self.refresh().await;
}
})
}
}
+58 -9
View File
@@ -2,12 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
use config::NymConfig;
use nymsphinx::params::PacketSize;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::path::PathBuf;
use std::time::Duration;
use url::Url;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
pub mod persistence;
pub const MISSING_VALUE: &str = "MISSING VALUE";
@@ -236,6 +240,18 @@ impl<T: NymConfig> Config<T> {
self.debug.topology_resolution_timeout
}
pub fn get_disabled_loop_cover_traffic_stream(&self) -> bool {
self.debug.disable_loop_cover_traffic_stream
}
pub fn get_disabled_main_poisson_packet_distribution(&self) -> bool {
self.debug.disable_main_poisson_packet_distribution
}
pub fn get_use_extended_packet_size(&self) -> Option<ExtendedPacketSize> {
self.debug.use_extended_packet_size.clone()
}
pub fn get_version(&self) -> &str {
&self.client.version
}
@@ -252,6 +268,7 @@ impl<T: NymConfig> Default for Config<T> {
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(getter_with_clone))]
pub struct GatewayEndpoint {
/// gateway_id specifies ID of the gateway to which the client should send messages.
/// If initially omitted, a random gateway will be chosen from the available topology.
@@ -398,53 +415,72 @@ pub struct Debug {
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
average_packet_delay: Duration,
pub average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
average_ack_delay: Duration,
pub average_ack_delay: Duration,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 1.
ack_wait_multiplier: f64,
pub ack_wait_multiplier: f64,
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
#[serde(with = "humantime_serde")]
ack_wait_addition: Duration,
pub ack_wait_addition: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
#[serde(with = "humantime_serde")]
loop_cover_traffic_average_delay: Duration,
pub loop_cover_traffic_average_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
#[serde(with = "humantime_serde")]
message_sending_average_delay: Duration,
pub message_sending_average_delay: Duration,
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
#[serde(with = "humantime_serde")]
gateway_response_timeout: Duration,
pub gateway_response_timeout: Duration,
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
#[serde(with = "humantime_serde")]
topology_refresh_rate: Duration,
pub topology_refresh_rate: Duration,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
#[serde(with = "humantime_serde")]
topology_resolution_timeout: Duration,
pub topology_resolution_timeout: Duration,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Controls whether the sent sphinx packet use a NON-DEFAULT bigger size.
pub use_extended_packet_size: Option<ExtendedPacketSize>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ExtendedPacketSize {
Extended8,
Extended16,
Extended32,
}
impl Default for Debug {
@@ -459,6 +495,19 @@ impl Default for Debug {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_loop_cover_traffic_stream: false,
disable_main_poisson_packet_distribution: false,
use_extended_packet_size: None,
}
}
}
impl From<ExtendedPacketSize> for PacketSize {
fn from(size: ExtendedPacketSize) -> PacketSize {
match size {
ExtendedPacketSize::Extended8 => PacketSize::ExtendedPacket8,
ExtendedPacketSize::Extended16 => PacketSize::ExtendedPacket16,
ExtendedPacketSize::Extended32 => PacketSize::ExtendedPacket32,
}
}
}
+2
View File
@@ -24,4 +24,6 @@ pub enum ClientCoreError {
ListOfValidatorApisIsEmpty,
#[error("Could not load existing gateway configuration: {0}")]
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
#[error("The current network topology seem to be insufficient to route any packets through")]
InsufficientNetworkTopology,
}
+1
View File
@@ -90,6 +90,7 @@ async fn register_with_gateway(
gateway.owner.clone(),
our_identity.clone(),
timeout,
#[cfg(not(target_arch = "wasm32"))]
None,
);
gateway_client
+19
View File
@@ -1,4 +1,23 @@
use std::future::Future;
pub mod client;
pub mod config;
pub mod error;
pub mod init;
#[cfg(target_arch = "wasm32")]
pub(crate) fn spawn_future<F>(future: F)
where
F: Future<Output = ()> + 'static,
{
wasm_bindgen_futures::spawn_local(future);
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn spawn_future<F>(future: F)
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
tokio::spawn(future);
}
+3 -2
View File
@@ -9,16 +9,17 @@ edition = "2021"
async-trait = "0.1.52"
bip39 = "1.0.1"
cfg-if = "0.1"
clap = { version = "3.0.10", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
pickledb = "0.4.1"
rand = "0.7.3"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
url = "2.2"
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
coconut-interface = { path = "../../common/coconut-interface" }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
+7
View File
@@ -3,6 +3,7 @@
use async_trait::async_trait;
use clap::{Args, Subcommand};
use completions::ArgShell;
use pickledb::PickleDb;
use rand::rngs::OsRng;
use std::str::FromStr;
@@ -28,6 +29,12 @@ pub(crate) enum Commands {
ListDeposits(ListDeposits),
/// Get a credential for a given deposit
GetCredential(GetCredential),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
#[async_trait]
+6
View File
@@ -12,6 +12,8 @@ cfg_if::cfg_if! {
use commands::{Commands, Execute};
use error::Result;
use network_defaults::setup_env;
use clap::CommandFactory;
use completions::fig_generate;
use clap::Parser;
use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
@@ -52,10 +54,14 @@ cfg_if::cfg_if! {
),
};
let bin_name = "nym-credential-client";
match &args.command {
Commands::Deposit(m) => m.execute(&mut db, shared_storage).await?,
Commands::ListDeposits(m) => m.execute(&mut db, shared_storage).await?,
Commands::GetCredential(m) => m.execute(&mut db, shared_storage).await?,
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name)
}
Ok(())
+5 -2
View File
@@ -20,23 +20,26 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it
# and the single instance of abortable we have should really be refactored anyway
url = "2.2"
clap = { version = "3.2.8", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
dirs = "4.0"
log = "0.4" # self explanatory
pretty_env_logger = "0.4" # for formatting log messages
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
sled = "0.34" # for storage of replySURB decryption keys
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal"] } # async runtime
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime
tokio-tungstenite = "0.14" # websocket
## internal
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
logging = { path = "../../common/logging"}
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
network-defaults = { path = "../../common/network-defaults" }
+57 -40
View File
@@ -6,9 +6,7 @@ use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
};
use client_core::client::key_manager::KeyManager;
use client_core::client::mix_traffic::{
BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController,
};
use client_core::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use client_core::client::real_messages_control;
use client_core::client::real_messages_control::RealMessagesController;
use client_core::client::received_buffer::{
@@ -20,6 +18,7 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
@@ -35,6 +34,7 @@ use nymsphinx::receiver::ReconstructedMessage;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use crate::websocket;
pub(crate) mod config;
@@ -90,7 +90,7 @@ impl NymClient {
) {
info!("Starting loop cover traffic stream...");
LoopCoverTrafficStream::new(
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_average_packet_delay(),
@@ -100,9 +100,14 @@ impl NymClient {
mix_tx,
self.as_mix_recipient(),
topology_accessor,
shutdown,
)
.start();
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
stream.set_custom_packet_size(size.into());
}
stream.start_with_shutdown(shutdown);
}
fn start_real_traffic_controller(
@@ -114,16 +119,24 @@ impl NymClient {
mix_sender: BatchMixMessageSender,
shutdown: ShutdownListener,
) {
let controller_config = real_messages_control::Config::new(
let mut controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(),
self.config.get_base().get_ack_wait_multiplier(),
self.config.get_base().get_ack_wait_addition(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_message_sending_average_delay(),
self.config.get_base().get_average_packet_delay(),
self.config
.get_base()
.get_disabled_main_poisson_packet_distribution(),
self.as_mix_recipient(),
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
controller_config.set_custom_packet_size(size.into());
}
info!("Starting real traffic stream...");
RealMessagesController::new(
@@ -133,9 +146,8 @@ impl NymClient {
mix_sender,
topology_accessor,
reply_key_storage,
shutdown,
)
.start();
.start_with_shutdown(shutdown);
}
// buffer controlling all messages fetched from provider
@@ -153,9 +165,8 @@ impl NymClient {
query_receiver,
mixnet_receiver,
reply_key_storage,
shutdown,
)
.start()
.start_with_shutdown(shutdown)
}
async fn start_gateway_client(
@@ -223,7 +234,7 @@ impl NymClient {
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) {
) -> Result<(), ClientError> {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -238,14 +249,16 @@ impl NymClient {
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology.into());
}
info!("Starting topology refresher...");
topology_refresher.start(shutdown);
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -253,13 +266,13 @@ impl NymClient {
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(
&mut self,
mix_rx: BatchMixMessageReceiver,
gateway_client: GatewayClient,
shutdown: ShutdownListener,
) {
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client, shutdown).start();
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
}
fn start_websocket_listener(
@@ -319,8 +332,8 @@ impl NymClient {
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) {
let shutdown = self.start().await;
pub async fn run_forever(&mut self) -> Result<(), ClientError> {
let shutdown = self.start().await?;
wait_for_signal().await;
println!(
@@ -337,20 +350,16 @@ impl NymClient {
//shutdown.wait_for_shutdown().await;
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");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// sphinx_message_sender is the transmitter for any component generating sphinx packets that are to be sent to the mixnet
// they are used by cover traffic stream and real traffic stream
// sphinx_message_receiver is the receiver used by MixTrafficController that sends the actual traffic
let (sphinx_message_sender, sphinx_message_receiver) = mpsc::unbounded();
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
@@ -375,7 +384,7 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await;
.await?;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
@@ -387,11 +396,13 @@ impl NymClient {
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
.await;
self.start_mix_traffic_controller(
sphinx_message_receiver,
gateway_client,
shutdown.subscribe(),
);
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
reply_key_storage,
@@ -401,11 +412,17 @@ impl NymClient {
shutdown.subscribe(),
);
self.start_cover_traffic_stream(
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
if !self
.config
.get_base()
.get_disabled_loop_cover_traffic_stream()
{
self.start_cover_traffic_stream(
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
}
match self.config.get_socket_type() {
SocketType::WebSocket => {
@@ -431,6 +448,6 @@ impl NymClient {
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
shutdown
Ok(shutdown)
}
}
+16 -2
View File
@@ -2,7 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use crate::error::ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
pub(crate) mod init;
pub(crate) mod run;
@@ -62,6 +65,12 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -75,12 +84,17 @@ pub(crate) struct OverrideConfig {
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";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Run(m) => run::execute(m).await?,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
Ok(())
}
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
+5 -4
View File
@@ -4,6 +4,7 @@
use crate::{
client::{config::Config, NymClient},
commands::{override_config, OverrideConfig},
error::ClientError,
};
use clap::Args;
@@ -73,14 +74,14 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) {
pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
return;
return Err(ClientError::FailedToLoadConfig(id.to_string()));
}
};
@@ -89,8 +90,8 @@ pub(crate) async fn execute(args: &Run) {
if !version_check(&config) {
error!("failed the local version check");
return;
return Err(ClientError::FailedLocalVersionCheck);
}
NymClient::new(config).run_forever().await;
NymClient::new(config).run_forever().await
}
+2 -2
View File
@@ -103,7 +103,7 @@ fn minor_0_12_upgrade(
Version::new(0, 12, 0)
};
print_start_upgrade(&config_version, &to_version);
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
@@ -111,7 +111,7 @@ fn minor_0_12_upgrade(
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&config_version, &to_version);
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
+23
View File
@@ -0,0 +1,23 @@
use client_core::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("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
pub mod client;
pub mod error;
pub mod websocket;
+5 -24
View File
@@ -2,20 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use error::ClientError;
use logging::setup_logging;
use network_defaults::setup_env;
pub mod client;
pub mod commands;
pub mod error;
pub mod websocket;
#[tokio::main]
async fn main() {
async fn main() -> Result<(), ClientError> {
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
commands::execute(&args).await
}
fn banner() -> String {
@@ -34,25 +37,3 @@ fn banner() -> String {
crate_version!()
)
}
fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
log_builder.parse_filters(&s);
} else {
// default to 'Info'
log_builder.filter(None, log::LevelFilter::Info);
}
log_builder
.filter_module("hyper", log::LevelFilter::Warn)
.filter_module("tokio_reactor", log::LevelFilter::Warn)
.filter_module("reqwest", log::LevelFilter::Warn)
.filter_module("mio", log::LevelFilter::Warn)
.filter_module("want", log::LevelFilter::Warn)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.filter_module("handlebars", log::LevelFilter::Warn)
.filter_module("sled", log::LevelFilter::Warn)
.init();
}
+5 -2
View File
@@ -11,7 +11,7 @@ name = "nym_socks5"
path = "src/lib.rs"
[dependencies]
clap = { version = "3.2.8", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
dirs = "4.0"
futures = "0.3"
log = "0.4"
@@ -20,16 +20,19 @@ pretty_env_logger = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization
snafu = "0.6"
tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal"] }
thiserror = "1.0.34"
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] }
url = "2.2"
# internal
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
logging = { path = "../../common/logging"}
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
network-defaults = { path = "../../common/network-defaults" }
+69 -48
View File
@@ -3,14 +3,18 @@
use std::sync::atomic::Ordering;
use crate::client::config::Config;
use crate::error::Socks5ClientError;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
};
use client_core::client::key_manager::KeyManager;
use client_core::client::mix_traffic::{
BatchMixMessageReceiver, BatchMixMessageSender, MixTrafficController,
};
use client_core::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use client_core::client::real_messages_control::RealMessagesController;
use client_core::client::received_buffer::{
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
@@ -20,6 +24,7 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use futures::StreamExt;
@@ -33,12 +38,6 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use task::{wait_for_signal, ShutdownListener, ShutdownNotifier};
use crate::client::config::Config;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
pub mod config;
// Channels used to control the main task from outside
@@ -91,7 +90,7 @@ impl NymClient {
) {
info!("Starting loop cover traffic stream...");
LoopCoverTrafficStream::new(
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_average_packet_delay(),
@@ -101,9 +100,14 @@ impl NymClient {
mix_tx,
self.as_mix_recipient(),
topology_accessor,
shutdown,
)
.start();
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
stream.set_custom_packet_size(size.into());
}
stream.start_with_shutdown(shutdown);
}
fn start_real_traffic_controller(
@@ -115,16 +119,24 @@ impl NymClient {
mix_sender: BatchMixMessageSender,
shutdown: ShutdownListener,
) {
let controller_config = client_core::client::real_messages_control::Config::new(
let mut controller_config = client_core::client::real_messages_control::Config::new(
self.key_manager.ack_key(),
self.config.get_base().get_ack_wait_multiplier(),
self.config.get_base().get_ack_wait_addition(),
self.config.get_base().get_average_ack_delay(),
self.config.get_base().get_message_sending_average_delay(),
self.config.get_base().get_average_packet_delay(),
self.config
.get_base()
.get_disabled_main_poisson_packet_distribution(),
self.as_mix_recipient(),
);
if let Some(size) = self.config.get_base().get_use_extended_packet_size() {
log::debug!("Setting extended packet size: {:?}", size);
controller_config.set_custom_packet_size(size.into());
}
info!("Starting real traffic stream...");
RealMessagesController::new(
@@ -134,9 +146,8 @@ impl NymClient {
mix_sender,
topology_accessor,
reply_key_storage,
shutdown,
)
.start();
.start_with_shutdown(shutdown);
}
// buffer controlling all messages fetched from provider
@@ -154,9 +165,8 @@ impl NymClient {
query_receiver,
mixnet_receiver,
reply_key_storage,
shutdown,
)
.start()
.start_with_shutdown(shutdown);
}
async fn start_gateway_client(
@@ -224,7 +234,7 @@ impl NymClient {
&mut self,
topology_accessor: TopologyAccessor,
shutdown: ShutdownListener,
) {
) -> Result<(), Socks5ClientError> {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -239,14 +249,16 @@ impl NymClient {
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
log::error!(
"The current network topology seem to be insufficient to route any packets through \
- check if enough nodes and a gateway are online"
);
return Err(ClientCoreError::InsufficientNetworkTopology.into());
}
info!("Starting topology refresher...");
topology_refresher.start(shutdown);
topology_refresher.start_with_shutdown(shutdown);
Ok(())
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -254,13 +266,13 @@ impl NymClient {
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(
&mut self,
mix_rx: BatchMixMessageReceiver,
gateway_client: GatewayClient,
shutdown: ShutdownListener,
) {
) -> BatchMixMessageSender {
info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client, shutdown).start();
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
}
fn start_socks5_listener(
@@ -285,8 +297,8 @@ impl NymClient {
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) {
let mut shutdown = self.start().await;
pub async fn run_forever(&mut self) -> Result<(), Socks5ClientError> {
let mut shutdown = self.start().await?;
wait_for_signal().await;
log::info!("Sending shutdown");
@@ -297,11 +309,15 @@ impl NymClient {
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client");
Ok(())
}
// Variant of `run_forever` that listends for remote control messages
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
let mut shutdown = self.start().await;
pub async fn run_and_listen(
&mut self,
mut receiver: Socks5ControlMessageReceiver,
) -> Result<(), Socks5ClientError> {
let mut shutdown = self.start().await?;
tokio::select! {
message = receiver.next() => {
log::debug!("Received message: {:?}", message);
@@ -327,20 +343,16 @@ impl NymClient {
shutdown.wait_for_shutdown().await;
log::info!("Stopping nym-socks5-client");
Ok(())
}
pub async fn start(&mut self) -> ShutdownNotifier {
pub async fn start(&mut self) -> Result<ShutdownNotifier, Socks5ClientError> {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// sphinx_message_sender is the transmitter for any component generating sphinx packets that are to be sent to the mixnet
// they are used by cover traffic stream and real traffic stream
// sphinx_message_receiver is the receiver used by MixTrafficController that sends the actual traffic
let (sphinx_message_sender, sphinx_message_receiver) = mpsc::unbounded();
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
@@ -365,7 +377,7 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone(), shutdown.subscribe())
.await;
.await?;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
@@ -377,11 +389,13 @@ impl NymClient {
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
.await;
self.start_mix_traffic_controller(
sphinx_message_receiver,
gateway_client,
shutdown.subscribe(),
);
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, shutdown.subscribe());
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
reply_key_storage,
@@ -391,11 +405,18 @@ impl NymClient {
shutdown.subscribe(),
);
self.start_cover_traffic_stream(
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
if !self
.config
.get_base()
.get_disabled_loop_cover_traffic_stream()
{
self.start_cover_traffic_stream(
shared_topology_accessor,
sphinx_message_sender,
shutdown.subscribe(),
);
}
self.start_socks5_listener(
received_buffer_request_sender,
input_sender,
@@ -405,6 +426,6 @@ impl NymClient {
info!("Client startup finished!");
info!("The address of this client is: {}", self.as_mix_recipient());
shutdown
Ok(shutdown)
}
}
+16 -2
View File
@@ -2,7 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::error::Socks5ClientError;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
use config::parse_validators;
pub mod init;
@@ -63,6 +66,12 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -75,12 +84,17 @@ pub(crate) struct OverrideConfig {
enabled_credentials_mode: bool,
}
pub(crate) async fn execute(args: &Cli) {
pub(crate) async fn execute(args: &Cli) -> Result<(), Socks5ClientError> {
let bin_name = "nym-socks5-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Run(m) => run::execute(m).await?,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
Ok(())
}
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
+5 -4
View File
@@ -4,6 +4,7 @@
use crate::{
client::{config::Config, NymClient},
commands::{override_config, OverrideConfig},
error::Socks5ClientError,
};
use clap::Args;
@@ -80,14 +81,14 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub(crate) async fn execute(args: &Run) {
pub(crate) async fn execute(args: &Run) -> Result<(), Socks5ClientError> {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
Err(err) => {
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", id, err);
return;
return Err(Socks5ClientError::FailedToLoadConfig(id.to_string()));
}
};
@@ -96,8 +97,8 @@ pub(crate) async fn execute(args: &Run) {
if !version_check(&config) {
error!("failed the local version check");
return;
return Err(Socks5ClientError::FailedLocalVersionCheck);
}
NymClient::new(config).run_forever().await;
NymClient::new(config).run_forever().await
}
+2 -2
View File
@@ -102,7 +102,7 @@ fn minor_0_12_upgrade(
Version::new(0, 12, 0)
};
print_start_upgrade(&config_version, &to_version);
print_start_upgrade(config_version, &to_version);
config
.get_base_mut()
@@ -110,7 +110,7 @@ fn minor_0_12_upgrade(
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(&config_version, &to_version);
print_failed_upgrade(config_version, &to_version);
process::exit(1);
});
+23
View File
@@ -0,0 +1,23 @@
use client_core::error::ClientCoreError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
use gateway_client::error::GatewayClientError;
use validator_client::ValidatorClientError;
#[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("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
pub mod client;
pub mod error;
pub mod socks;
+5 -22
View File
@@ -2,20 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, Parser};
use error::Socks5ClientError;
use logging::setup_logging;
use network_defaults::setup_env;
pub mod client;
mod commands;
pub mod error;
pub mod socks;
#[tokio::main]
async fn main() {
async fn main() -> Result<(), Socks5ClientError> {
setup_logging();
println!("{}", banner());
let args = commands::Cli::parse();
setup_env(args.config_env_file.clone());
commands::execute(&args).await;
commands::execute(&args).await
}
fn banner() -> String {
@@ -34,23 +37,3 @@ fn banner() -> String {
crate_version!()
)
}
fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
log_builder.parse_filters(&s);
} else {
// default to 'Info'
log_builder.filter(None, log::LevelFilter::Info);
}
log_builder
.filter_module("hyper", log::LevelFilter::Warn)
.filter_module("tokio_reactor", log::LevelFilter::Warn)
.filter_module("reqwest", log::LevelFilter::Warn)
.filter_module("mio", log::LevelFilter::Warn)
.filter_module("want", log::LevelFilter::Warn)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.init();
}
+8 -4
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.0.0"
version = "1.0.1"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -20,13 +20,15 @@ coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
[dependencies]
futures = "0.3"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = { version = "=0.2.78", features = ["serde-serialize"] }
serde-wasm-bindgen = "0.4"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
url = "2.2"
# internal
client-core = { path = "../client-core", default-features = false, features = ["wasm"] }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
@@ -53,6 +55,8 @@ wee_alloc = { version = "0.4", optional = true }
wasm-bindgen-test = "0.3"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
wasm-opt = true
[profile.release]
lto = true
opt-level = 'z'
+11 -1
View File
@@ -4,6 +4,16 @@ This example application demonstrates how to use WebAssembly to create Sphinx pa
## 🚴 Usage
Build the WASM package for bundling:
```
wasm-pack build --scope nymproject --target no-modules
```
in the `clients/webassembly` directory (one up).
Start the webpack dev server:
```
npm install # set up dependencies
npm run start # starts a web server at http://localhost:8001
@@ -15,4 +25,4 @@ Check your dev console for output.
Install `wasm-pack`. Instruction are at the [Rust WASM tutorial](https://rustwasm.github.io/docs/book/game-of-life/hello-world.html).
`wasm-pack build` in the `clients/webassembly` directory (one up) will rebuild the wasm package if you make changes to the Rust source. That will be automatically picked up (and reloaded, if need be) by the npm dev server.
`wasm-pack build --scope nymproject --target no-modules` in the `clients/webassembly` directory (one up) will rebuild the wasm package if you make changes to the Rust source. That will be automatically picked up (and reloaded, if need be) by the npm dev server.
+2 -2
View File
@@ -1,5 +1,5 @@
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
import("./index.js")
.catch(e => console.error("Error importing `index.js`:", e));
import('./index.js')
.catch(e => console.error('Error importing `index.js`:', e));
+68 -56
View File
@@ -1,4 +1,4 @@
// Copyright 2020 Nym Technologies SA
// Copyright 2020-2022 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,55 +12,67 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {
NymClient,
set_panic_hook
} from "@nymproject/nym-client-wasm"
class WebWorkerClient {
worker = null;
// current limitation of rust-wasm for async stuff : (
let client = null
constructor() {
this.worker = new Worker('./worker.js');
this.worker.onmessage = (ev) => {
if (ev.data && ev.data.kind) {
switch (ev.data.kind) {
case 'Ready':
const { selfAddress } = ev.data.args;
displaySenderAddress(selfAddress);
break;
case 'ReceiveMessage':
const { message } = ev.data.args;
displayReceived(message);
break;
}
}
};
}
sendMessage = (message, recipient) => {
if (!this.worker) {
console.error('Could not send message because worker does not exist');
return;
}
this.worker.postMessage({
kind: 'SendMessage',
args: {
message, recipient,
},
});
};
}
let client = null;
async function main() {
// sets up better stack traces in case of in-rust panics
set_panic_hook();
client = new WebWorkerClient();
// validator server we will use to get topology from
const validator = "https://validator.nymtech.net/api"; //"http://localhost:8081";
client = new NymClient(validator);
const on_message = (msg) => displayReceived(msg);
const on_connect = () => console.log("Established (and authenticated) gateway connection!");
client.set_on_message(on_message);
client.set_on_gateway_connect(on_connect);
// this is current limitation of wasm in rust - for async methods you can't take self my reference...
// I'm trying to figure out if I can somehow hack my way around it, but for time being you have to re-assign
// the object (it's the same one)
client = await client.initial_setup();
const self_address = client.self_address();
displaySenderAddress(self_address);
const sendButton = document.querySelector('#send-button');
sendButton.onclick = function () {
sendMessageTo();
}
const sendButton = document.querySelector('#send-button');
sendButton.onclick = function() {
sendMessageTo();
};
}
/**
* Create a Sphinx packet and send it to the mixnet through the gateway node.
*
*
* Message and recipient are taken from the values in the user interface.
*
* @param {Client} nymClient the nym client to use for message sending
*/
async function sendMessageTo() {
const message = document.getElementById("message").value;
const recipient = document.getElementById("recipient").value;
client = await client.send_message(message, recipient);
displaySend(message);
const message = document.getElementById('message').value;
const recipient = document.getElementById('recipient').value;
await client.sendMessage(message, recipient);
displaySend(message);
}
/**
@@ -69,16 +81,16 @@ async function sendMessageTo() {
* @param {string} message
*/
function displaySend(message) {
let timestamp = new Date().toISOString().substr(11, 12);
let timestamp = new Date().toISOString().substr(11, 12);
let sendDiv = document.createElement("div")
let paragraph = document.createElement("p")
paragraph.setAttribute('style', 'color: blue')
let paragraphContent = document.createTextNode(timestamp + " sent >>> " + message)
paragraph.appendChild(paragraphContent)
let sendDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: blue');
let paragraphContent = document.createTextNode(timestamp + ' sent >>> ' + message);
paragraph.appendChild(paragraphContent);
sendDiv.appendChild(paragraph)
document.getElementById("output").appendChild(sendDiv)
sendDiv.appendChild(paragraph);
document.getElementById('output').appendChild(sendDiv);
}
/**
@@ -87,17 +99,17 @@ function displaySend(message) {
* @param {string} message
*/
function displayReceived(message) {
const content = message.message
const replySurb = message.replySurb
const content = message;
let timestamp = new Date().toISOString().substr(11, 12);
let receivedDiv = document.createElement("div")
let paragraph = document.createElement("p")
paragraph.setAttribute('style', 'color: green')
let paragraphContent = document.createTextNode(timestamp + " received >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)"))
paragraph.appendChild(paragraphContent)
receivedDiv.appendChild(paragraph)
document.getElementById("output").appendChild(receivedDiv)
let timestamp = new Date().toISOString().substr(11, 12);
let receivedDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: green');
let paragraphContent = document.createTextNode(timestamp + ' received >>> ' + content);
// let paragraphContent = document.createTextNode(timestamp + " received >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)"))
paragraph.appendChild(paragraphContent);
receivedDiv.appendChild(paragraph);
document.getElementById('output').appendChild(receivedDiv);
}
@@ -107,7 +119,7 @@ function displayReceived(message) {
* @param {Client} nymClient
*/
function displaySenderAddress(address) {
document.getElementById("sender").value = address;
document.getElementById('sender').value = address;
}
// Let's get started!
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -36,4 +36,4 @@
"dependencies": {
"@nymproject/nym-client-wasm": "file:../pkg"
}
}
}
@@ -1,15 +1,33 @@
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
entry: "./bootstrap.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bootstrap.js",
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
mode: "development",
entry: {
bootstrap: './bootstrap.js',
worker: './worker.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
// mode: 'development',
mode: 'production',
plugins: [
new CopyWebpackPlugin({ patterns: ['index.html'] })
new CopyWebpackPlugin({
patterns: [
'index.html',
{
from: 'node_modules/@nymproject/nym-client-wasm/*.(js|wasm)',
to: '[name][ext]',
},
],
}),
],
experiments: { syncWebAssembly: true }
experiments: { syncWebAssembly: true },
};
+132
View File
@@ -0,0 +1,132 @@
// Copyright 2020-2022 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
importScripts('nym_client_wasm.js');
console.log('Initializing worker');
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
const { default_debug, get_gateway, NymClient, set_panic_hook, Config } = wasm_bindgen;
class ClientWrapper {
constructor(config, onMessageHandler) {
this.rustClient = new NymClient(config);
this.rustClient.set_on_message(onMessageHandler);
this.rustClient.set_on_gateway_connect(this.onConnect);
}
selfAddress = () => {
return this.rustClient.self_address();
};
onConnect = () => {
console.log('Established (and authenticated) gateway connection!');
};
start = async () => {
// this is current limitation of wasm in rust - for async methods you can't take self by reference...
// I'm trying to figure out if I can somehow hack my way around it, but for time being you have to re-assign
// the object (it's the same one)
this.rustClient = await this.rustClient.start();
};
sendMessage = async (recipient, message) => {
this.rustClient = await this.rustClient.send_message(recipient, message);
};
sendBinaryMessage = async (recipient, message) => {
this.rustClient = await this.rustClient.send_binary_message(recipient, message);
};
}
let client = null;
async function main() {
// load WASM package
await wasm_bindgen('nym_client_wasm_bg.wasm');
console.log('Loaded WASM');
// sets up better stack traces in case of in-rust panics
set_panic_hook();
console.error("the current mainnet is not compatible with v2! - either use the pre-merge branch or explicitly set the client to use one of V2 QA networks")
return
// validator server we will use to get topology from
// MAINNET (V1):
const validator = 'https://validator.nymtech.net/api'; //"http://localhost:8081";
const preferredGateway = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
// QA (V2):
// const validator = 'https://qa-validator-api.nymtech.net/api'; //"http://localhost:8081";
// const preferredGateway = 'CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM';
const gatewayEndpoint = await get_gateway(validator, preferredGateway);
gatewayEndpoint.gateway_listener = "wss://gateway1.nymtech.net:443"; // this is needed if we want it to work on the web. However this gateway is a v1 gateway, we will need to change for v2 once we get there
// only really useful if you want to adjust some settings like traffic rate
// (if not needed you can just pass a null)
const debug = default_debug();
debug.disable_main_poisson_packet_distribution = true;
debug.disable_loop_cover_traffic_stream = true;
debug.use_extended_packet_size = true;
// debug.average_packet_delay_ms = BigInt(10);
// debug.average_ack_delay_ms = BigInt(10);
// debug.ack_wait_addition_ms = BigInt(3000);
// debug.ack_wait_multiplier = 10;
debug.topology_refresh_rate_ms = BigInt(60000)
const config = new Config('my-awesome-wasm-client', validator, gatewayEndpoint, debug);
const onMessageHandler = (message) => {
self.postMessage({
kind: 'ReceiveMessage',
args: {
message,
},
});
};
console.log('Instantiating WASM client...');
client = new ClientWrapper(config, onMessageHandler);
console.log('Web worker creating WASM client...');
await client.start();
console.log('WASM client running!');
const selfAddress = client.rustClient.self_address();
console.log(`Client address is ${selfAddress}`);
self.postMessage({
kind: 'Ready',
args: {
selfAddress,
},
});
// Set callback to handle messages passed to the worker.
self.onmessage = async event => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'SendMessage': {
const { message, recipient } = event.data.args;
await client.sendMessage(message, recipient);
}
}
}
};
}
// Let's get started!
main();
-11
View File
@@ -1,11 +0,0 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
}
}
}
+164
View File
@@ -0,0 +1,164 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
#![allow(clippy::drop_non_drop)]
use client_core::config::{Debug as ConfigDebug, ExtendedPacketSize, GatewayEndpoint};
use std::time::Duration;
use url::Url;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Config {
/// ID specifies the human readable ID of this particular client.
pub(crate) id: String,
pub(crate) validator_api_url: Url,
pub(crate) disabled_credentials_mode: bool,
/// Information regarding how the client should send data to gateway.
pub(crate) gateway_endpoint: GatewayEndpoint,
pub(crate) debug: ConfigDebug,
}
#[wasm_bindgen]
impl Config {
#[wasm_bindgen(constructor)]
pub fn new(
id: String,
validator_server: String,
gateway_endpoint: GatewayEndpoint,
debug: Option<Debug>,
) -> Self {
Config {
id,
validator_api_url: validator_server
.parse()
.expect("provided url was malformed"),
disabled_credentials_mode: true,
gateway_endpoint,
debug: debug.map(Into::into).unwrap_or_default(),
}
}
}
// just a helper structure to more easily pass through the JS boundary
#[wasm_bindgen]
pub struct Debug {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
pub average_packet_delay_ms: u64,
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
pub average_ack_delay_ms: u64,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 1.
pub ack_wait_multiplier: f64,
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
pub ack_wait_addition_ms: u64,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
pub loop_cover_traffic_average_delay_ms: u64,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
pub message_sending_average_delay_ms: u64,
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
pub gateway_response_timeout_ms: u64,
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
pub topology_refresh_rate_ms: u64,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
pub topology_resolution_timeout_ms: u64,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay_ms])
pub disable_loop_cover_traffic_stream: bool,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
pub use_extended_packet_size: bool,
}
impl From<Debug> for ConfigDebug {
fn from(debug: Debug) -> Self {
// For now we just always use the (older) 32kb extended size
let use_extended_packet_size = debug
.use_extended_packet_size
.then(|| ExtendedPacketSize::Extended32);
ConfigDebug {
average_packet_delay: Duration::from_millis(debug.average_packet_delay_ms),
average_ack_delay: Duration::from_millis(debug.average_ack_delay_ms),
ack_wait_multiplier: debug.ack_wait_multiplier,
ack_wait_addition: Duration::from_millis(debug.ack_wait_addition_ms),
loop_cover_traffic_average_delay: Duration::from_millis(
debug.loop_cover_traffic_average_delay_ms,
),
message_sending_average_delay: Duration::from_millis(
debug.message_sending_average_delay_ms,
),
gateway_response_timeout: Duration::from_millis(debug.gateway_response_timeout_ms),
topology_refresh_rate: Duration::from_millis(debug.topology_refresh_rate_ms),
topology_resolution_timeout: Duration::from_millis(
debug.topology_resolution_timeout_ms,
),
disable_loop_cover_traffic_stream: debug.disable_loop_cover_traffic_stream,
disable_main_poisson_packet_distribution: debug
.disable_main_poisson_packet_distribution,
use_extended_packet_size,
}
}
}
impl From<ConfigDebug> for Debug {
fn from(debug: ConfigDebug) -> Self {
Debug {
average_packet_delay_ms: debug.average_packet_delay.as_millis() as u64,
average_ack_delay_ms: debug.average_ack_delay.as_millis() as u64,
ack_wait_multiplier: debug.ack_wait_multiplier,
ack_wait_addition_ms: debug.ack_wait_addition.as_millis() as u64,
loop_cover_traffic_average_delay_ms: debug.loop_cover_traffic_average_delay.as_millis()
as u64,
message_sending_average_delay_ms: debug.message_sending_average_delay.as_millis()
as u64,
gateway_response_timeout_ms: debug.gateway_response_timeout.as_millis() as u64,
topology_refresh_rate_ms: debug.topology_refresh_rate.as_millis() as u64,
topology_resolution_timeout_ms: debug.topology_resolution_timeout.as_millis() as u64,
disable_loop_cover_traffic_stream: debug.disable_loop_cover_traffic_stream,
disable_main_poisson_packet_distribution: debug
.disable_main_poisson_packet_distribution,
use_extended_packet_size: debug.use_extended_packet_size.is_some(),
}
}
}
#[wasm_bindgen]
pub fn default_debug() -> Debug {
ConfigDebug::default().into()
}
+298 -189
View File
@@ -1,47 +1,46 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crypto::asymmetric::{encryption, identity};
use self::config::Config;
use client_core::client::{
cover_traffic_stream::LoopCoverTrafficStream,
inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender},
key_manager::KeyManager,
mix_traffic::{BatchMixMessageSender, MixTrafficController},
real_messages_control::{self, RealMessagesController},
received_buffer::{
ReceivedBufferMessage, ReceivedBufferRequestReceiver, ReceivedBufferRequestSender,
ReceivedMessagesBufferController,
},
topology_control::{TopologyAccessor, TopologyRefresher, TopologyRefresherConfig},
};
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::GatewayClient;
use nymsphinx::acknowledgements::AckKey;
use futures::StreamExt;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
};
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::preparer::MessagePreparer;
use rand::rngs::OsRng;
use received_processor::ReceivedMessagesProcessor;
use std::sync::Arc;
use std::time::Duration;
use topology::{gateway, nym_topology_from_bonds, NymTopology};
use url::Url;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::{console_log, console_warn};
pub(crate) mod received_processor;
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
pub mod config;
#[wasm_bindgen]
pub struct NymClient {
validator_server: Url,
disabled_credentials_mode: bool,
config: Config,
// TODO: technically this doesn't need to be an Arc since wasm is run on a single thread
// however, once we eventually combine this code with the native-client's, it will make things
// easier.
identity: Arc<identity::KeyPair>,
encryption_keys: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
message_preparer: Option<MessagePreparer<OsRng>>,
// message_receiver: MessageReceiver,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
// TODO: this should be stored somewhere persistently
// received_keys: HashSet<SURBEncryptionKey>,
topology: Option<NymTopology>,
gateway_client: Option<GatewayClient>,
/// Channel used for transforming 'raw' messages into sphinx packets and sending them
/// through the mix network.
input_tx: Option<InputMessageSender>,
// callbacks
on_message: Option<js_sys::Function>,
@@ -51,31 +50,24 @@ pub struct NymClient {
#[wasm_bindgen]
impl NymClient {
#[wasm_bindgen(constructor)]
pub fn new(validator_server: String) -> Self {
let mut rng = OsRng;
// for time being generate new keys each time...
let identity = identity::KeyPair::new(&mut rng);
let encryption_keys = encryption::KeyPair::new(&mut rng);
let ack_key = AckKey::new(&mut rng);
pub fn new(config: Config) -> Self {
Self {
identity: Arc::new(identity),
encryption_keys: Arc::new(encryption_keys),
ack_key: Arc::new(ack_key),
validator_server: validator_server
.parse()
.expect("malformed validator server url provided"),
message_preparer: None,
// received_keys: Default::default(),
topology: None,
gateway_client: None,
config,
key_manager: Self::setup_key_manager(),
on_message: None,
on_gateway_connect: None,
disabled_credentials_mode: true,
input_tx: None,
}
}
// perhaps this should be public?
fn setup_key_manager() -> KeyManager {
let mut rng = OsRng;
// for time being generate new keys each time...
console_log!("generated new set of keys");
KeyManager::new(&mut rng)
}
pub fn set_on_message(&mut self, on_message: js_sys::Function) {
self.on_message = Some(on_message);
}
@@ -85,62 +77,137 @@ impl NymClient {
self.on_gateway_connect = Some(on_connect)
}
pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) {
console_log!(
"Setting disabled credentials mode to {}",
disabled_credentials_mode
);
self.disabled_credentials_mode = disabled_credentials_mode;
}
fn self_recipient(&self) -> Recipient {
fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.identity.public_key(),
*self.encryption_keys.public_key(),
self.gateway_client
.as_ref()
.expect("gateway connection was not established!")
.gateway_identity(),
*self.key_manager.identity_keypair().public_key(),
*self.key_manager.encryption_keypair().public_key(),
identity::PublicKey::from_base58_string(&self.config.gateway_endpoint.gateway_id)
.expect("no gateway has been selected"),
)
}
pub fn self_address(&self) -> String {
self.self_recipient().to_string()
self.as_mix_recipient().to_string()
}
// Right now it's impossible to have async exported functions to take `&self` rather than self
pub async fn initial_setup(self) -> Self {
let disabled_credentials_mode = self.disabled_credentials_mode;
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
&self,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
) {
console_log!("Starting loop cover traffic stream...");
let bandwidth_controller = None;
let mut client = self.get_and_update_topology().await;
let gateway = client.choose_gateway();
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded();
let mut gateway_client = GatewayClient::new(
gateway.clients_address(),
Arc::clone(&client.identity),
gateway.identity_key,
gateway.owner.clone(),
None,
mixnet_messages_sender,
ack_sender,
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
bandwidth_controller,
let mut stream = LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
self.config.debug.average_ack_delay,
self.config.debug.average_packet_delay,
self.config.debug.loop_cover_traffic_average_delay,
mix_tx,
self.as_mix_recipient(),
topology_accessor,
);
gateway_client.set_disabled_credentials_mode(disabled_credentials_mode);
if let Some(size) = &self.config.debug.use_extended_packet_size {
stream.set_custom_packet_size(size.clone().into());
}
gateway_client
stream.start();
}
fn start_real_traffic_controller(
&self,
topology_accessor: TopologyAccessor,
ack_receiver: AcknowledgementReceiver,
input_receiver: InputMessageReceiver,
mix_sender: BatchMixMessageSender,
) {
let mut controller_config = real_messages_control::Config::new(
self.key_manager.ack_key(),
self.config.debug.ack_wait_multiplier,
self.config.debug.ack_wait_addition,
self.config.debug.average_ack_delay,
self.config.debug.message_sending_average_delay,
self.config.debug.average_packet_delay,
self.config.debug.disable_main_poisson_packet_distribution,
self.as_mix_recipient(),
);
if let Some(size) = &self.config.debug.use_extended_packet_size {
controller_config.set_custom_packet_size(size.clone().into());
}
console_log!("Starting real traffic stream...");
RealMessagesController::new(
controller_config,
ack_receiver,
input_receiver,
mix_sender,
topology_accessor,
)
.start();
}
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
fn start_received_messages_buffer_controller(
&self,
query_receiver: ReceivedBufferRequestReceiver,
mixnet_receiver: MixnetMessageReceiver,
) {
console_log!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
self.key_manager.encryption_keypair(),
query_receiver,
mixnet_receiver,
)
.start()
}
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
) -> GatewayClient {
let gateway_id = self.config.gateway_endpoint.gateway_id.clone();
if gateway_id.is_empty() {
panic!("The identity of the gateway is unknown - did you run `get_gateway()`?")
}
let gateway_owner = self.config.gateway_endpoint.gateway_owner.clone();
if gateway_owner.is_empty() {
panic!("The owner of the gateway is unknown - did you run `get_gateway()`?")
}
let gateway_address = self.config.gateway_endpoint.gateway_listener.clone();
if gateway_address.is_empty() {
panic!("The address of the gateway is unknown - did you run `get_gateway()`?")
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
gateway_owner,
None,
mixnet_message_sender,
ack_sender,
self.config.debug.gateway_response_timeout,
None,
);
gateway_client.set_disabled_credentials_mode(self.config.disabled_credentials_mode);
let shared_keys = gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
self.key_manager.insert_gateway_shared_key(shared_keys);
client.gateway_client = Some(gateway_client);
match client.on_gateway_connect.as_ref() {
match self.on_gateway_connect.as_ref() {
Some(callback) => {
callback
.call0(&JsValue::null())
@@ -149,124 +216,166 @@ impl NymClient {
None => console_log!("Gateway connection established - no callback specified"),
};
let rng = rand::rngs::OsRng;
let message_preparer = MessagePreparer::new(
rng,
client.self_recipient(),
DEFAULT_AVERAGE_PACKET_DELAY,
DEFAULT_AVERAGE_ACK_DELAY,
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
vec![self.config.validator_api_url.clone()],
self.config.debug.topology_refresh_rate,
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
console_log!("Obtaining initial network topology");
topology_refresher.refresh().await;
let received_processor = ReceivedMessagesProcessor::new(
Arc::clone(&client.encryption_keys),
Arc::clone(&client.ack_key),
);
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
);
}
client.message_preparer = Some(message_preparer);
console_log!("Starting topology refresher...");
spawn_local(received_processor.start_processing(
// TODO: re-enable
topology_refresher.start();
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
fn start_mix_traffic_controller(gateway_client: GatewayClient) -> BatchMixMessageSender {
console_log!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
mix_traffic_controller.start();
mix_tx
}
// TODO: this procedure is extremely overcomplicated, because it's based off native client's behaviour
// which doesn't fully apply in this case
fn start_reconstructed_pusher(
&mut self,
received_buffer_request_sender: ReceivedBufferRequestSender,
) {
let on_message = self.on_message.take();
spawn_local(async move {
let (reconstructed_sender, mut reconstructed_receiver) = mpsc::unbounded();
// tell the buffer to start sending stuff to us
received_buffer_request_sender
.unbounded_send(ReceivedBufferMessage::ReceiverAnnounce(
reconstructed_sender,
))
.expect("the buffer request failed!");
let this = JsValue::null();
while let Some(reconstructed) = reconstructed_receiver.next().await {
if let Some(ref callback) = on_message {
for msg in reconstructed {
if msg.reply_surb.is_some() {
console_log!("the received message contained a reply-surb that we do not know how to handle (yet)")
}
let stringified = String::from_utf8_lossy(&msg.message).into_owned();
let arg1 = serde_wasm_bindgen::to_value(&stringified).unwrap();
callback.call1(&this, &arg1).expect("on message failed!");
}
} else {
console_warn!("no on_message callback was specified. the received message content is getting dropped");
console_log!("the raw messages: {:?}", reconstructed)
}
}
});
}
pub async fn start(mut self) -> NymClient {
console_log!("Starting wasm client '{}'", self.config.id);
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
// rather than creating them here, so say for example the buffer controller would create the request channels
// and would allow anyone to clone the sender channel
// unwrapped_sphinx_sender is the transmitter of mixnet messages received from the gateway
// unwrapped_sphinx_receiver is the receiver for said messages - used by ReceivedMessagesBuffer
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
// used for announcing connection or disconnection of a channel for pushing re-assembled messages to
let (received_buffer_request_sender, received_buffer_request_receiver) = mpsc::unbounded();
// channels responsible for controlling real messages
let (input_sender, input_receiver) = mpsc::unbounded::<InputMessage>();
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor = TopologyAccessor::new();
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
ack_receiver,
client.on_message.take().expect("on_message was not set!"),
));
);
client
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let sphinx_message_sender = Self::start_mix_traffic_controller(gateway_client);
self.start_real_traffic_controller(
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
sphinx_message_sender.clone(),
);
if !self.config.debug.disable_loop_cover_traffic_stream {
self.start_cover_traffic_stream(shared_topology_accessor, sphinx_message_sender);
}
self.start_reconstructed_pusher(received_buffer_request_sender);
self.input_tx = Some(input_sender);
self
}
// Right now it's impossible to have async exported functions to take `&mut self` rather than mut self
// TODO: try Rc<RefCell<Self>> approach?
pub async fn send_message(mut self, message: String, recipient: String) -> Self {
pub async fn send_message(self, message: String, recipient: String) -> Self {
console_log!("Sending {} to {}", message, recipient);
let message_bytes = message.into_bytes();
self.send_binary_message(message_bytes, recipient).await
}
pub async fn send_binary_message(self, message: Vec<u8>, recipient: String) -> Self {
console_log!("Sending {} bytes to {}", message.len(), recipient);
let recipient = Recipient::try_from_base58_string(recipient).unwrap();
let topology = self
.topology
let input_msg = InputMessage::new_fresh(recipient, message, false);
self.input_tx
.as_ref()
.expect("did not obtain topology before");
let message_preparer = self.message_preparer.as_mut().unwrap();
let (split_message, _reply_keys) = message_preparer
.prepare_and_split_message(message_bytes, false, topology)
.expect("failed to split the message");
let mut mix_packets = Vec::with_capacity(split_message.len());
for message_chunk in split_message {
// don't bother with acks etc. for time being
let prepared_fragment = message_preparer
.prepare_chunk_for_sending(message_chunk, topology, &self.ack_key, &recipient)
.unwrap();
console_warn!("packet is going to have round trip time of {:?}, but we're not going to do anything for acks anyway ", prepared_fragment.total_delay);
mix_packets.push(prepared_fragment.mix_packet);
}
self.gateway_client
.as_mut()
.unwrap()
.batch_send_mix_packets(mix_packets)
.await
.expect("start method was not called before!")
.unbounded_send(input_msg)
.unwrap();
self
}
pub(crate) fn choose_gateway(&self) -> &gateway::Node {
let topology = self
.topology
.as_ref()
.expect("did not obtain topology before");
// choose the first one available
assert!(!topology.gateways().is_empty());
topology.gateways().first().unwrap()
}
// Right now it's impossible to have async exported functions to take `&mut self` rather than mut self
// self: Rc<Self>
// or this: Rc<RefCell<Self>>
pub async fn get_and_update_topology(mut self) -> Self {
let new_topology = self.get_nym_topology().await;
self.update_topology(new_topology);
self
}
pub(crate) fn update_topology(&mut self, topology: NymTopology) {
self.topology = Some(topology)
}
// when updated to 0.10.0, to prevent headache later on, this function requires those two imports:
// use js_sys::Promise;
// use wasm_bindgen_futures::future_to_promise;
//
// pub fn get_full_topology_json(&self) -> Promise {
// let validator_client_config = validator_client::Config::new(
// vec![self.validator_server.clone()],
// &self.mixnet_contract_address,
// );
// let validator_client = validator_client::Client::new(validator_client_config);
//
// future_to_promise(async move {
// let topology = &validator_client.get_active_topology().await.unwrap();
// Ok(JsValue::from_serde(&topology).unwrap())
// })
// }
pub(crate) async fn get_nym_topology(&self) -> NymTopology {
let validator_client = validator_client::ApiClient::new(self.validator_server.clone());
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
Err(err) => panic!("{:?}", err),
Ok(mixes) => mixes,
};
let gateways = match validator_client.get_cached_gateways().await {
Err(err) => panic!("{}", err),
Ok(gateways) => gateways,
};
let topology = nym_topology_from_bonds(mixnodes, gateways);
let version = env!("CARGO_PKG_VERSION");
topology.filter_system_version(version)
}
}
@@ -1,183 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crypto::asymmetric::encryption;
use futures::StreamExt;
use gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
use nymsphinx::acknowledgements::identifier::recover_identifier;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::sync::Arc;
use wasm_bindgen::JsValue;
use wasm_utils::{console_error, console_log, console_warn};
#[derive(Serialize, Deserialize)]
pub struct ProcessedMessage {
pub message: String,
pub reply_surb: Option<String>,
}
impl From<ReconstructedMessage> for ProcessedMessage {
fn from(reconstructed: ReconstructedMessage) -> Self {
ProcessedMessage {
message: String::from_utf8_lossy(&reconstructed.message).into_owned(),
reply_surb: reconstructed
.reply_surb
.map(|reply_surb| reply_surb.to_base58_string()),
}
}
}
pub(crate) struct ReceivedMessagesProcessor {
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
message_receiver: MessageReceiver,
recently_reconstructed: HashSet<i32>,
}
impl ReceivedMessagesProcessor {
pub(crate) fn new(
local_encryption_keypair: Arc<encryption::KeyPair>,
ack_key: Arc<AckKey>,
) -> Self {
ReceivedMessagesProcessor {
local_encryption_keypair,
ack_key,
message_receiver: MessageReceiver::new(),
recently_reconstructed: HashSet::new(),
}
}
// TODO: duplicate code from received_buffer.rs in client-core....
fn process_received_fragment(&mut self, raw_fragment: Vec<u8>) -> Option<ProcessedMessage> {
let fragment_data = match self
.message_receiver
.recover_plaintext(self.local_encryption_keypair.private_key(), raw_fragment)
{
Err(e) => {
console_warn!("failed to recover fragment data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e);
return None;
}
Ok(frag_data) => frag_data,
};
if nymsphinx::cover::is_cover(&fragment_data) {
// currently won't be the case for a loooong time
console_log!("The message was a loop cover message! Skipping it");
return None;
}
let fragment = match self.message_receiver.recover_fragment(&fragment_data) {
Err(e) => {
console_warn!("failed to recover fragment from raw data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e);
return None;
}
Ok(frag) => frag,
};
if self.recently_reconstructed.contains(&fragment.id()) {
console_warn!("Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost", fragment.id());
return None;
}
// if we returned an error the underlying message is malformed in some way
match self.message_receiver.insert_new_fragment(fragment) {
Err(err) => match err {
MessageRecoveryError::MalformedReconstructedMessage(message_sets) => {
// TODO: should we really insert reconstructed sets? could this be abused for some attack?
for set_id in message_sets {
if !self.recently_reconstructed.insert(set_id) {
// or perhaps we should even panic at this point?
console_error!(
"Reconstructed another message containing already used set id!"
)
}
}
None
}
_ => unreachable!(
"no other error kind should have been returned here! If so, it's a bug!"
),
},
Ok(reconstruction_result) => match reconstruction_result {
Some((reconstructed_message, used_sets)) => {
for set_id in used_sets {
if !self.recently_reconstructed.insert(set_id) {
// or perhaps we should even panic at this point?
console_error!(
"Reconstructed another message containing already used set id!"
)
}
}
Some(reconstructed_message.into())
}
None => None,
},
}
}
// TODO: duplicate code from acknowledgement listener...
fn process_received_ack(&self, ack_content: Vec<u8>) {
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
.map(FragmentIdentifier::try_from_bytes)
{
Some(Ok(frag_id)) => frag_id,
_ => {
console_warn!("Received invalid ACK!"); // should we do anything else about that?
return;
}
};
// if we received an ack for cover message or a reply there will be nothing to remove,
// because nothing was inserted in the first place
if frag_id == COVER_FRAG_ID {
return;
} else if frag_id.is_reply() {
console_warn!("Received an ack for a reply message - no need to do anything! (don't know what to do!)");
// TODO: probably there will need to be some extra procedure here, something to notify
// user that his reply reached the recipient (since we got an ack)
return;
}
console_log!(
"Received an ack for fragment {:?} but can't do anything more about it just yet...",
frag_id
);
// here be ack handling...
}
// TODO: this needs to have a shutdown signal!
pub(crate) async fn start_processing(
mut self,
mixnet_messages_receiver: MixnetMessageReceiver,
ack_receiver: AcknowledgementReceiver,
on_message: js_sys::Function,
) {
let mut fused_mixnet_messages_receiver = mixnet_messages_receiver.fuse();
let mut fused_ack_receiver = ack_receiver.fuse();
let this = JsValue::null();
loop {
futures::select! {
mix_msgs = fused_mixnet_messages_receiver.next() => {
for mix_msg in mix_msgs.unwrap() {
if let Some(processed) = self.process_received_fragment(mix_msg) {
let arg1 = JsValue::from_serde(&processed).unwrap();
on_message.call1(&this, &arg1).expect("on message failed!");
}
}
}
acks = fused_ack_receiver.next() => {
for ack in acks.unwrap() {
self.process_received_ack(ack);
}
}
}
}
}
}
@@ -0,0 +1,44 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use client_core::config::GatewayEndpoint;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpoint {
let validator_client = validator_client::ApiClient::new(api_server.parse().unwrap());
let gateways = match validator_client.get_cached_gateways().await {
Err(err) => panic!("failed to obtain list of all gateways - {}", err),
Ok(gateways) => gateways,
};
if let Some(preferred) = preferred {
if let Some(details) = gateways
.iter()
.find(|g| g.gateway.identity_key == preferred)
{
return GatewayEndpoint {
gateway_id: details.gateway.identity_key.clone(),
gateway_owner: details.owner.to_string(),
gateway_listener: format!(
"ws://{}:{}",
details.gateway.host, details.gateway.clients_port
),
};
}
}
let details = gateways
.first()
.expect("current topology holds no gateways");
GatewayEndpoint {
gateway_id: details.gateway.identity_key.clone(),
gateway_owner: details.owner.to_string(),
gateway_listener: format!(
"ws://{}:{}",
details.gateway.host, details.gateway.clients_port
),
}
}
+2
View File
@@ -5,6 +5,8 @@ use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
mod client;
#[cfg(target_arch = "wasm32")]
pub mod gateway_selector;
#[wasm_bindgen]
pub fn set_panic_hook() {
+2
View File
@@ -0,0 +1,2 @@
allow-unwrap-in-tests = true
allow-expect-in-tests = true
+6 -6
View File
@@ -14,9 +14,8 @@ log = "0.4"
thiserror = "1.0"
url = "2.2"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
secp256k1 = { version = "0.20.3", optional = true }
web3 = { version = "0.17.0", default-features = false, optional = true }
async-trait = { version = "0.1.51" }
tokio = { version = "1.21.2", features = ["macros"] }
# internal
coconut-interface = { path = "../../coconut-interface", optional = true }
@@ -28,13 +27,14 @@ nymsphinx = { path = "../../nymsphinx" }
pemstore = { path = "../../pemstore" }
validator-client = { path = "../validator-client", optional = true }
[dependencies.tungstenite]
version = "0.13"
default-features = false
# non-wasm-only dependencies
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
version = "1.19.1"
version = "1.21.2"
features = ["macros", "rt", "net", "sync", "time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
@@ -61,8 +61,9 @@ version = "0.4"
path = "../../wasm-utils"
# only import it in wasm. Prefer proper tokio timer in non-wasm
[target."cfg(target_arch = \"wasm32\")".dependencies.fluvio-wasm-timer]
version = "0.2.5"
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-timer]
git = "https://github.com/mmsinclair/wasm-timer"
rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"
# this is due to tungstenite using `rand` 0.8 and associated changes in `getrandom` crate
# which now does not support wasm32-unknown-unknown target by default.
@@ -80,4 +81,3 @@ features = ["js"]
[features]
coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut"]
wasm = []
default = ["web3/default", "secp256k1"]
@@ -36,7 +36,7 @@ use tungstenite::protocol::Message;
use tokio_tungstenite::connect_async;
#[cfg(target_arch = "wasm32")]
use fluvio_wasm_timer as wasm_timer;
use wasm_timer;
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
@@ -11,8 +11,6 @@ use thiserror::Error;
use tungstenite::Error as WsError;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsValue;
#[cfg(not(feature = "coconut"))]
use web3::{contract::Error as Web3ContractError, Error as Web3Error};
#[derive(Debug, Error)]
pub enum GatewayClientError {
@@ -37,18 +35,6 @@ pub enum GatewayClientError {
#[error("There was a network error")]
NetworkErrorWasm(JsValue),
#[cfg(not(feature = "coconut"))]
#[error("Could not burn ERC20 token in Ethereum smart contract - {0}")]
BurnTokenError(#[from] Web3Error),
#[cfg(not(feature = "coconut"))]
#[error("Could not run web3 contract - {0}")]
Web3ContractError(#[from] Web3ContractError),
#[cfg(not(feature = "coconut"))]
#[error("Invalid Ethereum private key")]
InvalidEthereumPrivateKey,
#[error("Invalid URL - {0}")]
InvalidURL(String),
@@ -25,3 +25,15 @@ pub(crate) fn cleanup_socket_message(
None => Err(GatewayClientError::ConnectionAbruptlyClosed),
}
}
pub(crate) fn cleanup_socket_messages(
msgs: Option<Vec<Result<Message, WsError>>>,
) -> Result<Vec<Message>, GatewayClientError> {
match msgs {
Some(msgs) => msgs
.into_iter()
.map(|msg| msg.map_err(GatewayClientError::NetworkError))
.collect(),
None => Err(GatewayClientError::ConnectionAbruptlyClosed),
}
}
@@ -59,11 +59,22 @@ impl PacketRouter {
} else if received_packet.len()
== PacketSize::RegularPacket.plaintext_size() - ack_overhead
{
trace!("routing regular packet");
received_messages.push(received_packet);
} else if received_packet.len()
== PacketSize::ExtendedPacket.plaintext_size() - ack_overhead
== PacketSize::ExtendedPacket8.plaintext_size() - ack_overhead
{
warn!("received extended packet? Did not expect this...");
trace!("routing extended8 packet");
received_messages.push(received_packet);
} else if received_packet.len()
== PacketSize::ExtendedPacket16.plaintext_size() - ack_overhead
{
trace!("routing extended16 packet");
received_messages.push(received_packet);
} else if received_packet.len()
== PacketSize::ExtendedPacket32.plaintext_size() - ack_overhead
{
trace!("routing extended32 packet");
received_messages.push(received_packet);
} else {
// this can happen if other clients are not padding their messages
@@ -1,12 +1,12 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::cleanup_socket_message;
use crate::cleanup_socket_messages;
use crate::error::GatewayClientError;
use crate::packet_router::PacketRouter;
use futures::channel::oneshot;
use futures::stream::{SplitSink, SplitStream};
use futures::{FutureExt, SinkExt, StreamExt};
use futures::{SinkExt, StreamExt};
use gateway_requests::registration::handshake::SharedKeys;
use gateway_requests::BinaryResponse;
use log::*;
@@ -44,16 +44,15 @@ pub(crate) struct PartiallyDelegated {
}
impl PartiallyDelegated {
fn route_socket_message(
ws_msg: Message,
packet_router: &mut PacketRouter,
shared_key: &SharedKeys,
) -> Result<(), GatewayClientError> {
match ws_msg {
Message::Binary(bin_msg) => {
// this function decrypts the request and checks the MAC
let plaintext =
match BinaryResponse::try_from_encrypted_tagged_bytes(bin_msg, shared_key) {
fn recover_received_plaintexts(ws_msgs: Vec<Message>, shared_key: &SharedKeys) -> Vec<Vec<u8>> {
let mut plaintexts = Vec::with_capacity(ws_msgs.len());
for ws_msg in ws_msgs {
match ws_msg {
Message::Binary(bin_msg) => {
// this function decrypts the request and checks the MAC
let plaintext = match BinaryResponse::try_from_encrypted_tagged_bytes(
bin_msg, shared_key,
) {
Ok(bin_response) => match bin_response {
BinaryResponse::PushedMixMessage(plaintext) => plaintext,
},
@@ -62,29 +61,37 @@ impl PartiallyDelegated {
"message received from the gateway was malformed! - {:?}",
err
);
return Ok(());
continue;
}
};
plaintexts.push(plaintext)
}
// 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
// not ordered (for some peculiar reason) we wouldn't lose anything.
// This would also require NOT discarding any text responses here.
// TODO: some batching mechanism to allow reading and sending more than
// one packet at the time, because the receiver can easily handle it
packet_router.route_received(vec![plaintext])
}
// 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
// not ordered (for some peculiar reason) we wouldn't lose anything.
// This would also require NOT discarding any text responses here.
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
Message::Text(text) => {
trace!(
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
Message::Text(text) => {
trace!(
"received a text message - probably a response to some previous query! - {}",
text
);
Ok(())
continue;
}
_ => continue,
}
_ => Ok(()),
}
plaintexts
}
fn route_socket_messages(
ws_msgs: Vec<Message>,
packet_router: &mut PacketRouter,
shared_key: &SharedKeys,
) -> Result<(), GatewayClientError> {
let plaintexts = Self::recover_received_plaintexts(ws_msgs, shared_key);
packet_router.route_received(plaintexts)
}
pub(crate) fn split_and_listen_for_mixnet_messages(
@@ -101,47 +108,46 @@ impl PartiallyDelegated {
let (sink, mut stream) = conn.split();
let mixnet_receiver_future = async move {
let mut fused_receiver = notify_receiver.fuse();
let mut fused_stream = (&mut stream).fuse();
let mut notify_receiver = notify_receiver;
let mut chunk_stream = (&mut stream).ready_chunks(8);
let mut packet_router = packet_router;
// Bit of an ugly workaround for selecting on an `Option` without having access to
// `tokio::select`
#[cfg(not(target_arch = "wasm32"))]
let shutdown = {
let m_shutdown = shutdown.clone();
async {
if let Some(mut s) = m_shutdown {
if let Some(mut s) = shutdown {
s.recv().await
} else {
std::future::pending::<()>().await
}
}
.fuse()
};
#[cfg(not(target_arch = "wasm32"))]
tokio::pin!(shutdown);
#[cfg(target_arch = "wasm32")]
let mut shutdown = std::future::pending::<()>().fuse();
let mut shutdown = std::future::pending::<()>();
let ret_err = loop {
futures::select! {
_ = shutdown => {
tokio::select! {
_ = &mut shutdown => {
log::trace!("GatewayClient listener: Received shutdown");
log::debug!("GatewayClient listener: Exiting");
return;
}
_ = &mut fused_receiver => {
_ = &mut notify_receiver => {
break Ok(());
}
msg = fused_stream.next() => {
let ws_msg = match cleanup_socket_message(msg) {
msgs = chunk_stream.next() => {
let ws_msgs = match cleanup_socket_messages(msgs) {
Err(err) => break Err(err),
Ok(msg) => msg
Ok(msgs) => msgs
};
if let Err(err) = Self::route_socket_message(ws_msg, &mut packet_router, shared_key.as_ref()) {
log::warn!("Route socket message failed: {:?}", err);
if let Err(err) = Self::route_socket_messages(ws_msgs, &mut packet_router, shared_key.as_ref()) {
log::warn!("Route socket messages failed: {:?}", err);
}
}
};
+1 -1
View File
@@ -9,7 +9,7 @@ edition = "2021"
[dependencies]
futures = "0.3"
log = "0.4.8"
tokio = { version = "1.19.1", features = ["time", "net", "rt"] }
tokio = { version = "1.21.2", features = ["time", "net", "rt"] }
tokio-util = { version = "0.7.3", features = ["codec"] }
# internal
@@ -23,6 +23,7 @@ pub struct Config {
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_connection_buffer_size: usize,
use_legacy_version: bool,
}
impl Config {
@@ -31,12 +32,14 @@ impl Config {
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_connection_buffer_size: usize,
use_legacy_version: bool,
) -> Self {
Config {
initial_reconnection_backoff,
maximum_reconnection_backoff,
initial_connection_timeout,
maximum_connection_buffer_size,
use_legacy_version,
}
}
}
@@ -186,7 +189,7 @@ impl Client {
address.into(),
receiver,
initial_connection_timeout,
&*current_reconnection_attempt,
&current_reconnection_attempt,
)
.await
});
@@ -201,7 +204,8 @@ impl SendWithoutResponse for Client {
packet_mode: PacketMode,
) -> io::Result<()> {
trace!("Sending packet to {:?}", address);
let framed_packet = FramedSphinxPacket::new(packet, packet_mode);
let framed_packet =
FramedSphinxPacket::new(packet, packet_mode, self.config.use_legacy_version);
if let Some(sender) = self.conn_new.get_mut(&address) {
if let Err(err) = sender.channel.try_send(framed_packet) {
@@ -259,6 +263,7 @@ mod tests {
maximum_reconnection_backoff: Duration::from_millis(300_000),
initial_connection_timeout: Duration::from_millis(1_500),
maximum_connection_buffer_size: 128,
use_legacy_version: false,
})
}
@@ -24,12 +24,14 @@ impl PacketForwarder {
maximum_reconnection_backoff: Duration,
initial_connection_timeout: Duration,
maximum_connection_buffer_size: usize,
use_legacy_version: bool,
) -> (PacketForwarder, MixForwardingSender) {
let client_config = Config::new(
initial_reconnection_backoff,
maximum_reconnection_backoff,
initial_connection_timeout,
maximum_connection_buffer_size,
use_legacy_version,
);
let (packet_sender, packet_receiver) = mpsc::unbounded();
@@ -13,6 +13,7 @@ colored = "2.0"
cw3 = "0.13.1"
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-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" }
multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
vesting-contract = { path = "../../../contracts/vesting" }
@@ -22,7 +23,7 @@ reqwest = { version = "0.11", features = ["json"] }
thiserror = "1"
log = "0.4"
url = { version = "2.2", features = ["serde"] }
tokio = { version = "1.19.1", features = ["sync", "time"] }
tokio = { version = "1.21.2", features = ["sync", "time"] }
futures = "0.3"
coconut-interface = { path = "../../coconut-interface" }
+266 -295
View File
@@ -2,35 +2,33 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{validator_api, ValidatorClientError};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::MixId;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef};
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
};
#[cfg(feature = "nymd-client")]
use validator_api_requests::models::{MixNodeBondAnnotated, UptimeResponse};
use crate::nymd::traits::MixnetQueryClient;
#[cfg(feature = "nymd-client")]
use crate::nymd::{
self, error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
use crate::nymd::{self, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient};
#[cfg(feature = "nymd-client")]
use mixnet_contract_common::{
mixnode::DelegationEvent, ContractStateParams, Delegation, IdentityKey, Interval,
MixnetContractVersion, MixnodeRewardingStatusResponse, RewardedSetNodeStatus,
RewardedSetUpdateDetails,
mixnode::MixNodeBond,
pending_events::{PendingEpochEvent, PendingIntervalEvent},
Delegation, RewardedSetNodeStatus, UnbondedMixnode,
};
#[cfg(feature = "nymd-client")]
use network_defaults::NymNetworkDetails;
#[cfg(feature = "nymd-client")]
use std::collections::{HashMap, HashSet};
use validator_api_requests::models::MixNodeBondAnnotated;
#[cfg(feature = "nymd-client")]
#[must_use]
@@ -191,12 +189,9 @@ impl Client<QueryNymdClient> {
}
}
// nymd wrappers
#[cfg(feature = "nymd-client")]
impl<C> Client<C> {
pub fn change_validator_api(&mut self, new_endpoint: Url) {
self.validator_api.change_url(new_endpoint)
}
// use case: somebody initialised client without a contract in order to upload and initialise one
// and now they want to actually use it without making new client
pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) {
@@ -208,203 +203,22 @@ impl<C> Client<C> {
self.nymd.mixnet_contract_address().clone()
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
pub async fn get_cached_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes_detailed().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes_detailed().await?)
}
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_active_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes_detailed().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_contract_settings().await?)
}
pub async fn get_operator_rewards(&self, address: String) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_operator_rewards(address).await?.u128())
}
pub async fn get_delegator_rewards(
&self,
address: String,
mix_identity: IdentityKey,
proxy: Option<String>,
) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_delegator_rewards(address, mix_identity, proxy)
.await?
.u128())
}
pub async fn get_pending_delegation_events(
&self,
owner_address: String,
proxy_address: Option<String>,
) -> Result<Vec<DelegationEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_pending_delegation_events(owner_address, proxy_address)
.await?)
}
pub async fn get_current_epoch(&self) -> Result<Interval, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_epoch().await?)
}
pub async fn get_current_operator_cost(&self) -> Result<u64, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_operator_cost().await?)
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
{
self.nymd.get_mixnet_contract_version().await
}
pub async fn get_rewarding_status(
&self,
mix_identity: mixnet_contract_common::IdentityKey,
rewarding_interval_nonce: u32,
) -> Result<MixnodeRewardingStatusResponse, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_rewarding_status(mix_identity, rewarding_interval_nonce)
.await?)
}
pub async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_reward_pool().await?.u128())
}
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_circulating_supply().await?.u128())
}
pub async fn get_sybil_resistance_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_sybil_resistance_percent().await?)
}
pub async fn get_active_set_work_factor(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_active_set_work_factor().await?)
}
pub async fn get_epochs_in_interval(&self) -> Result<u64, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_epochs_in_interval().await?)
}
pub async fn get_interval_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_interval_reward_percent().await?)
}
pub async fn get_current_rewarded_set_update_details(
&self,
) -> Result<RewardedSetUpdateDetails, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.query_current_rewarded_set_update_details()
.await?)
}
// basically handles paging for us
pub async fn get_all_nymd_rewarded_set_mixnode_identities(
pub async fn get_all_nymd_rewarded_set_mixnodes(
&self,
) -> Result<Vec<(IdentityKey, RewardedSetNodeStatus)>, ValidatorClientError>
) -> Result<Vec<(MixId, RewardedSetNodeStatus)>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut identities = Vec::new();
let mut start_after = None;
let mut height = None;
loop {
let mut paged_response = self
.nymd
.get_rewarded_set_identities_paged(
start_after.take(),
self.rewarded_set_page_limit,
height,
)
.get_rewarded_set_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
identities.append(&mut paged_response.identities);
if height.is_none() {
// keep using the same height (the first query happened at the most recent height)
height = Some(paged_response.at_height)
}
identities.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
@@ -416,83 +230,122 @@ impl<C> Client<C> {
Ok(identities)
}
pub async fn get_nymd_rewarded_and_active_sets(
&self,
) -> Result<Vec<(MixNodeBond, RewardedSetNodeStatus)>, ValidatorClientError>
pub async fn get_all_nymd_mixnode_bonds(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let rewarded_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.collect::<HashMap<_, _>>();
Ok(all_mixnodes
.into_iter()
.filter_map(|node| {
rewarded_set_identities
.get(node.identity())
.map(|status| (node, *status))
})
.collect())
}
/// If you need both rewarded and the active set, consider using [Self::get_nymd_rewarded_and_active_sets] instead
pub async fn get_nymd_rewarded_set(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let rewarded_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.map(|(identity, _status)| identity)
.collect::<HashSet<_>>();
Ok(all_mixnodes
.into_iter()
.filter(|node| rewarded_set_identities.contains(node.identity()))
.collect())
}
/// If you need both rewarded and the active set, consider using [Self::get_nymd_rewarded_and_active_sets] instead
pub async fn get_nymd_active_set(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let active_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.filter_map(|(identity, status)| {
if status.is_active() {
Some(identity)
} else {
None
}
})
.collect::<HashSet<_>>();
Ok(all_mixnodes
.into_iter()
.filter(|node| active_set_identities.contains(node.identity()))
.collect())
}
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mixnodes_paged(start_after.take(), self.mixnode_page_limit)
.get_mixnode_bonds_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mixnodes_detailed_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_unbonded_mixnodes(
&self,
) -> Result<Vec<(MixId, UnbondedMixnode)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_unbonded_mixnodes_by_owner(
&self,
owner: &cosmrs::AccountId,
) -> Result<Vec<(MixId, UnbondedMixnode)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_by_owner_paged(owner, self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_unbonded_mixnodes_by_identity(
&self,
identity_key: String,
) -> Result<Vec<(MixId, UnbondedMixnode)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_by_identity_paged(
identity_key.clone(),
self.mixnode_page_limit,
start_after.take(),
)
.await?;
mixnodes.append(&mut paged_response.nodes);
@@ -508,7 +361,7 @@ impl<C> Client<C> {
pub async fn get_all_nymd_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut gateways = Vec::new();
let mut start_after = None;
@@ -531,18 +384,18 @@ impl<C> Client<C> {
pub async fn get_all_nymd_single_mixnode_delegations(
&self,
identity: IdentityKey,
mix_id: MixId,
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mix_delegations_paged(
identity.clone(),
.get_mixnode_delegations_paged(
mix_id,
start_after.take(),
self.mixnode_delegations_page_limit,
)
@@ -564,7 +417,7 @@ impl<C> Client<C> {
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
C: CosmWasmClient + Sync + Send,
{
let mut delegations = Vec::new();
let mut start_after = None;
@@ -589,10 +442,128 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_mixnode_avg_uptimes(
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_all_network_delegations_paged(
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_pending_epoch_events(
&self,
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_avg_uptimes().await?)
) -> Result<Vec<PendingEpochEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut events = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_pending_epoch_events_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
events.append(&mut paged_response.events);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(events)
}
pub async fn get_all_nymd_pending_interval_events(
&self,
) -> Result<Vec<PendingIntervalEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut events = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_pending_interval_events_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
events.append(&mut paged_response.events);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(events)
}
}
// validator-api wrappers
#[cfg(feature = "nymd-client")]
impl<C> Client<C> {
pub fn change_validator_api(&mut self, new_endpoint: Url) {
self.validator_api.change_url(new_endpoint)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
pub async fn get_cached_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes_detailed().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes_detailed().await?)
}
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_active_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes_detailed().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn blind_sign(
@@ -630,17 +601,17 @@ impl ApiClient {
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
@@ -652,7 +623,7 @@ impl ApiClient {
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
) -> Result<GatewayCoreStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_gateway_core_status_count(identity, since)
@@ -661,39 +632,39 @@ impl ApiClient {
pub async fn get_mixnode_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
) -> Result<MixnodeCoreStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_core_status_count(identity, since)
.get_mixnode_core_status_count(mix_id, since)
.await?)
}
pub async fn get_mixnode_status(
&self,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
) -> Result<MixnodeStatusResponse, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_status(identity).await?)
Ok(self.validator_api.get_mixnode_status(mix_id).await?)
}
pub async fn get_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
) -> Result<RewardEstimationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_reward_estimation(identity)
.get_mixnode_reward_estimation(mix_id)
.await?)
}
pub async fn get_mixnode_stake_saturation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
) -> Result<StakeSaturationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_stake_saturation(identity)
.get_mixnode_stake_saturation(mix_id)
.await?)
}
@@ -1,11 +1,12 @@
use crate::nymd::error::NymdError;
use crate::nymd::{Config as ClientConfig, NymdClient, QueryNymdClient};
use crate::ApiClient;
use network_defaults::all::Network;
use crate::nymd::traits::MixnetQueryClient;
use colored::Colorize;
use core::fmt;
use itertools::Itertools;
use network_defaults::NymNetworkDetails;
use std::collections::HashMap;
use std::hash::BuildHasher;
use std::time::Duration;
@@ -17,12 +18,12 @@ const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2;
// Run connection tests for all specified nymd and api urls. These are all run concurrently.
pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
nymd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
mixnet_contract_address: HashMap<NymNetworkDetails, cosmrs::AccountId, H>,
) -> (
HashMap<Network, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
) {
// Setup all the clients for the connection tests
let connection_test_clients =
@@ -45,16 +46,16 @@ pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
}
fn setup_connection_tests<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
nymd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
mixnet_contract_address: HashMap<NymNetworkDetails, cosmrs::AccountId, H>,
) -> impl Iterator<Item = ClientForConnectionTest> {
let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| {
let address = mixnet_contract_address
.get(&network)
.expect("No configured contract address")
.clone();
let config = ClientConfig::try_from_nym_network_details(&network.details())
let config = ClientConfig::try_from_nym_network_details(&network)
.expect("failed to create valid nymd client config");
if let Ok(mut client) = NymdClient::<QueryNymdClient>::connect(config, url.as_str()) {
@@ -80,7 +81,7 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
fn extract_and_collect_results_into_map(
connection_results: &[ConnectionResult],
url_type: &UrlType,
) -> HashMap<Network, Vec<(Url, bool)>> {
) -> HashMap<NymNetworkDetails, Vec<(Url, bool)>> {
connection_results
.iter()
.filter(|c| &c.url_type() == url_type)
@@ -92,7 +93,7 @@ fn extract_and_collect_results_into_map(
}
async fn test_nymd_connection(
network: Network,
network: NymNetworkDetails,
url: &Url,
client: &NymdClient<QueryNymdClient>,
) -> ConnectionResult {
@@ -104,56 +105,47 @@ async fn test_nymd_connection(
{
Ok(Err(NymdError::TendermintError(e))) => {
// If we get a tendermint-rpc error, we classify the node as not contactable
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {}",
"failed".red(),
e
);
log::debug!("Checking: nymd_url: {url}: {}: {}", "failed".red(), e);
false
}
Ok(Err(NymdError::AbciError(code, log))) => {
// We accept the mixnet contract not found as ok from a connection standpoint. This happens
// for example on a pre-launch network.
log::debug!(
"Checking: nymd_url: {network}: {url}: {}, but with abci error: {code}: {log}",
"Checking: nymd_url: {url}: {}, but with abci error: {code}: {log}",
"success".green()
);
code == 18
}
Ok(Err(error @ NymdError::NoContractAddressAvailable)) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {error}",
"failed".red()
);
log::debug!("Checking: nymd_url: {url}: {}: {error}", "failed".red());
false
}
Ok(Err(e)) => {
// For any other error, we're optimistic and just try anyway.
log::debug!(
"Checking: nymd_url: {network}: {url}: {}, but with error: {e}",
"Checking: nymd_url: {url}: {}, but with error: {e}",
"success".green()
);
true
}
Ok(Ok(_)) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}",
"success".green()
);
log::debug!("Checking: nymd_url: {url}: {}", "success".green());
true
}
Err(e) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {e}",
"failed".red()
);
log::debug!("Checking: nymd_url: {url}: {}: {e}", "failed".red());
false
}
};
ConnectionResult::Nymd(network, url.clone(), result)
}
async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> ConnectionResult {
async fn test_api_connection(
network: NymNetworkDetails,
url: &Url,
client: &ApiClient,
) -> ConnectionResult {
let result = match timeout(
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
client.get_cached_mixnodes(),
@@ -161,21 +153,15 @@ async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) ->
.await
{
Ok(Ok(_)) => {
log::debug!("Checking: api_url: {network}: {url}: {}", "success".green());
log::debug!("Checking: api_url: {url}: {}", "success".green());
true
}
Ok(Err(e)) => {
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
false
}
Err(e) => {
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
false
}
};
@@ -183,8 +169,8 @@ async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) ->
}
enum ClientForConnectionTest {
Nymd(Network, Url, Box<NymdClient<QueryNymdClient>>),
Api(Network, Url, ApiClient),
Nymd(NymNetworkDetails, Url, Box<NymdClient<QueryNymdClient>>),
Api(NymNetworkDetails, Url, ApiClient),
}
impl ClientForConnectionTest {
@@ -217,12 +203,12 @@ impl fmt::Display for UrlType {
#[derive(Debug)]
enum ConnectionResult {
Nymd(Network, Url, bool),
Api(Network, Url, bool),
Nymd(NymNetworkDetails, Url, bool),
Api(NymNetworkDetails, Url, bool),
}
impl ConnectionResult {
fn result(&self) -> (&Network, &Url, &bool) {
fn result(&self) -> (&NymNetworkDetails, &Url, &bool) {
match self {
ConnectionResult::Nymd(network, url, result)
| ConnectionResult::Api(network, url, result) => (network, url, result),
@@ -239,11 +225,8 @@ impl ConnectionResult {
impl fmt::Display for ConnectionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (network, url, result) = self.result();
let (_network, url, result) = self.result();
let url_type = self.url_type();
write!(
f,
"{network}: {url}: {url_type}: connection is successful: {result}"
)
write!(f, "{url}: {url_type}: connection is successful: {result}")
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,388 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::error::NymdError;
use crate::nymd::NymdClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use mixnet_contract_common::delegation::{MixNodeDelegationResponse, OwnerProxySubKey};
use mixnet_contract_common::mixnode::{
MixNodeDetails, MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse,
PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse,
};
use mixnet_contract_common::reward_params::{Performance, RewardingParams};
use mixnet_contract_common::rewarding::{
EstimatedCurrentEpochRewardResponse, PendingRewardResponse,
};
use mixnet_contract_common::{
delegation, ContractBuildInformation, ContractState, ContractStateParams,
CurrentIntervalResponse, EpochEventId, GatewayBondResponse, GatewayOwnershipResponse,
IdentityKey, IntervalEventId, LayerDistribution, MixId, MixOwnershipResponse,
MixnodeDetailsResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedGatewayResponse, PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse,
PagedRewardedSetResponse, PendingEpochEventsResponse, PendingIntervalEventsResponse,
QueryMsg as MixnetQueryMsg,
};
use serde::Deserialize;
#[async_trait]
pub trait MixnetQueryClient {
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>;
// state/sys-params-related
async fn get_mixnet_contract_version(&self) -> Result<ContractBuildInformation, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetContractVersion {})
.await
}
async fn get_rewarding_validator_address(&self) -> Result<AccountId, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardingValidatorAddress {})
.await
}
async fn get_mixnet_contract_settings(&self) -> Result<ContractStateParams, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStateParams {})
.await
}
async fn get_mixnet_contract_state(&self) -> Result<ContractState, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetState {})
.await
}
async fn get_rewarding_parameters(&self) -> Result<RewardingParams, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardingParams {})
.await
}
async fn get_current_interval_details(&self) -> Result<CurrentIntervalResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetCurrentIntervalDetails {})
.await
}
async fn get_rewarded_set_paged(
&self,
start_after: Option<MixId>,
limit: Option<u32>,
) -> Result<PagedRewardedSetResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardedSet { limit, start_after })
.await
}
// mixnode-related:
async fn get_mixnode_bonds_paged(
&self,
limit: Option<u32>,
start_after: Option<MixId>,
) -> Result<PagedMixnodeBondsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodeBonds { limit, start_after })
.await
}
async fn get_mixnodes_detailed_paged(
&self,
limit: Option<u32>,
start_after: Option<MixId>,
) -> Result<PagedMixnodesDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodesDetailed { limit, start_after })
.await
}
async fn get_unbonded_paged(
&self,
limit: Option<u32>,
start_after: Option<MixId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodes { limit, start_after })
.await
}
async fn get_unbonded_by_owner_paged(
&self,
owner: &AccountId,
limit: Option<u32>,
start_after: Option<MixId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByOwner {
owner: owner.to_string(),
limit,
start_after,
})
.await
}
async fn get_unbonded_by_identity_paged(
&self,
identity_key: String,
limit: Option<u32>,
start_after: Option<MixId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByIdentityKey {
identity_key,
limit,
start_after,
})
.await
}
async fn get_owned_mixnode(
&self,
address: &AccountId,
) -> Result<MixOwnershipResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetOwnedMixnode {
address: address.to_string(),
})
.await
}
async fn get_mixnode_details(
&self,
mix_id: MixId,
) -> Result<MixnodeDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDetails { mix_id })
.await
}
async fn get_mixnode_rewarding_details(
&self,
mix_id: MixId,
) -> Result<MixnodeRewardingDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeRewardingDetails { mix_id })
.await
}
async fn get_mixnode_stake_saturation(
&self,
mix_id: MixId,
) -> Result<StakeSaturationResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStakeSaturation { mix_id })
.await
}
async fn get_unbonded_mixnode_information(
&self,
mix_id: MixId,
) -> Result<UnbondedMixnodeResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodeInformation { mix_id })
.await
}
async fn get_layer_distribution(&self) -> Result<LayerDistribution, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetLayerDistribution {})
.await
}
// gateway-related:
async fn get_gateways_paged(
&self,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> Result<PagedGatewayResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetGateways { start_after, limit })
.await
}
/// Checks whether there is a bonded gateway associated with the provided identity key
async fn get_gateway_bond(
&self,
identity: IdentityKey,
) -> Result<GatewayBondResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetGatewayBond { identity })
.await
}
/// Checks whether there is a bonded gateway associated with the provided client's address
async fn get_owned_gateway(
&self,
address: &AccountId,
) -> Result<GatewayOwnershipResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetOwnedGateway {
address: address.to_string(),
})
.await
}
// delegation-related:
/// Gets list of all delegations towards particular mixnode on particular page.
async fn get_mixnode_delegations_paged(
&self,
mix_id: MixId,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<PagedMixNodeDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDelegations {
mix_id,
start_after,
limit,
})
.await
}
/// Gets list of all the mixnodes to which a particular address delegated.
async fn get_delegator_delegations_paged(
&self,
delegator: String,
start_after: Option<(MixId, OwnerProxySubKey)>,
limit: Option<u32>,
) -> Result<PagedDelegatorDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetDelegatorDelegations {
delegator,
start_after,
limit,
})
.await
}
/// Checks value of delegation of given client towards particular mixnode.
async fn get_delegation_details(
&self,
mix_id: MixId,
delegator: &AccountId,
proxy: Option<String>,
) -> Result<MixNodeDelegationResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetDelegationDetails {
mix_id,
delegator: delegator.to_string(),
proxy,
})
.await
}
/// Gets all the delegations on the entire network
async fn get_all_network_delegations_paged(
&self,
start_after: Option<delegation::StorageKey>,
limit: Option<u32>,
) -> Result<PagedAllDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetAllDelegations { start_after, limit })
.await
}
// rewards related
async fn get_pending_operator_reward(
&self,
operator: &AccountId,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingOperatorReward {
address: operator.to_string(),
})
.await
}
async fn get_pending_mixnode_operator_reward(
&self,
mix_id: MixId,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingMixNodeOperatorReward { mix_id })
.await
}
async fn get_pending_delegator_reward(
&self,
delegator: &AccountId,
mix_id: MixId,
proxy: Option<String>,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingDelegatorReward {
address: delegator.to_string(),
mix_id,
proxy,
})
.await
}
// given the provided performance, estimate the reward at the end of the current epoch
async fn get_estimated_current_epoch_operator_reward(
&self,
mix_id: MixId,
estimated_performance: Performance,
) -> Result<EstimatedCurrentEpochRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochOperatorReward {
mix_id,
estimated_performance,
})
.await
}
// given the provided performance, estimate the reward at the end of the current epoch
async fn get_estimated_current_epoch_delegator_reward(
&self,
delegator: &AccountId,
mix_id: MixId,
proxy: Option<String>,
estimated_performance: Performance,
) -> Result<EstimatedCurrentEpochRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochDelegatorReward {
address: delegator.to_string(),
mix_id,
proxy,
estimated_performance,
})
.await
}
// interval-related
async fn get_pending_epoch_events_paged(
&self,
start_after: Option<EpochEventId>,
limit: Option<u32>,
) -> Result<PendingEpochEventsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingEpochEvents { start_after, limit })
.await
}
async fn get_pending_interval_events_paged(
&self,
start_after: Option<IntervalEventId>,
limit: Option<u32>,
) -> Result<PendingIntervalEventsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingIntervalEvents { start_after, limit })
.await
}
async fn get_mixnode_details_by_identity(
&self,
mix_identity: IdentityKey,
) -> Result<Option<MixNodeDetails>, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
mix_identity,
})
.await
}
}
#[async_trait]
impl<C> MixnetQueryClient for NymdClient<C>
where
C: CosmWasmClient + Sync + Send,
{
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(self.mixnet_contract_address(), &query)
.await
}
}
#[async_trait]
impl<C> MixnetQueryClient for crate::Client<C>
where
C: CosmWasmClient + Sync + Send,
{
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>,
{
self.nymd.query_mixnet_contract(query).await
}
}
@@ -0,0 +1,496 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::coin::Coin;
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Fee, NymdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use cosmrs::AccountId;
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance};
use mixnet_contract_common::{
ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, MixId, MixNode,
};
#[async_trait]
pub trait MixnetSigningClient {
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>;
// state/sys-params-related
async fn update_rewarding_validator_address(
&self,
address: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateRewardingValidatorAddress {
address: address.to_string(),
},
vec![],
)
.await
}
async fn update_contract_state_params(
&self,
updated_parameters: ContractStateParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateContractStateParams { updated_parameters },
vec![],
)
.await
}
async fn update_active_set_size(
&self,
active_set_size: u32,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateActiveSetSize {
active_set_size,
force_immediately,
},
vec![],
)
.await
}
async fn update_rewarding_parameters(
&self,
updated_params: IntervalRewardingParamsUpdate,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateRewardingParams {
updated_params,
force_immediately,
},
vec![],
)
.await
}
async fn update_interval_config(
&self,
epochs_in_interval: u32,
epoch_duration_secs: u64,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateIntervalConfig {
epochs_in_interval,
epoch_duration_secs,
force_immediately,
},
vec![],
)
.await
}
async fn advance_current_epoch(
&self,
new_rewarded_set: Vec<MixId>,
expected_active_set_size: u32,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::AdvanceCurrentEpoch {
new_rewarded_set,
expected_active_set_size,
},
vec![],
)
.await
}
async fn reconcile_epoch_events(
&self,
limit: Option<u32>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::ReconcileEpochEvents { limit },
vec![],
)
.await
}
// mixnode-related:
async fn bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
},
vec![pledge],
)
.await
}
async fn bond_mixnode_on_behalf(
&self,
owner: AccountId,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondMixnodeOnBehalf {
mix_node,
cost_params,
owner_signature,
owner: owner.to_string(),
},
vec![pledge],
)
.await
}
async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
.await
}
async fn unbond_mixnode_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UnbondMixnodeOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeCostParams { new_costs },
vec![],
)
.await
}
async fn update_mixnode_cost_params_on_behalf(
&self,
owner: AccountId,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeCostParamsOnBehalf {
new_costs,
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn update_mixnode_config(
&self,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeConfig { new_config },
vec![],
)
.await
}
async fn update_mixnode_config_on_behalf(
&self,
owner: AccountId,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeConfigOnBehalf {
new_config,
owner: owner.to_string(),
},
vec![],
)
.await
}
// gateway-related:
async fn bond_gateway(
&self,
gateway: Gateway,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondGateway {
gateway,
owner_signature,
},
vec![pledge],
)
.await
}
async fn bond_gateway_on_behalf(
&self,
owner: AccountId,
gateway: Gateway,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondGatewayOnBehalf {
gateway,
owner_signature,
owner: owner.to_string(),
},
vec![pledge],
)
.await
}
async fn unbond_gateway(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondGateway {}, vec![])
.await
}
async fn unbond_gateway_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UnbondGatewayOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
// delegation-related:
async fn delegate_to_mixnode(
&self,
mix_id: MixId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DelegateToMixnode { mix_id },
vec![amount],
)
.await
}
async fn delegate_to_mixnode_on_behalf(
&self,
delegate: AccountId,
mix_id: MixId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DelegateToMixnodeOnBehalf {
mix_id,
delegate: delegate.to_string(),
},
vec![amount],
)
.await
}
async fn undelegate_from_mixnode(
&self,
mix_id: MixId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UndelegateFromMixnode { mix_id },
vec![],
)
.await
}
async fn undelegate_to_mixnode_on_behalf(
&self,
delegate: AccountId,
mix_id: MixId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UndelegateFromMixnodeOnBehalf {
mix_id,
delegate: delegate.to_string(),
},
vec![],
)
.await
}
// reward-related
async fn reward_mixnode(
&self,
mix_id: MixId,
performance: Performance,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::RewardMixnode {
mix_id,
performance,
},
vec![],
)
.await
}
async fn withdraw_operator_reward(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::WithdrawOperatorReward {}, vec![])
.await
}
async fn withdraw_operator_reward_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawOperatorRewardOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn withdraw_delegator_reward(
&self,
mix_id: MixId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawDelegatorReward { mix_id },
vec![],
)
.await
}
async fn withdraw_delegator_reward_on_behalf(
&self,
owner: AccountId,
mix_id: MixId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf {
mix_id,
owner: owner.to_string(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> MixnetSigningClient for NymdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.mixnet_contract_address(),
&msg,
fee,
memo,
funds,
)
.await
}
}
#[async_trait]
impl<C> MixnetSigningClient for crate::Client<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
self.nymd.execute_mixnet_contract(fee, msg, funds).await
}
}
@@ -1,8 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod coconut_bandwidth_query_client;
mod coconut_bandwidth_signing_client;
mod mixnet_query_client;
mod mixnet_signing_client;
mod multisig_query_client;
mod multisig_signing_client;
mod vesting_query_client;
@@ -10,6 +12,8 @@ mod vesting_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use mixnet_query_client::MixnetQueryClient;
pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_query_client::MultisigQueryClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use vesting_query_client::VestingQueryClient;
@@ -6,8 +6,10 @@ pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::error::NymdError;
use crate::nymd::NymdClient;
use async_trait::async_trait;
use contracts_common::ContractBuildInformation;
use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp};
use mixnet_contract_common::IdentityKey;
use mixnet_contract_common::MixId;
use serde::Deserialize;
use vesting_contract::vesting::Account;
use vesting_contract_common::{
messages::QueryMsg as VestingQueryMsg, AllDelegationsResponse, DelegationTimesResponse,
@@ -16,6 +18,15 @@ use vesting_contract_common::{
#[async_trait]
pub trait VestingQueryClient {
async fn query_vesting_contract<T>(&self, query: VestingQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>;
async fn get_vesting_contract_version(&self) -> Result<ContractBuildInformation, NymdError> {
self.query_vesting_contract(VestingQueryMsg::GetContractVersion {})
.await
}
async fn locked_coins(
&self,
address: &str,
@@ -76,12 +87,12 @@ pub trait VestingQueryClient {
async fn get_delegation_timestamps(
&self,
address: &str,
mix_identity: String,
mix_id: MixId,
) -> Result<DelegationTimesResponse, NymdError>;
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, IdentityKey, u64)>,
start_after: Option<(u32, MixId, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NymdError>;
@@ -107,6 +118,15 @@ pub trait VestingQueryClient {
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn query_vesting_contract<T>(&self, query: VestingQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(self.vesting_contract_address(), &query)
.await
}
async fn locked_coins(
&self,
vesting_account_address: &str,
@@ -269,11 +289,11 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn get_delegation_timestamps(
&self,
address: &str,
mix_identity: String,
mix_id: MixId,
) -> Result<DelegationTimesResponse, NymdError> {
let request = VestingQueryMsg::GetDelegationTimes {
address: address.to_string(),
mix_identity,
mix_id,
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
@@ -282,7 +302,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, IdentityKey, u64)>,
start_after: Option<(u32, MixId, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NymdError> {
let request = VestingQueryMsg::GetAllDelegations { start_after, limit };
@@ -6,14 +6,29 @@ use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Coin, Fee, NymdClient};
use async_trait::async_trait;
use mixnet_contract_common::{Gateway, IdentityKey, IdentityKeyRef, MixNode};
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::{Gateway, MixId, MixNode};
use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification};
use vesting_contract_common::PledgeCap;
#[async_trait]
pub trait VestingSigningClient {
async fn execute_vesting_contract(
&self,
fee: Option<Fee>,
msg: VestingExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_config(
&self,
profix_margin_percent: u8,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
@@ -43,10 +58,12 @@ pub trait VestingSigningClient {
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_mixnode(
@@ -65,21 +82,21 @@ pub trait VestingSigningClient {
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
mix_id: MixId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_delegate_to_mixnode<'a>(
async fn vesting_delegate_to_mixnode(
&self,
mix_identity: IdentityKeyRef<'a>,
mix_id: MixId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_undelegate_from_mixnode<'a>(
async fn vesting_undelegate_from_mixnode(
&self,
mix_identity: IdentityKeyRef<'a>,
mix_id: MixId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
@@ -89,21 +106,53 @@ pub trait VestingSigningClient {
staking_address: Option<String>,
vesting_spec: Option<VestingSpecification>,
amount: Coin,
cap: Option<PledgeCap>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient<C> {
async fn execute_vesting_contract(
&self,
fee: Option<Fee>,
msg: VestingExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.name().to_string();
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&msg,
fee,
memo,
funds,
)
.await
}
async fn vesting_update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::UpdateMixnodeCostParams { new_costs },
vec![],
)
.await
}
async fn vesting_update_mixnode_config(
&self,
profit_margin_percent: u8,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
};
let req = VestingExecuteMsg::UpdateMixnodeConfig { new_config };
self.client
.execute(
self.address(),
@@ -203,26 +252,22 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::BondMixnode {
mix_node,
owner_signature: owner_signature.to_string(),
amount: pledge.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::BondMixnode",
vec![],
)
.await
self.execute_vesting_contract(
fee,
VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature: owner_signature.to_string(),
amount: pledge.into(),
},
vec![],
)
.await
}
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
@@ -262,6 +307,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn withdraw_vested_coins(
&self,
amount: Coin,
@@ -282,72 +328,54 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
mix_id: MixId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_identity,
amount: amount.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::TrackUndelegation",
vec![],
)
.await
}
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.into(),
amount: amount.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::DelegateToMixnode",
vec![],
)
.await
self.execute_vesting_contract(
fee,
VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_id,
amount: amount.into(),
},
vec![],
)
.await
}
async fn vesting_undelegate_from_mixnode<'a>(
async fn vesting_delegate_to_mixnode(
&self,
mix_identity: IdentityKeyRef<'a>,
mix_id: MixId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UndelegateFromMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::UndelegateFromMixnode",
vec![],
)
.await
self.execute_vesting_contract(
fee,
VestingExecuteMsg::DelegateToMixnode {
mix_id,
amount: amount.into(),
},
vec![],
)
.await
}
async fn vesting_undelegate_from_mixnode(
&self,
mix_id: MixId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::UndelegateFromMixnode { mix_id },
vec![],
)
.await
}
async fn create_periodic_vesting_account(
@@ -356,6 +384,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
staking_address: Option<String>,
vesting_spec: Option<VestingSpecification>,
amount: Coin,
cap: Option<PledgeCap>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
@@ -363,6 +392,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
owner_address: owner_address.to_string(),
staking_address,
vesting_spec,
cap,
};
self.client
.execute(
@@ -72,7 +72,7 @@ impl DirectSecp256k1HdWallet {
}
fn derive_keypair(&self, hd_path: &DerivationPath) -> Result<Secp256k1Keypair, NymdError> {
let extended_private_key = XPrv::derive_from_path(&self.seed, hd_path)?;
let extended_private_key = XPrv::derive_from_path(self.seed, hd_path)?;
let private_key: SigningKey = extended_private_key.into();
let public_key = private_key.public_key();
@@ -207,8 +207,9 @@ impl DirectSecp256k1HdWalletBuilder {
#[cfg(test)]
mod tests {
use network_defaults::NymNetworkDetails;
use super::*;
use network_defaults::all::Network::*;
#[test]
fn generating_account_addresses() {
@@ -218,7 +219,9 @@ mod tests {
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
];
let prefix = MAINNET.bech32_prefix();
let prefix = NymNetworkDetails::new_mainnet()
.chain_details
.bech32_account_prefix;
let addrs = vec![
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
@@ -1,4 +1,5 @@
use thiserror::Error;
use validator_api_requests::models::RequestError;
#[derive(Error, Debug)]
pub enum ValidatorAPIError {
@@ -10,4 +11,7 @@ pub enum ValidatorAPIError {
#[error("Request failed with error message - {0}")]
GenericRequestFailure(String),
#[error("The validator API has failed to resolve our request. It returned status code {status} and additional error message: {}", error.message())]
ApiRequestFailure { status: u16, error: RequestError },
}
@@ -3,17 +3,20 @@
use crate::validator_api::error::ValidatorAPIError;
use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use reqwest::Response;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
CoreNodeStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
};
pub mod error;
@@ -46,6 +49,19 @@ impl Client {
&self.url
}
async fn send_get_request<K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<Response, ValidatorAPIError>
where
K: AsRef<str>,
V: AsRef<str>,
{
let url = create_api_url(&self.url, path, params);
Ok(self.reqwest_client.get(url).send().await?)
}
async fn query_validator_api<T, K, V>(
&self,
path: PathSegments<'_>,
@@ -56,8 +72,36 @@ impl Client {
K: AsRef<str>,
V: AsRef<str>,
{
let url = create_api_url(&self.url, path, params);
Ok(self.reqwest_client.get(url).send().await?.json().await?)
let res = self.send_get_request(path, params).await?;
if res.status().is_success() {
Ok(res.json().await?)
} else {
Err(ValidatorAPIError::GenericRequestFailure(res.text().await?))
}
}
// This works for endpoints returning Result<Json<T>, ErrorResponse>
async fn query_validator_api_fallible<T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, ValidatorAPIError>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let res = self.send_get_request(path, params).await?;
let status = res.status();
if res.status().is_success() {
Ok(res.json().await?)
} else {
let request_error: RequestError = res.json().await?;
Err(ValidatorAPIError::ApiRequestFailure {
status: status.as_u16(),
error: request_error,
})
}
}
async fn post_validator_api<B, T, K, V>(
@@ -83,7 +127,7 @@ impl Client {
}
}
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
.await
}
@@ -103,7 +147,7 @@ impl Client {
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
NO_PARAMS,
@@ -126,7 +170,7 @@ impl Client {
.await
}
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
NO_PARAMS,
@@ -134,6 +178,74 @@ impl Client {
.await
}
pub async fn get_mixnode_report(
&self,
mix_id: MixId,
) -> Result<MixnodeStatusReportResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS,
routes::MIXNODE,
&mix_id.to_string(),
routes::REPORT,
],
NO_PARAMS,
)
.await
}
pub async fn get_gateway_report(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<GatewayStatusReportResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS,
routes::GATEWAY,
identity,
routes::REPORT,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_history(
&self,
mix_id: MixId,
) -> Result<MixnodeUptimeHistoryResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS,
routes::MIXNODE,
&mix_id.to_string(),
routes::HISTORY,
],
NO_PARAMS,
)
.await
}
pub async fn get_gateway_history(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<GatewayUptimeHistoryResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS,
routes::GATEWAY,
identity,
routes::HISTORY,
],
NO_PARAMS,
)
.await
}
pub async fn get_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorAPIError> {
@@ -149,28 +261,11 @@ impl Client {
.await
}
pub async fn get_probs_mixnode_rewarded(
&self,
mixnode_id: &str,
) -> Result<HashMap<String, f32>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::MIXNODES,
routes::REWARDED,
routes::INCLUSION_CHANCE,
mixnode_id,
],
NO_PARAMS,
)
.await
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
) -> Result<GatewayCoreStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
@@ -199,16 +294,16 @@ impl Client {
pub async fn get_mixnode_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
) -> Result<MixnodeCoreStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
CORE_STATUS_COUNT,
],
&[(SINCE_ARG, since.to_string())],
@@ -220,7 +315,8 @@ impl Client {
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
CORE_STATUS_COUNT,
],
NO_PARAMS,
)
@@ -230,14 +326,14 @@ impl Client {
pub async fn get_mixnode_status(
&self,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
) -> Result<MixnodeStatusResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::STATUS,
],
NO_PARAMS,
@@ -247,14 +343,14 @@ impl Client {
pub async fn get_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
self.query_validator_api(
self.query_validator_api_fallible(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::REWARD_ESTIMATION,
],
NO_PARAMS,
@@ -264,14 +360,14 @@ impl Client {
pub async fn get_mixnode_stake_saturation(
&self,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
) -> Result<StakeSaturationResponse, ValidatorAPIError> {
self.query_validator_api(
self.query_validator_api_fallible(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::STAKE_SATURATION,
],
NO_PARAMS,
@@ -281,14 +377,14 @@ impl Client {
pub async fn get_mixnode_inclusion_probability(
&self,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
) -> Result<InclusionProbabilityResponse, ValidatorAPIError> {
self.query_validator_api(
self.query_validator_api_fallible(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
&mix_id.to_string(),
routes::INCLUSION_CHANCE,
],
NO_PARAMS,
@@ -298,27 +394,14 @@ impl Client {
pub async fn get_mixnode_avg_uptime(
&self,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
) -> Result<UptimeResponse, ValidatorAPIError> {
self.query_validator_api(
self.query_validator_api_fallible(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::AVG_UPTIME,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_avg_uptimes(&self) -> Result<Vec<UptimeResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODES,
&mix_id.to_string(),
routes::AVG_UPTIME,
],
NO_PARAMS,
@@ -10,7 +10,6 @@ pub const GATEWAYS: &str = "gateways";
pub const DETAILED: &str = "detailed";
pub const ACTIVE: &str = "active";
pub const REWARDED: &str = "rewarded";
pub const COCONUT_ROUTES: &str = "coconut";
pub const BANDWIDTH: &str = "bandwidth";
@@ -28,6 +27,8 @@ pub const CORE_STATUS_COUNT: &str = "core-status-count";
pub const SINCE_ARG: &str = "since";
pub const STATUS: &str = "status";
pub const REPORT: &str = "report";
pub const HISTORY: &str = "history";
pub const REWARD_ESTIMATION: &str = "reward-estimation";
pub const AVG_UPTIME: &str = "avg_uptime";
pub const STAKE_SATURATION: &str = "stake-saturation";
+5
View File
@@ -28,6 +28,11 @@ pub fn pretty_cosmwasm_coin(coin: &CosmWasmCoin) -> String {
format!("{} {}", amount, denom)
}
pub fn pretty_decimal_with_denom(value: Decimal, denom: &str) -> String {
// TODO: we might have to truncate the value here (that's why I moved it to separate function)
format!("{} {}", value, denom)
}
pub fn show_error<E>(e: E)
where
E: Display,
@@ -51,7 +51,7 @@ pub async fn query_balance(
return;
}
let denom = args.denom.unwrap_or_else(|| "".to_string());
let denom = args.denom.unwrap_or_default();
for coin in coins {
if denom.is_empty() || denom.eq_ignore_ascii_case(&coin.denom) {
@@ -4,12 +4,16 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::Coin;
use mixnet_contract_common::{Coin, MixId};
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<MixId>,
#[clap(long)]
pub identity_key: Option<String>,
#[clap(long)]
pub amount: u128,
@@ -20,10 +24,25 @@ pub async fn delegate_to_mixnode(args: Args, client: SigningClient) {
info!("Starting delegation to mixnode");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let coin = Coin::new(args.amount, denom);
let res = client
.delegate_to_mixnode(&*args.identity_key, coin.into(), None)
.delegate_to_mixnode(mix_id, coin.into(), None)
.await
.expect("failed to delegate to mixnode!");
@@ -8,8 +8,8 @@ use crate::context::SigningClientWithValidatorAPI;
use crate::utils::{pretty_cosmwasm_coin, show_error_passthrough};
use comfy_table::Table;
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::Delegation;
use cosmwasm_std::Addr;
use mixnet_contract_common::{Delegation, PendingEpochEvent, PendingEpochEventKind};
#[derive(Debug, Parser)]
pub struct Args {}
@@ -26,19 +26,7 @@ pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) {
.map_err(show_error_passthrough);
let mixnet_contract_events = client
.nymd
.get_pending_delegation_events(client.nymd.address().to_string(), None)
.await
.map_err(show_error_passthrough);
let vesting_contract = client.nymd.vesting_contract_address();
let vesting_contract_events = client
.nymd
.get_pending_delegation_events(
client.nymd.address().to_string(),
Some(vesting_contract.to_string()),
)
.get_all_nymd_pending_epoch_events()
.await
.map_err(show_error_passthrough);
@@ -58,13 +46,6 @@ pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) {
print_delegation_events(res, &client).await;
}
}
if let Ok(res) = vesting_contract_events {
if !res.is_empty() {
println!();
println!("Pending delegations (locked tokens):");
print_delegation_events(res, &client).await;
}
}
}
async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidatorAPI) -> String {
@@ -77,14 +58,17 @@ async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidator
async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientWithValidatorAPI) {
let mut table = Table::new();
table.set_header(vec!["Timestamp", "Identity Key", "Delegation", "Proxy"]);
table.set_header(vec!["Timestamp", "Mix Id", "Delegation", "Proxy"]);
for delegation in delegations {
table.add_row(vec![
to_iso_timestamp(delegation.block_height as u32, client).await,
delegation.node_identity.to_string(),
to_iso_timestamp(delegation.height as u32, client).await,
delegation.mix_id.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
format!("{:?}", delegation.proxy),
delegation
.proxy
.map(Addr::into_string)
.unwrap_or_else(|| "-".into()),
]);
}
@@ -92,36 +76,53 @@ async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientW
}
async fn print_delegation_events(
events: Vec<DelegationEvent>,
events: Vec<PendingEpochEvent>,
client: &SigningClientWithValidatorAPI,
) {
let mut table = Table::new();
table.set_header(vec![
"Timestamp",
"Identity Key",
"Mix id",
"Delegation",
"Event Type",
"Proxy",
]);
for event in events {
match event {
DelegationEvent::Delegate(delegation) => {
table.add_row(vec![
to_iso_timestamp(delegation.block_height as u32, client).await,
delegation.node_identity.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
"Delegate".to_string(),
]);
match event.event.kind {
PendingEpochEventKind::Delegate {
owner,
mix_id,
amount,
proxy,
} => {
if owner.as_str() == client.nymd.address().as_ref() {
table.add_row(vec![
"not-sure-if-applicable".into(),
mix_id.to_string(),
pretty_cosmwasm_coin(&amount),
"Delegate".to_string(),
proxy.map(Addr::into_string).unwrap_or_else(|| "-".into()),
]);
}
}
DelegationEvent::Undelegate(undelegate) => {
table.add_row(vec![
to_iso_timestamp(undelegate.block_height() as u32, client).await,
undelegate.mix_identity().to_string(),
"-".to_string(),
"Undelegate".to_string(),
]);
PendingEpochEventKind::Undelegate {
owner,
mix_id,
proxy,
} => {
if owner.as_str() == client.nymd.address().as_ref() {
table.add_row(vec![
"not-sure-if-applicable".into(),
mix_id.to_string(),
"-".to_string(),
"Undelegate".to_string(),
proxy.map(Addr::into_string).unwrap_or_else(|| "-".into()),
]);
}
}
_ => {}
}
}
@@ -4,18 +4,38 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::MixId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<MixId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn claim_delegator_reward(args: Args, client: SigningClient) {
info!("Claim delegator reward");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.execute_claim_delegator_reward(args.identity_key, None)
.withdraw_delegator_reward(mix_id, None)
.await
.expect("failed to claim delegator-reward");
@@ -4,18 +4,38 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::MixId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity: String,
pub mix_id: Option<MixId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn vesting_claim_delegator_reward(args: Args, client: SigningClient) {
info!("Claim vesting delegator reward");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.execute_vesting_claim_delegator_reward(args.identity, None)
.withdraw_delegator_reward_on_behalf(client.address().clone(), mix_id, None)
.await
.expect("failed to claim vesting delegator-reward");
@@ -4,18 +4,38 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::MixId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<MixId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn undelegate_from_mixnode(args: Args, client: SigningClient) {
info!("removing stake from mix-node");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.remove_mixnode_delegation(&*args.identity_key, None)
.undelegate_from_mixnode(mix_id, None)
.await
.expect("failed to remove stake from mixnode!");
@@ -4,7 +4,8 @@
use clap::Parser;
use log::info;
use mixnet_contract_common::Coin;
use mixnet_contract_common::{Coin, MixId};
use validator_client::nymd::traits::MixnetQueryClient;
use validator_client::nymd::VestingSigningClient;
use crate::context::SigningClient;
@@ -12,7 +13,10 @@ use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<MixId>,
#[clap(long)]
pub identity_key: Option<String>,
#[clap(long)]
pub amount: u128,
@@ -23,10 +27,25 @@ pub async fn vesting_delegate_to_mixnode(args: Args, client: SigningClient) {
info!("Starting vesting delegation to mixnode");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_delegate_to_mixnode(&*args.identity_key, coin.into(), None)
.vesting_delegate_to_mixnode(mix_id, coin.into(), None)
.await
.expect("failed to delegate to mixnode!");
@@ -3,7 +3,8 @@
use clap::Parser;
use log::info;
use mixnet_contract_common::MixId;
use validator_client::nymd::traits::MixnetQueryClient;
use validator_client::nymd::VestingSigningClient;
use crate::context::SigningClient;
@@ -11,14 +12,32 @@ use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub identity_key: String,
pub mix_id: Option<MixId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn vesting_undelegate_from_mixnode(args: Args, client: SigningClient) {
info!("removing stake from vesting mix-node");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.vesting_undelegate_from_mixnode(&*args.identity_key, None)
.vesting_undelegate_from_mixnode(mix_id, None)
.await
.expect("failed to remove stake from vesting account on mixnode!");
@@ -6,6 +6,7 @@ use clap::Parser;
use log::{info, warn};
use mixnet_contract_common::Coin;
use network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -68,7 +68,7 @@ pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_bond_gateway(gateway, &*args.signature, coin.into(), None)
.vesting_bond_gateway(gateway, &args.signature, coin.into(), None)
.await
.expect("failed to bond gateway!");
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -2,12 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use mixnet_contract_common::Coin;
use mixnet_contract_common::{Coin, MixNodeCostParams, Percent};
use network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use validator_client::nymd::traits::MixnetSigningClient;
use validator_client::nymd::CosmWasmCoin;
use crate::context::SigningClient;
@@ -40,6 +43,12 @@ pub struct Args {
#[clap(long)]
pub profit_margin_percent: Option<u8>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
@@ -71,13 +80,23 @@ pub async fn bond_mixnode(args: Args, client: SigningClient) {
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
profit_margin_percent: args.profit_margin_percent.unwrap_or(10),
};
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
},
};
let res = client
.bond_mixnode(mixnode, args.signature, coin.into(), None)
.bond_mixnode(mixnode, cost_params, args.signature, coin.into(), None)
.await
.expect("failed to bond mixnode!");
@@ -4,6 +4,7 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -12,7 +13,7 @@ pub async fn claim_operator_reward(_args: Args, client: SigningClient) {
info!("Claim operator reward");
let res = client
.execute_claim_operator_reward(None)
.withdraw_operator_reward(None)
.await
.expect("failed to claim operator reward");

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