Compare commits

...

156 Commits

Author SHA1 Message Date
Simon Wicky e0b8638163 add cover timing and ack logging 2023-04-20 09:09:12 +00:00
Jon Häggblad fdb5727e5a Rework Poisson process throttling 2023-04-17 13:02:40 +00:00
Simon Wicky 3da131de15 add packets logging 2023-04-17 14:44:05 +02:00
farbanas ba3a6eaeb5 Merge branch 'release/v1.1.14' 2023-04-04 11:38:40 +02:00
farbanas 9d0bc8ab9e update changelogs 2023-04-04 11:38:01 +02:00
farbanas 21611a9434 update versions of contracts in preparation for release v1.1.14 2023-04-04 11:04:44 +02:00
farbanas 78d9030567 update versions in preparation for release v1.1.14 2023-04-04 10:30:20 +02:00
Tommy Verrall f3c16476f9 Merge pull request #3267 from nymtech/feature/gateway-settings-version-and-location
location and version needed in gateway settings updates
2023-04-03 12:11:46 +01:00
fmtabbara 4678059eaf add validation for version and location 2023-04-03 11:22:49 +01:00
fmtabbara a69d4bb457 location and version needed in gateway settings updates 2023-04-03 10:40:08 +01:00
Jędrzej Stuczyński 134ab2f0d6 Stop using rewarding reports as a source of truth for whether mixnodes got rewards (#3227)
use contract information instead
2023-03-27 14:52:40 +01:00
Jędrzej Stuczyński 1a4e7433b2 Bugfix: NR after #3199 (#3225)
* updated NR config template

* upgrading NR config if usingn <= 1.1.13
2023-03-27 12:21:35 +01:00
durch 9d91a018b2 Dont clone reconstructor 2023-03-24 16:22:14 +01:00
Tommy Verrall ae9c1b4070 Merge pull request #3199 from nymtech/chore/group-client-debug-config
chore: tidy up client `Debug` config section
2023-03-24 15:48:00 +03:00
Jędrzej Stuczyński adcd8703d3 attempting to upgrade old config in 'init' 2023-03-23 16:06:22 +00:00
Jędrzej Stuczyński aee4c2d80d Feature/families signatures (#3156)
* wip family creation signatures + cli

* nym-cli commands for creating families

* Changed family join signature inside the contract

* Generating family join permit via nym-cli

* ability to join families via nym-cli

* more strongly typed FamilyHead arguments

* initial work on removing redundant family signatures

* removed all redundant signatures from families in the mixnet contract

* moved up the call stack

* nym-cli family operations

* fixed family related unit tests

* family member kick

* removed family operations from the wallet

* clippy
2023-03-23 16:36:10 +01:00
Jędrzej Stuczyński 5e04f48500 bugfix: drop tasks to connections closed by remote (#3190)
* applied patch #3187

* applied the same concept to the verloc listener
2023-03-23 12:09:43 +01:00
Fouad a7610a7a88 Gateway settings (#2725)
* add gateway settings button

* remove unneeded mixnode type check

* add additional properties to gateway type

* update node settings nav options

* set up gateway update requests

* create gateway settings page

* use update gateway validation

* PR updates

* dont show playground on gateways

* set up gateway config update

* fix lint errors in wallet

* run cargo fmt
2023-03-23 10:38:33 +01:00
Tommy Verrall 28e6e9140c Merge pull request #3203 from nymtech/chore/disable-sign-ext
disable sign-ext when using wasm-opt + update wasm-opt
2023-03-23 10:32:58 +02:00
Jędrzej Stuczyński 271a126556 removed migration code from mixnet and vesting contracts (#3207) 2023-03-22 14:27:51 +00:00
Jędrzej Stuczyński 7da444c7a4 clippy 2023-03-22 13:51:38 +00:00
Jędrzej Stuczyński 2a7fd71416 explicitly added old version number to 'oldconfig' 2023-03-22 13:51:38 +00:00
Jędrzej Stuczyński 6ffd211e51 backwards compatibility 2023-03-22 13:51:38 +00:00
Jędrzej Stuczyński f61ed48240 updated templates 2023-03-22 13:51:38 +00:00
Jędrzej Stuczyński 8d3dc405a5 clippy 2023-03-22 13:51:38 +00:00
Jędrzej Stuczyński 21bf0fb27b wasm changes 2023-03-22 13:51:36 +00:00
Jędrzej Stuczyński 7313f56c01 grouped debug config options inside client-core into substructs 2023-03-22 13:49:30 +00:00
Jon Häggblad cfef9e96e6 chore: update nym-connect mobile lock file 2023-03-22 12:17:21 +01:00
Jędrzej Stuczyński 8b9a5cf500 updated installed wasm-opt version to v112 2023-03-22 10:38:39 +00:00
Jędrzej Stuczyński ce94ce058f disabling sign-ext 2023-03-22 10:37:52 +00:00
farbanas 018e4d6079 chore: update lock files 2023-03-22 11:29:27 +01:00
farbanas ebadd9799f fix: add contracts-common back to dependencies 2023-03-22 11:29:25 +01:00
Jędrzej Stuczyński 268fa02b83 fix: restored 'nym-contracts-common' dependency in the mixnet contract 2023-03-22 10:25:42 +00:00
farbanas cb525477e5 fix: update CHANGELOG for contracts 2023-03-22 10:39:48 +01:00
farbanas 4d6d2f0d33 Merge branch 'master' into develop 2023-03-22 10:31:12 +01:00
farbanas c7f8b05604 fix: wallet CHANGELOG 2023-03-22 10:23:19 +01:00
farbanas 2878e9be9d Merge branch 'release/v1.1.13' 2023-03-22 10:15:32 +01:00
farbanas 7b419c2b12 bump version of contracts 2023-03-22 10:12:33 +01:00
Fran Arbanas 0049126a91 Merge pull request #3200 from nymtech/jon/chore/explicit-cosmwasm-versions-across-workspaces
Set cosmwasm versions on workspace and strictly use 1.0.0
2023-03-22 10:06:18 +01:00
Jon Häggblad 80c5194d8b Add explicit cosmwasm-crypto 1.0.0 dev-dependency 2023-03-21 16:34:06 +01:00
Jon Häggblad 27a6b99453 Even more workspace version missed earlier 2023-03-21 16:34:06 +01:00
Jon Häggblad 61982de511 A few more workspace versions 2023-03-21 16:34:06 +01:00
Jon Häggblad efd9883197 Use workspace deps for coconut contracts 2023-03-21 16:34:06 +01:00
Jon Häggblad ce4ae8d90c Set cosmwasm versions on workspace and strictly use 1.0.0 2023-03-21 16:34:06 +01:00
farbanas 1d2722f994 update workflows with specific wasm-opt version 2023-03-21 16:30:40 +01:00
Drazen Urch 7e109e7f2d Generalise MessageReceiver (#3042)
* Generalise MessageReceiver

* Generics all the way

* Generalise MessageReceiver

* Generics all the way

* Fix Cargo.lock

---------

Co-authored-by: benedettadavico <benedetta.davico@gmail.com>
2023-03-21 15:25:51 +01:00
farbanas f7a0b305df update common crate version in contracts 2023-03-21 11:10:55 +01:00
farbanas 746ec71a0d update package versions 2023-03-21 10:54:18 +01:00
Jon Häggblad 41a63a0985 Tidy top-level Makefile (#3192)
* Reorder Makefile

* split out the fmt targets

* split cargo test

* Split up clippy targets

* Add commit

* Use env variable for no-mobile instead

* Extract out target generation to use function

* Remove commented out code

* Add comment

* Minor tidy
2023-03-20 15:44:13 +01:00
Pierre Dommerc 5a149c5492 feat(nc-mobile): select service provider (#3196)
* feat: select service provider ui

* refactor: adjust ui

* fix: remove dead code
2023-03-20 13:53:29 +01:00
Jędrzej Stuczyński 7ff0becf72 replaced usage of 'serde_json' in mixnet contract in favour of 'serde_json_wasm' (#3191)
* replaced usage of 'serde_json' in mixnet contract in favour of 'serde_json_wasm'

* using stricter version number
2023-03-17 14:31:38 +00:00
Jon Häggblad 4c2967a733 Delete stray .gitignore file 2023-03-16 15:27:45 +00:00
Jon Häggblad 2b80e5d1c9 Remove leftover file from deleted crate 2023-03-16 12:59:46 +00:00
Jon Häggblad 4e7ff53214 Contract and client support for updating gateway config (#3166)
* mixnet-contract: add update gateway config

* mixnet-contract: tests for updating gateway config

* vesting-contract: add update gateway config

* validator-client: add update gateway config

* wallet: add update_gateway_config

* common/commands: add support for setting gateway config

* Remove commented out line

* Review fixes

* Generate ts file for GatewayConfigUpdate type

* Add generated GatewayConfigUpdate.ts file
2023-03-16 13:43:56 +01:00
Jon Häggblad 9ec36e49b7 contracts: remove .gitignore with Cargo.lock in it
While developing the service-provider-directory contract I ran into
issues with the lock file being inconsistent for cosmwasm-std (1.0 vs
1.2) and was hidden due to ignoring the lock file
2023-03-16 12:28:35 +00:00
farbanas cdfa5ee540 chore: update versions for release/v1.1.13 2023-03-15 15:23:55 +01:00
farbanas 71853f69f3 chore: update changelogs for release/v1.1.13 2023-03-15 15:23:15 +01:00
pierre dd13073037 docs(connect-android): add build note 2023-03-15 12:29:23 +01:00
Pierre Dommerc 1010df1077 refactor(wallet): ui adjustments (#3182) 2023-03-15 12:22:00 +01:00
Bogdan-Ștefan Neacşu 9eaf9cf491 Feature/fix resharing (#3139)
* Compare verified vks against current group instead of initial dealers

* Fix various dkg logs

* API auto-advance epoch even on corrupt states

* Use verified vks as ultimate truth for dealers

* Set initial dealers based of verified vk

* Extend register period even more

* Fix test

* Use shares from current epoch

* Save initial dealers only when triggering resharing

* Fix tests

* Backup the last InProgress state too

* Reset previous signers that are not initial dealers

* Add unit test for bug reproduction

* More verbose debug logging

* Handle edge case for coconut keypair removal

* Update dkg api test

* Remove dealings directly for each key

* Replacement data is saved only on the first reshare start

* More debug logging

* On failed DKG, just reset

* Clippy fix
2023-03-15 11:16:43 +00:00
pierre 8e96318478 build(connect-android): try to fix fdroid build 2023-03-15 10:25:53 +01:00
Tommy Verrall bedff1f258 Merge pull request #3153 from nymtech/oak-14
Validating new interval config parameters to prevent division by zero
2023-03-14 12:38:43 +02:00
Fouad 71a10a9a8b add blockstream green to sp list (#3180) 2023-03-13 17:05:21 +01:00
Jędrzej Stuczyński 54287666e8 chore: simplify mnemonic zeroize story (#3165)
* updated bip39 dependency to simplify our zeroize story

* Replaced UserPassword wrapper with Zeroizing type alias

* fixed wallet-types cosmwasm-std dependency version
2023-03-13 11:29:56 +00:00
Tommy Verrall 6de829163d Merge pull request #3173 from nymtech/feature/nym-cli-tweak
Feature/nym cli tweak
2023-03-13 13:24:13 +02:00
Jon Häggblad 605aed6f20 mock-nym-api: fix .storybook lint error (#3178) 2023-03-13 12:23:17 +01:00
benedettadavico adb5ed7c30 typo fix 2023-03-13 12:14:14 +01:00
benedettadavico 2b019e57df merge develop 2023-03-13 12:12:00 +01:00
benedettadavico 30c07712e3 format 2023-03-13 12:03:38 +01:00
benedettadavico 82c92501d9 vesting stuff 2023-03-13 12:01:22 +01:00
benedettadavico c2a871a1a7 typo 2023-03-10 17:10:27 +01:00
benedettadavico dfd7bd5889 adding pledge more 2023-03-10 17:10:01 +01:00
Tommy Verrall 5aee4b1660 Merge pull request #3167 from nymtech/feature/wallet-3132
Feature/wallet 3132
2023-03-10 17:56:09 +02:00
Tommy Verrall 68a7bb67de Merge pull request #3169 from nymtech/feature/explorer-3168
Feature/explorer 3168
2023-03-10 17:32:22 +02:00
Pierre Dommerc 7ff043d8df Feature/bonding signature UI (#3157)
* wip

* updated gateways 'sign' command

* in-wallet verification of mix bonding signature

* changed signature of vesting contract trait method

* updated wallet bonding endpoints

* tauri commands for generating signing payloads

* renamed signer to sender

* verifying new signatures in the contract

* fixed existing mixnet unit tests

* unit tests for invalid signatures

* fixed other usages of MessageSignature + FromStr

* using base58-encoded serialization

* removed owner-signature from details response

* added ability to construct bonding payloads via nym-cli

* removed signature from bonding payload args

* moved 'message_type' from 'ContractMessageContent' to 'SignableMessage'

* refactor(wallet-rust): rename owner_signature args

* feat(wallet-bonding): handle user signature

* feat(wallet-bonding): fix bonding

* feat(wallet-bonding): fix lint issue

* feat(wallet-bonding): ui adjustment

* make the location field mandatory for payload signing

* feat(wallet-bonding): remove ownersignature field, remove dead code

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: Tommy Verrall <tommyvez@protonmail.com>
2023-03-10 15:53:21 +01:00
fmtabbara 31e93428cf use new locked rewards and locked coins endpoints 2023-03-09 16:48:05 +00:00
fmtabbara 5f56b3eeea add bond % tooltip 2023-03-09 16:34:22 +00:00
fmtabbara e69b05693a switch avg and routing score table positions 2023-03-09 16:30:58 +00:00
fmtabbara 8ae2451340 add y-axis label 2023-03-09 16:28:30 +00:00
fmtabbara b3b3279345 fix gateway click thorough from gateway version field 2023-03-09 14:45:45 +00:00
farbanas 94a451c79b fix: updated build-and-upload-binaries-ci workflow with explorer-api and the new contracts 2023-03-09 13:41:22 +01:00
Jędrzej Stuczyński ec7b959028 removed source of future clippy complaints 2023-03-09 11:30:30 +00:00
Jędrzej Stuczyński 7061beea6e exposing tauri operations for spendable vested and reward coins 2023-03-09 11:28:50 +00:00
Fran Arbanas e408162e26 Merge pull request #2747 from nymtech/339-net-switch-bttn
339 net switch bttn
2023-03-09 10:52:00 +01:00
Gala 25ae3895cb updating branch 2023-03-09 10:39:30 +01:00
fmtabbara 60296f2a41 update vesting schedule ui 2023-03-08 17:51:30 +00:00
Fouad 3b97844310 Feature/explorer 2979 (#3147)
* additional unfiltered endpoints for nym-api

* add poor performance UI

* display appropriate UI when node is blacklisted

* update explorer api with blacklisted nodes

* add new unfiltered endpoint

add new unfiltered endpoint

* show blacklisted detail even when node description is unavailable

remove console.log

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2023-03-08 16:15:32 +01:00
Jon Häggblad c904d245d2 Remove old erc20 bandwidth-claim-contract (#3162) 2023-03-08 13:57:11 +01:00
Jon Häggblad d315a2a91b Add nym prefix to coconut contracts and crates (#3160)
* Add nym prefix to coconut-dkg

* Add nym prefix to coconut-bandwidth

* Add nym prefix to bandwidth-claim-contract

* Add nym prefix to coconut-bandwidth-contract-common

* Add nym prefix to coconut-dkg-common

* Add nym prefix to group-contract-common

* Add nym prefix to multisig-contract-common

* Add nym prefix to coconut-interface

* Add nym prefix to credential-storage

* rustfmt

* Mark coconut-test crate as private

* Fix build errors

* rustfmt
2023-03-08 13:17:09 +01:00
Tommy Verrall 0741d05ab3 Merge pull request #3140 from nymtech/feature/mock-nym-api
Add mock Nym API Typescript package
2023-03-08 14:06:54 +02:00
Jon Häggblad 61aa920362 Fix unnecessary parentheses around index expression (#3159) 2023-03-08 12:21:24 +01:00
Jon Häggblad 24b9b17e64 Add a few more nym- crate prefixes (#3158)
* Add nym- to socks5-prefixes crate

* Update imports

* rustfmt

* Add nym-socks5 prefix to proxy-helpers crate

* rustfmt

* Add nym prefix to ordered-buffer crate

* rustfmt

* Add nym prefix to service-providers-common crate

* rustfmt

* Add nym prefix to dkg crate

* Add nym prefix to credentials crate

* rustfmt

* fix build fail in tests
2023-03-08 11:56:29 +01:00
Jon Häggblad 5e3e633c4b Merge branch 'jon/fix/docs-rs-build' into develop 2023-03-08 09:37:54 +01:00
Jon Häggblad 8e6d3c34e2 Merge branch 'jon/fix/docs-rs-build' 2023-03-08 09:37:31 +01:00
Jon Häggblad f3cff902ba Bump nym-vesting-contract to 1.2.0-pre.1 2023-03-08 09:21:27 +01:00
Jon Häggblad 4fe1b4c26f Fix docs.rs build for nym-vesting-contract and nym-bin-common 2023-03-08 09:14:07 +01:00
farbanas cb24a08b06 Merge branch 'master' into develop 2023-03-07 15:22:49 +01:00
farbanas 597a53d11a chore: bump mixnet and vesting contracts to prerelease version, update cargo locks 2023-03-07 15:17:07 +01:00
farbanas 639deeb502 Merge branch 'release/v1.1.12' 2023-03-07 14:32:34 +01:00
farbanas 03e082725e chore: update changelogs for release 2023-03-07 14:30:38 +01:00
farbanas 3e3307887e chore: bumped versions for release 2023-03-07 14:19:45 +01:00
farbanas d85683aaa8 chore: bumped version of contracts 2023-03-07 13:49:03 +01:00
farbanas e89ed985fc chore: bumped version of common crates 2023-03-07 13:27:03 +01:00
Jędrzej Stuczyński b1fb8bb18c Validating new interval config parameters to prevent division by zero 2023-03-07 09:51:18 +00:00
Jon Häggblad 45ebd7c37a Add list of published common crates 2023-03-07 09:58:07 +01:00
Jon Häggblad 3506020e55 Update Cargo.lock (#3152) 2023-03-07 09:57:09 +01:00
Pierre Dommerc eca77d684b feat(wallet): send - add user fees settings and memo field (#3146)
* feat(wallet): send - add user fees settings, memo field

* fix(wallet): send, custom fees validation
2023-03-06 18:26:07 +01:00
Jon Häggblad e263a4a21f wallet_storage: fix clippy useless-conversion (#3148) 2023-03-06 17:24:20 +01:00
Jon Häggblad dc4353c682 Fix docs.rs build (#3145)
* contracts: fix docs.rs build

* Cargo.lock

* rustfmt

* vesting: option_env for git info

* Change to vergen =7.4.3
2023-03-06 16:38:14 +01:00
Fouad e1d8069967 Explorer Service Provider List (#3142)
* create service providers route

* make request for well known service providers

* fetch and display service providers

* service provider overview

handle undefined data

fix linting

fix type

* use full width column
2023-03-06 16:36:55 +01:00
farbanas 060726b7a3 fix: add step to install wasm-opt to our CI for building and uploading binaries 2023-03-06 16:35:31 +01:00
Fouad f72ccb0f0d Update tooltips for routing and average score (#3133)
* update tooltips for routing and average score

* fix up table alignment

fix lint errors

* add node_performance to explorer api response for mixnodes

* use mixnode node_performance for avg and lastest values

* move stake sat to top table

fix lint errors

* update stake saturation text color
2023-03-06 15:58:33 +01:00
Jędrzej Stuczyński 2ff6bfbdd8 feat: ability to inject custom topology into clients (#3055)
* ability to specify custom TopologyProvider in TopologyRefresher

* topology provider builder method for base client

* ability to take manual control over topology

* wasm fixes

* added topology injection to nym-sdk API

* added examples to nym-sdk and exposed additional helper methods
2023-03-06 13:50:06 +00:00
Bogdan-Ștefan Neacșu d4f0b4772b Fix Service stats address 2023-03-06 14:00:14 +02:00
Jędrzej Stuczyński 17d258d094 renamed serialized GetCW2ContractVersion to 'get_cw2_contract_version' instead of 'get_c_w2_contract_version' 2023-03-03 15:29:10 +00:00
Drazen Urch 8288d38257 Audit fixes (#2922)
* oak-2

* oak-8

* oak-13

* oak-15

* oak-18

* Minor clippy nit

* 2023-01-13-OAK-6

* 2023-01-13-OAK-3

* 2023-01-13-OAK-13

Implemented via direct dependency on cw2 and calling the appropriate code on migration

* Removed few instances of password being unecessarily copied

* 2023-01-13-OAK-10

* 2023-01-13-OAK-12

* 2021-09-13-JP-S-NYM-02

* 2021-09-13-JP-S-NYM-03

* Removed further instances of needlessly copying the mnemonic

* 2021-09-13-JP-O-PROT-03

* 2021-09-13-JP-S-NYM-01*

*: we still have one vulnerability on 'time' pulled from chrono via sqlx. However, apparently its usage is fine... Having said that, I'd still recommend removing all dependencies on chrono, but this will require some database migrations...

* 2023-01-13-OAK-11 (#3009)

* wip

* Introducing the concept of starting epoch transition in `nym-api`

* split epoch operations into multiple files

* epoch operation failure recovery

* sending rewarding transactions in correct order

* tests and fixes due to epoch state progression

* lint

* missed rebasing import changes

* Setting cw2 contract version during first migration run

* calling 'reconcile_epoch_events' at least once

* Made message to BeginEpochTransition more consistent with other variants

* Merge layer assignment updates

---------

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2023-03-03 14:15:38 +00:00
Jędrzej Stuczyński 1cd560c85e fixed standalone build of 'client-core' 2023-03-03 11:39:59 +00:00
Mark Sinclair 9018642992 Add mock Nym API Typescript package 2023-03-03 11:09:35 +00:00
Pierre Dommerc 04e652441e fix(explorer): fix layout responsiveness in mixnode details view (#3130) 2023-03-01 16:29:47 +01:00
Fouad ccf5990bc7 update selected service provider description style (#3128) 2023-03-01 16:22:52 +01:00
Fouad 3d7c9ee2b8 NE - fix gateways' version number sorting on the gateway list page (#3131)
* fix filter input sizes

* reorder gateway columns

* update tooltips

* fix column sorting in explorer lists

fix lint errors
2023-03-01 16:21:33 +01:00
Jędrzej Stuczyński ad35c4006e Fixed logged hours value when determining staleness of persisted surb data (#2690) 2023-03-01 14:30:49 +00:00
Pierre Dommerc 2ec790e851 fix(explorer): fix layout responsiveness in mixnode details view (#3130) 2023-03-01 15:16:40 +01:00
Jędrzej Stuczyński 8f8cec0785 Fixed the build badge 2023-03-01 10:37:44 +00:00
farbanas 4a80cd301b fix: add step to install wasm-opt to our CI for building and uploading binaries 2023-03-01 11:06:40 +01:00
farbanas 63524eceff fix: update versions of the latest release in changelogs from 1.0.11 to 1.1.11 2023-03-01 11:06:40 +01:00
Fouad a477b007e1 TS Validator Client updates (#3085)
* Squashed clients/validator content from old branch

* fix up tests

fix up linting

* add bundle script

* add build prod script to package json

update tests

* update readme + copy to dist output

update global types

update types and tests!

* update package build

* move types and tests into src

* Squashed clients/validator content from old branch

fix up tests

fix up linting

* add bundle script

* add build prod script to package json

update tests

* update readme + copy to dist output

update global types

update types and tests!

update package build

* move types and tests into src

* build to sub-dir

* Fixing the few broken tests

---------

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
Co-authored-by: benedettadavico <benedetta.davico@gmail.com>
2023-03-01 10:22:35 +01:00
Jędrzej Stuczyński 5b81510325 added additional makefile targets that skip mobile-related steps (#3127) 2023-03-01 09:12:47 +00:00
farbanas 3bef973326 fix: update versions of the latest release in changelogs from 1.0.11 to 1.1.11 2023-02-28 17:27:58 +01:00
farbanas bc5bb271d8 feat: remove unused workflows, update a couple of if conditions in github workflows and add the command for optimizing contract sizes to Makefile 2023-02-28 17:26:39 +01:00
farbanas 158e3cb073 Merge branch 'master' into develop 2023-02-28 17:09:58 +01:00
farbanas 36fb0eba29 Update versions as part of release/v1.1.11 2023-02-28 13:53:14 +01:00
farbanas 1b37e85418 Update changelogs as part of release/v1.1.11 2023-02-28 13:43:32 +01:00
Bogdan-Ștefan Neacşu 6a93497c8f Extend public key submission in case no dealer registered (#3106) 2023-02-28 12:58:01 +01:00
Tommy Verrall b8ee3465f8 Update build_and_run.sh
remove unnecessary environment variable
2023-02-28 10:33:45 +01:00
Mark Sinclair a7dfb36a84 Merge pull request #3111 from nymtech/feature/sdk-1.1.7
Typescript SDK 1.1.7
2023-02-27 18:07:08 +00:00
Mark Sinclair 14a7b5bdc8 Typescript SDK 1.1.7 2023-02-27 17:55:41 +00:00
Tommy Verrall ffbd76539a Merge pull request #3108 from nymtech/feature/nym-connect-select-sp
Feature/nym connect select sp
2023-02-27 17:48:31 +02:00
fmtabbara bdcc19e86a PR update 2023-02-27 15:33:54 +00:00
fmtabbara 7929bac685 turn off user defined SP address is input it empty 2023-02-27 14:20:16 +00:00
Jon Häggblad f590aad42c nym-api: uptime rework (#3053)
* nym-api: cache updates as node performance

* nym-api: update get mixnode avg_uptime endpoint

* nym-api: mixnode report to use cached data

* nym-api: annotate gateway bond with node performance

* nym-api: gateway report to use cached data

* wip

* Add get_gateway_avg_uptime

* Add comment

* update NR gateways to include node_performance on frontend

* use node_performance values on frontend

* fixup select gateway from list

* fix up lint errors

---------

Co-authored-by: fmtabbara <fmtabbara@hotmail.co.uk>
2023-02-27 12:40:00 +01:00
fmtabbara ec23f3dcb7 disable input when connected
fix lint errors
2023-02-27 11:28:38 +00:00
fmtabbara 9644eb4329 pick service provider 2023-02-27 11:28:38 +00:00
fmtabbara 7a4c6e4ed4 storage type
update types
2023-02-27 11:28:36 +00:00
fmtabbara d5ad504104 reuseable storage functions 2023-02-27 11:28:36 +00:00
Tommy Verrall d684f6d7ae Merge pull request #3099 from nymtech/feature/nym-connect-select-sp
NymConnect Select a service provider
2023-02-27 12:26:00 +02:00
Bogdan-Ștefan Neacşu f0d9703587 Feature/fix db name collision (#3103)
* Fix db naming collision

* Increase gateway timeout to accommodate bandwidth spending time
2023-02-24 16:52:56 +02:00
fmtabbara c08efef8ed add autofocus to IdentityKeyComponent 2023-02-23 22:58:59 +00:00
fmtabbara 6d29774744 style tweaks 2023-02-23 22:57:21 +00:00
fmtabbara af6bab7703 fix lint error 2023-02-23 17:40:39 +00:00
fmtabbara 7033f92d82 add UI for picking service provider 2023-02-23 17:33:03 +00:00
Jędrzej Stuczyński 128dfa6d81 Feature/latency based gateway selection (#3081)
* wip

* new option to select gateways based on latency

* further changes for wasm-compatibility

* post rebase fixes + clippy

I know, I should have probably included them properly during rebasing ¯\_(ツ)_/¯

* android change

* wasm: the gift that keeps on giving
2023-02-23 17:09:22 +00:00
Bogdan-Ștefan Neacşu 0b0ec075bb Use saturating_sub with an additional 1 second buffer (#3095) 2023-02-23 13:44:54 +01:00
Tommy Verrall cca4d21e7c Merge pull request #3097 from nymtech/feature/update-checker-to-use-master
Feature/update checker to use master
2023-02-23 14:24:59 +02:00
Gala 4f59678ded center layout 2022-12-22 15:31:35 +01:00
Gala 8a1d2af3cf adding changes 2022-12-22 14:44:58 +01:00
634 changed files with 29122 additions and 9321 deletions
@@ -79,6 +79,9 @@ jobs:
override: true
components: rustfmt, clippy
- name: Install wasm-opt
run: cargo install --version 0.112.0 wasm-opt
- name: Build release contracts
run: make wasm
@@ -96,9 +99,14 @@ jobs:
cp target/release/nym-network-statistics $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/credential $OUTPUT_DIR
cp target/release/explorer-api $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_bandwidth.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
- name: Deploy branch to CI www
continue-on-error: true
+4 -1
View File
@@ -6,7 +6,7 @@ on:
jobs:
build:
if: ${{ startsWith(github.ref, 'refs/tags/nym-contracts-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-contracts-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
runs-on: [self-hosted, custom-runner-linux]
steps:
- uses: actions/checkout@v2
@@ -19,6 +19,9 @@ jobs:
override: true
components: rustfmt, clippy
- name: Install wasm-opt
run: cargo install --version 0.112.0 wasm-opt
- name: Build release contracts
run: make wasm
@@ -1,57 +0,0 @@
name: CI for Network Explorer API
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym:
if: ${{ startsWith(github.ref, 'refs/tags/nym-explorer-api-') && (github.event_name == 'release' || github.event_name == 'workflow_dispatch') }}
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
continue-on-error: true
- name: Check the release tag starts with `nym-explorer-api-`
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-explorer-api-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build all explorer-api
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path explorer-api/Cargo.toml --workspace --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact
path: |
target/release/explorer-api
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/explorer-api
-50
View File
@@ -1,50 +0,0 @@
name: Publish Nym CLI binaries
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym-cli:
if: ${{ startsWith(github.ref, 'refs/tags/nym-cli-') && (github.event_name == 'release' || github.event_name == 'workflow_dispatch') }}
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04, windows-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Check the release tag starts with `nym-cli-`
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-cli-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build binary
run: make build-nym-cli
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: nym-cli-${{ matrix.platform }}
path: |
target/release/nym-cli*
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/nym-cli
@@ -10,7 +10,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
@@ -10,7 +10,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
+1 -1
View File
@@ -16,7 +16,7 @@ env:
jobs:
publish-nym:
if: ${{ startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
@@ -10,7 +10,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
@@ -9,7 +9,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
-2
View File
@@ -39,7 +39,5 @@ validator-api-config.toml
dist
storybook-static
envs/qwerty.env
Cargo.lock
nym-connect/Cargo.lock
.parcel-cache
**/.DS_Store
+66
View File
@@ -4,6 +4,72 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.14] (2023-04-04)
- Investigate cause of qwerty validator being in invalid rewarding state ([#3224])
- Fix NR config due to changes in #3199 ([#3223])
- [Issue] Mixnodes and gateway do not close connections properly ([#3187])
- disable sign-ext when using wasm-opt + update wasm-opt ([#3203])
- chore: tidy up client `Debug` config section ([#3199])
[#3224]: https://github.com/nymtech/nym/issues/3224
[#3223]: https://github.com/nymtech/nym/issues/3223
[#3187]: https://github.com/nymtech/nym/issues/3187
[#3203]: https://github.com/nymtech/nym/pull/3203
[#3199]: https://github.com/nymtech/nym/pull/3199
## [v1.1.13] (2023-03-15)
- NE - instead of throwing a "Mixnode/Gateway not found" error for blacklisted nodes due to bad performance, show their history but tag them as "Having poor performance" ([#2979])
- NE - Upgrade Sandbox and make below changes: ([#2332])
- Explorer - Updates ([#3168])
- Website v2 - deploy infrastructure for strapi and CI ([#2213])
- add blockstream green to sp list ([#3180])
- mock-nym-api: fix .storybook lint error ([#3178])
- Validating new interval config parameters to prevent division by zero ([#3153])
[#2979]: https://github.com/nymtech/nym/issues/2979
[#2332]: https://github.com/nymtech/nym/issues/2332
[#3168]: https://github.com/nymtech/nym/issues/3168
[#2213]: https://github.com/nymtech/nym/issues/2213
[#3180]: https://github.com/nymtech/nym/pull/3180
[#3178]: https://github.com/nymtech/nym/pull/3178
[#3153]: https://github.com/nymtech/nym/pull/3153
## [v1.1.12] (2023-03-07)
- Fix generated docs for mixnet and vesting contract on docs.rs ([#3093])
- Introduce a way of injecting topology into the client ([#3044])
- Update mixnet TypeScript client methods #1 ([#2783])
- Update tooltips for routing and average score ([#3133])
- update selected service provider description style ([#3128])
[#3093]: https://github.com/nymtech/nym/issues/3093
[#3044]: https://github.com/nymtech/nym/issues/3044
[#2783]: https://github.com/nymtech/nym/issues/2783
[#3133]: https://github.com/nymtech/nym/pull/3133
[#3128]: https://github.com/nymtech/nym/pull/3128
## [v1.1.11] (2023-02-28)
- Fix empty dealer set loop ([#3105])
- The nym-api db.sqlite is broken when trying to run against it it in `enabled-credentials-mode true` there is an ordering issue with migrations when using the credential binary to purchase bandwidth ([#3100])
- Feature/latency based gateway selection ([#3081])
- Fix the credential binary to handle transactions to sleep when in non-inProgress epochs ([#3057])
- Publish mixnet contract to crates.io ([#1919])
- Publish vesting contract to crates.io ([#1920])
- Feature/update checker to use master ([#3097])
- Feature/improve binary checks ([#3094])
[#3105]: https://github.com/nymtech/nym/issues/3105
[#3100]: https://github.com/nymtech/nym/issues/3100
[#3081]: https://github.com/nymtech/nym/pull/3081
[#3057]: https://github.com/nymtech/nym/issues/3057
[#1919]: https://github.com/nymtech/nym/issues/1919
[#1920]: https://github.com/nymtech/nym/issues/1920
[#3097]: https://github.com/nymtech/nym/pull/3097
[#3094]: https://github.com/nymtech/nym/pull/3094
## [v1.1.10] (2023-02-21)
- Verloc listener causing mixnode unexpected shutdown ([#3038])
Generated
+668 -854
View File
File diff suppressed because it is too large Load Diff
+13 -3
View File
@@ -22,7 +22,6 @@ members = [
"clients/native",
"clients/native/websocket-requests",
"clients/socks5",
"common/bandwidth-claim-contract",
"common/bin-common",
"common/client-libs/gateway-client",
"common/client-libs/mixnet-client",
@@ -105,9 +104,20 @@ edition = "2021"
license = "Apache-2.0"
[workspace.dependencies]
async-trait = "0.1.63"
async-trait = "0.1.64"
bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0"
dotenv = "0.15.0"
cosmwasm-derive = "=1.0.0"
cosmwasm-schema = "=1.0.0"
cosmwasm-std = "=1.0.0"
cosmwasm-storage = "=1.0.0"
cw-utils = "=0.13.4"
cw-storage-plus = "=0.13.4"
cw2 = { version = "=0.13.4" }
cw3 = { version = "=0.13.4" }
cw3-fixed-multisig = { version = "=0.13.4" }
cw4 = { version = "=0.13.4" }
dotenvy = "0.15.6"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
+59 -101
View File
@@ -1,132 +1,90 @@
# Default target
all: test
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-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-connect-mobile clippy-all-wasm-client
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect clippy-happy-connect-mobile
cargo-test: test-main test-contracts test-wallet test-connect test-connect-mobile
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive
build: build-contracts build-wallet build-main build-main-examples build-connect build-connect-mobile build-wasm-client
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-connect-mobile fmt-wasm-client
clippy-happy-main:
cargo clippy
# -----------------------------------------------------------------------------
# Define targets for a given workspace
# $(1): name
# $(2): path to workspace
# $(3): extra arguments to cargo
# -----------------------------------------------------------------------------
define add_cargo_workspace
clippy-happy-contracts:
cargo clippy --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown
clippy-happy-$(1):
cargo clippy --manifest-path $(2)/Cargo.toml $(3)
clippy-happy-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml
clippy-$(1):
cargo clippy --manifest-path $(2)/Cargo.toml --workspace $(3) -- -D warnings
clippy-happy-connect:
cargo clippy --manifest-path nym-connect/desktop/Cargo.toml
clippy-$(1)-examples:
cargo clippy --manifest-path $(2)/Cargo.toml --workspace --examples -- -D warnings
clippy-happy-connect-mobile:
cargo clippy --manifest-path nym-connect/mobile/src-tauri/Cargo.toml
test-$(1):
cargo test --manifest-path $(2)/Cargo.toml --workspace
clippy-main:
cargo clippy --workspace -- -D warnings
test-$(1)-expensive:
cargo test --manifest-path $(2)/Cargo.toml --workspace -- --ignored
clippy-main-examples:
cargo clippy --workspace --examples -- -D warnings
build-$(1):
cargo build --manifest-path $(2)/Cargo.toml --workspace $(3)
clippy-wasm:
cargo clippy --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --workspace -- -D warnings
build-$(1)-examples:
cargo build --manifest-path $(2)/Cargo.toml --workspace --examples
fmt-$(1):
cargo fmt --manifest-path $(2)/Cargo.toml --all
clippy-all-contracts:
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
clippy-happy: clippy-happy-$(1)
clippy-all: clippy-$(1) clippy-$(1)-examples
cargo-test: test-$(1)
cargo-test-expensive: test-$(1)-expensive
build: build-$(1) build-$(1)-examples
fmt: fmt-$(1)
clippy-all-wallet:
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
endef
clippy-all-connect:
cargo clippy --workspace --manifest-path nym-connect/desktop/Cargo.toml --all-features -- -D warnings
# -----------------------------------------------------------------------------
# Rust workspaces
# -----------------------------------------------------------------------------
clippy-all-connect-mobile:
cargo clippy --workspace --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-features -- -D warnings
# Generate targets for the various cargo workspaces
clippy-all-wasm-client:
cargo clippy --workspace --manifest-path clients/webassembly/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
$(eval $(call add_cargo_workspace,main,.))
$(eval $(call add_cargo_workspace,contracts,contracts,--target wasm32-unknown-unknown))
$(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown))
$(eval $(call add_cargo_workspace,wallet,nym-wallet,))
$(eval $(call add_cargo_workspace,connect,nym-connect/desktop))
ifndef NYM_NO_MOBILE
$(eval $(call add_cargo_workspace,connect-mobile,nym-connect/mobile/src-tauri))
endif
test-main:
cargo test --workspace
test-main-expensive:
cargo test --workspace -- --ignored
test-contracts:
cargo test --manifest-path contracts/Cargo.toml --all-features
test-contracts-expensive:
cargo test --manifest-path contracts/Cargo.toml --all-features -- --ignored
test-wallet:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
test-wallet-expensive:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features -- --ignored
test-connect:
cargo test --manifest-path nym-connect/desktop/Cargo.toml --all-features
test-connect-expensive:
cargo test --manifest-path nym-connect/desktop/Cargo.toml --all-features -- --ignored
test-connect-mobile:
cargo test --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-features
test-connect-mobile-expensive:
cargo test --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-features -- --ignored
build-main:
cargo build --workspace
build-main-examples:
cargo build --workspace --examples
build-contracts:
cargo build --manifest-path contracts/Cargo.toml --workspace
build-wallet:
cargo build --manifest-path nym-wallet/Cargo.toml --workspace
build-connect:
cargo build --manifest-path nym-connect/desktop/Cargo.toml --workspace
build-connect-mobile:
cargo build --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --workspace
# -----------------------------------------------------------------------------
# Convenience targets for crates that are already part of the main workspace
# -----------------------------------------------------------------------------
build-explorer-api:
cargo build --manifest-path explorer-api/Cargo.toml --workspace
build-wasm-client:
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
cargo build -p explorer-api
build-nym-cli:
cargo build --release --manifest-path tools/nym-cli/Cargo.toml
cargo build -p nym-cli --release
fmt-main:
cargo fmt --all
fmt-contracts:
cargo fmt --manifest-path contracts/Cargo.toml --all
fmt-wallet:
cargo fmt --manifest-path nym-wallet/Cargo.toml --all
fmt-connect:
cargo fmt --manifest-path nym-connect/desktop/Cargo.toml --all
fmt-connect-mobile:
cargo fmt --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all
fmt-wasm-client:
cargo fmt --manifest-path clients/webassembly/Cargo.toml --all
# -----------------------------------------------------------------------------
# Misc
# -----------------------------------------------------------------------------
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
wasm-opt --disable-sign-ext -Os contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
wasm-opt --disable-sign-ext -Os contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm
# NOTE: this seems deprecated an not needed anymore?
mixnet-opt: wasm
cd contracts/mixnet && make opt
+1 -1
View File
@@ -16,7 +16,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)
[![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
[![Build Status](https://img.shields.io/github/actions/workflow/status/nymtech/nym/build.yml?branch=develop&style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
### Building
+11 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "1.1.10"
version = "1.1.14"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.66"
@@ -8,7 +8,7 @@ rust-version = "1.66"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { version = "0.1.58" }
async-trait = { workspace = true }
dirs = "4.0"
dashmap = "5.4.0"
futures = "0.3"
@@ -20,6 +20,7 @@ serde_json = "1.0.89"
tap = "1.0.1"
thiserror = "1.0.34"
url = { version ="2.2", features = ["serde"] }
tungstenite = { version = "0.13.0", default-features = false }
tokio = { version = "1.24.1", features = ["macros"]}
time = "0.3.17"
@@ -36,6 +37,10 @@ nym-topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
nym-task = { path = "../../common/task" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.validator-client]
path = "../../common/client-libs/validator-client"
features = ["nyxd-client"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
version = "0.1.11"
features = ["time"]
@@ -44,6 +49,9 @@ features = ["time"]
version = "1.24.1"
features = ["time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
version = "0.6.2"
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
@@ -65,6 +73,7 @@ features = ["futures"]
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
path = "../../common/wasm-utils"
features = ["websocket"]
[target."cfg(target_arch = \"wasm32\")".dependencies.time]
version = "0.3.17"
@@ -5,7 +5,11 @@ use crate::{client::replies::reply_storage, config::DebugConfig};
pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> reply_storage::Empty {
reply_storage::Empty {
min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold,
max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold,
min_surb_threshold: debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
max_surb_threshold: debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
}
}
@@ -15,6 +15,7 @@ use crate::client::replies::reply_controller::{ReplyControllerReceiver, ReplyCon
use crate::client::replies::reply_storage::{
CombinedReplyStorage, PersistentReplyStorage, ReplyStorageBackend, SentReplyKeys,
};
use crate::client::topology_control::nym_api_provider::NymApiTopologyProvider;
use crate::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
@@ -34,13 +35,15 @@ use nym_crypto::asymmetric::{encryption, identity};
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_sphinx::receiver::ReconstructedMessage;
use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use std::sync::Arc;
use std::time::Duration;
use tap::TapFallible;
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::CosmWasmClient;
@@ -90,6 +93,7 @@ impl ClientOutput {
pub struct ClientState {
pub shared_lane_queue_lengths: LaneQueueLengths,
pub reply_controller_sender: ReplyControllerSender,
pub topology_accessor: TopologyAccessor,
}
pub enum ClientInputStatus {
@@ -154,6 +158,7 @@ pub struct BaseClientBuilder<'a, B, C: Clone> {
nym_api_endpoints: Vec<Url>,
reply_storage_backend: B,
custom_topology_provider: Option<Box<dyn TopologyProvider>>,
bandwidth_controller: Option<BandwidthController<C>>,
key_manager: KeyManager,
}
@@ -177,6 +182,7 @@ where
bandwidth_controller,
reply_storage_backend,
key_manager,
custom_topology_provider: None,
}
}
@@ -195,11 +201,17 @@ where
disabled_credentials: credentials_toggle.is_disabled(),
nym_api_endpoints,
reply_storage_backend,
custom_topology_provider: None,
bandwidth_controller,
key_manager,
}
}
pub fn with_topology_provider(mut self, provider: Box<dyn TopologyProvider>) -> Self {
self.custom_topology_provider = Some(provider);
self
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
@@ -224,15 +236,15 @@ where
let mut stream = LoopCoverTrafficStream::new(
ack_key,
debug_config.average_ack_delay,
debug_config.average_packet_delay,
debug_config.loop_cover_traffic_average_delay,
debug_config.acknowledgements.average_ack_delay,
debug_config.traffic.average_packet_delay,
debug_config.cover_traffic.loop_cover_traffic_average_delay,
mix_tx,
self_address,
topology_accessor,
);
if let Some(size) = debug_config.use_extended_packet_size {
if let Some(size) = debug_config.traffic.use_extended_packet_size {
log::debug!("Setting extended packet size: {:?}", size);
stream.set_custom_packet_size(size.into());
}
@@ -282,14 +294,15 @@ where
shutdown: TaskClient,
) {
info!("Starting received messages buffer controller...");
ReceivedMessagesBufferController::new(
local_encryption_keypair,
query_receiver,
mixnet_receiver,
reply_key_storage,
reply_controller_sender,
)
.start_with_shutdown(shutdown)
let controller: ReceivedMessagesBufferController<SphinxMessageReceiver> =
ReceivedMessagesBufferController::new(
local_encryption_keypair,
query_receiver,
mixnet_receiver,
reply_key_storage,
reply_controller_sender,
);
controller.start_with_shutdown(shutdown)
}
async fn start_gateway_client(
@@ -304,7 +317,7 @@ where
}
let gateway_address = self.gateway_config.gateway_listener.clone();
if gateway_address.is_empty() {
return Err(ClientCoreError::GatwayAddressUnknown);
return Err(ClientCoreError::GatewayAddressUnknown);
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
@@ -325,7 +338,9 @@ where
shared_key,
mixnet_message_sender,
ack_sender,
self.debug_config.gateway_response_timeout,
self.debug_config
.gateway_connection
.gateway_response_timeout,
self.bandwidth_controller.take(),
shutdown,
);
@@ -341,25 +356,38 @@ where
Ok(gateway_client)
}
fn setup_topology_provider(
custom_provider: Option<Box<dyn TopologyProvider>>,
nym_api_urls: Vec<Url>,
) -> Box<dyn TopologyProvider> {
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| {
Box::new(NymApiTopologyProvider::new(
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
))
})
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
nym_api_urls: Vec<Url>,
topology_provider: Box<dyn TopologyProvider>,
refresh_rate: Duration,
topology_accessor: TopologyAccessor,
shutdown: TaskClient,
) -> Result<(), ClientCoreError> {
let topology_refresher_config = TopologyRefresherConfig::new(
nym_api_urls,
refresh_rate,
env!("CARGO_PKG_VERSION").to_string(),
let topology_refresher_config = TopologyRefresherConfig::new(refresh_rate);
let mut topology_refresher = TopologyRefresher::new(
topology_refresher_config,
topology_accessor,
topology_provider,
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
topology_refresher.try_refresh().await;
if let Err(err) = topology_refresher.ensure_topology_is_routable().await {
log::error!(
@@ -468,9 +496,13 @@ where
)
.await?;
let topology_provider = Self::setup_topology_provider(
self.custom_topology_provider.take(),
self.nym_api_endpoints,
);
Self::start_topology_refresher(
self.nym_api_endpoints.clone(),
self.debug_config.topology_refresh_rate,
topology_provider,
self.debug_config.topology.topology_refresh_rate,
shared_topology_accessor.clone(),
task_manager.subscribe(),
)
@@ -506,7 +538,7 @@ where
self_address,
);
if let Some(size) = self.debug_config.use_extended_packet_size {
if let Some(size) = self.debug_config.traffic.use_extended_packet_size {
log::debug!("Setting extended packet size: {:?}", size);
controller_config.set_custom_packet_size(size.into());
}
@@ -525,12 +557,16 @@ where
task_manager.subscribe(),
);
if !self.debug_config.disable_loop_cover_traffic_stream {
if !self
.debug_config
.cover_traffic
.disable_loop_cover_traffic_stream
{
Self::start_cover_traffic_stream(
self.debug_config,
self.key_manager.ack_key(),
self_address,
shared_topology_accessor,
shared_topology_accessor.clone(),
sphinx_message_sender,
task_manager.subscribe(),
);
@@ -554,6 +590,7 @@ where
client_state: ClientState {
shared_lane_queue_lengths,
reply_controller_sender,
topology_accessor: shared_topology_accessor,
},
task_manager,
})
@@ -30,8 +30,12 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
// it will only be happening on the very first run and in practice won't incur huge
// costs since the storage is going to be empty
let mem_store = CombinedReplyStorage::new(
debug_config.minimum_reply_surb_storage_threshold,
debug_config.maximum_reply_surb_storage_threshold,
debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
);
storage_backend
.init_fresh(&mem_store)
@@ -46,8 +50,12 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
fn setup_inactive_backend(debug_config: &DebugConfig) -> fs_backend::Backend {
info!("creating inactive surb database");
fs_backend::Backend::new_inactive(
debug_config.minimum_reply_surb_storage_threshold,
debug_config.maximum_reply_surb_storage_threshold,
debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
)
}
@@ -10,6 +10,7 @@ use nym_sphinx::{
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
/// Module responsible for listening for any data resembling acknowledgements from the network
/// and firing actions to remove them from the 'Pending' state.
@@ -48,10 +49,20 @@ impl AcknowledgementListener {
// because nothing was inserted in the first place
if frag_id == COVER_FRAG_ID {
trace!("Received an ack for a cover message - no need to do anything");
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
println!("cover ack : _{}", time);
return;
}
trace!("Received {} from the mix network", frag_id);
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
println!("real ack : /{:?}/{}", frag_id, time);
self.action_sender
.unbounded_send(Action::new_remove(frag_id))
@@ -9,6 +9,7 @@ use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng};
use std::time::{SystemTime, UNIX_EPOCH};
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
/// putting everything into sphinx packets, etc.
@@ -48,6 +49,11 @@ where
lane: TransmissionLane,
) {
// offload reply handling to the dedicated task
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
println!("sending reply: _{}_{}", time, data.len());
self.reply_controller_sender
.send_reply(recipient_tag, data, lane)
}
@@ -58,6 +64,11 @@ where
content: Vec<u8>,
lane: TransmissionLane,
) {
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
println!("sending plain: _{}_{}", time, content.len());
if let Err(err) = self
.message_handler
.try_send_plain_message(recipient, content, lane)
@@ -74,6 +85,11 @@ where
reply_surbs: u32,
lane: TransmissionLane,
) {
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
println!("sending anonymous: _{}_{}", time, content.len());
if let Err(err) = self
.message_handler
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane)
@@ -153,25 +153,35 @@ impl Config {
ack_key,
self_recipient,
packet_size: Default::default(),
ack_wait_addition: base_client_debug_config.ack_wait_addition,
ack_wait_multiplier: base_client_debug_config.ack_wait_multiplier,
average_message_sending_delay: base_client_debug_config.message_sending_average_delay,
average_packet_delay_duration: base_client_debug_config.average_packet_delay,
average_ack_delay_duration: base_client_debug_config.average_ack_delay,
ack_wait_addition: base_client_debug_config.acknowledgements.ack_wait_addition,
ack_wait_multiplier: base_client_debug_config
.acknowledgements
.ack_wait_multiplier,
average_message_sending_delay: base_client_debug_config
.traffic
.message_sending_average_delay,
average_packet_delay_duration: base_client_debug_config.traffic.average_packet_delay,
average_ack_delay_duration: base_client_debug_config.acknowledgements.average_ack_delay,
disable_main_poisson_packet_distribution: base_client_debug_config
.traffic
.disable_main_poisson_packet_distribution,
minimum_reply_surb_request_size: base_client_debug_config
.reply_surbs
.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: base_client_debug_config
.reply_surbs
.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: base_client_debug_config
.reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: base_client_debug_config
.reply_surbs
.maximum_reply_surb_rerequest_waiting_period,
maximum_reply_surb_drop_waiting_period: base_client_debug_config
.reply_surbs
.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: base_client_debug_config.maximum_reply_surb_age,
maximum_reply_key_age: base_client_debug_config.maximum_reply_key_age,
maximum_reply_surb_age: base_client_debug_config.reply_surbs.maximum_reply_surb_age,
maximum_reply_key_age: base_client_debug_config.reply_surbs.maximum_reply_key_age,
}
}
@@ -24,6 +24,7 @@ use rand::{CryptoRng, Rng};
use std::pin::Pin;
use std::sync::Arc;
use std::time::Duration;
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time;
@@ -232,7 +233,11 @@ where
return;
}
};
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
println!("cover sent_{}_{:?}",time, self.config.cover_packet_size.size());
(
generate_loop_cover_packet(
&mut self.rng,
@@ -250,6 +255,11 @@ where
)
}
StreamMessage::Real(real_message) => {
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
println!("real sent: /{:?}/{}/{}", real_message.fragment_id, time, real_message.mix_packet.sphinx_packet().payload.len());
(real_message.mix_packet, Some(real_message.fragment_id))
}
};
@@ -297,22 +307,29 @@ where
self.sending_delay_controller.current_multiplier()
);
// Even just a single used slot is enough to signal backpressure
if used_slots > 0 {
if self
.sending_delay_controller
.is_backpressure_currently_detected(used_slots)
{
log::trace!("Backpressure detected");
self.sending_delay_controller.record_backpressure_detected();
}
// If the buffer is running out, slow down the sending rate
// If the buffer is running out, slow down the sending rate by increasing the delay
// multiplier.
if self.mix_tx.capacity() == 0
&& self.sending_delay_controller.not_increased_delay_recently()
{
self.sending_delay_controller.increase_delay_multiplier();
}
// Very carefully step up the sending rate in case it seems like we can solidly handle the
// current rate.
if self.sending_delay_controller.is_sending_reliable() {
// If it looks like we are sending reliably, increase the sending rate by decreasing the
// sending delay multiplier.
if !self
.sending_delay_controller
.was_backpressure_detected_recently()
&& self.sending_delay_controller.not_decreased_delay_recently()
{
self.sending_delay_controller.decrease_delay_multiplier();
}
}
@@ -11,11 +11,14 @@ 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;
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 3;
// The queue length that is required for us to register that backpressure occured. If there are
// more than this many packets waiting to be sent, we consider the channel to be under
// backpressure.
const BACKPRESSURE_THRESHOLD: usize = 10;
// 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;
// lowering the average delay.
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 1;
// 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.
@@ -100,22 +103,27 @@ impl SendingDelayController {
}
}
pub(crate) fn record_backpressure_detected(&mut self) {
self.time_when_backpressure_detected = get_time_now();
}
pub(crate) fn not_increased_delay_recently(&self) -> bool {
get_time_now()
> self.time_when_changed + Duration::from_secs(INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
}
pub(crate) fn is_sending_reliable(&self) -> bool {
let now = get_time_now();
let delay_change_interval = Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS);
let acceptable_time_without_backpressure =
Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS);
pub(crate) fn not_decreased_delay_recently(&self) -> bool {
get_time_now()
> self.time_when_changed + Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
}
now > self.time_when_backpressure_detected + acceptable_time_without_backpressure
&& now > self.time_when_changed + delay_change_interval
pub(crate) fn is_backpressure_currently_detected(&self, queue_length: usize) -> bool {
queue_length > BACKPRESSURE_THRESHOLD
}
pub(crate) fn record_backpressure_detected(&mut self) {
self.time_when_backpressure_detected = get_time_now();
}
pub(crate) fn was_backpressure_detected_recently(&self) -> bool {
get_time_now()
< self.time_when_backpressure_detected
+ Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS)
}
}
@@ -20,6 +20,7 @@ use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
use std::collections::HashSet;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
// 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"
@@ -30,13 +31,13 @@ pub type ReceivedBufferRequestReceiver = mpsc::UnboundedReceiver<ReceivedBufferM
pub type ReconstructedMessagesSender = mpsc::UnboundedSender<Vec<ReconstructedMessage>>;
pub type ReconstructedMessagesReceiver = mpsc::UnboundedReceiver<Vec<ReconstructedMessage>>;
struct ReceivedMessagesBufferInner {
struct ReceivedMessagesBufferInner<R: MessageReceiver> {
messages: Vec<ReconstructedMessage>,
local_encryption_keypair: Arc<encryption::KeyPair>,
// TODO: looking how it 'looks' here, perhaps `MessageReceiver` should be renamed to something
// else instead.
message_receiver: MessageReceiver,
message_receiver: R,
message_sender: Option<ReconstructedMessagesSender>,
// TODO: this will get cleared upon re-running the client
@@ -45,10 +46,16 @@ struct ReceivedMessagesBufferInner {
recently_reconstructed: HashSet<i32>,
}
impl ReceivedMessagesBufferInner {
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
fn recover_from_fragment(&mut self, fragment_data: &[u8]) -> Option<NymMessage> {
let fragment_len = fragment_data.len();
if nym_sphinx::cover::is_cover(fragment_data) {
trace!("The message was a loop cover message! Skipping it");
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
println!("Cover received_{}_{}",time, fragment_len);
return None;
}
@@ -60,6 +67,12 @@ impl ReceivedMessagesBufferInner {
Ok(frag) => frag,
};
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
println!("real received : _{:?}_{}_{}_{}", &fragment.id(),&fragment.current_fragment(),time, fragment_len);
if self.recently_reconstructed.contains(&fragment.id()) {
debug!("Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost", fragment.id());
return None;
@@ -102,13 +115,13 @@ impl ReceivedMessagesBufferInner {
&mut self,
reply_ciphertext: &mut [u8],
reply_key: SurbEncryptionKey,
) -> Option<NymMessage> {
) -> Result<Option<NymMessage>, MessageRecoveryError> {
// note: this performs decryption IN PLACE without extra allocation
self.message_receiver
.recover_plaintext_from_reply(reply_ciphertext, reply_key);
.recover_plaintext_from_reply(reply_ciphertext, reply_key)?;
let fragment_data = reply_ciphertext;
self.recover_from_fragment(fragment_data)
Ok(self.recover_from_fragment(fragment_data))
}
fn process_received_regular_packet(&mut self, mut raw_fragment: Vec<u8>) -> Option<NymMessage> {
@@ -130,13 +143,13 @@ impl ReceivedMessagesBufferInner {
#[derive(Debug, Clone)]
// Note: you should NEVER create more than a single instance of this using 'new()'.
// You should always use .clone() to create additional instances
struct ReceivedMessagesBuffer {
inner: Arc<Mutex<ReceivedMessagesBufferInner>>,
struct ReceivedMessagesBuffer<R: MessageReceiver> {
inner: Arc<Mutex<ReceivedMessagesBufferInner<R>>>,
reply_key_storage: SentReplyKeys,
reply_controller_sender: ReplyControllerSender,
}
impl ReceivedMessagesBuffer {
impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
fn new(
local_encryption_keypair: Arc<encryption::KeyPair>,
reply_key_storage: SentReplyKeys,
@@ -146,7 +159,7 @@ impl ReceivedMessagesBuffer {
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
messages: Vec::new(),
local_encryption_keypair,
message_receiver: MessageReceiver::new(),
message_receiver: R::new(),
message_sender: None,
recently_reconstructed: HashSet::new(),
})),
@@ -328,7 +341,10 @@ impl ReceivedMessagesBuffer {
})
}
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
async fn handle_new_received(
&mut self,
msgs: Vec<Vec<u8>>,
) -> Result<(), MessageRecoveryError> {
trace!(
"Processing {:?} new message that might get added to the buffer!",
msgs.len()
@@ -344,7 +360,7 @@ impl ReceivedMessagesBuffer {
// if yes - this is a reply message
let completed_message =
if let Some((reply_key, reply_message)) = self.get_reply_key(&mut msg) {
inner_guard.process_received_reply(reply_message, reply_key)
inner_guard.process_received_reply(reply_message, reply_key)?
} else {
inner_guard.process_received_regular_packet(msg)
};
@@ -360,6 +376,7 @@ impl ReceivedMessagesBuffer {
if !completed_messages.is_empty() {
self.handle_reconstructed_messages(completed_messages).await
}
Ok(())
}
}
@@ -372,14 +389,14 @@ pub enum ReceivedBufferMessage {
ReceiverDisconnect,
}
struct RequestReceiver {
received_buffer: ReceivedMessagesBuffer,
struct RequestReceiver<R: MessageReceiver> {
received_buffer: ReceivedMessagesBuffer<R>,
query_receiver: ReceivedBufferRequestReceiver,
}
impl RequestReceiver {
impl<R: MessageReceiver> RequestReceiver<R> {
fn new(
received_buffer: ReceivedMessagesBuffer,
received_buffer: ReceivedMessagesBuffer<R>,
query_receiver: ReceivedBufferRequestReceiver,
) -> Self {
RequestReceiver {
@@ -422,14 +439,14 @@ impl RequestReceiver {
}
}
struct FragmentedMessageReceiver {
received_buffer: ReceivedMessagesBuffer,
struct FragmentedMessageReceiver<R: MessageReceiver> {
received_buffer: ReceivedMessagesBuffer<R>,
mixnet_packet_receiver: MixnetMessageReceiver,
}
impl FragmentedMessageReceiver {
impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
fn new(
received_buffer: ReceivedMessagesBuffer,
received_buffer: ReceivedMessagesBuffer<R>,
mixnet_packet_receiver: MixnetMessageReceiver,
) -> Self {
FragmentedMessageReceiver {
@@ -438,13 +455,16 @@ impl FragmentedMessageReceiver {
}
}
async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
async fn run_with_shutdown(
&mut self,
mut shutdown: nym_task::TaskClient,
) -> Result<(), MessageRecoveryError> {
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
while !shutdown.is_shutdown() {
tokio::select! {
new_messages = self.mixnet_packet_receiver.next() => {
if let Some(new_messages) = new_messages {
self.received_buffer.handle_new_received(new_messages).await;
self.received_buffer.handle_new_received(new_messages).await?;
} else {
log::trace!("FragmentedMessageReceiver: Stopping since channel closed");
break;
@@ -457,15 +477,16 @@ impl FragmentedMessageReceiver {
}
shutdown.recv_timeout().await;
log::debug!("FragmentedMessageReceiver: Exiting");
Ok(())
}
}
pub(crate) struct ReceivedMessagesBufferController {
fragmented_message_receiver: FragmentedMessageReceiver,
request_receiver: RequestReceiver,
pub(crate) struct ReceivedMessagesBufferController<R: MessageReceiver> {
fragmented_message_receiver: FragmentedMessageReceiver<R>,
request_receiver: RequestReceiver<R>,
}
impl ReceivedMessagesBufferController {
impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferController<R> {
pub(crate) fn new(
local_encryption_keypair: Arc<encryption::KeyPair>,
query_receiver: ReceivedBufferRequestReceiver,
@@ -494,9 +515,13 @@ impl ReceivedMessagesBufferController {
let shutdown_handle = shutdown.clone();
spawn_future(async move {
fragmented_message_receiver
match fragmented_message_receiver
.run_with_shutdown(shutdown_handle)
.await;
.await
{
Ok(_) => {}
Err(e) => error!("{e}"),
}
});
spawn_future(async move {
request_receiver.run_with_shutdown(shutdown).await;
@@ -35,8 +35,12 @@ impl ReplyStorageBackend for Backend {
) -> Result<Self, Self::StorageError> {
Ok(Backend {
empty: Empty {
min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold,
max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold,
min_surb_threshold: debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
max_surb_threshold: debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
},
})
}
@@ -155,18 +155,21 @@ impl Backend {
// (assuming no key rotation has happened)
// but the way it's currently coded, everyone will purge old data
let since_last_flush = OffsetDateTime::now_utc() - last_flush;
if since_last_flush.whole_days() > 0 {
info!("it's been over {} days and {} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
let days = since_last_flush.whole_days();
let hours = since_last_flush.whole_hours() % 24;
if days > 0 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now.");
manager.delete_all_reply_surb_data().await?;
}
if since_last_flush.whole_days() > 1 {
info!("it's been over {} days and {} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
if days > 1 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now.");
manager.delete_all_reply_keys().await?;
}
if since_last_flush.whole_days() > 2 {
info!("it's been over {} days and {} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
if days > 2 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.");
manager.delete_all_tags().await?;
}
@@ -35,8 +35,12 @@ impl ReplyStorageBackend for Empty {
_db_path: Option<PathBuf>,
) -> Result<Self, Self::StorageError> {
Ok(Self {
min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold,
max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold,
min_surb_threshold: debug_config
.reply_surbs
.minimum_reply_surb_storage_threshold,
max_surb_threshold: debug_config
.reply_surbs
.maximum_reply_surb_storage_threshold,
})
}
@@ -1,336 +0,0 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::DEFAULT_NUM_MIX_HOPS;
use nym_topology::{nym_topology_from_detailed, NymTopology, NymTopologyError};
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{RwLock, RwLockReadGuard};
use url::Url;
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
#[derive(Debug)]
pub struct TopologyAccessorInner(Option<NymTopology>);
impl AsRef<Option<NymTopology>> for TopologyAccessorInner {
fn as_ref(&self) -> &Option<NymTopology> {
&self.0
}
}
impl TopologyAccessorInner {
fn new() -> Self {
TopologyAccessorInner(None)
}
fn update(&mut self, new: Option<NymTopology>) {
self.0 = new;
}
}
pub struct TopologyReadPermit<'a> {
permit: RwLockReadGuard<'a, TopologyAccessorInner>,
}
impl<'a> Deref for TopologyReadPermit<'a> {
type Target = TopologyAccessorInner;
fn deref(&self) -> &Self::Target {
&self.permit
}
}
impl<'a> TopologyReadPermit<'a> {
/// Using provided topology read permit, tries to get an immutable reference to the underlying
/// topology. For obvious reasons the lifetime of the topology reference is bound to the permit.
pub(super) fn try_get_valid_topology_ref(
&'a self,
ack_recipient: &Recipient,
packet_recipient: Option<&Recipient>,
) -> Result<&'a NymTopology, NymTopologyError> {
// 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through?
let topology = self
.permit
.as_ref()
.as_ref()
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
// 2. does it have any mixnode at all?
// 3. does it have any gateways at all?
// 4. does it have a mixnode on each layer?
topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?;
// 5. does it contain OUR gateway (so that we could create an ack packet)?
if !topology.gateway_exists(ack_recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: ack_recipient.gateway().to_base58_string(),
});
}
// 6. for our target recipient, does it contain THEIR gateway (so that we could create
if let Some(recipient) = packet_recipient {
if !topology.gateway_exists(recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: recipient.gateway().to_base58_string(),
});
}
}
Ok(topology)
}
}
impl<'a> From<RwLockReadGuard<'a, TopologyAccessorInner>> for TopologyReadPermit<'a> {
fn from(read_permit: RwLockReadGuard<'a, TopologyAccessorInner>) -> Self {
TopologyReadPermit {
permit: read_permit,
}
}
}
#[derive(Clone, Debug)]
pub struct TopologyAccessor {
// `RwLock` *seems to* be the better approach for this as write access is only requested every
// few seconds, while reads are needed every single packet generated.
// However, proper benchmarks will be needed to determine if `RwLock` is indeed a better
// approach than a `Mutex`
inner: Arc<RwLock<TopologyAccessorInner>>,
}
impl TopologyAccessor {
pub fn new() -> Self {
TopologyAccessor {
inner: Arc::new(RwLock::new(TopologyAccessorInner::new())),
}
}
pub async fn get_read_permit(&self) -> TopologyReadPermit<'_> {
self.inner.read().await.into()
}
async fn update_global_topology(&self, new_topology: Option<NymTopology>) {
self.inner.write().await.update(new_topology);
}
// only used by the client at startup to get a slightly more reasonable error message
// (currently displays as unused because health checker is disabled due to required changes)
pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> {
match &self.inner.read().await.0 {
None => Err(NymTopologyError::EmptyNetworkTopology),
Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
}
}
}
impl Default for TopologyAccessor {
fn default() -> Self {
TopologyAccessor::new()
}
}
pub struct TopologyRefresherConfig {
nym_api_urls: Vec<Url>,
refresh_rate: Duration,
client_version: String,
}
impl TopologyRefresherConfig {
pub fn new(nym_api_urls: Vec<Url>, refresh_rate: Duration, client_version: String) -> Self {
TopologyRefresherConfig {
nym_api_urls,
refresh_rate,
client_version,
}
}
}
pub struct TopologyRefresher {
validator_client: validator_client::client::NymApiClient,
client_version: String,
nym_api_urls: Vec<Url>,
topology_accessor: TopologyAccessor,
refresh_rate: Duration,
currently_used_api: usize,
was_latest_valid: bool,
}
impl TopologyRefresher {
pub fn new(mut cfg: TopologyRefresherConfig, topology_accessor: TopologyAccessor) -> Self {
cfg.nym_api_urls.shuffle(&mut thread_rng());
TopologyRefresher {
validator_client: validator_client::client::NymApiClient::new(
cfg.nym_api_urls[0].clone(),
),
client_version: cfg.client_version,
nym_api_urls: cfg.nym_api_urls,
topology_accessor,
refresh_rate: cfg.refresh_rate,
currently_used_api: 0,
was_latest_valid: true,
}
}
fn use_next_nym_api(&mut self) {
if self.nym_api_urls.len() == 1 {
warn!("There's only a single nym API available - it won't be possible to use a different one");
return;
}
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
self.validator_client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
}
/// Verifies whether nodes a reasonably distributed among all mix layers.
///
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
/// However, this is a rather unrealistic expectation, instead we check whether there exists
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
///
/// # Arguments
///
/// * `topology`: active topology constructed from validator api data
fn check_layer_distribution(&self, active_topology: &NymTopology) -> bool {
let mixes = active_topology.mixes();
let mixnodes_count = active_topology.num_mixnodes();
if active_topology.gateways().is_empty() {
return false;
}
// trivial check to see if have at least a single node on each layer (regardless of active set size)
if mixes.get(&1).is_none() || mixes.get(&2).is_none() || mixes.get(&3).is_none() {
return false;
}
let upper_bound = (mixnodes_count as f32 * 0.66) as usize;
let lower_bound = (mixnodes_count as f32 * 0.15) as usize;
let layer1 = mixes.get(&1).unwrap().len();
let layer2 = mixes.get(&2).unwrap().len();
let layer3 = mixes.get(&3).unwrap().len();
if layer1 < lower_bound || layer1 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
if layer2 < lower_bound || layer2 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
if layer3 < lower_bound || layer3 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
true
}
async fn get_current_compatible_topology(&self) -> Option<NymTopology> {
// TODO: optimization for the future:
// only refresh mixnodes on timer and refresh gateways only when
// we have to send to a new, unknown, gateway
let mixnodes = match self.validator_client.get_cached_active_mixnodes().await {
Err(err) => {
error!("failed to get network mixnodes - {err}");
return None;
}
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
}
Ok(gateways) => gateways,
};
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
if !self.check_layer_distribution(&topology) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
None
} else {
Some(topology)
}
}
pub async fn refresh(&mut self) {
trace!("Refreshing the topology");
let new_topology = self.get_current_compatible_topology().await;
if new_topology.is_none() {
self.use_next_nym_api();
}
if new_topology.is_none() && self.was_latest_valid {
// if we failed to grab this topology, but the one before it was alright, let's assume
// validator had a tiny hiccup and use the old data
warn!("we're going to keep on using the old topology for this iteration");
self.was_latest_valid = false;
return;
} else if new_topology.is_some() {
self.was_latest_valid = true;
}
self.topology_accessor
.update_global_topology(new_topology)
.await;
}
pub async fn ensure_topology_is_routable(&self) -> Result<(), NymTopologyError> {
self.topology_accessor.ensure_is_routable().await
}
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
spawn_future(async move {
debug!("Started TopologyRefresher with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while !shutdown.is_shutdown() {
tokio::select! {
_ = interval.next() => {
self.refresh().await;
},
_ = shutdown.recv() => {
log::trace!("TopologyRefresher: Received shutdown");
},
}
}
shutdown.recv_timeout().await;
log::debug!("TopologyRefresher: Exiting");
})
}
}
@@ -0,0 +1,154 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::DEFAULT_NUM_MIX_HOPS;
use nym_topology::{NymTopology, NymTopologyError};
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::{Notify, RwLock, RwLockReadGuard};
#[derive(Debug)]
pub struct TopologyAccessorInner {
controlled_manually: AtomicBool,
released_manual_control: Notify,
// `RwLock` *seems to* be the better approach for this as write access is only requested every
// few seconds, while reads are needed every single packet generated.
// However, proper benchmarks will be needed to determine if `RwLock` is indeed a better
// approach than a `Mutex`
topology: RwLock<Option<NymTopology>>,
}
impl TopologyAccessorInner {
fn new() -> Self {
TopologyAccessorInner {
controlled_manually: AtomicBool::new(false),
released_manual_control: Notify::new(),
topology: RwLock::new(None),
}
}
async fn update(&self, new: Option<NymTopology>) {
*self.topology.write().await = new;
}
}
pub struct TopologyReadPermit<'a> {
permit: RwLockReadGuard<'a, Option<NymTopology>>,
}
impl<'a> Deref for TopologyReadPermit<'a> {
type Target = Option<NymTopology>;
fn deref(&self) -> &Self::Target {
&self.permit
}
}
impl<'a> TopologyReadPermit<'a> {
/// Using provided topology read permit, tries to get an immutable reference to the underlying
/// topology. For obvious reasons the lifetime of the topology reference is bound to the permit.
pub(crate) fn try_get_valid_topology_ref(
&'a self,
ack_recipient: &Recipient,
packet_recipient: Option<&Recipient>,
) -> Result<&'a NymTopology, NymTopologyError> {
// 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through?
let topology = self
.permit
.as_ref()
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
// 2. does it have any mixnode at all?
// 3. does it have any gateways at all?
// 4. does it have a mixnode on each layer?
topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?;
// 5. does it contain OUR gateway (so that we could create an ack packet)?
if !topology.gateway_exists(ack_recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: ack_recipient.gateway().to_base58_string(),
});
}
// 6. for our target recipient, does it contain THEIR gateway (so that we could create
if let Some(recipient) = packet_recipient {
if !topology.gateway_exists(recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: recipient.gateway().to_base58_string(),
});
}
}
Ok(topology)
}
}
impl<'a> From<RwLockReadGuard<'a, Option<NymTopology>>> for TopologyReadPermit<'a> {
fn from(read_permit: RwLockReadGuard<'a, Option<NymTopology>>) -> Self {
TopologyReadPermit {
permit: read_permit,
}
}
}
#[derive(Clone, Debug)]
pub struct TopologyAccessor {
inner: Arc<TopologyAccessorInner>,
}
impl TopologyAccessor {
pub fn new() -> Self {
TopologyAccessor {
inner: Arc::new(TopologyAccessorInner::new()),
}
}
pub fn controlled_manually(&self) -> bool {
self.inner.controlled_manually.load(Ordering::SeqCst)
}
pub async fn get_read_permit(&self) -> TopologyReadPermit<'_> {
self.inner.topology.read().await.into()
}
pub(crate) async fn update_global_topology(&self, new_topology: Option<NymTopology>) {
self.inner.update(new_topology).await;
}
pub(crate) async fn wait_for_released_manual_control(&self) {
self.inner.released_manual_control.notified().await
}
pub async fn current_topology(&self) -> Option<NymTopology> {
self.inner.topology.read().await.clone()
}
pub async fn manually_change_topology(&self, new_topology: NymTopology) {
self.inner.controlled_manually.store(true, Ordering::SeqCst);
self.inner.update(Some(new_topology)).await;
}
pub fn release_manual_control(&self) {
self.inner
.controlled_manually
.store(false, Ordering::SeqCst);
self.inner.released_manual_control.notify_waiters();
}
// only used by the client at startup to get a slightly more reasonable error message
// (currently displays as unused because health checker is disabled due to required changes)
pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> {
match self.inner.topology.read().await.deref() {
None => Err(NymTopologyError::EmptyNetworkTopology),
Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
}
}
}
impl Default for TopologyAccessor {
fn default() -> Self {
TopologyAccessor::new()
}
}
@@ -0,0 +1,115 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit};
use futures::StreamExt;
use log::*;
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::NymTopologyError;
use std::time::Duration;
mod accessor;
pub(crate) mod nym_api_provider;
// TODO: move it to config later
const MAX_FAILURE_COUNT: usize = 10;
pub struct TopologyRefresherConfig {
refresh_rate: Duration,
}
impl TopologyRefresherConfig {
pub fn new(refresh_rate: Duration) -> Self {
TopologyRefresherConfig { refresh_rate }
}
}
pub struct TopologyRefresher {
topology_provider: Box<dyn TopologyProvider>,
topology_accessor: TopologyAccessor,
refresh_rate: Duration,
consecutive_failure_count: usize,
}
impl TopologyRefresher {
pub fn new(
cfg: TopologyRefresherConfig,
topology_accessor: TopologyAccessor,
topology_provider: Box<dyn TopologyProvider>,
) -> Self {
TopologyRefresher {
topology_provider,
topology_accessor,
refresh_rate: cfg.refresh_rate,
consecutive_failure_count: 0,
}
}
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider>) {
self.topology_provider = provider;
}
pub async fn try_refresh(&mut self) {
trace!("Refreshing the topology");
if self.topology_accessor.controlled_manually() {
info!("topology is being controlled manually - we're going to wait until the control is released...");
self.topology_accessor
.wait_for_released_manual_control()
.await;
}
let new_topology = self.topology_provider.get_new_topology().await;
if new_topology.is_none() {
warn!("failed to obtain new network topology");
}
if new_topology.is_none() && self.consecutive_failure_count < MAX_FAILURE_COUNT {
// if we failed to grab this topology, but the one before it was alright, let's assume
// validator had a tiny hiccup and use the old data
warn!("we're going to keep on using the old topology for this iteration");
self.consecutive_failure_count += 1;
return;
} else if new_topology.is_some() {
self.consecutive_failure_count = 0;
}
self.topology_accessor
.update_global_topology(new_topology)
.await;
}
pub async fn ensure_topology_is_routable(&self) -> Result<(), NymTopologyError> {
self.topology_accessor.ensure_is_routable().await
}
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
spawn_future(async move {
debug!("Started TopologyRefresher with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while !shutdown.is_shutdown() {
tokio::select! {
_ = interval.next() => {
self.try_refresh().await;
},
_ = shutdown.recv() => {
log::trace!("TopologyRefresher: Received shutdown");
},
}
}
shutdown.recv_timeout().await;
log::debug!("TopologyRefresher: Exiting");
})
}
}
@@ -0,0 +1,106 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use log::{error, warn};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::{nym_topology_from_detailed, NymTopology, NymTopologyError};
use rand::prelude::SliceRandom;
use rand::thread_rng;
use url::Url;
pub(crate) struct NymApiTopologyProvider {
validator_client: validator_client::client::NymApiClient,
nym_api_urls: Vec<Url>,
client_version: String,
currently_used_api: usize,
}
impl NymApiTopologyProvider {
pub(crate) fn new(mut nym_api_urls: Vec<Url>, client_version: String) -> Self {
nym_api_urls.shuffle(&mut thread_rng());
NymApiTopologyProvider {
validator_client: validator_client::client::NymApiClient::new(nym_api_urls[0].clone()),
nym_api_urls,
client_version,
currently_used_api: 0,
}
}
fn use_next_nym_api(&mut self) {
if self.nym_api_urls.len() == 1 {
warn!("There's only a single nym API available - it won't be possible to use a different one");
return;
}
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
self.validator_client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
}
/// Verifies whether nodes a reasonably distributed among all mix layers.
///
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
/// However, this is a rather unrealistic expectation, instead we check whether there exists
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
///
/// # Arguments
///
/// * `topology`: active topology constructed from validator api data
fn check_layer_distribution(
&self,
active_topology: &NymTopology,
) -> Result<(), NymTopologyError> {
let lower_threshold = 0.15;
let upper_threshold = 0.66;
active_topology.ensure_even_layer_distribution(lower_threshold, upper_threshold)
}
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
let mixnodes = match self.validator_client.get_cached_active_mixnodes().await {
Err(err) => {
error!("failed to get network mixnodes - {err}");
return None;
}
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
}
Ok(gateways) => gateways,
};
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
if let Err(err) = self.check_layer_distribution(&topology) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used: {err}");
self.use_next_nym_api();
None
} else {
Some(topology)
}
}
}
// hehe, wasm
#[cfg(not(target_arch = "wasm32"))]
#[async_trait]
impl TopologyProvider for NymApiTopologyProvider {
async fn get_new_topology(&mut self) -> Option<NymTopology> {
self.get_current_compatible_topology().await
}
}
#[cfg(target_arch = "wasm32")]
#[async_trait(?Send)]
impl TopologyProvider for NymApiTopologyProvider {
async fn get_new_topology(&mut self) -> Option<NymTopology> {
self.get_current_compatible_topology().await
}
}
+195 -94
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use nym_config::defaults::NymNetworkDetails;
use nym_config::{NymConfig, OptionalSet, DB_FILE_NAME};
use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME};
use nym_sphinx::params::PacketSize;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
@@ -13,6 +13,7 @@ use url::Url;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
pub mod old_config_v1_1_13;
pub mod persistence;
pub const MISSING_VALUE: &str = "MISSING VALUE";
@@ -210,11 +211,12 @@ impl<T> Config<T> {
}
pub fn set_high_default_traffic_volume(&mut self) {
self.debug.average_packet_delay = Duration::from_millis(10);
self.debug.traffic.average_packet_delay = Duration::from_millis(10);
// basically don't really send cover messages
self.debug.loop_cover_traffic_average_delay = Duration::from_millis(2_000_000);
self.debug.cover_traffic.loop_cover_traffic_average_delay =
Duration::from_millis(2_000_000);
// 250 "real" messages / s
self.debug.message_sending_average_delay = Duration::from_millis(4);
self.debug.traffic.message_sending_average_delay = Duration::from_millis(4);
}
pub fn with_disabled_cover_traffic(mut self, disabled: bool) -> Self {
@@ -225,8 +227,8 @@ impl<T> Config<T> {
}
pub fn set_no_cover_traffic(&mut self) {
self.debug.disable_loop_cover_traffic_stream = true;
self.debug.disable_main_poisson_packet_distribution = true;
self.debug.cover_traffic.disable_loop_cover_traffic_stream = true;
self.debug.traffic.disable_main_poisson_packet_distribution = true;
}
pub fn set_custom_version(&mut self, version: &str) {
@@ -311,87 +313,93 @@ impl<T> Config<T> {
}
pub fn get_average_packet_delay(&self) -> Duration {
self.debug.average_packet_delay
self.debug.traffic.average_packet_delay
}
pub fn get_average_ack_delay(&self) -> Duration {
self.debug.average_ack_delay
self.debug.acknowledgements.average_ack_delay
}
pub fn get_ack_wait_multiplier(&self) -> f64 {
self.debug.ack_wait_multiplier
self.debug.acknowledgements.ack_wait_multiplier
}
pub fn get_ack_wait_addition(&self) -> Duration {
self.debug.ack_wait_addition
self.debug.acknowledgements.ack_wait_addition
}
pub fn get_loop_cover_traffic_average_delay(&self) -> Duration {
self.debug.loop_cover_traffic_average_delay
self.debug.cover_traffic.loop_cover_traffic_average_delay
}
pub fn get_message_sending_average_delay(&self) -> Duration {
self.debug.message_sending_average_delay
self.debug.traffic.message_sending_average_delay
}
pub fn get_gateway_response_timeout(&self) -> Duration {
self.debug.gateway_response_timeout
self.debug.gateway_connection.gateway_response_timeout
}
pub fn get_topology_refresh_rate(&self) -> Duration {
self.debug.topology_refresh_rate
self.debug.topology.topology_refresh_rate
}
pub fn get_topology_resolution_timeout(&self) -> Duration {
self.debug.topology_resolution_timeout
self.debug.topology.topology_resolution_timeout
}
pub fn get_disabled_loop_cover_traffic_stream(&self) -> bool {
self.debug.disable_loop_cover_traffic_stream
self.debug.cover_traffic.disable_loop_cover_traffic_stream
}
pub fn get_disabled_main_poisson_packet_distribution(&self) -> bool {
self.debug.disable_main_poisson_packet_distribution
self.debug.traffic.disable_main_poisson_packet_distribution
}
pub fn get_use_extended_packet_size(&self) -> Option<ExtendedPacketSize> {
self.debug.use_extended_packet_size
self.debug.traffic.use_extended_packet_size
}
pub fn get_minimum_reply_surb_storage_threshold(&self) -> usize {
self.debug.minimum_reply_surb_storage_threshold
self.debug.reply_surbs.minimum_reply_surb_storage_threshold
}
pub fn get_maximum_reply_surb_storage_threshold(&self) -> usize {
self.debug.maximum_reply_surb_storage_threshold
self.debug.reply_surbs.maximum_reply_surb_storage_threshold
}
pub fn get_minimum_reply_surb_request_size(&self) -> u32 {
self.debug.minimum_reply_surb_request_size
self.debug.reply_surbs.minimum_reply_surb_request_size
}
pub fn get_maximum_reply_surb_request_size(&self) -> u32 {
self.debug.maximum_reply_surb_request_size
self.debug.reply_surbs.maximum_reply_surb_request_size
}
pub fn get_maximum_allowed_reply_surb_request_size(&self) -> u32 {
self.debug.maximum_allowed_reply_surb_request_size
self.debug
.reply_surbs
.maximum_allowed_reply_surb_request_size
}
pub fn get_maximum_reply_surb_rerequest_waiting_period(&self) -> Duration {
self.debug.maximum_reply_surb_rerequest_waiting_period
self.debug
.reply_surbs
.maximum_reply_surb_rerequest_waiting_period
}
pub fn get_maximum_reply_surb_drop_waiting_period(&self) -> Duration {
self.debug.maximum_reply_surb_drop_waiting_period
self.debug
.reply_surbs
.maximum_reply_surb_drop_waiting_period
}
pub fn get_maximum_reply_surb_age(&self) -> Duration {
self.debug.maximum_reply_surb_age
self.debug.reply_surbs.maximum_reply_surb_age
}
pub fn get_maximum_reply_key_age(&self) -> Duration {
self.debug.maximum_reply_key_age
self.debug.reply_surbs.maximum_reply_key_age
}
}
@@ -450,63 +458,63 @@ impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
pub struct Client<T> {
/// Version of the client for which this configuration was created.
#[serde(default = "missing_string_value")]
version: String,
pub version: String,
/// ID specifies the human readable ID of this particular client.
id: String,
pub id: String,
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
#[serde(default)]
disabled_credentials_mode: bool,
pub disabled_credentials_mode: bool,
/// Addresses to nyxd validators via which the client can communicate with the chain.
#[serde(alias = "validator_urls")]
nyxd_urls: Vec<Url>,
pub nyxd_urls: Vec<Url>,
/// Addresses to APIs running on validator from which the client gets the view of the network.
#[serde(alias = "validator_api_urls")]
nym_api_urls: Vec<Url>,
pub nym_api_urls: Vec<Url>,
/// Path to file containing private identity key.
private_identity_key_file: PathBuf,
pub private_identity_key_file: PathBuf,
/// Path to file containing public identity key.
public_identity_key_file: PathBuf,
pub public_identity_key_file: PathBuf,
/// Path to file containing private encryption key.
private_encryption_key_file: PathBuf,
pub private_encryption_key_file: PathBuf,
/// Path to file containing public encryption key.
public_encryption_key_file: PathBuf,
pub public_encryption_key_file: PathBuf,
/// Path to file containing shared key derived with the specified gateway that is used
/// for all communication with it.
gateway_shared_key_file: PathBuf,
pub gateway_shared_key_file: PathBuf,
/// Path to file containing key used for encrypting and decrypting the content of an
/// acknowledgement so that nobody besides the client knows which packet it refers to.
ack_key_file: PathBuf,
pub ack_key_file: PathBuf,
/// Information regarding how the client should send data to gateway.
gateway_endpoint: GatewayEndpointConfig,
pub gateway_endpoint: GatewayEndpointConfig,
/// Path to the database containing bandwidth credentials of this client.
database_path: PathBuf,
pub database_path: PathBuf,
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
// this was set to use #[serde(default)] for the purposes of compatibility for multi-surbs introduced in 1.1.4.
// if you're reading this message and we have already introduced some breaking changes, feel free
// to remove that attribute since at this point the client configs should have gotten regenerated
#[serde(default)]
reply_surb_database_path: PathBuf,
pub reply_surb_database_path: PathBuf,
/// nym_home_directory specifies absolute path to the home nym Clients directory.
/// It is expected to use default value and hence .toml file should not redefine this field.
nym_root_directory: PathBuf,
pub nym_root_directory: PathBuf,
#[serde(skip)]
super_struct: PhantomData<T>,
pub super_struct: PhantomData<T>,
}
impl<T: NymConfig> Default for Client<T> {
@@ -579,7 +587,7 @@ impl<T: NymConfig> Client<T> {
}
fn default_database_path(id: &str) -> PathBuf {
T::default_data_directory(id).join(DB_FILE_NAME)
T::default_data_directory(id).join(CRED_DB_FILE_NAME)
}
}
@@ -587,9 +595,9 @@ impl<T: NymConfig> Client<T> {
#[serde(deny_unknown_fields)]
pub struct Logging {}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfig {
pub struct Traffic {
/// 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
@@ -597,6 +605,74 @@ pub struct DebugConfig {
#[serde(with = "humantime_serde")]
pub average_packet_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")]
pub message_sending_average_delay: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Controls whether the sent sphinx packet use a NON-DEFAULT bigger size.
pub use_extended_packet_size: Option<ExtendedPacketSize>,
}
impl Default for Traffic {
fn default() -> Self {
Traffic {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
use_extended_packet_size: None,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTraffic {
/// 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")]
pub loop_cover_traffic_average_delay: 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,
}
impl Default for CoverTraffic {
fn default() -> Self {
CoverTraffic {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
disable_loop_cover_traffic_stream: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnection {
/// 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")]
pub gateway_response_timeout: Duration,
}
impl Default for GatewayConnection {
fn default() -> Self {
GatewayConnection {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Acknowledgements {
/// 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
@@ -614,24 +690,21 @@ pub struct DebugConfig {
/// In an ideal network with 0 latency, this value would have been 0.
#[serde(with = "humantime_serde")]
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")]
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")]
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")]
pub gateway_response_timeout: Duration,
impl Default for Acknowledgements {
fn default() -> Self {
Acknowledgements {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Topology {
/// 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")]
@@ -642,18 +715,20 @@ pub struct DebugConfig {
/// did not reach its destination.
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
}
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Controls whether the sent sphinx packet use a NON-DEFAULT bigger size.
pub use_extended_packet_size: Option<ExtendedPacketSize>,
impl Default for Topology {
fn default() -> Self {
Topology {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbs {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
/// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize,
@@ -691,29 +766,9 @@ pub struct DebugConfig {
pub maximum_reply_key_age: Duration,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ExtendedPacketSize {
Extended8,
Extended16,
Extended32,
}
impl Default for DebugConfig {
impl Default for ReplySurbs {
fn default() -> Self {
DebugConfig {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
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,
ReplySurbs {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
@@ -728,6 +783,52 @@ impl Default for DebugConfig {
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfig {
/// Defines all configuration options related to traffic streams.
pub traffic: Traffic,
/// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTraffic,
/// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnection,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: Acknowledgements,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: Topology,
/// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbs,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ExtendedPacketSize {
Extended8,
Extended16,
Extended32,
}
// it could be derived, sure, but I'd rather have an explicit implementation in case we had to change
// something manually at some point
#[allow(clippy::derivable_impls)]
impl Default for DebugConfig {
fn default() -> Self {
DebugConfig {
traffic: Default::default(),
cover_traffic: Default::default(),
gateway_connection: Default::default(),
acknowledgements: Default::default(),
topology: Default::default(),
reply_surbs: Default::default(),
}
}
}
impl From<ExtendedPacketSize> for PacketSize {
fn from(size: ExtendedPacketSize) -> PacketSize {
match size {
@@ -0,0 +1,198 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, ExtendedPacketSize,
GatewayConnection, Logging, ReplySurbs, Topology, Traffic, DEFAULT_ACK_WAIT_ADDITION,
DEFAULT_ACK_WAIT_MULTIPLIER, DEFAULT_AVERAGE_PACKET_DELAY, DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
DEFAULT_MAXIMUM_REPLY_KEY_AGE, DEFAULT_MAXIMUM_REPLY_SURB_AGE,
DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
DEFAULT_TOPOLOGY_REFRESH_RATE, DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
};
use nym_config::NymConfig;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use std::time::Duration;
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13<T> {
pub client: Client<T>,
#[serde(default)]
logging: Logging,
#[serde(default)]
debug: OldDebugConfigV1_1_13,
}
impl<T: NymConfig> Default for OldConfigV1_1_13<T> {
fn default() -> Self {
OldConfigV1_1_13 {
client: Client::<T>::default(),
logging: Default::default(),
debug: Default::default(),
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct OldDebugConfigV1_1_13 {
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
pub ack_wait_multiplier: f64,
#[serde(with = "humantime_serde")]
pub ack_wait_addition: Duration,
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
pub disable_loop_cover_traffic_stream: bool,
pub disable_main_poisson_packet_distribution: bool,
pub use_extended_packet_size: Option<ExtendedPacketSize>,
pub minimum_reply_surb_storage_threshold: usize,
pub maximum_reply_surb_storage_threshold: usize,
pub minimum_reply_surb_request_size: u32,
pub maximum_reply_surb_request_size: u32,
pub maximum_allowed_reply_surb_request_size: u32,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_rerequest_waiting_period: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_drop_waiting_period: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_age: Duration,
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
}
impl From<OldDebugConfigV1_1_13> for DebugConfig {
fn from(value: OldDebugConfigV1_1_13) -> Self {
DebugConfig {
traffic: Traffic {
average_packet_delay: value.average_packet_delay,
message_sending_average_delay: value.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
.disable_main_poisson_packet_distribution,
use_extended_packet_size: value.use_extended_packet_size,
},
cover_traffic: CoverTraffic {
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream,
},
gateway_connection: GatewayConnection {
gateway_response_timeout: value.gateway_response_timeout,
},
acknowledgements: Acknowledgements {
average_ack_delay: value.average_ack_delay,
ack_wait_multiplier: value.ack_wait_multiplier,
ack_wait_addition: value.ack_wait_addition,
},
topology: Topology {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
},
reply_surbs: ReplySurbs {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: value.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: value
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: value
.maximum_reply_surb_rerequest_waiting_period,
maximum_reply_surb_drop_waiting_period: value
.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: value.maximum_reply_surb_age,
maximum_reply_key_age: value.maximum_reply_key_age,
},
}
}
}
impl Default for OldDebugConfigV1_1_13 {
fn default() -> Self {
OldDebugConfigV1_1_13 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
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,
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
maximum_reply_surb_rerequest_waiting_period:
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
}
}
}
impl<T, U> From<OldConfigV1_1_13<T>> for Config<U> {
fn from(value: OldConfigV1_1_13<T>) -> Self {
Config {
client: Client {
version: value.client.version,
id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode,
nyxd_urls: value.client.nyxd_urls,
nym_api_urls: value.client.nym_api_urls,
private_identity_key_file: value.client.private_identity_key_file,
public_identity_key_file: value.client.public_identity_key_file,
private_encryption_key_file: value.client.private_encryption_key_file,
public_encryption_key_file: value.client.public_encryption_key_file,
gateway_shared_key_file: value.client.gateway_shared_key_file,
ack_key_file: value.client.ack_key_file,
gateway_endpoint: value.client.gateway_endpoint,
database_path: value.client.database_path,
reply_surb_database_path: value.client.reply_surb_database_path,
nym_root_directory: value.client.nym_root_directory,
super_struct: PhantomData,
},
logging: value.logging,
debug: value.debug.into(),
}
}
}
+27 -1
View File
@@ -3,6 +3,7 @@
use gateway_client::error::GatewayClientError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_topology::gateway::GatewayConversionError;
use nym_topology::NymTopologyError;
use validator_client::ValidatorClientError;
@@ -53,7 +54,32 @@ pub enum ClientCoreError {
GatewayOwnerUnknown,
#[error("The address of the gateway is unknown - did you run init?")]
GatwayAddressUnknown,
GatewayAddressUnknown,
#[error("The gateway is malformed: {source}")]
MalformedGateway {
#[from]
source: GatewayConversionError,
},
#[error("failed to establish connection to gateway: {source}")]
GatewayConnectionFailure {
#[from]
source: tungstenite::Error,
},
#[cfg(target_arch = "wasm32")]
#[error("failed to establish gateway connection (wasm)")]
GatewayJsConnectionFailure,
#[error("Gateway connection was abruptly closed")]
GatewayConnectionAbruptlyClosed,
#[error("Timed out while trying to establish gateway connection")]
GatewayConnectionTimeout,
#[error("No ping measurements for the gateway ({identity}) performed")]
NoGatewayMeasurements { identity: String },
#[error("failed to register receiver for reconstructed mixnet messages")]
FailedToRegisterReceiver,
+195 -24
View File
@@ -6,52 +6,223 @@ use crate::{
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
error::ClientCoreError,
};
#[cfg(target_arch = "wasm32")]
use gateway_client::wasm_mockups::SigningNyxdClient;
use futures::{SinkExt, StreamExt};
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
use log::{debug, info, trace, warn};
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity;
use nym_topology::{filter::VersionFilterable, gateway};
use rand::{seq::SliceRandom, thread_rng};
use rand::{seq::SliceRandom, thread_rng, Rng};
use std::{sync::Arc, time::Duration};
use tap::TapFallible;
use tungstenite::Message;
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::SigningNyxdClient;
pub(super) async fn query_gateway_details(
validator_servers: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
) -> Result<gateway::Node, ClientCoreError> {
let nym_api = validator_servers
.choose(&mut thread_rng())
#[cfg(not(target_arch = "wasm32"))]
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
#[cfg(target_arch = "wasm32")]
use gateway_client::wasm_mockups::SigningNyxdClient;
#[cfg(target_arch = "wasm32")]
use wasm_timer::Instant;
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
#[cfg(target_arch = "wasm32")]
type WsConn = JSWebsocket;
const MEASUREMENTS: usize = 3;
#[cfg(not(target_arch = "wasm32"))]
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
const PING_TIMEOUT: Duration = Duration::from_millis(1000);
struct GatewayWithLatency {
gateway: gateway::Node,
latency: Duration,
}
impl GatewayWithLatency {
fn new(gateway: gateway::Node, latency: Duration) -> Self {
GatewayWithLatency { gateway, latency }
}
}
async fn current_gateways<R: Rng>(
rng: &mut R,
nym_apis: Vec<Url>,
) -> Result<Vec<gateway::Node>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let validator_client = validator_client::client::NymApiClient::new(nym_api.clone());
let client = validator_client::client::NymApiClient::new(nym_api.clone());
log::trace!("Fetching list of gateways from: {}", nym_api);
let gateways = validator_client.get_cached_gateways().await?;
let gateways = client.get_cached_gateways().await?;
let valid_gateways = gateways
.into_iter()
.filter_map(|gateway| gateway.try_into().ok())
.collect::<Vec<gateway::Node>>();
// we were always filtering by version so I'm not removing that 'feature'
let filtered_gateways = valid_gateways.filter_by_version(env!("CARGO_PKG_VERSION"));
Ok(filtered_gateways)
}
// if we have chosen particular gateway - use it, otherwise choose a random one.
// (remember that in active topology all gateways have at least 100 reputation so should
// be working correctly)
if let Some(gateway_id) = chosen_gateway_id {
filtered_gateways
.iter()
.find(|gateway| gateway.identity_key == gateway_id)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_id.to_string()))
.cloned()
#[cfg(not(target_arch = "wasm32"))]
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
Ok(Err(conn_failure)) => Err(conn_failure.into()),
Ok(Ok((stream, _))) => Ok(stream),
}
}
#[cfg(target_arch = "wasm32")]
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure)
}
async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, ClientCoreError> {
let addr = gateway.clients_address();
trace!(
"establishing connection to {} ({addr})...",
gateway.identity_key,
);
let mut stream = connect(&addr).await?;
let mut results = Vec::new();
for _ in 0..MEASUREMENTS {
let measurement_future = async {
let ping_content = vec![1, 2, 3];
let start = Instant::now();
stream.send(Message::Ping(ping_content.clone())).await?;
match stream.next().await {
Some(Ok(Message::Pong(content))) => {
if content == ping_content {
let elapsed = Instant::now().duration_since(start);
trace!("current ping time: {elapsed:?}");
results.push(elapsed);
} else {
warn!("received a pong message with different content? wtf.")
}
}
Some(Ok(_)) => warn!("received a message that's not a pong!"),
Some(Err(err)) => return Err(err.into()),
None => return Err(ClientCoreError::GatewayConnectionAbruptlyClosed),
}
Ok::<(), ClientCoreError>(())
};
// thanks to wasm we can't use tokio::time::timeout : (
#[cfg(not(target_arch = "wasm32"))]
let timeout = tokio::time::sleep(PING_TIMEOUT);
#[cfg(not(target_arch = "wasm32"))]
tokio::pin!(timeout);
#[cfg(target_arch = "wasm32")]
let mut timeout = wasm_timer::Delay::new(PING_TIMEOUT);
tokio::select! {
_ = &mut timeout => {
warn!("timed out while trying to perform measurement...")
}
res = measurement_future => res?,
}
}
let count = results.len() as u64;
if count == 0 {
return Err(ClientCoreError::NoGatewayMeasurements {
identity: gateway.identity_key.to_base58_string(),
});
}
let sum: Duration = results.into_iter().sum();
let avg = Duration::from_nanos(sum.as_nanos() as u64 / count);
Ok(GatewayWithLatency::new(gateway, avg))
}
async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
info!("choosing gateway by latency...");
let mut gateways_with_latency = Vec::new();
for gateway in gateways {
let id = *gateway.identity();
trace!("measuring latency to {id}...");
let with_latency = match measure_latency(gateway).await {
Ok(res) => res,
Err(err) => {
warn!("failed to measure {id}: {err}");
continue;
}
};
debug!(
"{id} ({}): {:?}",
with_latency.gateway.location, with_latency.latency
);
gateways_with_latency.push(with_latency)
}
let chosen = gateways_with_latency
.choose_weighted(rng, |item| 1. / item.latency.as_secs_f32())
.expect("invalid selection weight!");
info!(
"chose gateway {} (located at {}) with average latency of {:?}",
chosen.gateway.identity_key, chosen.gateway.location, chosen.latency
);
Ok(chosen.gateway.clone())
}
fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
gateways
.choose(rng)
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
}
pub(super) async fn query_gateway_details(
validator_servers: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<gateway::Node, ClientCoreError> {
let mut rng = thread_rng();
let gateways = current_gateways(&mut rng, validator_servers).await?;
// if we set an explicit gateway, use that one and nothing else
if let Some(explicitly_chosen) = chosen_gateway_id {
gateways
.into_iter()
.find(|gateway| gateway.identity_key == explicitly_chosen)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(explicitly_chosen.to_string()))
} else if by_latency {
choose_gateway_by_latency(&mut rng, gateways).await
} else {
filtered_gateways
.choose(&mut rand::thread_rng())
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
uniformly_random_gateway(&mut rng, gateways)
}
}
+12 -6
View File
@@ -77,9 +77,11 @@ pub async fn register_with_gateway(
key_manager: &mut KeyManager,
nym_api_endpoints: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError> {
// Get the gateway details of the gateway we will use
let gateway = helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id).await?;
let gateway =
helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id, by_latency).await?;
log::debug!("Querying gateway gives: {}", gateway);
let our_identity = key_manager.identity_keypair();
@@ -102,6 +104,7 @@ pub async fn setup_gateway_from_config<C, T>(
register_gateway: bool,
user_chosen_gateway_id: Option<identity::PublicKey>,
config: &Config<T>,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError>
where
C: NymConfig + ClientCoreConfigTrait,
@@ -109,17 +112,20 @@ where
{
let id = config.get_id();
// If we are not going to register gateway, and an explicitly chosed gateway is not passed in,
// If we are not going to register gateway, and an explicitly chosen gateway is not passed in,
// load the existing configuration file
if !register_gateway && user_chosen_gateway_id.is_none() {
println!("Not registering gateway, will reuse existing config and keys");
return load_existing_gateway_config::<C>(&id);
}
// Else, we preceed by querying the nym-api
let gateway =
helpers::query_gateway_details(config.get_nym_api_endpoints(), user_chosen_gateway_id)
.await?;
// Else, we proceed by querying the nym-api
let gateway = helpers::query_gateway_details(
config.get_nym_api_endpoints(),
user_chosen_gateway_id,
by_latency,
)
.await?;
log::debug!("Querying gateway gives: {}", gateway);
// If we are not registering, just return this and assume the caller has the keys already and
+4 -4
View File
@@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bip39 = "1.0.1"
bip39 = { workspace = true }
clap = { version = "4.0", features = ["cargo", "derive"] }
log = "0.4"
rand = "0.7.3"
@@ -15,10 +15,10 @@ thiserror = "1.0"
url = "2.2"
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
coconut-interface = { path = "../../common/coconut-interface" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
nym-bin-common = { path = "../../common/bin-common"}
nym-network-defaults = { path = "../../common/network-defaults" }
+5 -5
View File
@@ -7,11 +7,11 @@ use nym_bin_common::completions::ArgShell;
use rand::rngs::OsRng;
use std::str::FromStr;
use coconut_interface::{Base58, Parameters};
use credential_storage::storage::Storage;
use credential_storage::PersistentStorage;
use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use credentials::coconut::utils::obtain_aggregate_signature;
use nym_coconut_interface::{Base58, Parameters};
use nym_credential_storage::storage::Storage;
use nym_credential_storage::PersistentStorage;
use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use nym_credentials::coconut::utils::obtain_aggregate_signature;
use nym_crypto::asymmetric::{encryption, identity};
use nym_network_defaults::VOUCHER_INFO;
use validator_client::nyxd::traits::DkgQueryClient;
+2 -2
View File
@@ -4,8 +4,8 @@
use std::time::SystemTimeError;
use thiserror::Error;
use credential_storage::error::StorageError;
use credentials::error::Error as CredentialError;
use nym_credential_storage::error::StorageError;
use nym_credentials::error::Error as CredentialError;
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use validator_client::nyxd::error::NyxdError;
+11 -5
View File
@@ -11,7 +11,7 @@ use commands::*;
use error::Result;
use log::*;
use nym_bin_common::completions::fig_generate;
use nym_config::{DATA_DIR, DB_FILE_NAME};
use nym_config::{CRED_DB_FILE_NAME, DATA_DIR};
use nym_network_defaults::{setup_env, NymNetworkDetails};
use std::process::exit;
use std::time::{Duration, SystemTime};
@@ -51,8 +51,11 @@ async fn block_until_coconut_is_available<C: Clone + CosmWasmClient + Send + Syn
break;
} else {
// Use 20 additional seconds to avoid the exact moment of going into the final epoch state
let secs_until_final = epoch.final_timestamp_secs() + 20 - current_timestamp_secs;
// Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries
let secs_until_final = epoch
.final_timestamp_secs()
.saturating_sub(current_timestamp_secs)
+ 1;
info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final);
std::thread::sleep(Duration::from_secs(secs_until_final));
}
@@ -70,8 +73,11 @@ async fn main() -> Result<()> {
match args.command {
Command::Run(r) => {
let db_path = r.client_home_directory.join(DATA_DIR).join(DB_FILE_NAME);
let shared_storage = credential_storage::initialise_storage(db_path).await;
let db_path = r
.client_home_directory
.join(DATA_DIR)
.join(CRED_DB_FILE_NAME);
let shared_storage = nym_credential_storage::initialise_storage(db_path).await;
let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?;
let network_details = NymNetworkDetails::new_from_env();
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use credentials::coconut::bandwidth::BandwidthVoucher;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use std::fs::{create_dir_all, read_dir, File};
use std::io::{Read, Write};
use std::path::PathBuf;
+2 -2
View File
@@ -1,8 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_interface::Parameters;
use credentials::coconut::bandwidth::BandwidthVoucher;
use nym_coconut_interface::Parameters;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_crypto::asymmetric::{encryption, identity};
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.10"
version = "1.1.14"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -36,10 +36,10 @@ tokio-tungstenite = "0.14" # websocket
## internal
nym-bin-common = { path = "../../common/bin-common" }
client-core = { path = "../client-core", features = ["fs-surb-storage"] }
coconut-interface = { path = "../../common/coconut-interface" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-credentials = { path = "../../common/credentials" }
nym-crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
+1
View File
@@ -15,6 +15,7 @@ pub use client_core::config::Config as BaseConfig;
pub use client_core::config::MISSING_VALUE;
pub use client_core::config::{DebugConfig, GatewayEndpointConfig};
pub mod old_config_v1_1_13;
mod template;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
@@ -0,0 +1,60 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, Socket};
use client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
use nym_config::NymConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13 {
#[serde(flatten)]
base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
socket: Socket,
}
impl NymConfig for OldConfigV1_1_13 {
fn template() -> &'static str {
// not intended to be used
unimplemented!()
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("clients")
}
fn try_default_root_directory() -> Option<PathBuf> {
dirs::home_dir().map(|path| path.join(".nym").join("clients"))
}
fn root_directory(&self) -> PathBuf {
self.base.client.nym_root_directory.clone()
}
fn config_directory(&self) -> PathBuf {
self.root_directory()
.join(&self.base.client.id)
.join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory()
.join(&self.base.client.id)
.join("data")
}
}
impl From<OldConfigV1_1_13> for Config {
fn from(value: OldConfigV1_1_13) -> Self {
Config {
base: value.base.into(),
socket: value.socket,
}
}
}
+9 -4
View File
@@ -110,10 +110,15 @@ host = '{{ socket.host }}'
[debug]
average_packet_delay = '{{ debug.average_packet_delay }}'
average_ack_delay = '{{ debug.average_ack_delay }}'
loop_cover_traffic_average_delay = '{{ debug.loop_cover_traffic_average_delay }}'
message_sending_average_delay = '{{ debug.message_sending_average_delay }}'
[debug.traffic]
average_packet_delay = '{{ debug.traffic.average_packet_delay }}'
message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}'
[debug.acknowledgements]
average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
[debug.cover_traffic]
loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}'
"#
}
+2 -1
View File
@@ -74,7 +74,7 @@ impl SocketClient {
let client = validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
BandwidthController::new(
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
nym_credential_storage::initialise_storage(config.get_base().get_database_path()).await,
client,
)
}
@@ -101,6 +101,7 @@ impl SocketClient {
let ClientState {
shared_lane_queue_lengths,
reply_controller_sender,
..
} = client_state;
let websocket_handler = websocket::HandlerBuilder::new(
+10
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::try_upgrade_v1_1_13_config;
use crate::{
client::config::Config,
commands::{override_config, OverrideConfig},
@@ -25,6 +26,11 @@ pub(crate) struct Init {
#[clap(long)]
gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
@@ -116,6 +122,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
let already_init = Config::default_config_file_path(id).exists();
if already_init {
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
try_upgrade_v1_1_13_config(id)?;
println!("Client \"{id}\" was already initialised before");
}
@@ -143,6 +152,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
register_gateway,
user_chosen_gateway_id,
config.get_base(),
args.latency_based_selection,
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
+17 -1
View File
@@ -1,13 +1,15 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::client::config::{BaseConfig, Config};
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use lazy_static::lazy_static;
use log::info;
use nym_bin_common::build_information::BinaryBuildInformation;
use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_config::OptionalSet;
use nym_config::{NymConfig, OptionalSet};
use std::error::Error;
use std::net::IpAddr;
@@ -102,6 +104,20 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
)
}
fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> {
// explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+)
let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(());
};
info!("It seems the client is using <= v1.1.13 config template.");
info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into();
updated.save_to_file(None)
}
#[cfg(test)]
mod tests {
use super::*;
+5 -1
View File
@@ -4,12 +4,12 @@
use std::error::Error;
use std::net::IpAddr;
use crate::commands::try_upgrade_v1_1_13_config;
use crate::{
client::{config::Config, SocketClient},
commands::{override_config, OverrideConfig},
error::ClientError,
};
use clap::Args;
use log::*;
use nym_bin_common::version_checker::is_minor_version_compatible;
@@ -100,6 +100,10 @@ fn version_check(cfg: &Config) -> bool {
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
let id = &args.id;
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
try_upgrade_v1_1_13_config(id)?;
let mut config = match Config::load_from_file(id) {
Ok(cfg) => cfg,
Err(err) => {
+9 -9
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.10"
version = "1.1.14"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -29,26 +29,26 @@ url = "2.2"
# internal
nym-bin-common = { path = "../../common/bin-common" }
client-core = { path = "../client-core", features = ["fs-surb-storage"] }
coconut-interface = { path = "../../common/coconut-interface" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
credential-storage = { path = "../../common/credential-storage", optional = true }
nym-credential-storage = { path = "../../common/credential-storage", optional = true }
mobile-storage = { path = "../../common/mobile-storage", optional = true }
credentials = { path = "../../common/credentials" }
nym-credentials = { path = "../../common/credentials" }
nym-crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
nym-network-defaults = { path = "../../common/network-defaults" }
nym-sphinx = { path = "../../common/nymsphinx" }
ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
nym-pemstore = { path = "../../common/pemstore" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
service-providers-common = { path = "../../service-providers/common" }
socks5-requests = { path = "../../common/socks5/requests" }
nym-socks5-proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
nym-service-providers-common = { path = "../../service-providers/common" }
nym-socks5-requests = { path = "../../common/socks5/requests" }
nym-task = { path = "../../common/task" }
nym-topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] }
[features]
default = ["credential-storage"]
default = ["nym-credential-storage"]
eth = []
mobile = ["mobile-storage", "gateway-client/mobile"]
+3 -2
View File
@@ -7,14 +7,15 @@ pub use client_core::config::MISSING_VALUE;
use client_core::config::{ClientCoreConfigTrait, DebugConfig};
use nym_config::defaults::DEFAULT_SOCKS5_LISTENING_PORT;
use nym_config::{NymConfig, OptionalSet};
use nym_service_providers_common::interface::ProviderInterfaceVersion;
use nym_socks5_requests::Socks5ProtocolVersion;
use nym_sphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
use service_providers_common::interface::ProviderInterfaceVersion;
use socks5_requests::Socks5ProtocolVersion;
use std::fmt::Debug;
use std::path::PathBuf;
use std::str::FromStr;
pub mod old_config_v1_1_13;
mod template;
const DEFAULT_CONNECTION_START_SURBS: u32 = 20;
@@ -0,0 +1,66 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, Socks5, Socks5Debug};
use client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
use nym_config::NymConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OldConfigV1_1_13 {
#[serde(flatten)]
base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
socks5: Socks5,
#[serde(default)]
socks5_debug: Socks5Debug,
}
impl NymConfig for OldConfigV1_1_13 {
fn template() -> &'static str {
// not intended to be used
unimplemented!()
}
fn default_root_directory() -> PathBuf {
#[cfg(not(feature = "mobile"))]
let base_dir = dirs::home_dir().expect("Failed to evaluate $HOME value");
#[cfg(feature = "mobile")]
let base_dir = PathBuf::from("/tmp");
base_dir.join(".nym").join("socks5-clients")
}
fn try_default_root_directory() -> Option<PathBuf> {
dirs::home_dir().map(|path| path.join(".nym").join("socks5-clients"))
}
fn root_directory(&self) -> PathBuf {
self.base.client.nym_root_directory.clone()
}
fn config_directory(&self) -> PathBuf {
self.root_directory()
.join(&self.base.client.id)
.join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory()
.join(&self.base.client.id)
.join("data")
}
}
impl From<OldConfigV1_1_13> for Config {
fn from(value: OldConfigV1_1_13) -> Self {
Config {
base: value.base.into(),
socks5: value.socks5,
socks5_debug: value.socks5_debug,
}
}
}
+9 -4
View File
@@ -114,10 +114,15 @@ send_anonymously = {{ socks5.send_anonymously }}
[debug]
average_packet_delay = '{{ debug.average_packet_delay }}'
average_ack_delay = '{{ debug.average_ack_delay }}'
loop_cover_traffic_average_delay = '{{ debug.loop_cover_traffic_average_delay }}'
message_sending_average_delay = '{{ debug.message_sending_average_delay }}'
[debug.traffic]
average_packet_delay = '{{ debug.traffic.average_packet_delay }}'
message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}'
[debug.acknowledgements]
average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
[debug.cover_traffic]
loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}'
"#
}
+2 -2
View File
@@ -92,7 +92,7 @@ impl NymClient {
#[cfg(not(feature = "mobile"))]
let storage =
credential_storage::initialise_storage(config.get_base().get_database_path()).await;
nym_credential_storage::initialise_storage(config.get_base().get_database_path()).await;
#[cfg(feature = "mobile")]
let storage = mobile_storage::PersistentStorage {};
@@ -123,7 +123,7 @@ impl NymClient {
let ClientState {
shared_lane_queue_lengths,
reply_controller_sender: _,
..
} = client_status;
let authenticator = Authenticator::new(auth_methods, allowed_users);
+10
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::try_upgrade_v1_1_13_config;
use crate::{
client::config::Config,
commands::{override_config, OverrideConfig},
@@ -37,6 +38,11 @@ pub(crate) struct Init {
#[clap(long)]
gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
@@ -119,6 +125,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
let already_init = Config::default_config_file_path(id).exists();
if already_init {
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
try_upgrade_v1_1_13_config(id)?;
println!("SOCKS5 client \"{id}\" was already initialised before");
}
@@ -149,6 +158,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
register_gateway,
user_chosen_gateway_id,
config.get_base(),
args.latency_based_selection,
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
+17 -1
View File
@@ -1,13 +1,15 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::client::config::{BaseConfig, Config};
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use lazy_static::lazy_static;
use log::info;
use nym_bin_common::build_information::BinaryBuildInformation;
use nym_bin_common::completions::{fig_generate, ArgShell};
use nym_config::OptionalSet;
use nym_config::{NymConfig, OptionalSet};
use std::error::Error;
pub mod init;
@@ -101,6 +103,20 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
)
}
fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> {
// explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+)
let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(());
};
info!("It seems the client is using <= v1.1.13 config template.");
info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into();
updated.save_to_file(None)
}
#[cfg(test)]
mod tests {
use super::*;
+5 -1
View File
@@ -1,12 +1,12 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::commands::try_upgrade_v1_1_13_config;
use crate::{
client::{config::Config, NymClient},
commands::{override_config, OverrideConfig},
error::Socks5ClientError,
};
use clap::Args;
use log::*;
use nym_bin_common::version_checker::is_minor_version_compatible;
@@ -108,6 +108,10 @@ fn version_check(cfg: &Config) -> bool {
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let id = &args.id;
// in case we're using old config, try to upgrade it
// (if we're using the current version, it's a no-op)
try_upgrade_v1_1_13_config(id)?;
let mut config = match Config::load_from_file(id) {
Ok(cfg) => cfg,
Err(err) => {
+1 -1
View File
@@ -1,6 +1,6 @@
use crate::socks::types::SocksProxyError;
use client_core::error::ClientCoreError;
use socks5_requests::{ConnectionError, ConnectionId};
use nym_socks5_requests::{ConnectionError, ConnectionId};
#[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError {
+8 -8
View File
@@ -8,19 +8,19 @@ use client_core::client::inbound_messages::{InputMessage, InputMessageSender};
use futures::channel::mpsc;
use futures::task::{Context, Poll};
use log::*;
use nym_service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion};
use nym_socks5_proxy_helpers::connection_controller::{
ConnectionReceiver, ControllerCommand, ControllerSender,
};
use nym_socks5_proxy_helpers::proxy_runner::ProxyRunner;
use nym_socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
};
use nym_sphinx::addressing::clients::Recipient;
use nym_task::connections::{LaneQueueLengths, TransmissionLane};
use nym_task::TaskClient;
use pin_project::pin_project;
use proxy_helpers::connection_controller::{
ConnectionReceiver, ControllerCommand, ControllerSender,
};
use proxy_helpers::proxy_runner::ProxyRunner;
use rand::RngCore;
use service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion};
use socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
};
use std::io;
use std::net::SocketAddr;
use std::pin::Pin;
+3 -3
View File
@@ -4,11 +4,11 @@ use log::*;
use client_core::client::received_buffer::ReconstructedMessagesReceiver;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender};
use nym_service_providers_common::interface::{ControlResponse, ResponseContent};
use nym_socks5_proxy_helpers::connection_controller::ControllerSender;
use nym_socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::TaskClient;
use proxy_helpers::connection_controller::ControllerSender;
use service_providers_common::interface::{ControlResponse, ResponseContent};
use socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
use crate::error::Socks5ClientError;
+1 -1
View File
@@ -8,10 +8,10 @@ use client_core::client::{
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
};
use log::*;
use nym_socks5_proxy_helpers::connection_controller::Controller;
use nym_sphinx::addressing::clients::Recipient;
use nym_task::connections::{ConnectionCommandSender, LaneQueueLengths};
use nym_task::TaskClient;
use proxy_helpers::connection_controller::Controller;
use std::net::SocketAddr;
use tap::TapFallible;
use tokio::net::TcpListener;
+1 -1
View File
@@ -1,4 +1,4 @@
use socks5_requests::Socks5RequestError;
use nym_socks5_requests::Socks5RequestError;
use std::string::FromUtf8Error;
use thiserror::Error;
+68 -80
View File
@@ -1,83 +1,71 @@
{
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 2019,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["prettier", "mocha"],
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"prettier"],
"rules": {
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.[jt]s",
"**/*.spec.[jt]s"
]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"js": "never"
}
]
},
"overrides": [
{
"files": "**/*.ts",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.ts",
"**/*.spec.ts"
]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 2019,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["prettier", "mocha"],
"extends": ["airbnb-base", "airbnb-typescript/base", "prettier"],
"rules": {
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": ["**/*.test.[jt]s", "**/*.spec.[jt]s"]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"js": "never"
}
]
},
"overrides": [
{
"files": "**/*.ts",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": ["plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": ["**/*.test.ts", "**/*.spec.ts"]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
],
"ignorePatterns": ["tsconfig.json", "*.d.ts", "dist/**/*", "dist", "node_modules"]
}
+2 -1
View File
@@ -4,4 +4,5 @@ coverage
dist
docs
examples/accounts
node_modules
node_modules
.env
+3 -1
View File
@@ -1,3 +1,5 @@
coverage
node_modules
tests
tests
src
type
+24 -25
View File
@@ -1,40 +1,39 @@
Nym Validator Client
====================
# Nym Validator Client (Typescript)
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
Running examples
-----------------
With the code checked out, `cd examples`. This folder contains runnable example code that will set up a blockchain and allow you to interact with it through the client.
Running tests
-------------
Include the Nym Validator in your project:
```
npm test
yarn add @nymproject/nym-validator-client
```
You can also trigger test execution with a test watcher. I don't have the centuries of life left to me that are needed to fight through the arcana of wiring up a working TypeScript mocha triggered execution setup, so for now my Cargo-based hack is:
Connect to validator and make queries
```
cargo watch -s "cd clients/validator && npm test"
import Validator from '@nymproject/nym-validator-client'
const main = async () => {
const client = await Validator.connectForQuery(rpcAddress, validatorAddress, prefix, mixnetContractAddress, vestingContractAddress, denom)
client.getBalance(address)
}
```
It's ugly but works fine if you have Cargo installed. TypeScript setup help happily accepted here.
Connect to validator for performing actions
Generating Documentation
------------------------
```
import Validator from '@nymproject/nym-validator-client'
You can generate docs by running `npm run docs`. Generated output will appear in the `docs` directory.
const main = async () => {
Packaging
------------------------
const client = await Validator.connect(mnemonic, rpcAddress, validatorAddress, prefix, mixnetContractAddress, vestingContractAddress, denom)
If you're a Nym platform developer who's made changes to the client and wants to re-publish the package to NPM, here's how you do it:
const res = await client.send(address, [{ amount: '10000000', denom: 'unym' }]);
1. Bump the version number (use SemVer)
1. `npm run build`
1. `npm login` (if you haven't already)
1. `npm publish`
}
```
+34 -18
View File
@@ -4,15 +4,23 @@
"description": "A TypeScript client for interacting with smart contracts in Nym validators",
"repository": "https://github.com/nymtech/nym",
"main": "./dist/index.js",
"types": "dist/index.d.ts",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "ts-mocha tests/**/*.test.ts",
"build": "rollup -c ./rollup.config.mjs",
"build:types": "rollup-type-bundler --dist ./dist/nym-validator-client",
"build:prod": "sh ./scripts/build-prod.sh",
"test": "ts-mocha -p ./tsconfig.test.json ./src/tests/**/*.test.ts",
"testmock": "ts-mocha -p ./tsconfig.test.json ./src/tests/mock/*.test.ts",
"coverage": "nyc npm test",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"clean": "rm -rf ./dist",
"lint": "eslint",
"lint:fix": "eslint --fix",
"lint:tsc": "tsc --noEmit",
"docs": "typedoc --out docs src/index.ts"
},
"files": [
"./dist/*"
],
"keywords": [],
"author": "Nym Technologies SA (https://nymtech.net)",
"contributors": [
@@ -21,6 +29,14 @@
],
"license": "Apache-2.0",
"devDependencies": {
"@favware/rollup-type-bundler": "^2.0.0",
"@nymproject/types": "^1.0.0",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-typescript": "^11.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"rollup": "^3.17.2",
"rollup-plugin-dts": "^5.2.0",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.7.0",
"eslint": "^7.18.0",
@@ -31,21 +47,21 @@
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-mocha": "^10.0.3",
"eslint-plugin-prettier": "^4.0.0",
"expect": "^28.1.3",
"mocha": "^10.0.0",
"prettier": "^2.5.1",
"typedoc": "^0.22.13",
"ts-mocha": "^10.0.0",
"typescript": "^4.6.2"
},
"dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.28.0",
"@cosmjs/crypto": "^0.28.0",
"@cosmjs/math": "^0.28.0",
"@cosmjs/proto-signing": "^0.28.0",
"@cosmjs/stargate": "^0.28.0",
"@cosmjs/tendermint-rpc": "^0.28.0",
"axios": "^0.26.1",
"cosmjs-types": "^0.4.1"
"typedoc": "^0.22.13",
"typescript": "^4.6.2",
"cosmjs-types": "^0.4.1",
"dotenv": "^16.0.3",
"expect": "^28.1.3",
"moq.ts": "^7.3.4",
"@cosmjs/cosmwasm-stargate": "^0.29.5",
"@cosmjs/crypto": "^0.29.5",
"@cosmjs/math": "^0.29.5",
"@cosmjs/proto-signing": "^0.29.5",
"@cosmjs/stargate": "^0.29.5",
"@cosmjs/tendermint-rpc": "^0.29.5",
"axios": "^1.3.3"
}
}
+15
View File
@@ -0,0 +1,15 @@
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import json from '@rollup/plugin-json';
import commonjs from '@rollup/plugin-commonjs';
export default [
{
input: './src/index.ts',
output: {
dir: 'dist/nym-validator-client',
format: 'cjs',
},
plugins: [resolve(), typescript(), commonjs(), json()],
},
];
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
rm -rf ./dist || true
rm -rf ../../dist || true
# Bundle application
yarn build
# Bundle types
yarn build:types
# Build package.json for bundle
node ./scripts/buildPackageJson.mjs
# Copy README
cp README.md dist/nym-validator-client
# move the output outside of the yarn/npm workspaces
mv ./dist ../../
echo "Output can be found in:"
realpath ../../dist
@@ -0,0 +1,20 @@
import * as fs from 'fs';
// parse the package.json from the SDK, so we can keep fields like the name and version
const json = JSON.parse(fs.readFileSync('./package.json').toString());
// defaults (NB: these are in the output file locations)
const main = 'index.js';
const types = 'index.d.ts';
// make a package.json for the bundle
const packageJson = {
name: json.name,
version: json.version,
license: json.license,
author: json.author,
main,
types,
};
fs.writeFileSync('./dist/nym-validator-client/package.json', JSON.stringify(packageJson, null, 2));
+1 -9
View File
@@ -1,5 +1,6 @@
import { Decimal } from '@cosmjs/math';
import { Coin } from '@cosmjs/stargate';
import { CoinMap } from './types/shared';
// NARROW NO-BREAK SPACE (U+202F)
const thinSpace = '\u202F';
@@ -34,15 +35,6 @@ export function nativeToPrintable(nativeValue: string): string {
return Decimal.fromAtomics(nativeValue, 6).toString();
}
export interface MappedCoin {
readonly denom: string;
readonly fractionalDigits: number;
}
export interface CoinMap {
readonly [key: string]: MappedCoin;
}
export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin {
if (!coinMap) return coin;
+133 -75
View File
@@ -1,6 +1,3 @@
import { Bip39, Random } from '@cosmjs/crypto';
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
import { coin as cosmosCoin, Coin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate';
import {
ExecuteResult,
InstantiateOptions,
@@ -8,45 +5,37 @@ import {
MigrateResult,
UploadResult,
} from '@cosmjs/cosmwasm-stargate';
import SigningClient, { ISigningClient } from './signing-client';
import { Bip39, Random } from '@cosmjs/crypto';
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
import { Coin, coin as cosmosCoin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate';
import {
ContractStateParams,
Delegation,
Gateway,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNode,
MixNodeBond,
MixNodeCostParams,
MixNodeDetails,
MixNodeRewarding,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
} from './types';
import {
CoinMap,
displayAmountToNative,
MappedCoin,
nativeCoinToDisplay,
nativeToPrintable,
printableBalance,
printableCoin,
} from './currency';
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
RewardingParams,
StakeSaturationResponse,
UnbondedMixnodeResponse,
} from '@nymproject/types';
import QueryClient from './query-client';
import { nymGasPrice } from './stargate-helper';
export { coins, coin } from '@cosmjs/stargate';
export { Coin };
export {
displayAmountToNative,
nativeCoinToDisplay,
printableCoin,
printableBalance,
nativeToPrintable,
MappedCoin,
CoinMap,
};
export { nymGasPrice };
import SigningClient, { ISigningClient } from './signing-client';
import { ContractState } from './types/shared';
export interface INymClient {
readonly mixnetContract: string;
@@ -147,7 +136,7 @@ export default class ValidatorClient implements INymClient {
return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions);
}
getBalance(address: string): Promise<Coin> {
async getBalance(address: string): Promise<Coin> {
return this.client.getBalance(address, this.denom);
}
@@ -159,15 +148,39 @@ export default class ValidatorClient implements INymClient {
return this.client.getCachedMixnodes();
}
async getActiveMixnodes(): Promise<MixNodeBond[]> {
async getStakeSaturation(mixId: number): Promise<StakeSaturationResponse> {
return this.client.getStakeSaturation(this.mixnetContract, mixId);
}
async getActiveMixnodes(): Promise<MixNodeDetails[]> {
return this.client.getActiveMixnodes();
}
async getUnbondedMixNodeInformation(mixId: number): Promise<UnbondedMixnodeResponse> {
return this.client.getUnbondedMixNodeInformation(this.mixnetContract, mixId);
}
async getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.client.getRewardedMixnodes();
}
public async getMixnetContractSettings(): Promise<ContractStateParams> {
async getMixnodeRewardingDetails(mixId: number): Promise<MixNodeRewarding> {
return this.client.getMixnodeRewardingDetails(this.mixnetContract, mixId);
}
async getOwnedMixnode(address: string): Promise<MixOwnershipResponse> {
return this.client.getOwnedMixnode(this.mixnetContract, address);
}
async ownsGateway(address: string): Promise<GatewayOwnershipResponse> {
return this.client.ownsGateway(this.mixnetContract, address);
}
async getLayerDistribution(): Promise<LayerDistribution> {
return this.client.getLayerDistribution(this.mixnetContract);
}
public async getMixnetContractSettings(): Promise<ContractState> {
return this.client.getStateParams(this.mixnetContract);
}
@@ -175,29 +188,74 @@ export default class ValidatorClient implements INymClient {
return this.client.getContractVersion(this.mixnetContract);
}
public async getRewardPool(): Promise<string> {
return this.client.getRewardPool(this.mixnetContract);
public async getVestingContractVersion(): Promise<MixnetContractVersion> {
return this.client.getContractVersion(this.vestingContract);
}
public async getCirculatingSupply(): Promise<string> {
return this.client.getCirculatingSupply(this.mixnetContract);
public async getSpendableCoins(vestingAccountAddress: string): Promise<MixnetContractVersion> {
return this.client.getSpendableCoins(this.vestingContract, vestingAccountAddress);
}
public async getSybilResistancePercent(): Promise<number> {
return this.client.getSybilResistancePercent(this.mixnetContract);
public async getRewardParams(): Promise<RewardingParams> {
return this.client.getRewardParams(this.mixnetContract);
}
public async getIntervalRewardPercent(): Promise<number> {
return this.client.getIntervalRewardPercent(this.mixnetContract);
async getUnbondedMixNodes(): Promise<UnbondedMixnodeResponse[]> {
let mixNodes: UnbondedMixnodeResponse[] = [];
const limit = 50;
let startAfter;
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedUnbondedMixnodesResponse = await this.client.getUnbondedMixNodes(
this.mixnetContract,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return mixNodes;
}
public async getAllNyxdMixnodes(): Promise<MixNodeBond[]> {
public async getMixNodeBonds(): Promise<MixNodeBond[]> {
let mixNodes: MixNodeBond[] = [];
const limit = 50;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixnodeResponse = await this.client.getMixNodesPaged(this.mixnetContract, limit);
const pagedResponse: PagedMixNodeBondResponse = await this.client.getMixNodeBonds(
this.mixnetContract,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return mixNodes;
}
public async getMixNodesDetailed(): Promise<MixNodeDetails[]> {
let mixNodes: MixNodeDetails[] = [];
const limit = 50;
let startAfter;
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeDetailsResponse = await this.client.getMixNodesDetailed(
this.mixnetContract,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
@@ -210,37 +268,24 @@ export default class ValidatorClient implements INymClient {
}
public async getAllNyxdGateways(): Promise<GatewayBond[]> {
let gateways: GatewayBond[] = [];
const limit = 50;
let startAfter;
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract, limit);
gateways = gateways.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return gateways;
const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract);
return pagedResponse.nodes;
}
/**
* Gets list of all delegations towards particular mixnode.
*
* @param mixIdentity identity of the node to which the delegation was sent
* @param mix_id identity of the node to which the delegation was sent
*/
public async getAllNyxdSingleMixnodeDelegations(mixIdentity: string): Promise<Delegation[]> {
public async getAllNyxdSingleMixnodeDelegations(mix_id: number): Promise<Delegation[]> {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixDelegationsResponse = await this.client.getMixNodeDelegationsPaged(
this.mixnetContract,
mixIdentity,
mix_id,
limit,
startAfter,
);
@@ -259,7 +304,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedDelegatorDelegationsResponse = await this.client.getDelegatorDelegationsPaged(
this.mixnetContract,
@@ -278,13 +323,13 @@ export default class ValidatorClient implements INymClient {
return delegations;
}
public async getAllNyxdNetworkDelegations(): Promise<Delegation[]> {
public async getAllNyxdDelegations(): Promise<Delegation[]> {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllNetworkDelegationsPaged(
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllDelegationsPaged(
this.mixnetContract,
limit,
startAfter,
@@ -300,6 +345,10 @@ export default class ValidatorClient implements INymClient {
return delegations;
}
public async getDelegationDetails(mix_id: number, delegator: string): Promise<Delegation> {
return this.client.getDelegationDetails(this.mixnetContract, mix_id, delegator);
}
/**
* Generate a minimum gateway bond required to create a fresh mixnode.
*
@@ -308,7 +357,7 @@ export default class ValidatorClient implements INymClient {
public async minimumMixnodePledge(): Promise<Coin> {
const stateParams = await this.getMixnetContractSettings();
// we trust the contract to return a valid number
return cosmosCoin(stateParams.minimum_mixnode_pledge, this.prefix);
return cosmosCoin(stateParams.params.minimum_mixnode_pledge, this.prefix);
}
/**
@@ -319,7 +368,7 @@ export default class ValidatorClient implements INymClient {
public async minimumGatewayPledge(): Promise<Coin> {
const stateParams = await this.getMixnetContractSettings();
// we trust the contract to return a valid number
return cosmosCoin(stateParams.minimum_gateway_pledge, this.prefix);
return cosmosCoin(stateParams.params.minimum_gateway_pledge, this.prefix);
}
public async send(
@@ -393,12 +442,21 @@ export default class ValidatorClient implements INymClient {
public async bondMixNode(
mixNode: MixNode,
ownerSignature: string,
costParams: MixNodeCostParams,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).bondMixNode(this.mixnetContract, mixNode, ownerSignature, pledge, fee, memo);
return (this.client as ISigningClient).bondMixNode(
this.mixnetContract,
mixNode,
costParams,
ownerSignature,
pledge,
fee,
memo,
);
}
public async unbondMixNode(fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult> {
@@ -423,29 +481,29 @@ export default class ValidatorClient implements INymClient {
}
public async delegateToMixNode(
mixIdentity: string,
mixId: number,
amount: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixIdentity, amount, fee, memo);
return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixId, amount, fee, memo);
}
public async undelegateFromMixNode(
mixIdentity: string,
mixId: number,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixIdentity, fee, memo);
return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixId, fee, memo);
}
public async updateMixnodeConfig(
mixIdentity: string,
mixId: number,
fee: StdFee | 'auto' | number,
profitPercentage: number,
): Promise<ExecuteResult> {
return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixIdentity, profitPercentage, fee);
return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixId, profitPercentage, fee);
}
public async updateContractStateParams(
+3 -4
View File
@@ -2,9 +2,8 @@
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import axios from 'axios';
import { GatewayBond, MixNodeBond } from './types';
import { GatewayBond, MixNodeBond, MixNodeDetails } from '@nymproject/types';
export const NYM_API_VERSION = '/v1';
export const NYM_API_GATEWAYS_PATH = `${NYM_API_VERSION}/gateways`;
@@ -17,7 +16,7 @@ export interface INymApiQuery {
getCachedGateways(): Promise<GatewayBond[]>;
getActiveMixnodes(): Promise<MixNodeBond[]>;
getActiveMixnodes(): Promise<MixNodeDetails[]>;
getRewardedMixnodes(): Promise<MixNodeBond[]>;
}
@@ -51,7 +50,7 @@ export default class NymApiQuerier implements INymApiQuery {
throw new Error('None of the provided validator APIs seem to be alive');
}
async getActiveMixnodes(): Promise<MixNodeBond[]> {
async getActiveMixnodes(): Promise<MixNodeDetails[]> {
const url = new URL(this.nymApiUrl);
url.pathname += NYM_API_ACTIVE_MIXNODES_PATH;
+72 -56
View File
@@ -2,28 +2,24 @@
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import { JsonObject } from '@cosmjs/cosmwasm-stargate';
// eslint-disable-next-line import/no-cycle
import { INyxdQuery } from './query-client';
import { Delegation, RewardingParams, StakeSaturationResponse } from '@nymproject/types';
import {
ContractStateParams,
Delegation,
UnbondedMixnodeResponse,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingStatus,
} from './types';
interface SmartContractQuery {
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
LayerDistribution,
} from '@nymproject/types';
import { ContractState, SmartContractQuery } from './types/shared';
export default class NyxdQuerier implements INyxdQuery {
client: SmartContractQuery;
@@ -38,15 +34,44 @@ export default class NyxdQuerier implements INyxdQuery {
});
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mix_nodes: {
get_mix_node_bonds: {
limit,
start_after: startAfter,
},
});
}
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mix_nodes_detailed: {
limit,
start_after: startAfter,
},
});
}
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_stake_saturation: { mix_id: mixId },
});
}
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<any> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mixnode_rewarding_details: { mix_id: mixId },
});
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_gateways: {
@@ -56,35 +81,51 @@ export default class NyxdQuerier implements INyxdQuery {
});
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
owns_mixnode: {
get_owned_mixnode: {
address,
},
});
}
getUnbondedMixNodes(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedUnbondedMixnodesResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_unbonded_mix_nodes: { limit, start_after: startAfter },
});
}
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_unbonded_mix_node_information: { mix_id: mixId },
});
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
owns_gateway: {
get_owned_gateway: {
address,
},
});
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
getStateParams(mixnetContractAddress: string): Promise<ContractState> {
return this.client.queryContractSmart(mixnetContractAddress, {
state_params: {},
get_state: {},
});
}
getAllNetworkDelegationsPaged(
getAllDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_all_network_delegations: {
get_all_delegations: {
start_after: startAfter,
limit,
},
@@ -93,13 +134,13 @@ export default class NyxdQuerier implements INyxdQuery {
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mixnode_delegations: {
mix_identity: mixIdentity,
mix_id: mix_id,
start_after: startAfter,
limit,
},
@@ -121,10 +162,10 @@ export default class NyxdQuerier implements INyxdQuery {
});
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_delegation_details: {
mix_identity: mixIdentity,
mix_id: mix_id,
delegator,
},
});
@@ -132,44 +173,19 @@ export default class NyxdQuerier implements INyxdQuery {
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.client.queryContractSmart(mixnetContractAddress, {
layer_distribution: {},
get_layer_distribution: {},
});
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_reward_pool: {},
get_rewarding_params: {},
});
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_circulating_supply: {},
});
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_interval_reward_percent: {},
});
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_sybil_resistance_percent: {},
});
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_rewarding_status: {
mix_identity: mixIdentity,
rewarding_interval_nonce: rewardingIntervalNonce,
},
getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise<any> {
return this.client.queryContractSmart(vestingContractAddress, {
vesting_account_address: vestingAccountAddress,
});
}
}
+84 -93
View File
@@ -1,75 +1,56 @@
import { CosmWasmClient, JsonObject } from '@cosmjs/cosmwasm-stargate';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
import {
Account,
Block,
Coin,
DeliverTxResponse,
IndexedTx,
SearchTxFilter,
SearchTxQuery,
SequenceResponse,
} from '@cosmjs/stargate';
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
// eslint-disable-next-line import/no-cycle
import NyxdQuerier from './nyxd-querier';
import {
ContractStateParams,
Delegation,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNodeBond,
MixNodeDetails,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingStatus,
} from './types';
import NymApiQuerier, { INymApiQuery as INymApiQuery } from './nym-api-querier';
export interface ICosmWasmQuery {
// methods exposed by `CosmWasmClient`
getChainId(): Promise<string>;
getHeight(): Promise<number>;
getAccount(searchAddress: string): Promise<Account | null>;
getSequence(address: string): Promise<SequenceResponse>;
getBlock(height?: number): Promise<Block>;
getBalance(address: string, searchDenom: string): Promise<Coin>;
getTx(id: string): Promise<IndexedTx | null>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
disconnect(): void;
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>;
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>;
getContracts(codeId: number): Promise<readonly string[]>;
getContract(address: string): Promise<Contract>;
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
StakeSaturationResponse,
UnbondedMixnodeResponse,
MixNodeBond,
MixNodeRewarding,
} from '@nymproject/types';
import NymApiQuerier, { INymApiQuery } from './nym-api-querier';
import { ContractState, ICosmWasmQuery } from './types/shared';
import { RewardingParams } from '@nymproject/types';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
export interface INyxdQuery {
// nym-specific implemented inside NymQuerier
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse>;
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse>;
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse>;
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>;
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams>;
getAllNetworkDelegationsPaged(
getStateParams(mixnetContractAddress: string): Promise<ContractState>;
getAllDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse>;
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse>;
@@ -79,21 +60,15 @@ export interface INyxdQuery {
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse>;
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation>;
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation>;
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>;
getRewardPool(mixnetContractAddress: string): Promise<string>;
getCirculatingSupply(mixnetContractAddress: string): Promise<string>;
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number>;
getSybilResistancePercent(mixnetContractAddress: string): Promise<number>;
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus>;
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams>;
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse>;
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse>;
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding>;
}
export interface IQueryClient extends ICosmWasmQuery, INyxdQuery, INymApiQuery { }
export interface IQueryClient extends ICosmWasmQuery, INyxdQuery, INymApiQuery {}
export default class QueryClient extends CosmWasmClient implements IQueryClient {
private nyxdQuerier: NyxdQuerier;
@@ -115,41 +90,73 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
return this.nyxdQuerier.getContractVersion(mixnetContractAddress);
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nyxdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse> {
return this.nyxdQuerier.getMixNodeBonds(mixnetContractAddress, limit, startAfter);
}
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse> {
return this.nyxdQuerier.getMixNodesDetailed(mixnetContractAddress, limit, startAfter);
}
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse> {
return this.nyxdQuerier.getStakeSaturation(mixnetContractAddress, mixId);
}
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding> {
return this.nyxdQuerier.getMixnodeRewardingDetails(mixnetContractAddress, mixId);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.ownsMixNode(mixnetContractAddress, address);
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.getOwnedMixnode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address);
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
getUnbondedMixNodes(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedUnbondedMixnodesResponse> {
return this.nyxdQuerier.getUnbondedMixNodes(mixnetContractAddress, limit, startAfter);
}
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse> {
return this.nyxdQuerier.getUnbondedMixNodeInformation(mixnetContractAddress, mixId);
}
getStateParams(mixnetContractAddress: string): Promise<ContractState> {
return this.nyxdQuerier.getStateParams(mixnetContractAddress);
}
getAllNetworkDelegationsPaged(
getAllDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.nyxdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
return this.nyxdQuerier.getAllDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mix_id, limit, startAfter);
}
getDelegatorDelegationsPaged(
@@ -161,36 +168,16 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mix_id, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getIntervalRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nyxdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams> {
return this.nyxdQuerier.getRewardParams(mixnetContractAddress);
}
getCachedGateways(): Promise<GatewayBond[]> {
@@ -201,11 +188,15 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
return this.nymApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeBond[]> {
getActiveMixnodes(): Promise<MixNodeDetails[]> {
return this.nymApiQuerier.getActiveMixnodes();
}
getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.nymApiQuerier.getRewardedMixnodes();
}
getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise<any> {
return this.nyxdQuerier.getSpendableCoins(vestingContractAddress, vestingAccountAddress);
}
}
+73 -47
View File
@@ -25,15 +25,22 @@ import {
MixnetContractVersion,
MixNode,
MixNodeBond,
MixNodeCostParams,
MixNodeDetails,
MixNodeRewarding,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingStatus,
} from './types';
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
RewardingParams,
UnbondedMixnodeResponse,
} from '@nymproject/types';
import NymApiQuerier from './nym-api-querier';
import { ContractState } from './types/shared';
// methods exposed by `SigningCosmWasmClient`
export interface ICosmWasmSigning {
@@ -143,6 +150,7 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
costParams: MixNodeCostParams,
ownerSignature: string,
pledge: Coin,
fee?: StdFee | 'auto' | number,
@@ -164,7 +172,7 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign
delegateToMixNode(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
amount: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
@@ -172,14 +180,14 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign
undelegateFromMixNode(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
updateMixnodeConfig(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
profitMarginPercent: number,
fee: StdFee | 'auto' | number,
): Promise<ExecuteResult>;
@@ -235,44 +243,76 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
// query related:
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.nyxdQuerier.getContractVersion(mixnetContractAddress);
return this.getContractVersion(mixnetContractAddress);
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nyxdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse> {
return this.nyxdQuerier.getMixNodeBonds(mixnetContractAddress, limit, startAfter);
}
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse> {
return this.nyxdQuerier.getMixNodesDetailed(mixnetContractAddress, limit, startAfter);
}
getStakeSaturation(mixnetContractAddress: string, mixId: number) {
return this.nyxdQuerier.getStakeSaturation(mixnetContractAddress, mixId);
}
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse> {
return this.nyxdQuerier.getUnbondedMixNodeInformation(mixnetContractAddress, mixId);
}
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding> {
return this.nyxdQuerier.getMixnodeRewardingDetails(mixnetContractAddress, mixId);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.ownsMixNode(mixnetContractAddress, address);
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.getOwnedMixnode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address);
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
getStateParams(mixnetContractAddress: string): Promise<ContractState> {
return this.nyxdQuerier.getStateParams(mixnetContractAddress);
}
getAllNetworkDelegationsPaged(
getAllDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.nyxdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
return this.getAllDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getUnbondedMixNodes(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedUnbondedMixnodesResponse> {
return this.nyxdQuerier.getUnbondedMixNodes(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mix_id, limit, startAfter);
}
getDelegatorDelegationsPaged(
@@ -284,36 +324,16 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mix_id, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getIntervalRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nyxdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams> {
return this.nyxdQuerier.getRewardParams(mixnetContractAddress);
}
getCachedGateways(): Promise<GatewayBond[]> {
@@ -324,7 +344,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nymApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeBond[]> {
getActiveMixnodes(): Promise<MixNodeDetails[]> {
return this.nymApiQuerier.getActiveMixnodes();
}
@@ -332,11 +352,16 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nymApiQuerier.getRewardedMixnodes();
}
getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise<any> {
return this.nyxdQuerier.getSpendableCoins(vestingContractAddress, vestingAccountAddress);
}
// signing related:
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
costParams: MixNodeCostParams,
ownerSignature: string,
pledge: Coin,
fee: StdFee | 'auto' | number = 'auto',
@@ -348,6 +373,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
{
bond_mixnode: {
mix_node: mixNode,
cost_params: costParams,
owner_signature: ownerSignature,
},
},
@@ -414,7 +440,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
delegateToMixNode(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
amount: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Delegation from Typescript',
@@ -424,7 +450,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
mixnetContractAddress,
{
delegate_to_mixnode: {
mix_identity: mixIdentity,
mix_id: mixId,
},
},
fee,
@@ -435,7 +461,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
undelegateFromMixNode(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Undelegation from Typescript',
): Promise<ExecuteResult> {
@@ -444,7 +470,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
mixnetContractAddress,
{
undelegate_from_mixnode: {
mix_identity: mixIdentity,
mix_id: mixId,
},
},
fee,
@@ -454,14 +480,14 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
updateMixnodeConfig(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
profitMarginPercent: number,
fee: StdFee | 'auto' | number,
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{ update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_identity: mixIdentity } },
{ update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_id: mixId } },
fee,
);
}
@@ -0,0 +1,178 @@
import expect from 'expect';
export const amountDemon = {
amount: expect.any(String),
denom: expect.any(String)
}
export const delegation = {
owner: expect.any(String),
mix_id: expect.any(Number),
cumulative_reward_ratio: expect.any(String),
amount: amountDemon,
height: expect.any(Number || BigInt),
proxy: expect.any(String || null)
}
export const detailedDelegation = {
delegation: delegation,
mixnode_still_bonded: expect.any(Boolean)
}
export const gateway = {
pledge_amount: amountDemon,
owner: expect.any(String),
block_height: expect.any(Number || BigInt),
gateway: {
host: expect.any(String),
mix_port: expect.any(Number),
clients_port: expect.any(Number),
location: expect.any(String),
sphinx_key: expect.any(String),
identity_key: expect.any(String),
version: expect.any(String),
},
proxy: expect.any(String || null)
}
export const pagedGateway = {
nodes: gateway,
per_page: expect.any(Number),
start_next_after: expect.any(Number)
}
export const ownGateway = {
address: expect.any(String),
gateway: gateway
}
export const rewardingdetails = {
cost_params: {
profit_margin_percent: expect.any(String),
interval_operating_cost: {
denom: expect.any(String),
amount: expect.any(String)
}
},
operator: expect.any(String),
delegates: expect.any(String),
total_unit_reward: expect.any(String),
unit_delegation: expect.any(String),
last_rewarded_epoch: expect.any(Number),
unique_delegations: expect.any(Number)
}
export const mix_node = {
host: expect.any(String),
mix_port: expect.any(Number),
verloc_port: expect.any(Number),
http_api_port: expect.any(Number),
sphinx_key: expect.any(String),
identity_key: expect.any(String),
version: expect.any(String)
}
export const mixnodebond = {
mix_id: expect.any(Number),
owner: expect.any(String),
original_pledge: {
denom: expect.any(String),
amount: expect.any(String)
},
layer: expect.any(String),
mix_node: mix_node,
proxy: expect.any(String) || null,
bonding_height: expect.any(Number || BigInt),
is_unbonding: expect.any(Boolean)
}
export const mixnode = {
bond_information: mixnodebond,
rewarding_details: rewardingdetails
}
export const ownedNode = {
address: expect.any(String),
mixnode_details: {
bond_information: mixnodebond,
rewarding_details: rewardingdetails
}
}
export const saturation = {
mix_id: expect.any(Number),
current_saturation: expect.any(String),
uncapped_saturation: expect.any(String)
}
export const contractVersion = {
build_timestamp: expect.any(String),
build_version: expect.any(String),
commit_sha: expect.any(String),
commit_timestamp: expect.any(String),
commit_branch: expect.any(String),
rustc_version: expect.any(String)
};
export const stateParams = {
minimum_gateway_pledge: amountDemon,
minimum_mixnode_pledge: expect.any(String),
mixnode_rewarded_set_size: expect.any(Number),
mixnode_active_set_size: expect.any(Number)
}
export const contract = {
owner: expect.any(Number),
rewarding_validator_address: expect.any(Number),
vesting_contract_address: expect.any(Number),
rewarding_denom: expect.any(String),
params: stateParams
}
export const rewardingnode = {
mix_id: expect.any(Number),
rewarding_details: rewardingdetails
}
export const unbondednode = {
mix_id: expect.any(Number),
unbonded_info: {
identity_key: expect.any(String),
owner: expect.any(String),
proxy: expect.any(String) || null,
unbonding_height: expect.any(Number)
}
}
export const allunbondednodes = [
expect.any(Number), {
identity_key: expect.any(String),
owner: expect.any(String),
proxy: expect.any(String) || null,
unbonding_height: expect.any(Number)
}
]
export const layerDistribution = {
layer1: expect.any(Number),
layer2: expect.any(Number),
layer3: expect.any(Number)
}
export const intervalRewardParams = {
reward_pool: expect.any(Number),
staking_supply: expect.any(Number),
staking_supply_scale_factor: expect.any(Number),
epoch_reward_budget: expect.any(Number),
stake_saturation_point: expect.any(Number),
sybil_resistance: expect.any(Number),
active_set_work_factor: expect.any(Number),
interval_pool_emission: expect.any(Number)
}
export const rewardingParams = {
interval: intervalRewardParams,
rewarded_set_size: expect.any(Number),
active_set_size: expect.any(Number)
}
@@ -0,0 +1,19 @@
import { Mock, Times } from 'moq.ts';
import expect from 'expect';
import { INyxdQuery } from '../../query-client';
export class TestHelper {
buildMethod = async <T>(methodName: string, args: any[], expectedResult: any): Promise<T> => {
const client = new Mock<INyxdQuery>()
.setup((nym) => nym[methodName](...args))
.returns(Promise.resolve(expectedResult));
const obj = client.object();
const actualDetails = await obj[methodName](...args);
client.verify((nym) => nym[methodName](...args), Times.Exactly(1));
expect(Object.keys([actualDetails])).toEqual(Object.keys(expectedResult));
expect(actualDetails).toBeDefined();
return actualDetails;
};
}
@@ -0,0 +1,40 @@
import expect from 'expect';
import { Delegation } from '@nymproject/types';
import { PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse } from '../../types/shared-types';
import { TestHelper } from './client';
import { mixnet, mixnodeowneraddress, mix_id } from './testData';
describe('Delegation mock tests', () => {
const testHelper = new TestHelper();
it('get Delegation Details', () => {
const execute = testHelper.buildMethod('getDelegationDetails', [mixnet, mix_id, mixnodeowneraddress], <Delegation>{
owner: mixnodeowneraddress,
mix_id: 0,
cumulative_reward_ratio: '',
amount: {
denom: 'nym',
amount: '10',
},
height: 1314134144132n,
proxy: 'null',
});
expect(execute).toBeTruthy();
});
it('get All Delegations Paged', () => {
const execute = testHelper.buildMethod('getAllDelegationsPaged', [mixnet], <PagedAllDelegationsResponse>{
delegations: [],
});
expect(execute).toBeTruthy();
});
it('get Delegator Delegations Paged', () => {
const execute = testHelper.buildMethod('getDelegatorDelegationsPaged', [mixnet, mixnodeowneraddress], <
PagedDelegatorDelegationsResponse
>{
delegations: [],
});
expect(execute).toBeTruthy();
});
});
@@ -0,0 +1,24 @@
import expect from 'expect';
import { GatewayOwnershipResponse, PagedGatewayResponse } from '../../types/shared-types';
import { TestHelper } from './client';
import { gatewayowneraddress, mixnet } from './testData';
describe('Gateway mock tests', () => {
const testHelper = new TestHelper();
it('get Gateways Paged', () => {
const execute = testHelper.buildMethod('getGatewaysPaged', [mixnet], <PagedGatewayResponse>{
nodes: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('owns Gateway', () => {
const execute = testHelper.buildMethod('ownsGateway', [mixnet, gatewayowneraddress], <GatewayOwnershipResponse>{
address: gatewayowneraddress,
gateway: {},
});
expect(execute).toBeTruthy();
});
});
@@ -0,0 +1,65 @@
import expect from 'expect';
import { LayerDistribution, MixnetContractVersion, StakeSaturationResponse } from '@nymproject/types';
import { TestHelper } from './client';
import { mixnet, mix_id } from './testData';
import { RewardingParams } from '@nymproject/types';
import { ContractState } from '../../types/shared';
describe('Mixnet mock tests', () => {
const testHelper = new TestHelper();
it('get Layer Distribution', () => {
const execute = testHelper.buildMethod('getLayerDistribution', [mixnet], <LayerDistribution>{
layer1: 2,
layer2: 2,
layer3: 5,
});
expect(execute).toBeTruthy();
});
it('get Reward Params', () => {
const execute = testHelper.buildMethod('getRewardParams', [mixnet], <RewardingParams>{
interval: {},
rewarded_set_size: 0,
active_set_size: 0,
});
expect(execute).toBeTruthy();
});
it('get Stake Saturation', () => {
const execute = testHelper.buildMethod('getStakeSaturation', [mixnet, mix_id], <StakeSaturationResponse>{
mix_id: 0,
current_saturation: '',
uncapped_saturation: '',
});
expect(execute).toBeTruthy();
});
it('get State Params', () => {
const execute = testHelper.buildMethod('getStateParams', [mixnet], <ContractState>{
owner: '',
rewarding_validator_address: '',
vesting_contract_address: '',
rewarding_denom: 'unym',
params: {
minimum_mixnode_pledge: '',
minimum_gateway_pledge: '',
mixnode_rewarded_set_size: 240,
mixnode_active_set_size: 240,
},
});
expect(execute).toBeTruthy();
});
it('get Contract Version', () => {
const execute = testHelper.buildMethod('getContractVersion', [mixnet], <MixnetContractVersion>{
build_timestamp: 'test',
commit_branch: 'test',
build_version: 'test',
rustc_version: 'test',
commit_sha: 'test',
commit_timestamp: 'test',
});
expect(execute).toBeTruthy();
});
});
@@ -0,0 +1,71 @@
import expect from 'expect';
import {
MixNodeRewarding,
MixOwnershipResponse,
PagedMixDelegationsResponse,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
UnbondedMixnodeResponse,
} from '@nymproject/types';
import { TestHelper } from './client';
import { mixnet, mixnodeowneraddress, mix_id } from './testData';
describe('Mixnode mock tests', () => {
const testHelper = new TestHelper();
it('get Mixnode Bonds', () => {
const execute = testHelper.buildMethod('getMixNodeBonds', [mixnet], <PagedMixNodeBondResponse>{
nodes: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('get Mixnode Delegations Paged', () => {
const execute = testHelper.buildMethod('getMixNodeDelegationsPaged', [mixnet, mix_id], <
PagedMixDelegationsResponse
>{
delegations: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('get Mixnodes Detailed', () => {
const execute = testHelper.buildMethod('getMixNodesDetailed', [mixnet], <PagedMixNodeDetailsResponse>{
nodes: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('get Mixnode Rewarding Details', () => {
const execute = testHelper.buildMethod('getMixnodeRewardingDetails', [mixnet, mix_id], <MixNodeRewarding>{
cost_params: {},
operator: '',
delegates: '',
total_unit_reward: '',
unit_delegation: '',
last_rewarded_epoch: 1,
unique_delegations: 1,
});
expect(execute).toBeTruthy();
});
it('get Owned Mixnode', () => {
const execute = testHelper.buildMethod('getOwnedMixnode', [mixnet, mixnodeowneraddress], <MixOwnershipResponse>{
address: '',
mixnode: {},
});
expect(execute).toBeTruthy();
});
it('get Unbonded Mixnode Information', () => {
const execute = testHelper.buildMethod(
'getUnbondedMixNodeInformation',
[mixnet, mix_id],
<UnbondedMixnodeResponse>{},
);
expect(execute).toBeTruthy();
});
});
@@ -0,0 +1,8 @@
export const mixnet = 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g';
export const gatewayowneraddress = 'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts';
// export const mixId = 436207616;
export const mix_id = 26;
export const nodeIdentityKey = 'ATmVJknZarDF6Yj53M7h8NS9LLCUvWuToXpk3pDvYUH1';
export const mixnodeowneraddress = 'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47';
export const delegatorAddress = 'n1lemst75va9700tsrxq58adzujrh6h9s5x60h9h';
export const rewardingIntervalNonce = 1;
@@ -0,0 +1,180 @@
import expect from 'expect';
import ValidatorClient from '../../index';
import {
allunbondednodes,
contract,
delegation,
gateway,
layerDistribution,
mixnode,
mixnodebond,
ownedNode,
ownGateway,
rewardingnode,
saturation,
unbondednode,
} from '../expectedResponses';
import { delegatorAddress, gatewayowneraddress } from '../mock/testData';
const dotenv = require('dotenv');
dotenv.config();
describe('Mixnet queries', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connectForQuery(
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
//
// CONTRACT
//
it('can query for an account balance', async () => {
const balance = await client.getBalance('n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77');
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
});
it('can query for stake saturation', async () => {
const stakeSaturation = await client.getStakeSaturation(7);
expect(Object.keys(stakeSaturation)).toEqual(Object.keys(saturation));
expect(stakeSaturation).toBeTruthy();
expect(stakeSaturation?.current_saturation).toBeTruthy();
});
it('can query for contract version', async () => {
const contractV = await client.getMixnetContractVersion();
expect(contractV).toBeTruthy();
});
it('can query for mixnet contract settings', async () => {
const settings = await client.getMixnetContractSettings();
expect(Object.keys(settings)).toEqual(Object.keys(contract));
expect(settings).toBeTruthy();
});
it('can query for reward pool', async () => {
const rewardPool = await client.getRewardParams();
// TODO add velidation here
expect(rewardPool).toBeTruthy();
});
it('can query for layer distribution', async () => {
const layer = await client.getLayerDistribution();
expect(Object.keys(layer)).toEqual(Object.keys(layerDistribution));
expect(layer).toBeTruthy();
});
//
// MIXNODES
//
it('can query for unbonded mixnodes', async () => {
const unbondedNodes = await client.getUnbondedMixNodes();
for (let i = 0; i < unbondedNodes.length; i++) {
expect(Object.keys(unbondedNodes[0])).toEqual(Object.keys(allunbondednodes));
expect(unbondedNodes).toBeTruthy();
}
});
it('can query for unbonded mixnode information', async () => {
const unbondedMixnodeInfo = await client.getUnbondedMixNodeInformation(1);
expect(Object.keys(unbondedMixnodeInfo)).toEqual(Object.keys(unbondednode));
expect(unbondedMixnodeInfo).toBeTruthy();
});
it('can query for mixnode rewarding details', async () => {
const rewardingDetails = await client.getMixnodeRewardingDetails(1);
expect(Object.keys(rewardingDetails)).toEqual(Object.keys(rewardingnode));
expect(rewardingDetails).toBeTruthy();
});
it('can query for owned mixnode', async () => {
const ownedMixnode = await client.getOwnedMixnode('n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77');
expect(Object.keys(ownedMixnode)).toEqual(Object.keys(ownedNode));
expect(ownedMixnode).toBeTruthy();
});
it('can query for all mixnode bonds', async () => {
const mixnodeBonds = await client.getMixNodeBonds();
expect(Object.keys(mixnodeBonds[0])).toEqual(Object.keys(mixnodebond));
expect(mixnodeBonds).toBeTruthy();
expect(Array.isArray(mixnodeBonds)).toBeTruthy();
});
it('can query for all mixnode details', async () => {
const mixnodeDetails = await client.getMixNodesDetailed();
expect(Object.keys(mixnodeDetails[0])).toEqual(Object.keys(mixnode));
expect(mixnodeDetails).toBeTruthy();
expect(Array.isArray(mixnodeDetails)).toBeTruthy();
});
it('can query for all active mixnodes', async () => {
const activeNodes = await client.getActiveMixnodes();
expect(Object.keys(activeNodes[0])).toEqual(Object.keys(mixnode));
expect(activeNodes).toBeTruthy();
expect(Array.isArray(activeNodes)).toBeTruthy();
});
it('can query for rewarded mixnodes', async () => {
const rewardNodes = await client.getRewardedMixnodes();
expect(Object.keys(rewardNodes[0])).toEqual(Object.keys(mixnode));
expect(rewardNodes).toBeTruthy();
});
//
// DELEGATIONS
//
it('can query for account delegations', async () => {
const delegations = await client.getAllNyxdDelegatorDelegations('n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47');
expect(Object.keys(delegations[0])).toEqual(Object.keys(delegation));
expect(delegations).toBeTruthy();
expect(Array.isArray(delegations)).toBeTruthy();
});
it('can query for all delegations', async () => {
const allDelegations = await client.getAllNyxdDelegations();
expect(Object.keys(allDelegations[0])).toEqual(Object.keys(delegation));
expect(allDelegations).toBeTruthy();
expect(Array.isArray(allDelegations)).toBeTruthy();
});
it('can query for all delegations on a node', async () => {
const mixnodeDelegations = await client.getAllNyxdSingleMixnodeDelegations(1);
expect(Object.keys(mixnodeDelegations[0])).toEqual(Object.keys(delegation));
expect(mixnodeDelegations).toBeTruthy();
});
it('can query for detailed delegations', async () => {
const detailedDelegation = await client.getDelegationDetails(7, delegatorAddress);
expect(Object.keys(detailedDelegation)).toEqual(Object.keys(detailedDelegation));
expect(detailedDelegation).toBeTruthy();
});
//
// GATEWAYS
//
it('can query for all gateways', async () => {
const gateways = await client.getAllNyxdGateways();
expect(Object.keys(gateways[0])).toEqual(Object.keys(gateway));
expect(gateways).toBeTruthy();
expect(Array.isArray(gateways)).toBeTruthy();
}).timeout(10000);
it('can query for owned gateway', async () => {
const gateway = await client.ownsGateway(gatewayowneraddress);
expect(Object.keys(gateway)).toEqual(Object.keys(ownGateway));
expect(gateway).toBeTruthy();
}).timeout(10000);
});
@@ -0,0 +1,28 @@
import expect from 'expect';
import ValidatorClient from '../../index';
const dotenv = require('dotenv');
dotenv.config();
describe('Vesting queries', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connectForQuery(
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
it('can query for contract version', async () => {
const contract = await client.getVestingContractVersion();
expect(contract).toBeTruthy();
});
it('can get the balance on the account', () => {});
});
@@ -0,0 +1,107 @@
import expect from 'expect';
import ValidatorClient from '../../';
const dotenv = require('dotenv');
dotenv.config();
// TODO: implement for QA with .env for mnemonics
describe('Mixnet actions', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connect(
process.env.mnemonic || '',
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
it('can send tokens', async () => {
const res = await client.send(client.address, [{ amount: '10000000', denom: 'unym' }]);
expect(res.transactionHash).toBeDefined();
}).timeout(10000);
it.skip('can delegate tokens', async () => {
const [_, secondMixnode] = await client.getActiveMixnodes();
if (secondMixnode) {
const res = await client.delegateToMixNode(
secondMixnode.bond_information.mix_id,
{
amount: '15000000',
denom: 'unym',
},
{ gas: '1000000', amount: [{ amount: '100000', denom: 'unym' }] },
);
expect(res.transactionHash).toBeDefined();
}
}).timeout(10000);
// Need to provide a mix id that can be undelegated from
it.skip('can undelegate from a mixnode', async () => {
const mixId = 8;
const res = await client.undelegateFromMixNode(mixId);
expect(res.transactionHash).toBeDefined();
});
it.skip('Can unbond a mixnode', async () => {
const res = await client.unbondMixNode();
expect(res.transactionHash).toBeDefined();
}).timeout(10000);
it.skip('Can bond a mixnode', async () => {
const res = await client.bondMixNode(
{
identity_key: '3P6pAcF2p3pYMqWdpHqhbavu3ifyaBs5Qw5UmmCGwimx',
sphinx_key: 'GQMQKwUThaggatA6oZteSWTsCQoUfmLtamJ7o9YkP9aE',
host: '109.74.195.67',
mix_port: 1789,
verloc_port: 1790,
http_api_port: 8000,
version: '1.1.4',
},
'3rXWCQBUj5JQB3UBUkZcXhCk9Zh3cjduMF8aFHPTG7KTkkhZzDJTNmE2p2Ph1g6iQW5vRGTpQzjXF33WDwvhzHk6',
{ profit_margin_percent: '0.1', interval_operating_cost: { amount: '40', denom: 'nym' } },
{ amount: '100_000_000', denom: 'unym' },
{ gas: '1000000', amount: [{ amount: '100000', denom: 'unym' }] },
);
expect(res.transactionHash).toBeDefined();
}).timeout(10000);
it.skip('can unbond a gateway', async () => {
const res = await client.unbondGateway();
expect(res.transactionHash).toBeDefined();
});
it.skip('can bond a gateway', async () => {
const res = await client.bondGateway(
{
identity_key: '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL',
sphinx_key: 'G65Fwc2JNAotuHQFqmDKhQNQL5rn3r9pupUdmxMygNUZ',
host: '151.236.220.82',
version: '1.1.4',
mix_port: 1789,
clients_port: 9000,
location: 'Cuba',
},
'3ipSJksWHehZm1YfuH5Ahtg7b22NnrP9hEs6iEDXfUS5uiUhpWmCjGR3b3NDHuxeGjpZYJNYJ52D8WCPK5ZR7Szj',
{ amount: '100_000_000', denom: 'unym' },
);
expect(res.transactionHash).toBeDefined();
});
it.skip('can update contract state params', async () => {
const res = await client.updateContractStateParams({
minimum_mixnode_pledge: '',
minimum_gateway_pledge: '',
mixnode_rewarded_set_size: 2,
mixnode_active_set_size: 2,
});
expect(res.transactionHash).toBeDefined();
});
});
-15
View File
@@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./dist"
},
"exclude": [
"tests",
"dist",
"node_modules"
]
}
-157
View File
@@ -1,157 +0,0 @@
import { Coin } from '@cosmjs/stargate';
// TODO: ideally we'd have re-exported those using that fancy crate that builds ts types from rust
export type MixnetContractVersion = {
build_timestamp: string;
build_version: string;
commit_sha: string;
commit_timestamp: string;
commit_branch: string;
rustc_version: string;
};
export type PagedMixnodeResponse = {
nodes: MixNodeBond[];
per_page: number;
start_next_after?: string;
};
export type PagedGatewayResponse = {
nodes: GatewayBond[];
per_page: number;
start_next_after?: string;
};
export type MixOwnershipResponse = {
address: string;
mixnode?: MixNodeBond;
};
export type GatewayOwnershipResponse = {
address: string;
gateway?: GatewayBond;
};
export type ContractStateParams = {
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_pledge: string;
minimum_gateway_pledge: string;
mixnode_rewarded_set_size: number;
mixnode_active_set_size: number;
};
export type LayerDistribution = {
gateways: number;
layer1: number;
layer2: number;
layer3: number;
};
export type Delegation = {
owner: string;
node_identity: string;
amount: Coin;
block_height: number;
proxy?: string;
};
export type PagedMixDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedDelegatorDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedAllDelegationsResponse = {
delegations: Delegation[];
start_next_after?: [string, string];
};
export type RewardingResult = {
operator_reward: string;
total_delegator_reward: string;
};
export type NodeRewardParams = {
period_reward_pool: string;
k: string;
reward_blockstamp: number;
circulating_supply: string;
uptime: string;
sybil_resistance_percent: number;
};
export type DelegatorRewardParams = {
node_reward_params: NodeRewardParams;
sigma: number;
profit_margin: number;
node_profit: number;
};
export type PendingDelegatorRewarding = {
running_results: RewardingResult;
next_start: string;
rewarding_params: DelegatorRewardParams;
};
export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding };
export type MixnodeRewardingStatusResponse = {
status?: RewardingStatus;
};
export enum Layer {
Gateway,
One,
Two,
Three,
}
export type MixNodeBond = {
owner: string;
mix_node: MixNode;
layer: Layer;
bond_amount: Coin;
total_delegation: Coin;
};
export type MixNode = {
host: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
sphinx_key: string;
identity_key: string;
version: string;
profit_margin_percent: number;
};
export type GatewayBond = {
owner: string;
gateway: Gateway;
bond_amount: Coin;
total_delegation: Coin;
};
export type Gateway = {
host: string;
mix_port: number;
clients_port: number;
location: string;
sphinx_key: string;
identity_key: string;
version: string;
};
export type SendRequest = {
senderAddress: string;
recipientAddress: string;
transferAmount: readonly Coin[];
};
+11
View File
@@ -0,0 +1,11 @@
declare namespace NodeJS {
interface ProcessEnv {
rpcAddress: string;
validatorAddress: string;
prefix: string;
mixnetContractAddress: string;
vestingContractAddress: string;
denom: string;
mnemonic: string;
}
}
+259
View File
@@ -0,0 +1,259 @@
import { JsonObject } from '@cosmjs/cosmwasm-stargate';
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
import {
Account,
Block,
Coin,
DeliverTxResponse,
IndexedTx,
SearchTxFilter,
SearchTxQuery,
SequenceResponse,
} from '@cosmjs/stargate';
import {
MixNodeRewarding,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
StakeSaturationResponse,
UnbondedMixnodeResponse,
} from '@nymproject/types';
export type MixnetContractVersion = {
build_timestamp: string;
build_version: string;
commit_sha: string;
commit_timestamp: string;
commit_branch: string;
rustc_version: string;
};
export type PagedMixnodeResponse = {
nodes: MixNodeBond[];
per_page: number;
start_next_after?: string;
};
export type PagedGatewayResponse = {
nodes: GatewayBond[];
per_page: number;
start_next_after?: string;
};
export type MixOwnershipResponse = {
address: string;
mixnode?: MixNodeBond;
};
export type GatewayOwnershipResponse = {
address: string;
gateway?: GatewayBond;
};
export type ContractStateParams = {
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_pledge: string;
minimum_gateway_pledge: string;
mixnode_rewarded_set_size: number;
mixnode_active_set_size: number;
};
export type LayerDistribution = {
gateways: number;
layer1: number;
layer2: number;
layer3: number;
};
export type Delegation = {
owner: string;
node_identity: string;
amount: Coin;
block_height: number;
proxy?: string;
};
export type PagedMixDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedDelegatorDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedAllDelegationsResponse = {
delegations: Delegation[];
start_next_after?: [string, string];
};
export type RewardingResult = {
operator_reward: string;
total_delegator_reward: string;
};
export type NodeRewardParams = {
period_reward_pool: string;
k: string;
reward_blockstamp: number;
circulating_supply: string;
uptime: string;
sybil_resistance_percent: number;
};
export type DelegatorRewardParams = {
node_reward_params: NodeRewardParams;
sigma: number;
profit_margin: number;
node_profit: number;
};
export type PendingDelegatorRewarding = {
running_results: RewardingResult;
next_start: string;
rewarding_params: DelegatorRewardParams;
};
export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding };
export type MixnodeRewardingStatusResponse = {
status?: RewardingStatus;
};
export enum Layer {
Gateway,
One,
Two,
Three,
}
export type MixNodeBond = {
owner: string;
mix_node: MixNode;
layer: Layer;
bond_amount: Coin;
total_delegation: Coin;
};
export type MixNode = {
host: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
sphinx_key: string;
identity_key: string;
version: string;
profit_margin_percent: number;
};
export type GatewayBond = {
owner: string;
gateway: Gateway;
bond_amount: Coin;
total_delegation: Coin;
};
export type Gateway = {
host: string;
mix_port: number;
clients_port: number;
location: string;
sphinx_key: string;
identity_key: string;
version: string;
};
export type SendRequest = {
senderAddress: string;
recipientAddress: string;
transferAmount: readonly Coin[];
};
export interface SmartContractQuery {
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export interface ICosmWasmQuery {
// methods exposed by `CosmWasmClient`
getChainId(): Promise<string>;
getHeight(): Promise<number>;
getAccount(searchAddress: string): Promise<Account | null>;
getSequence(address: string): Promise<SequenceResponse>;
getBlock(height?: number): Promise<Block>;
getBalance(address: string, searchDenom: string): Promise<Coin>;
getTx(id: string): Promise<IndexedTx | null>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
disconnect(): void;
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>;
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>;
getContracts(codeId: number): Promise<readonly string[]>;
getContract(address: string): Promise<Contract>;
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export interface INymdQuery {
// nym-specific implemented inside NymQuerier
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse>;
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse>;
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>;
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(mixnetContractAddress: string): Promise<ContractState>;
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse>;
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse>;
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse>;
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation>;
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>;
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse>;
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse>;
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding>;
}
export interface IVestingQuerier {
getVestingContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
}
export interface MappedCoin {
readonly denom: string;
readonly fractionalDigits: number;
}
export interface CoinMap {
readonly [key: string]: MappedCoin;
}
export interface ContractState {
owner: string;
rewarding_validator_address: string;
vesting_contract_address: string;
rewarding_denom: string;
params: ContractStateParams;
}
@@ -1,11 +0,0 @@
import ValidatorClient from '../../validator/index';
import expect from 'expect';
describe('Query: balances', () => {
it('can query for an account balance', async () => {
const client = await ValidatorClient.connectForQuery(
'https://rpc.nymtech.net/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
}).timeout(5000);
})
-14
View File
@@ -1,14 +0,0 @@
import ValidatorClient from '../../dist';
import expect from 'expect';
// TODO: implement for QA with .env for mnemonics
// describe('Sign: send', () => {
// it('can send tokens', async () => {
// const client = await ValidatorClient.connect(
// '<ADD MNEMONIC HERE>',
// 'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
// await client.send('<ADD ADDRESS HERE>')
// const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
// expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
// }).timeout(5000);
// })
+21 -22
View File
@@ -1,23 +1,22 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./dist",
"skipLibCheck": true
},
"typedocOptions": {
"entryPoints": [
"src/index.ts"
],
"out": "docs"
},
"exclude": [
"dist",
"examples",
"tests",
"node_modules"
]
}
"compilerOptions": {
"outDir": "./dist/nym-validator-client",
"module": "ES2020",
"target": "es2021",
"allowJs": false,
"strict": true,
"lib": ["es2021", "dom", "dom.iterable", "esnext"],
"moduleResolution": "node",
"skipDefaultLibCheck": true,
"esModuleInterop": true,
"declaration": true,
"skipLibCheck": true,
"noImplicitAny": true,
"typeRoots": ["./src/types/global.d.ts"]
},
"typedocOptions": {
"entryPoints": ["./src/index.ts"],
"out": "docs"
},
"exclude": ["dist", "./src/tests/**/*", "node_module"]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS"
}
}
+2 -2
View File
@@ -31,8 +31,8 @@ wasm-bindgen-futures = "0.4"
# internal
client-core = { path = "../client-core", default-features = false, features = ["wasm"] }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-credentials = { path = "../../common/credentials" }
nym-crypto = { path = "../../common/crypto" }
nym-sphinx = { path = "../../common/nymsphinx" }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
+239 -88
View File
@@ -3,8 +3,15 @@
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
#![allow(clippy::drop_non_drop)]
// another issue due to #[wasm_bindgen] and `Copy` trait
#![allow(clippy::drop_copy)]
use client_core::config::{DebugConfig as ConfigDebug, ExtendedPacketSize, GatewayEndpointConfig};
use client_core::config::{
Acknowledgements as ConfigAcknowledgements, CoverTraffic as ConfigCoverTraffic,
DebugConfig as ConfigDebug, ExtendedPacketSize, GatewayConnection as ConfigGatewayConnection,
GatewayEndpointConfig, ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology,
Traffic as ConfigTraffic,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
@@ -48,15 +55,124 @@ impl Config {
}
}
// just a helper structure to more easily pass through the JS boundary
#[wasm_bindgen]
pub struct Debug {
#[derive(Debug, Copy, Clone)]
pub struct Traffic {
/// 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,
/// 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,
/// 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<Traffic> for ConfigTraffic {
fn from(traffic: Traffic) -> Self {
let use_extended_packet_size = traffic
.use_extended_packet_size
.then(|| ExtendedPacketSize::Extended32);
ConfigTraffic {
average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms),
message_sending_average_delay: Duration::from_millis(
traffic.message_sending_average_delay_ms,
),
disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution,
use_extended_packet_size,
}
}
}
impl From<ConfigTraffic> for Traffic {
fn from(traffic: ConfigTraffic) -> Self {
Traffic {
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u64,
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
as u64,
disable_main_poisson_packet_distribution: traffic
.disable_main_poisson_packet_distribution,
use_extended_packet_size: traffic.use_extended_packet_size.is_some(),
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct CoverTraffic {
/// 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,
/// 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,
}
impl From<CoverTraffic> for ConfigCoverTraffic {
fn from(cover_traffic: CoverTraffic) -> Self {
ConfigCoverTraffic {
loop_cover_traffic_average_delay: Duration::from_millis(
cover_traffic.loop_cover_traffic_average_delay_ms,
),
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
}
}
}
impl From<ConfigCoverTraffic> for CoverTraffic {
fn from(cover_traffic: ConfigCoverTraffic) -> Self {
CoverTraffic {
loop_cover_traffic_average_delay_ms: cover_traffic
.loop_cover_traffic_average_delay
.as_millis() as u64,
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct GatewayConnection {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
pub gateway_response_timeout_ms: u64,
}
impl From<GatewayConnection> for ConfigGatewayConnection {
fn from(gateway_connection: GatewayConnection) -> Self {
ConfigGatewayConnection {
gateway_response_timeout: Duration::from_millis(
gateway_connection.gateway_response_timeout_ms,
),
}
}
}
impl From<ConfigGatewayConnection> for GatewayConnection {
fn from(gateway_connection: ConfigGatewayConnection) -> Self {
GatewayConnection {
gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis()
as u64,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct Acknowledgements {
/// 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
@@ -72,21 +188,31 @@ pub struct Debug {
/// 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,
impl From<Acknowledgements> for ConfigAcknowledgements {
fn from(acknowledgements: Acknowledgements) -> Self {
ConfigAcknowledgements {
average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms),
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
ack_wait_addition: Duration::from_millis(acknowledgements.ack_wait_addition_ms),
}
}
}
/// 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,
impl From<ConfigAcknowledgements> for Acknowledgements {
fn from(acknowledgements: ConfigAcknowledgements) -> Self {
Acknowledgements {
average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u64,
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u64,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct Topology {
/// 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,
@@ -95,18 +221,31 @@ pub struct Debug {
/// 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,
impl From<Topology> for ConfigTopology {
fn from(topology: Topology) -> Self {
ConfigTopology {
topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms),
topology_resolution_timeout: Duration::from_millis(
topology.topology_resolution_timeout_ms,
),
}
}
}
/// 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<ConfigTopology> for Topology {
fn from(topology: ConfigTopology) -> Self {
Topology {
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64,
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64,
}
}
}
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct ReplySurbs {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
/// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize,
@@ -140,46 +279,80 @@ pub struct Debug {
pub maximum_reply_key_age_ms: u64,
}
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,
minimum_reply_surb_storage_threshold: debug.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: debug.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: debug.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: debug.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: debug.maximum_allowed_reply_surb_request_size,
impl From<ReplySurbs> for ConfigReplySurbs {
fn from(reply_surbs: ReplySurbs) -> Self {
ConfigReplySurbs {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: Duration::from_millis(
debug.maximum_reply_surb_rerequest_waiting_period_ms,
reply_surbs.maximum_reply_surb_rerequest_waiting_period_ms,
),
maximum_reply_surb_drop_waiting_period: Duration::from_millis(
debug.maximum_reply_surb_drop_waiting_period_ms,
reply_surbs.maximum_reply_surb_drop_waiting_period_ms,
),
maximum_reply_surb_age: Duration::from_millis(debug.maximum_reply_surb_age_ms),
maximum_reply_key_age: Duration::from_millis(debug.maximum_reply_key_age_ms),
maximum_reply_surb_age: Duration::from_millis(reply_surbs.maximum_reply_surb_age_ms),
maximum_reply_key_age: Duration::from_millis(reply_surbs.maximum_reply_key_age_ms),
}
}
}
impl From<ConfigReplySurbs> for ReplySurbs {
fn from(reply_surbs: ConfigReplySurbs) -> Self {
ReplySurbs {
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period_ms: reply_surbs
.maximum_reply_surb_rerequest_waiting_period
.as_millis() as u64,
maximum_reply_surb_drop_waiting_period_ms: reply_surbs
.maximum_reply_surb_drop_waiting_period
.as_millis() as u64,
maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u64,
maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u64,
}
}
}
// just a helper structure to more easily pass through the JS boundary
#[wasm_bindgen]
#[derive(Debug, Copy, Clone)]
pub struct Debug {
/// Defines all configuration options related to traffic streams.
pub traffic: Traffic,
/// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTraffic,
/// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnection,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: Acknowledgements,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: Topology,
/// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbs,
}
impl From<Debug> for ConfigDebug {
fn from(debug: Debug) -> Self {
ConfigDebug {
traffic: debug.traffic.into(),
cover_traffic: debug.cover_traffic.into(),
gateway_connection: debug.gateway_connection.into(),
acknowledgements: debug.acknowledgements.into(),
topology: debug.topology.into(),
reply_surbs: debug.reply_surbs.into(),
}
}
}
@@ -187,34 +360,12 @@ impl From<Debug> for ConfigDebug {
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(),
minimum_reply_surb_storage_threshold: debug.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: debug.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: debug.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: debug.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: debug.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period_ms: debug
.maximum_reply_surb_rerequest_waiting_period
.as_millis() as u64,
maximum_reply_surb_drop_waiting_period_ms: debug
.maximum_reply_surb_drop_waiting_period
.as_millis() as u64,
maximum_reply_surb_age_ms: debug.maximum_reply_surb_age.as_millis() as u64,
maximum_reply_key_age_ms: debug.maximum_reply_key_age.as_millis() as u64,
traffic: debug.traffic.into(),
cover_traffic: debug.cover_traffic.into(),
gateway_connection: debug.gateway_connection.into(),
acknowledgements: debug.acknowledgements.into(),
topology: debug.topology.into(),
reply_surbs: debug.reply_surbs.into(),
}
}
}
+8 -2
View File
@@ -82,8 +82,14 @@ impl NymClientBuilder {
// with no persistence
fn setup_reply_surb_storage_backend(config: &Config) -> browser_backend::Backend {
browser_backend::Backend::new(
config.debug.minimum_reply_surb_storage_threshold,
config.debug.maximum_reply_surb_storage_threshold,
config
.debug
.reply_surbs
.minimum_reply_surb_storage_threshold,
config
.debug
.reply_surbs
.maximum_reply_surb_storage_threshold,
)
}
@@ -1,10 +0,0 @@
[package]
name = "bandwidth-claim-contract"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }

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