Compare commits

..

97 Commits

Author SHA1 Message Date
farbanas deae210b82 update changelog in for release v1.1.18 2023-05-09 12:21:30 +01:00
farbanas 5b2b45a6eb updated versions for release v1.1.18 2023-05-09 12:20:07 +01:00
mx 896a3e1be6 temporarily removed compatibility table: will reintroduce once fixed 2023-05-09 11:40:07 +02:00
mx 1eaa13155c Merge pull request #3392 from nymtech/feature/release-1-1-18-docs
version bump to 1.1.18
2023-05-09 09:17:02 +00:00
mx 534187cc8f Merge pull request #3368 from nymtech/add-docs-template
Update issue templates
2023-05-05 12:21:17 +00:00
mx 25b4934f69 added ntv blog to community guides section 2023-05-05 14:12:20 +02:00
mx 5ef7e24893 removed additional whitespace 2023-05-05 13:56:35 +02:00
mx 87ef46bc05 version bump to 1.1.18 2023-05-05 13:49:52 +02:00
Pierre Dommerc 68ca41a6be refactor(wallet-bonding): fetch node data concurrently (#3362) 2023-05-04 10:10:50 +02:00
Fouad a1a5c7772d Use Loading Modal component when loading Delegations data (#3377)
* allow loading modal to display custom text

* use loading modal

* dont repeatedly reset delegation state

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

* fix lint errors

* log any delegations errors

* fix typo

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

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

* rustfmt

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

* wip

* Config option to disable topology refreshing

* extracted common parsing code

* helper trait for working on wasm topology

* wasm topology parsing

* restored (slightly modified) old js-example

* wip

* Moved message preparation into a trait

* wip

* long-winded way of sending test packet

* standalone NymNodeTester

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

* initial round of cleanup

* sending multiple test packets in normal NymClient

* javascript-side cleanup

* starting mixnode test on btn click

* Improved NymNodeTester constructors

* improved error handling and constructors

* tester utils error handling

* further cleanup + using BTreeMap for NymTopology mixnodes

* handling missed errors

* splitting up 'test_node'

* split up and cleaned up generation of test result

* clippy + fixed example

* post rebase fixes

* another broken test

* prevent running multiple parallel tests

* cargo fmt

* Added nym- prefix to node tester utils
2023-04-24 09:56:26 +01:00
Jon Häggblad 3f0d4846df Fix a few clippy warnings in contract test code (#3340)
* ci: don't fail fast for contracts

* contracts: fix clippy in tests
2023-04-24 10:30:22 +02:00
mx de020f46a6 added coconut credential generation example 2023-04-21 16:32:20 +02:00
Jon Häggblad 9bfcdbe8e2 Add --all-targets to clippy for contracts (#3337) 2023-04-21 11:47:43 +02:00
Tommy Verrall 8c4885ce2c Merge pull request #3294 from nymtech/feature/fix-clippy-warnings
A branch with all clippy warnings dealt with in contracts
2023-04-21 10:37:33 +01:00
Jędrzej Stuczyński f24bb5c038 reduced noise in CODEOWNERS (#3313)
* reduced noise in CODEOWNERS

* Add @octol to codeowners

* added @mfahampshire as owner of /documentation

---------

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2023-04-20 15:25:23 +01:00
Jon Häggblad 79dfe7eeda Add clippy target in top-level Makefile 2023-04-20 14:13:20 +02:00
Jon Häggblad 0108c6ed19 Merge remote-tracking branch 'origin/release/v1.1.16' into develop 2023-04-20 09:55:19 +02:00
Tommy Verrall 926389df89 Merge pull request #3300 from nymtech/bugfix/empty-ack-stream-map
make sure to clear inner 'ack_map' in 'GatewaysReader'
2023-04-20 08:31:31 +01:00
Tommy Verrall b55db00408 Merge pull request #3324 from nymtech/bugfix/nym-cli-gateway-commands
exposed missing gateway commands in nym-cli
2023-04-20 07:55:56 +01:00
Jędrzej Stuczyński cfcb64f7e5 Feature/reduce pledge (#3254)
* basic contract work for 'decrease_pledge' functionality

note: it doesn't yet return tokens back to the operator

* returning extra tokens after decreasing pledge

* added vesting message to track pledge decrease

* attaching the track message when processing delegation decrease

* checking for zero value request

* fixed event test

* allowing to decrease pledge from the vesting contract

* integration test for the feature

* reorganised the integration tests

* updated nyxd client traits

* wallet support

* typescript helpers

* moved 'pledge more' functionality to operator commands

* cli commands for decreasing pledge

* changed error variant to make clippy happier

* removed unused import

* eslint

* fixed post-rebase imports

* added cargo config

* added PendingMixNodeChanges to MixNodeDetails

* returning event id after creating it

* Streamlined getting mixnode details by identity key

* setting pending pledge changes on increase/decrease

* clearing the value on resolving the event

* checking for correct invariants when clearing events

* further pending events unit tests fixes

* new unit tests for tx endpoints

* queries for pending events (by id)

* migration code

* using default value for pending changes if unavailable

* improved integration test assertions
2023-04-20 07:52:10 +01:00
Itamar Perez 0e8f60d501 update mix-node setup docs with node description 2023-04-19 12:01:36 -07:00
Jon Häggblad 9c6c5f5170 Add --all-targets to nym-wallet CI clippy (#3326) 2023-04-19 10:44:46 +02:00
Jon Häggblad f28888e3e7 Update Cargo.lock files after bumping internal versions during 1.1.15 release 2023-04-19 09:41:03 +02:00
Jon Häggblad 6e30e6178b Update Cargo.lock files after bumping internal versions during release 2023-04-19 09:37:38 +02:00
Jędrzej Stuczyński 9549bed8bb exposed missing gateway commands in nym-cli 2023-04-18 16:28:13 +01:00
Jędrzej Stuczyński 7a50f0c3b2 make sure to clear inner 'ack_map' in 'GatewaysReader' 2023-04-13 10:51:03 +01:00
Dave Hrycyszyn 2da6a2fbfa Adding a clippy.toml so we can see correct warnings in mixnet contract 2023-04-11 14:57:04 +01:00
Dave Hrycyszyn d910a4e0ee The make test target seems to be wrapping differently than local 2023-04-11 14:53:51 +01:00
Dave Hrycyszyn 672ab79421 A branch with all clippy warnings dealt with in contracts 2023-04-11 14:46:45 +01:00
318 changed files with 12372 additions and 4009 deletions
+17 -22
View File
@@ -11,30 +11,25 @@
# In each subsection folders are ordered first by depth, then alphabetically.
# This should make it easy to add new rules without breaking existing ones.
# Something weird not covered by anything else
* @futurechimp @mmsinclair
# contracts
/contracts/mixnet @durch @jstuczyn
/contracts/vesting @durch @jstuczyn
/contracts/service-provider-directory @octol
# Rust rules:
*.rs @durch @futurechimp @jstuczyn @neacsu @octol
Cargo.* @durch @futurechimp @jstuczyn @neacsu @octol
# crypto code
/common/crypto/ @jstuczyn
/common/nymcoconut/ @jstuczyn
/common/dkg/ @jstuczyn
/common/nymsphinx/ @jstuczyn
# JS rules:
*.js @mmsinclair @fmtabbara
*.ts @mmsinclair @fmtabbara
*.tsx @mmsinclair @fmtabbara
*.jsx @mmsinclair @fmtabbara
# rust sdk
/sdk/rust/ @octol
# Something looking like possible documentation rules:
*.md @mfahampshire
# nym-connect (rust)
/nym-connect/desktop/src-tauri/ @octol
# our docker scripts
/docker/ @neacsu
# nym-wallet (rust)
/nym-wallet/src-tauri/ @octol
# if there are any changes in the core crypto, I feel like Ania should take a look:
/common/crypto/ @aniampio
/common/nymsphinx/ @aniampio
# Explorer and wallet should probably get looked by the product team
/explorer/ @nymtech/product
/nym-wallet/ @nymtech/product
/wallet-web/ @nymtech/product
# documentation
/documentation @mfahampshire
+14
View File
@@ -0,0 +1,14 @@
---
name: 'Documentation'
about: Suggest a fix or enhancement to the documentation or developer portal content
title: "[DOCS]"
labels: documentation
assignees: mfahampshire
---
Is your issue either:
- [ ] a fix to existing documentation (e.g. fixing a broken link or incorrect command)
- [ ] an enhancement (e.g. adding a description for an undocumented feature)
Please briefly describe your issue:
@@ -69,7 +69,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --release --all --features cpucycles
args: --workspace --release --all
- name: Install Rust stable
uses: actions-rs/toolchain@v1
+2 -1
View File
@@ -30,6 +30,7 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' }}
needs: matrix_prep
strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
steps:
- uses: actions/checkout@v2
@@ -63,4 +64,4 @@ jobs:
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/Cargo.toml --workspace -- -D warnings
args: --manifest-path contracts/Cargo.toml --workspace --all-targets -- -D warnings
@@ -6,7 +6,7 @@
},
{
"os":"windows-latest",
"os":"windows10",
"rust":"stable",
"runOnEvent":"schedule"
},
@@ -22,7 +22,7 @@
"runOnEvent":"schedule"
},
{
"os":"windows-latest",
"os":"windows10",
"rust":"beta",
"runOnEvent":"schedule"
},
@@ -38,7 +38,7 @@
"runOnEvent":"schedule"
},
{
"os":"windows-latest",
"os":"windows10",
"rust":"nightly",
"runOnEvent":"schedule"
},
+1 -1
View File
@@ -64,4 +64,4 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features -- -D warnings
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features --all-targets -- -D warnings
+50 -7
View File
@@ -4,14 +4,57 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
- nym-network-statistics properly handles signals ([#3209])
- add socks5 support for Rust SDK ([#3226], [#3255])
- add coconut bandwidth credential support for Rust SDK ([#3273])
## [v1.1.18] (2023-05-09)
[#3209]: https://github.com/nymtech/nym/issues/3209
[#3226]: https://github.com/nymtech/nym/pull/3226
[#3255]: https://github.com/nymtech/nym/pull/3255
[#3273]: https://github.com/nymtech/nym/pull/3273
- Implement heartbeat messages between socks5 proxy and network requester ([#3215])
[#3215]: https://github.com/nymtech/nym/issues/3215
## [v1.1.17] (2023-05-02)
- Add service-provider-directory-contract support to nym-cli ([#3334])
- Start using the node-testing-utils (implemented in #3270) in nym-api Network monitor to simplify the logic there ([#3312])
- Add service-provider-directory support to validator-client ([#3296])
- Allow topology injection in our WASM client ('test my node' feature) ([#3270])
- Expose service-provider-directory contract data in nym-api endpoints ([#3242])
- Cache service provider contract in nym-api ([#3241])
- Feature/1 1 17 docs ([#3370])
- adding a test for SP endpoint ([#3367])
- Feature/store cipher ([#3350])
[#3334]: https://github.com/nymtech/nym/issues/3334
[#3312]: https://github.com/nymtech/nym/issues/3312
[#3296]: https://github.com/nymtech/nym/issues/3296
[#3270]: https://github.com/nymtech/nym/issues/3270
[#3242]: https://github.com/nymtech/nym/issues/3242
[#3241]: https://github.com/nymtech/nym/issues/3241
[#3370]: https://github.com/nymtech/nym/pull/3370
[#3367]: https://github.com/nymtech/nym/pull/3367
[#3350]: https://github.com/nymtech/nym/pull/3350
## [v1.1.16] (2023-04-25)
- Explorer - Fix sorting function on Stake Saturation. It is currently working per page and not globally ([#3320])
- Poisson process gets stuck at too slow rate. Rework to more aggressively up-regulate ([#3309])
- decrease the logging level of warnings associated with clients dropping packets due to gateway being overloaded (I'd say reduce it to debug/trace) - there are few sources of those, e.g. in real and cover traffic streams ([#3299])
- Make the buffer size in `AvailableReader` depend on packet sizes the client is using + introduce read timeouts ([#3213])
- Rust SDK - Support coconut, credential storage etc ([#2755])
- version bump for next release ([#3349])
- added coconut credential generation example ([#3339])
- update mix-node setup docs with node description ([#3325])
- exposed missing gateway commands in nym-cli ([#3324])
- make sure to clear inner 'ack_map' in 'GatewaysReader' ([#3300])
[#3320]: https://github.com/nymtech/nym/issues/3320
[#3309]: https://github.com/nymtech/nym/issues/3309
[#3299]: https://github.com/nymtech/nym/issues/3299
[#3213]: https://github.com/nymtech/nym/issues/3213
[#2755]: https://github.com/nymtech/nym/issues/2755
[#3349]: https://github.com/nymtech/nym/pull/3349
[#3339]: https://github.com/nymtech/nym/pull/3339
[#3325]: https://github.com/nymtech/nym/pull/3325
[#3324]: https://github.com/nymtech/nym/pull/3324
[#3300]: https://github.com/nymtech/nym/pull/3300
## [v1.1.15] (2023-04-18)
Generated
+122 -23
View File
@@ -103,6 +103,17 @@ version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]]
name = "argon2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
dependencies = [
"base64ct",
"blake2 0.10.6",
"password-hash",
]
[[package]]
name = "arrayref"
version = "0.3.7"
@@ -366,6 +377,15 @@ dependencies = [
"opaque-debug 0.2.3",
]
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest 0.10.6",
]
[[package]]
name = "blake3"
version = "1.3.3"
@@ -1576,7 +1596,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.15"
version = "1.1.17"
dependencies = [
"chrono",
"clap 4.1.11",
@@ -2757,7 +2777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9"
dependencies = [
"arrayref",
"blake2",
"blake2 0.8.1",
"chacha",
"keystream",
]
@@ -3011,7 +3031,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.16"
version = "1.1.18"
dependencies = [
"anyhow",
"async-trait",
@@ -3047,7 +3067,9 @@ dependencies = [
"nym-inclusion-probability",
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-node-tester-utils",
"nym-pemstore",
"nym-service-provider-directory-common",
"nym-sphinx",
"nym-task",
"nym-topology",
@@ -3142,7 +3164,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.15"
version = "1.1.17"
dependencies = [
"anyhow",
"base64 0.13.1",
@@ -3189,6 +3211,7 @@ dependencies = [
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-network-defaults",
"nym-service-provider-directory-common",
"nym-validator-client",
"nym-vesting-contract-common",
"rand 0.6.5",
@@ -3203,7 +3226,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.15"
version = "1.1.17"
dependencies = [
"clap 4.1.11",
"dirs",
@@ -3415,7 +3438,7 @@ dependencies = [
[[package]]
name = "nym-crypto"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"aes 0.8.2",
"blake3",
@@ -3436,6 +3459,7 @@ dependencies = [
"subtle-encoding",
"thiserror",
"x25519-dalek",
"zeroize",
]
[[package]]
@@ -3471,7 +3495,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.1.15"
version = "1.1.17"
dependencies = [
"anyhow",
"async-trait",
@@ -3600,7 +3624,7 @@ dependencies = [
[[package]]
name = "nym-mixnet-contract-common"
version = "0.4.0"
version = "0.5.0"
dependencies = [
"bs58",
"cosmwasm-std",
@@ -3619,7 +3643,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.16"
version = "1.1.18"
dependencies = [
"anyhow",
"bs58",
@@ -3721,7 +3745,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.15"
version = "1.1.17"
dependencies = [
"async-file-watcher",
"async-trait",
@@ -3763,7 +3787,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "1.1.15"
version = "1.1.17"
dependencies = [
"dirs",
"log",
@@ -3778,6 +3802,24 @@ dependencies = [
"tokio",
]
[[package]]
name = "nym-node-tester-utils"
version = "0.1.0"
dependencies = [
"futures",
"log",
"nym-crypto",
"nym-sphinx",
"nym-task",
"nym-topology",
"rand 0.7.3",
"serde",
"serde_json",
"thiserror",
"tokio",
"wasm-utils",
]
[[package]]
name = "nym-nonexhaustive-delayqueue"
version = "0.1.0"
@@ -3809,9 +3851,8 @@ dependencies = [
"curve25519-dalek",
"fastrand",
"getrandom 0.2.8",
"rand 0.7.3",
"rayon",
"sphinx-packet",
"sphinx-packet 0.1.0 (git+https://github.com/nymtech/sphinx.git)",
"thiserror",
"zeroize",
]
@@ -3859,6 +3900,7 @@ name = "nym-service-provider-directory-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
@@ -3880,7 +3922,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.15"
version = "1.1.17"
dependencies = [
"clap 4.1.11",
"lazy_static",
@@ -3976,6 +4018,7 @@ dependencies = [
"nym-sphinx-forwarding",
"nym-sphinx-framing",
"nym-sphinx-params",
"nym-sphinx-routing",
"nym-sphinx-types",
"nym-topology",
"rand 0.7.3",
@@ -4062,7 +4105,6 @@ dependencies = [
"nym-sphinx-addressing",
"nym-sphinx-params",
"nym-sphinx-types",
"thiserror",
]
[[package]]
@@ -4086,13 +4128,20 @@ dependencies = [
"thiserror",
]
[[package]]
name = "nym-sphinx-routing"
version = "0.1.0"
dependencies = [
"nym-sphinx-addressing",
"nym-sphinx-types",
"thiserror",
]
[[package]]
name = "nym-sphinx-types"
version = "0.2.0"
dependencies = [
"nym-outfox",
"sphinx-packet",
"thiserror",
"sphinx-packet 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -4109,6 +4158,20 @@ dependencies = [
"tokio",
]
[[package]]
name = "nym-store-cipher"
version = "0.1.0"
dependencies = [
"aes-gcm",
"argon2",
"getrandom 0.2.8",
"rand 0.8.5",
"serde",
"serde_json",
"thiserror",
"zeroize",
]
[[package]]
name = "nym-task"
version = "0.1.0"
@@ -4132,6 +4195,7 @@ dependencies = [
"nym-crypto",
"nym-mixnet-contract-common",
"nym-sphinx-addressing",
"nym-sphinx-routing",
"nym-sphinx-types",
"rand 0.7.3",
"thiserror",
@@ -4190,6 +4254,7 @@ dependencies = [
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-network-defaults",
"nym-service-provider-directory-common",
"nym-vesting-contract",
"nym-vesting-contract-common",
"prost 0.10.4",
@@ -4206,7 +4271,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract"
version = "1.3.1"
version = "1.4.0"
dependencies = [
"cosmwasm-derive",
"cosmwasm-std",
@@ -4224,7 +4289,7 @@ dependencies = [
[[package]]
name = "nym-vesting-contract-common"
version = "0.5.0"
version = "0.6.0"
dependencies = [
"cosmwasm-std",
"nym-contracts-common",
@@ -4456,6 +4521,17 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "password-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core 0.6.4",
"subtle 2.4.1",
]
[[package]]
name = "paste"
version = "1.0.12"
@@ -5877,7 +5953,30 @@ checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4"
dependencies = [
"aes 0.7.5",
"arrayref",
"blake2",
"blake2 0.8.1",
"bs58",
"byteorder",
"chacha",
"curve25519-dalek",
"digest 0.9.0",
"hkdf 0.11.0",
"hmac 0.11.0",
"lioness",
"log",
"rand 0.7.3",
"rand_distr",
"sha2 0.9.9",
"subtle 2.4.1",
]
[[package]]
name = "sphinx-packet"
version = "0.1.0"
source = "git+https://github.com/nymtech/sphinx.git#ca107d94360cdf8bbfbdb12fe5320ed74f80e40c"
dependencies = [
"aes 0.7.5",
"arrayref",
"blake2 0.8.1",
"bs58",
"byteorder",
"chacha",
@@ -7431,9 +7530,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zeroize"
version = "1.5.7"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
dependencies = [
"zeroize_derive",
]
+3
View File
@@ -48,6 +48,7 @@ members = [
"common/ledger",
"common/mixnode-common",
"common/network-defaults",
"common/node-tester-utils",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nymsphinx",
@@ -59,12 +60,14 @@ members = [
"common/nymsphinx/forwarding",
"common/nymsphinx/framing",
"common/nymsphinx/params",
"common/nymsphinx/routing",
"common/nymsphinx/types",
"common/pemstore",
"common/socks5-client-core",
"common/socks5/proxy-helpers",
"common/socks5/requests",
"common/statistics",
"common/store-cipher",
"common/task",
"common/topology",
"common/types",
+6 -2
View File
@@ -13,6 +13,10 @@ happy: fmt clippy-happy test
# on all workspaces.
build-release: build-release-main wasm
# Deprecated
# For backwards compatibility
clippy-all: clippy
# -----------------------------------------------------------------------------
# Define targets for a given workspace
# $(1): name
@@ -52,11 +56,11 @@ fmt-$(1):
cargo fmt --manifest-path $(2)/Cargo.toml --all
clippy-happy: clippy-happy-$(1)
clippy-all: clippy-$(1) clippy-examples-$(1)
clippy: clippy-$(1) clippy-examples-$(1)
check: check-$(1)
cargo-test: test-$(1)
cargo-test-expensive: test-expensive-$(1)
build: build-$(1) build-$(1)-examples
build: build-$(1) build-examples-$(1)
build-release-all: build-release-$(1)
fmt: fmt-$(1)
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.15"
version = "1.1.18"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+4 -12
View File
@@ -16,7 +16,6 @@ use nym_client_core::client::received_buffer::{
};
use nym_client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_validator_client::nyxd::QueryNyxdClient;
@@ -120,7 +119,6 @@ impl SocketClient {
self_address,
shared_lane_queue_lengths,
reply_controller_sender,
None,
);
websocket::Listener::new(config.get_listening_ip(), config.get_listening_port())
@@ -180,10 +178,7 @@ impl SocketClient {
Ok(started_client.task_manager)
}
pub async fn start_direct(
self,
packet_type: Option<PacketType>,
) -> Result<DirectClient, ClientError> {
pub async fn start_direct(self) -> Result<DirectClient, ClientError> {
if self.config.get_socket_type().is_websocket() {
return Err(ClientError::InvalidSocketMode);
}
@@ -229,7 +224,6 @@ impl SocketClient {
reconstructed_receiver,
address,
shutdown_notifier: started_client.task_manager,
packet_type,
})
}
}
@@ -243,7 +237,6 @@ pub struct DirectClient {
// we need to keep reference to this guy otherwise things will start dropping
shutdown_notifier: TaskManager,
packet_type: Option<PacketType>,
}
impl DirectClient {
@@ -264,7 +257,7 @@ impl DirectClient {
/// well enough in local tests)
pub async fn send_regular_message(&mut self, recipient: Recipient, message: Vec<u8>) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
let input_msg = InputMessage::new_regular(recipient, message, lane);
self.client_input
.input_sender
@@ -283,8 +276,7 @@ impl DirectClient {
reply_surbs: u32,
) {
let lane = TransmissionLane::General;
let input_msg =
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
self.client_input
.input_sender
@@ -298,7 +290,7 @@ impl DirectClient {
/// well enough in local tests)
pub async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: Vec<u8>) {
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
self.client_input
.input_sender
+3 -10
View File
@@ -14,7 +14,6 @@ use nym_client_core::client::{
use nym_client_websocket_requests::{requests::ClientRequest, responses::ServerResponse};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::connections::{
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
@@ -42,7 +41,6 @@ pub(crate) struct HandlerBuilder {
self_full_address: Recipient,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
}
impl HandlerBuilder {
@@ -53,7 +51,6 @@ impl HandlerBuilder {
self_full_address: &Recipient,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
) -> Self {
Self {
msg_input,
@@ -62,7 +59,6 @@ impl HandlerBuilder {
self_full_address: *self_full_address,
lane_queue_lengths,
reply_controller_sender,
packet_type,
}
}
@@ -77,7 +73,6 @@ impl HandlerBuilder {
received_response_type: Default::default(),
lane_queue_lengths: self.lane_queue_lengths.clone(),
reply_controller_sender: self.reply_controller_sender.clone(),
packet_type: self.packet_type,
}
}
}
@@ -91,7 +86,6 @@ pub(crate) struct Handler {
received_response_type: ReceivedResponseType,
lane_queue_lengths: LaneQueueLengths,
reply_controller_sender: ReplyControllerSender,
packet_type: Option<PacketType>,
}
impl Drop for Handler {
@@ -166,7 +160,7 @@ impl Handler {
});
// the ack control is now responsible for chunking, etc.
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
let input_msg = InputMessage::new_regular(recipient, message, lane);
self.msg_input
.send(input_msg)
.await
@@ -197,8 +191,7 @@ impl Handler {
TransmissionLane::ConnectionId(id)
});
let input_msg =
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
self.msg_input
.send(input_msg)
.await
@@ -225,7 +218,7 @@ impl Handler {
TransmissionLane::ConnectionId(id)
});
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
self.msg_input
.send(input_msg)
.await
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.15"
version = "1.1.18"
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"
+2
View File
@@ -0,0 +1,2 @@
[build]
target = "wasm32-unknown-unknown"
+7
View File
@@ -17,6 +17,7 @@ default = ["console_error_panic_hook"]
offline-test = []
[dependencies]
bs58 = "0.4.0"
futures = "0.3"
js-sys = "0.3"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
@@ -28,8 +29,12 @@ tokio = { version = "1.24.1", features = ["sync"] }
url = "2.2"
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4"
thiserror = "1.0.40"
wasm-timer = { git = "https://github.com/mmsinclair/wasm-timer", rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"}
# internal
nym-node-tester-utils = { path = "../../common/node-tester-utils" }
nym-client-core = { path = "../../common/client-core", default-features = false, features = ["wasm"] }
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
@@ -37,6 +42,8 @@ nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-crypto = { path = "../../common/crypto" }
nym-sphinx = { path = "../../common/nymsphinx" }
nym-topology = { path = "../../common/topology" }
nym-gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" }
nym-task = { path = "../../common/task" }
@@ -0,0 +1,2 @@
node_modules
dist
+5
View File
@@ -0,0 +1,5 @@
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
import('./index.js')
.catch(e => console.error('Error importing `index.js`:', e));
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym WebAssembly Demo</title>
</head>
<body>
<p>
<label>Sender: </label><input disabled="true" size="85" type="text" id="sender" value="">
</p>
<p>
<label>Recipient: </label><input size="85" type="text" id="recipient" value="">
</p>
<p>
<label>Message: </label><input type="text" id="message" value="Hello mixnet!">
</p>
<p>
<button id="send-button">Send</button>
</p>
<div>
<label>Mixnode Identity: </label>
<input type="text" size = "60" id="mixnode_identity" value="...">
<button id="magic-button">✨ Magic Test Button ✨</button>
</div>
<p>Send messages from your browser, through the mixnet, and to the recipient using the "send" button.</p>
<p><span style='color: blue;'>Sent</span> messages show in blue, <span style='color: green;'>received</span>
messages show in green.</p>
<hr>
<p>
<span id="output"></span>
</p>
<script src="./bootstrap.js"></script>
</body>
</html>
+170
View File
@@ -0,0 +1,170 @@
// Copyright 2020-2023 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
class WebWorkerClient {
worker = null;
constructor() {
this.worker = new Worker('./worker.js');
this.worker.onmessage = (ev) => {
if (ev.data && ev.data.kind) {
switch (ev.data.kind) {
case 'Ready':
const {selfAddress} = ev.data.args;
displaySenderAddress(selfAddress);
break;
case 'ReceiveMessage':
const {message, senderTag, isTestPacket } = ev.data.args;
displayReceived(message, senderTag, isTestPacket);
break;
case 'DisableMagicTestButton':
const magicButton = document.querySelector('#magic-button');
magicButton.setAttribute('disabled', "true")
break;
case 'DisplayTesterResults':
const {score, sentPackets, receivedPackets, receivedAcks, duplicatePackets, duplicateAcks} = ev.data.args;
const resultText = `Test score: ${score}. Sent ${sentPackets} packets. Received ${receivedPackets} packets and ${receivedAcks} acks back. We also got ${duplicatePackets} duplicate packets and ${duplicateAcks} duplicate acks.`
displayReceivedRawString(resultText)
break;
}
}
};
}
sendMessage = (message, recipient) => {
if (!this.worker) {
console.error('Could not send message because worker does not exist');
return;
}
this.worker.postMessage({
kind: 'SendMessage',
args: {
message, recipient,
},
});
};
sendTestPacket = (mixnodeIdentity) => {
if (!this.worker) {
console.error('Could not send message because worker does not exist');
return;
}
this.worker.postMessage({
kind: 'TestPacket',
args: {
mixnodeIdentity,
},
});
}
}
let client = null;
async function main() {
client = new WebWorkerClient();
const sendButton = document.querySelector('#send-button');
sendButton.onclick = function () {
sendMessageTo();
};
const magicButton = document.querySelector('#magic-button');
magicButton.onclick = function () {
sendTestPacket();
}
}
/**
* Create a Sphinx packet and send it to the mixnet through the gateway node.
*
* Message and recipient are taken from the values in the user interface.
*
*/
async function sendMessageTo() {
const message = document.getElementById('message').value;
const recipient = document.getElementById('recipient').value;
await client.sendMessage(message, recipient);
displaySend(message);
}
async function sendTestPacket() {
const mixnodeIdentity = document.getElementById('mixnode_identity').value;
await client.sendTestPacket(mixnodeIdentity)
displaySend(`sending test packets to: ${mixnodeIdentity}...`);
}
/**
* Display messages that have been sent up the websocket. Colours them blue.
*
* @param {string} message
*/
function displaySend(message) {
let timestamp = new Date().toISOString().substr(11, 12);
let sendDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: blue');
let paragraphContent = document.createTextNode(timestamp + ' sent >>> ' + message);
paragraph.appendChild(paragraphContent);
sendDiv.appendChild(paragraph);
document.getElementById('output').appendChild(sendDiv);
}
/**
* Display received text messages in the browser. Colour them green.
*
* @param {Uint8Array} raw
*/
function displayReceived(raw, sender_tag, isTestPacket) {
let content = new TextDecoder().decode(raw);
if (sender_tag !== undefined) {
console.log("this message also contained some surbs from", sender_tag)
}
if (isTestPacket) {
const decoded = JSON.parse(content)
content = `Received packet ${decoded.msg_id} / ${decoded.total_msgs} for node ${decoded.encoded_node_identity} (test: ${decoded.test_id})`
}
displayReceivedRawString(content)
}
function displayReceivedRawString(raw) {
let timestamp = new Date().toISOString().substr(11, 12);
let receivedDiv = document.createElement('div');
let paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: green');
let paragraphContent = document.createTextNode(timestamp + ' received >>> ' + raw);
paragraph.appendChild(paragraphContent);
receivedDiv.appendChild(paragraph);
document.getElementById('output').appendChild(receivedDiv);
}
/**
* Display the nymClient's sender address in the user interface
*
* @param {String} address
*/
function displaySenderAddress(address) {
document.getElementById('sender').value = address;
}
main();
@@ -0,0 +1,39 @@
{
"name": "create-wasm-app",
"version": "0.1.0",
"description": "create an app to consume rust-generated wasm packages",
"main": "index.js",
"bin": {
"create-wasm-app": ".bin/create-wasm-app.js"
},
"scripts": {
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server --port 8001"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
},
"keywords": [
"webassembly",
"wasm",
"rust",
"webpack"
],
"author": "Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/nymtech/nym/issues"
},
"homepage": "https://nymtech.net/docs",
"devDependencies": {
"copy-webpack-plugin": "^10.2.4",
"hello-wasm-pack": "^0.1.0",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"@nymproject/nym-client-wasm": "file:../pkg"
}
}
@@ -0,0 +1,33 @@
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
entry: {
bootstrap: './bootstrap.js',
worker: './worker.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
// mode: 'development',
mode: 'production',
plugins: [
new CopyWebpackPlugin({
patterns: [
'index.html',
{
from: 'node_modules/@nymproject/nym-client-wasm/*.(js|wasm)',
to: '[name][ext]',
},
],
}),
],
experiments: { syncWebAssembly: true },
};
+294
View File
@@ -0,0 +1,294 @@
// Copyright 2020-2023 Nym Technologies SA
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
importScripts('nym_client_wasm.js');
console.log('Initializing worker');
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
const {
NymNodeTester,
WasmGateway,
WasmMixNode,
WasmNymTopology,
default_debug,
NymClientBuilder,
NymClient,
set_panic_hook,
Config,
GatewayEndpointConfig,
current_network_topology,
} = wasm_bindgen;
let client = null;
let tester = null;
function dummyTopology() {
const l1Mixnode = new WasmMixNode(
1,
'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47',
'178.79.143.65',
1789,
'4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz',
'8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG',
1,
'1.10.0',
);
const l2Mixnode = new WasmMixNode(
2,
'n1z93z44vf8ssvdhujjvxcj4rd5e3lz0l60wdk70',
'109.74.197.180',
1789,
'7sVjiMrPYZrDWRujku9QLxgE8noT7NTgBAqizCsu7AoK',
'GepXwRnKZDd8x2nBWAajGGBVvF3mrpVMQBkgfrGuqRCN',
2,
'1.10.0',
);
const l3Mixnode = new WasmMixNode(
3,
'n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77',
'176.58.101.80',
1789,
'FoM5Mx9Pxk1g3zEqkS3APgtBeTtTo3M8k7Yu4bV6kK1R',
'DeYjrDC2AcQRVFshiKnbUo6bRvPyZ33QGYR2DLeFJ9qD',
3,
'1.10.0',
);
const gateway = new WasmGateway(
'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts',
'85.159.212.96',
1789,
9000,
'336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9',
'BtYjoWihiuFihGKQypmpSspbhmWDPxzqeTVSd8ciCpWL',
'1.10.1',
);
const mixnodes = new Map();
mixnodes.set(1, [l1Mixnode]);
mixnodes.set(2, [l2Mixnode]);
mixnodes.set(3, [l3Mixnode]);
const gateways = [gateway];
return new WasmNymTopology(mixnodes, gateways)
}
function printAndDisplayTestResult(result) {
result.log_details();
self.postMessage({
kind: 'DisplayTesterResults',
args: {
score: result.score(),
sentPackets: result.sent_packets,
receivedPackets: result.received_packets,
receivedAcks: result.received_acks,
duplicatePackets: result.duplicate_packets,
duplicateAcks: result.duplicate_acks,
},
});
}
function dummyGatewayConfig() {
return new GatewayEndpointConfig(
'336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9',
'n1rqqw8km7a0rvf8lr6k8dsdqvvkyn2mglj7xxfm',
'ws://85.159.212.96:9000',
)
}
async function testWithTester() {
const gatewayConfig = dummyGatewayConfig();
// A) construct with hardcoded topology
const topology = dummyTopology()
const nodeTester = await new NymNodeTester(gatewayConfig, topology);
// B) first get topology directly from nym-api
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const topology = await current_network_topology(validator)
// const nodeTester = await new NymNodeTester(gatewayConfig, topology);
//
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
// const nodeTester = await NymNodeTester.new_with_api(gatewayConfig, validator)
self.onmessage = async event => {
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'TestPacket': {
const {mixnodeIdentity} = event.data.args;
console.log("starting node test...");
let result = await nodeTester.test_node(mixnodeIdentity);
printAndDisplayTestResult(result)
}
}
}
};
}
async function testWithNymClient() {
const gatewayConfig = dummyGatewayConfig();
const topology = dummyTopology()
let received = 0
const onMessageHandler = (message) => {
received += 1;
self.postMessage({
kind: 'ReceiveMessage',
args: {
message,
senderTag: undefined,
isTestPacket: true,
},
});
// it's really up to the user to create proper callback here...
console.log(`received ${received} packets so far`)
};
console.log('Instantiating WASM client...');
let clientBuilder = NymClientBuilder.new_tester(gatewayConfig, topology, onMessageHandler)
console.log('Web worker creating WASM client...');
let local_client = await clientBuilder.start_client();
console.log('WASM client running!');
const selfAddress = local_client.self_address();
// set the global (I guess we don't have to anymore?)
client = local_client;
console.log(`Client address is ${selfAddress}`);
self.postMessage({
kind: 'Ready',
args: {
selfAddress,
},
});
// Set callback to handle messages passed to the worker.
self.onmessage = async event => {
console.log(event)
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'SendMessage': {
const {message, recipient} = event.data.args;
let uint8Array = new TextEncoder().encode(message);
await client.send_regular_message(uint8Array, recipient);
break;
}
case 'TestPacket': {
const {mixnodeIdentity} = event.data.args;
const req = await client.try_construct_test_packet_request(mixnodeIdentity);
await client.change_hardcoded_topology(req.injectable_topology());
await client.try_send_test_packets(req);
break;
}
}
}
};
}
async function normalNymClientUsage() {
self.postMessage({kind: 'DisableMagicTestButton'});
// only really useful if you want to adjust some settings like traffic rate
// (if not needed you can just pass a null)
const debug = default_debug();
debug.disable_main_poisson_packet_distribution = true;
debug.disable_loop_cover_traffic_stream = true;
debug.use_extended_packet_size = false;
// debug.average_packet_delay_ms = BigInt(10);
// debug.average_ack_delay_ms = BigInt(10);
// debug.ack_wait_addition_ms = BigInt(3000);
// debug.ack_wait_multiplier = 10;
debug.topology_refresh_rate_ms = BigInt(60000)
const gatewayConfig = dummyGatewayConfig();
const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
const config = new Config('my-awesome-wasm-client', validator, gatewayConfig, debug);
const onMessageHandler = (message) => {
console.log(message);
self.postMessage({
kind: 'ReceiveMessage',
args: {
message,
},
});
};
console.log('Instantiating WASM client...');
let localClient = await new NymClient(config, onMessageHandler)
console.log('WASM client running!');
const selfAddress = localClient.self_address();
// set the global (I guess we don't have to anymore?)
client = localClient;
console.log(`Client address is ${selfAddress}`);
self.postMessage({
kind: 'Ready',
args: {
selfAddress,
},
});
// Set callback to handle messages passed to the worker.
self.onmessage = async event => {
console.log(event)
if (event.data && event.data.kind) {
switch (event.data.kind) {
case 'SendMessage': {
const {message, recipient} = event.data.args;
let uint8Array = new TextEncoder().encode(message);
await client.send_regular_message(uint8Array, recipient);
break;
}
}
}
};
}
async function main() {
// load WASM package
await wasm_bindgen('nym_client_wasm_bg.wasm');
console.log('Loaded WASM');
// sets up better stack traces in case of in-rust panics
set_panic_hook();
// run test on simplified and dedicated tester:
await testWithTester()
// hook-up the whole client for testing
// await testWithNymClient()
// 'Normal' client setup (to send 'normal' messages)
// await normalNymClientUsage()
}
// Let's get started!
main();
File diff suppressed because it is too large Load Diff
+13 -4
View File
@@ -25,7 +25,7 @@ pub struct Config {
/// ID specifies the human readable ID of this particular client.
pub(crate) id: String,
pub(crate) nym_api_url: Url,
pub(crate) nym_api_url: Option<Url>,
pub(crate) disabled_credentials_mode: bool,
@@ -46,9 +46,11 @@ impl Config {
) -> Self {
Config {
id,
nym_api_url: validator_server
.parse()
.expect("provided url was malformed"),
nym_api_url: Some(
validator_server
.parse()
.expect("provided url was malformed"),
),
disabled_credentials_mode: true,
gateway_endpoint,
debug: debug.map(Into::into).unwrap_or_default(),
@@ -229,6 +231,11 @@ pub struct Topology {
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
pub topology_resolution_timeout_ms: u64,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
}
impl From<Topology> for ConfigTopology {
@@ -238,6 +245,7 @@ impl From<Topology> for ConfigTopology {
topology_resolution_timeout: Duration::from_millis(
topology.topology_resolution_timeout_ms,
),
disable_refreshing: topology.disable_refreshing,
}
}
}
@@ -247,6 +255,7 @@ impl From<ConfigTopology> for Topology {
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,
disable_refreshing: topology.disable_refreshing,
}
}
}
+135 -7
View File
@@ -1,16 +1,42 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::tester::helpers::WasmTestMessageExt;
use crate::tester::{NodeTestMessage, DEFAULT_TEST_PACKETS};
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::base_client::ClientInput;
use nym_client_core::client::base_client::{ClientInput, ClientState};
use nym_client_core::client::inbound_messages::InputMessage;
use nym_topology::{MixLayer, NymTopology};
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, simple_js_error};
#[wasm_bindgen]
pub struct NymClientTestRequest {
// serialized NodeTestMessage
pub(crate) test_msgs: Vec<Vec<u8>>,
// specially constructed network topology that only contains the target
// node on the tested layer
pub(crate) testable_topology: NymTopology,
}
#[wasm_bindgen]
impl NymClientTestRequest {
pub fn injectable_topology(&self) -> WasmNymTopology {
self.testable_topology.clone().into()
}
}
// defining helper trait as we could directly call the method on the wrapper
pub(crate) trait InputSender {
fn send_message(&self, message: InputMessage) -> Promise;
fn send_messages(&self, messages: Vec<InputMessage>) -> Promise;
}
impl InputSender for Arc<ClientInput> {
@@ -19,12 +45,114 @@ impl InputSender for Arc<ClientInput> {
future_to_promise(async move {
match this.input_sender.send(message).await {
Ok(_) => Ok(JsValue::null()),
Err(_) => {
let js_error =
js_sys::Error::new("InputMessageReceiver has stopped receiving!");
Err(JsValue::from(js_error))
}
Err(_) => Err(simple_js_error(
"InputMessageReceiver has stopped receiving!",
)),
}
})
}
fn send_messages(&self, messages: Vec<InputMessage>) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
for message in messages {
if this.input_sender.send(message).await.is_err() {
return Err(simple_js_error(
"InputMessageReceiver has stopped receiving!",
));
}
}
Ok(JsValue::null())
})
}
}
pub(crate) trait WasmTopologyExt {
/// Changes the current network topology to the provided value.
fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise;
/// Returns the current network topology.
fn current_topology(&self) -> Promise;
/// Checks whether the provided node exists in the known network topology and if so, returns its layer.
fn check_for_mixnode_existence(&self, mixnode_identity: String) -> Promise;
/// Creates a `NymClientTestRequest` with a variant of `this` topology where the target node is the only one on its layer.
fn mix_test_request(
&self,
test_id: u32,
mixnode_identity: String,
num_test_packets: Option<u32>,
) -> Promise;
}
impl WasmTopologyExt for Arc<ClientState> {
fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
let nym_topology: NymTopology = topology.into();
console_log!("changing topology to {nym_topology:?}");
this.topology_accessor
.manually_change_topology(nym_topology)
.await;
Ok(JsValue::null())
})
}
fn current_topology(&self) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
match this.topology_accessor.current_topology().await {
Some(topology) => Ok(JsValue::from(WasmNymTopology::from(topology))),
None => Err(WasmClientError::UnavailableNetworkTopology.into()),
}
})
}
/// Checks whether the target mixnode exists in the known network topology and returns its layer.
fn check_for_mixnode_existence(&self, mixnode_identity: String) -> Promise {
let this = Arc::clone(self);
future_to_promise(async move {
let Some(current_topology) = this.topology_accessor.current_topology().await else {
return Err(WasmClientError::UnavailableNetworkTopology.into())
};
match current_topology.find_mix_by_identity(&mixnode_identity) {
None => Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into()),
Some(node) => Ok(JsValue::from(MixLayer::from(node.layer))),
}
})
}
fn mix_test_request(
&self,
test_id: u32,
mixnode_identity: String,
num_test_packets: Option<u32>,
) -> Promise {
let num_test_packets = num_test_packets.unwrap_or(DEFAULT_TEST_PACKETS);
let this = Arc::clone(self);
future_to_promise(async move {
let Some(current_topology) = this.topology_accessor.current_topology().await else {
return Err(WasmClientError::UnavailableNetworkTopology.into())
};
let Some(mix) = current_topology.find_mix_by_identity(&mixnode_identity) else {
return Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into());
};
let ext = WasmTestMessageExt::new(test_id);
let test_msgs = NodeTestMessage::mix_plaintexts(mix, num_test_packets, ext)
.map_err(WasmClientError::from)?;
let mut updated = current_topology.clone();
updated.set_mixes_in_layer(mix.layer.into(), vec![mix.to_owned()]);
Ok(JsValue::from(NymClientTestRequest {
test_msgs,
testable_topology: updated,
}))
})
}
}
+181 -106
View File
@@ -1,28 +1,36 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use self::config::Config;
use crate::client::helpers::InputSender;
use crate::client::helpers::{InputSender, NymClientTestRequest, WasmTopologyExt};
use crate::client::response_pusher::ResponsePusher;
use crate::error::WasmClientError;
use crate::helpers::{
parse_recipient, parse_sender_tag, setup_new_key_manager, setup_reply_surb_storage_backend,
};
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::base_client::{
BaseClientBuilder, ClientInput, ClientOutput, CredentialsToggle,
BaseClientBuilder, ClientInput, ClientOutput, ClientState, CredentialsToggle,
};
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::client::{inbound_messages::InputMessage, key_manager::KeyManager};
use nym_client_core::config::{
CoverTraffic, DebugConfig, GatewayEndpointConfig, Topology, Traffic,
};
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_task::connections::TransmissionLane;
use nym_task::TaskManager;
use nym_topology::provider_trait::{HardcodedTopologyProvider, TopologyProvider};
use nym_topology::NymTopology;
use rand::rngs::OsRng;
use rand::RngCore;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_error, console_log};
use wasm_utils::{check_promise_result, console_log, PromisableResult};
pub mod config;
mod helpers;
@@ -32,16 +40,21 @@ mod response_pusher;
pub struct NymClient {
self_address: String,
client_input: Arc<ClientInput>,
client_state: Arc<ClientState>,
// keep track of the "old" topology for the purposes of node tester
// so that it could be restored after the check is done
_full_topology: Option<NymTopology>,
// even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline
_task_manager: TaskManager,
packet_type: Option<PacketType>,
}
#[wasm_bindgen]
pub struct NymClientBuilder {
config: Config,
custom_topology: Option<NymTopology>,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
@@ -54,7 +67,6 @@ pub struct NymClientBuilder {
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
disabled_credentials: bool,
packet_type: Option<PacketType>,
}
#[wasm_bindgen]
@@ -63,120 +75,190 @@ impl NymClientBuilder {
pub fn new(config: Config, on_message: js_sys::Function) -> Self {
//, key_manager: Option<KeyManager>) {
NymClientBuilder {
reply_surb_storage_backend: Self::setup_reply_surb_storage_backend(&config),
reply_surb_storage_backend: setup_reply_surb_storage_backend(config.debug.reply_surbs),
config,
key_manager: Self::setup_key_manager(),
custom_topology: None,
key_manager: setup_new_key_manager(),
on_message,
bandwidth_controller: None,
disabled_credentials: true,
packet_type: None,
}
}
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate
// a prior shared keypair between the client and the gateway
// no cover traffic
// no poisson delay
// hardcoded topology
// NOTE: you most likely want to use `[NymNodeTester]` instead.
pub fn new_tester(
gateway_config: GatewayEndpointConfig,
topology: WasmNymTopology,
on_message: js_sys::Function,
) -> Self {
if !topology.ensure_contains(&gateway_config) {
panic!("the specified topology does not contain the gateway used by the client")
}
// perhaps this should be public?
fn setup_key_manager() -> KeyManager {
let mut rng = OsRng;
// for time being generate new keys each time...
console_log!("generated new set of keys");
KeyManager::new(&mut rng)
}
let full_config = Config {
id: "ephemeral-id".to_string(),
nym_api_url: None,
disabled_credentials_mode: true,
gateway_endpoint: gateway_config,
debug: DebugConfig {
traffic: Traffic {
disable_main_poisson_packet_distribution: true,
..Default::default()
},
cover_traffic: CoverTraffic {
disable_loop_cover_traffic_stream: true,
..Default::default()
},
topology: Topology {
disable_refreshing: true,
..Default::default()
},
..Default::default()
},
};
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no persistence
fn setup_reply_surb_storage_backend(config: &Config) -> browser_backend::Backend {
browser_backend::Backend::new(
config
.debug
.reply_surbs
.minimum_reply_surb_storage_threshold,
config
.debug
.reply_surbs
.maximum_reply_surb_storage_threshold,
)
NymClientBuilder {
reply_surb_storage_backend: setup_reply_surb_storage_backend(
full_config.debug.reply_surbs,
),
config: full_config,
custom_topology: Some(topology.into()),
// TODO: once we make keys persistent, we'll require some kind of `init` method to generate
// a prior shared keypair between the client and the gateway
key_manager: setup_new_key_manager(),
on_message,
bandwidth_controller: None,
disabled_credentials: true,
}
}
fn start_reconstructed_pusher(client_output: ClientOutput, on_message: js_sys::Function) {
ResponsePusher::new(client_output, on_message).start()
}
pub async fn start_client(self) -> Promise {
future_to_promise(async move {
console_log!("Starting the wasm client");
fn topology_provider(&mut self) -> Option<Box<dyn TopologyProvider>> {
if let Some(hardcoded_topology) = self.custom_topology.take() {
Some(Box::new(HardcodedTopologyProvider::new(hardcoded_topology)))
} else {
None
}
}
let disabled_credentials = if self.disabled_credentials {
CredentialsToggle::Disabled
} else {
CredentialsToggle::Enabled
};
async fn start_client_async(mut self) -> Result<NymClient, WasmClientError> {
console_log!("Starting the wasm client");
let base_builder = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&self.config.debug,
self.key_manager,
self.bandwidth_controller,
self.reply_surb_storage_backend,
disabled_credentials,
vec![self.config.nym_api_url.clone()],
);
let maybe_topology_provider = self.topology_provider();
let self_address = base_builder.as_mix_recipient().to_string();
let mut started_client = match base_builder.start_base().await {
Ok(base_client) => base_client,
Err(err) => {
let error_msg = format!("failed to start the base client components - {err}");
console_error!("{}", error_msg);
let js_error = js_sys::Error::new(&error_msg);
return Err(JsValue::from(js_error));
}
};
let disabled_credentials = if self.disabled_credentials {
CredentialsToggle::Disabled
} else {
CredentialsToggle::Enabled
};
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
let nym_api_endpoints = match self.config.nym_api_url {
Some(endpoint) => vec![endpoint],
None => Vec::new(),
};
let mut base_builder = BaseClientBuilder::new(
&self.config.gateway_endpoint,
&self.config.debug,
self.key_manager,
self.bandwidth_controller,
self.reply_surb_storage_backend,
disabled_credentials,
nym_api_endpoints,
);
if let Some(topology_provider) = maybe_topology_provider {
base_builder = base_builder.with_topology_provider(topology_provider);
}
Self::start_reconstructed_pusher(client_output, self.on_message);
let self_address = base_builder.as_mix_recipient().to_string();
let mut started_client = base_builder.start_base().await?;
Ok(JsValue::from(NymClient {
self_address,
client_input: Arc::new(client_input),
_task_manager: started_client.task_manager,
packet_type: self.packet_type,
}))
let client_input = started_client.client_input.register_producer();
let client_output = started_client.client_output.register_consumer();
Self::start_reconstructed_pusher(client_output, self.on_message);
Ok(NymClient {
self_address,
client_input: Arc::new(client_input),
client_state: Arc::new(started_client.client_state),
_full_topology: None,
_task_manager: started_client.task_manager,
})
}
pub fn start_client(self) -> Promise {
future_to_promise(async move { self.start_client_async().await.into_promise_result() })
}
}
#[wasm_bindgen]
impl NymClient {
async fn _new(
config: Config,
on_message: js_sys::Function,
) -> Result<NymClient, WasmClientError> {
NymClientBuilder::new(config, on_message)
.start_client_async()
.await
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(config: Config, on_message: js_sys::Function) -> Promise {
future_to_promise(async move { Self::_new(config, on_message).await.into_promise_result() })
}
pub fn self_address(&self) -> String {
self.self_address.clone()
}
fn parse_recipient(recipient: &str) -> Result<Recipient, JsValue> {
match Recipient::try_from_base58_string(recipient) {
Ok(recipient) => Ok(recipient),
Err(err) => {
let error_msg = format!("{recipient} is not a valid Nym network recipient - {err}");
console_error!("{}", error_msg);
let js_error = js_sys::Error::new(&error_msg);
Err(JsValue::from(js_error))
}
}
pub fn try_construct_test_packet_request(
&self,
mixnode_identity: String,
num_test_packets: Option<u32>,
) -> Promise {
// TODO: improve the source of rng (i.e. don't make it ephemeral...)
let mut ephemeral_rng = OsRng;
let test_id = ephemeral_rng.next_u32();
self.client_state
.mix_test_request(test_id, mixnode_identity, num_test_packets)
}
fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, JsValue> {
match AnonymousSenderTag::try_from_base58_string(tag) {
Ok(tag) => Ok(tag),
Err(err) => {
let error_msg = format!("{tag} is not a valid Nym AnonymousSenderTag - {err}");
console_error!("{}", error_msg);
let js_error = js_sys::Error::new(&error_msg);
Err(JsValue::from(js_error))
}
}
pub fn change_hardcoded_topology(&self, topology: WasmNymTopology) -> Promise {
self.client_state.change_hardcoded_topology(topology)
}
pub fn current_network_topology(&self) -> Promise {
self.client_state.current_topology()
}
/// Sends a test packet through the current network topology.
/// It's the responsibility of the caller to ensure the correct topology has been injected and
/// correct onmessage handlers have been setup.
pub fn try_send_test_packets(&mut self, request: NymClientTestRequest) -> Promise {
// TOOD: use the premade packets instead
console_log!(
"Attempting to send {} test packets",
request.test_msgs.len()
);
// our address MUST BE valid
let recipient = parse_recipient(&self.self_address()).unwrap();
let lane = TransmissionLane::General;
let input_msgs = request
.test_msgs
.into_iter()
.map(|p| InputMessage::new_regular(recipient, p, lane))
.collect();
self.client_input.send_messages(input_msgs)
}
/// The simplest message variant where no additional information is attached.
@@ -189,13 +271,11 @@ impl NymClient {
message.len() as f64 / 1024.0
);
let recipient = match Self::parse_recipient(&recipient) {
Ok(recipient) => recipient,
Err(err) => return Promise::reject(&err),
};
let recipient = check_promise_result!(parse_recipient(&recipient));
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
let input_msg = InputMessage::new_regular(recipient, message, lane);
self.client_input.send_message(input_msg)
}
@@ -218,14 +298,11 @@ impl NymClient {
message.len() as f64 / 1024.0
);
let recipient = match Self::parse_recipient(&recipient) {
Ok(recipient) => recipient,
Err(err) => return Promise::reject(&err),
};
let recipient = check_promise_result!(parse_recipient(&recipient));
let lane = TransmissionLane::General;
let input_msg =
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
self.client_input.send_message(input_msg)
}
@@ -239,13 +316,11 @@ impl NymClient {
message.len() as f64 / 1024.0
);
let sender_tag = match Self::parse_sender_tag(&recipient_tag) {
Ok(recipient) => recipient,
Err(err) => return Promise::reject(&err),
};
let sender_tag = check_promise_result!(parse_sender_tag(&recipient_tag));
let lane = TransmissionLane::General;
let input_msg = InputMessage::new_reply(sender_tag, message, lane, self.packet_type);
let input_msg = InputMessage::new_reply(sender_tag, message, lane);
self.client_input.send_message(input_msg)
}
}
+99
View File
@@ -0,0 +1,99 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::topology::WasmTopologyError;
use js_sys::Promise;
use nym_client_core::error::ClientCoreError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_gateway_client::error::GatewayClientError;
use nym_node_tester_utils::error::NetworkTestingError;
use nym_sphinx::addressing::clients::RecipientFormattingError;
use nym_sphinx::anonymous_replies::requests::InvalidAnonymousSenderTagRepresentation;
use nym_validator_client::ValidatorClientError;
use thiserror::Error;
use wasm_bindgen::JsValue;
use wasm_utils::simple_js_error;
// might as well start using well-defined error enum...
#[derive(Debug, Error)]
pub enum WasmClientError {
#[error(
"A node test is already in progress. Wait for it to finish before starting another one."
)]
TestInProgress,
#[error("experienced an issue with internal client components: {source}")]
BaseClientError {
#[from]
source: ClientCoreError,
},
#[error("The provided gateway identity is invalid: {source}")]
InvalidGatewayIdentity { source: Ed25519RecoveryError },
#[error("Gateway communication failure: {source}")]
GatewayClientError {
#[from]
source: GatewayClientError,
},
#[error("failed to query nym api: {source}")]
NymApiError {
#[from]
source: ValidatorClientError,
},
#[error("The provided topology was invalid: {source}")]
WasmTopologyError {
#[from]
source: WasmTopologyError,
},
#[error("failed to test the node: {source}")]
NodeTestingFailure {
#[from]
source: NetworkTestingError,
},
#[error("{raw} is not a valid url: {source}")]
MalformedUrl {
raw: String,
source: url::ParseError,
},
#[error("Network topology is currently unavailable")]
UnavailableNetworkTopology,
#[error("Mixnode {mixnode_identity} is not present in the current network topology")]
NonExistentMixnode { mixnode_identity: String },
#[error("{raw} is not a valid Nym network recipient: {source}")]
MalformedRecipient {
raw: String,
source: RecipientFormattingError,
},
#[error("{raw} is not a valid Nym AnonymousSenderTag: {source}")]
MalformedSenderTag {
raw: String,
source: InvalidAnonymousSenderTagRepresentation,
},
}
impl WasmClientError {
pub fn into_rejected_promise(self) -> Promise {
self.into()
}
}
impl From<WasmClientError> for JsValue {
fn from(value: WasmClientError) -> Self {
simple_js_error(value.to_string())
}
}
impl From<WasmClientError> for Promise {
fn from(value: WasmClientError) -> Self {
Promise::reject(&value.into())
}
}
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::topology::WasmNymTopology;
use js_sys::Promise;
use nym_client_core::client::key_manager::KeyManager;
use nym_client_core::client::replies::reply_storage::browser_backend;
use nym_client_core::config;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::NymTopology;
use nym_validator_client::NymApiClient;
use rand::rngs::OsRng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{console_log, PromisableResult};
pub(crate) fn setup_new_key_manager() -> KeyManager {
let mut rng = OsRng;
console_log!("generated new set of keys");
KeyManager::new(&mut rng)
}
// don't get too excited about the name, under the hood it's just a big fat placeholder
// with no persistence
pub(crate) fn setup_reply_surb_storage_backend(
config: config::ReplySurbs,
) -> browser_backend::Backend {
browser_backend::Backend::new(
config.minimum_reply_surb_storage_threshold,
config.maximum_reply_surb_storage_threshold,
)
}
pub(crate) fn parse_recipient(recipient: &str) -> Result<Recipient, WasmClientError> {
Recipient::try_from_base58_string(recipient).map_err(|source| {
WasmClientError::MalformedRecipient {
raw: recipient.to_string(),
source,
}
})
}
pub(crate) fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmClientError> {
AnonymousSenderTag::try_from_base58_string(tag).map_err(|source| {
WasmClientError::MalformedSenderTag {
raw: tag.to_string(),
source,
}
})
}
pub(crate) async fn current_network_topology_async(
nym_api_url: String,
) -> Result<WasmNymTopology, WasmClientError> {
let url: Url = match nym_api_url.parse() {
Ok(url) => url,
Err(source) => {
return Err(WasmClientError::MalformedUrl {
raw: nym_api_url,
source,
})
}
};
let api_client = NymApiClient::new(url);
let mixnodes = api_client.get_cached_active_mixnodes().await?;
let gateways = api_client.get_cached_gateways().await?;
Ok(NymTopology::from_detailed(mixnodes, gateways).into())
}
#[wasm_bindgen]
pub fn current_network_topology(nym_api_url: String) -> Promise {
future_to_promise(async move {
current_network_topology_async(nym_api_url)
.await
.into_promise_result()
})
}
+8
View File
@@ -7,11 +7,19 @@ use wasm_bindgen::prelude::*;
mod client;
#[cfg(target_arch = "wasm32")]
pub mod encoded_payload_helper;
pub mod error;
#[cfg(target_arch = "wasm32")]
pub mod gateway_selector;
#[cfg(target_arch = "wasm32")]
pub mod tester;
#[cfg(target_arch = "wasm32")]
pub mod topology;
#[cfg(target_arch = "wasm32")]
pub mod validation;
#[cfg(target_arch = "wasm32")]
mod helpers;
#[wasm_bindgen]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
@@ -0,0 +1,112 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::tester::helpers::{NodeTestResult, WasmTestMessageExt};
use futures::StreamExt;
use nym_node_tester_utils::processor::Received;
use nym_node_tester_utils::receiver::ReceivedReceiver;
use nym_sphinx::chunking::fragment::FragmentIdentifier;
use std::collections::HashSet;
use std::time::Duration;
use tokio::sync::MutexGuard as AsyncMutexGuard;
use wasm_utils::{console_error, console_log, console_warn};
pub(crate) struct EphemeralTestReceiver<'a> {
sent_packets: u32,
expected_acks: HashSet<FragmentIdentifier>,
received_valid_messages: HashSet<u32>,
received_valid_acks: HashSet<FragmentIdentifier>,
duplicate_packets: u32,
duplicate_acks: u32,
timeout_duration: Duration,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
}
impl<'a> EphemeralTestReceiver<'a> {
pub(crate) fn finish(self) -> NodeTestResult {
NodeTestResult {
sent_packets: self.sent_packets,
received_packets: self.received_valid_messages.len() as u32,
received_acks: self.received_valid_acks.len() as u32,
duplicate_packets: self.duplicate_packets,
duplicate_acks: self.duplicate_acks,
}
}
pub(crate) fn new(
sent_packets: u32,
expected_acks: HashSet<FragmentIdentifier>,
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
timeout: Duration,
) -> Self {
EphemeralTestReceiver {
sent_packets,
expected_acks,
received_valid_messages: Default::default(),
received_valid_acks: Default::default(),
duplicate_packets: 0,
duplicate_acks: 0,
timeout_duration: timeout,
receiver_permit,
}
}
fn on_next_received_packet(&mut self, packet: Option<Received<WasmTestMessageExt>>) -> bool {
let Some(received_packet) = packet else {
// can't do anything more...
console_error!("packet receiver has stopped processing results!");
return true
};
match received_packet {
Received::Message(msg) => {
if !self.received_valid_messages.insert(msg.msg_id) {
self.duplicate_packets += 1;
}
}
Received::Ack(frag_id) => {
if self.expected_acks.contains(&frag_id) {
if !self.received_valid_acks.insert(frag_id) {
self.duplicate_acks += 1
}
} else {
console_warn!("received an ack that was not part of the test! (id: {frag_id})")
}
}
}
if self.received_all() {
console_log!("already received all the packets! finishing the test...");
true
} else {
false
}
}
fn received_all(&self) -> bool {
self.received_valid_acks.len() == self.received_valid_messages.len()
&& self.received_valid_acks.len() == self.sent_packets as usize
}
pub(crate) async fn perform_test(mut self) -> NodeTestResult {
let mut timeout_fut = wasm_timer::Delay::new(self.timeout_duration);
loop {
tokio::select! {
_ = &mut timeout_fut => {
console_warn!("reached test timeout before receiving all packets.");
break
}
received_packet = self.receiver_permit.next() => {
let is_done = self.on_next_received_packet(received_packet);
if is_done {
break
}
}
}
}
self.finish()
}
}
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to expansion of #[wasm_bindgen] macro on NodeTestResult
#![allow(clippy::drop_non_drop)]
use nym_node_tester_utils::processor::Received;
use nym_node_tester_utils::receiver::ReceivedReceiver;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard};
use wasm_bindgen::prelude::*;
use wasm_utils::{console_log, console_warn};
#[derive(Clone)]
pub(super) struct ReceivedReceiverWrapper(Arc<AsyncMutex<ReceivedReceiver<WasmTestMessageExt>>>);
impl ReceivedReceiverWrapper {
pub(super) fn new(inner: ReceivedReceiver<WasmTestMessageExt>) -> Self {
ReceivedReceiverWrapper(Arc::new(AsyncMutex::new(inner)))
}
pub(super) async fn clear_received_channel(&self) {
let mut lost_msgs = 0;
let mut lost_acks = 0;
let mut permit = self.0.lock().await;
while let Ok(Some(received)) = permit.try_next() {
match received {
Received::Message(_) => lost_msgs += 1,
Received::Ack(_) => lost_acks += 1,
}
}
if lost_msgs > 0 || lost_acks > 0 {
console_warn!("while preparing for the test run, we cleared {lost_msgs} messages and {lost_acks} acks that were received in the meantime.")
}
}
pub(super) async fn lock(&self) -> AsyncMutexGuard<'_, ReceivedReceiver<WasmTestMessageExt>> {
self.0.lock().await
}
}
#[derive(Serialize, Deserialize, Copy, Clone)]
pub struct WasmTestMessageExt {
pub test_id: u32,
}
impl WasmTestMessageExt {
pub fn new(test_id: u32) -> Self {
WasmTestMessageExt { test_id }
}
}
// TODO: maybe put it in the tester utils
#[wasm_bindgen]
pub struct NodeTestResult {
pub sent_packets: u32,
pub received_packets: u32,
pub received_acks: u32,
pub duplicate_packets: u32,
pub duplicate_acks: u32,
}
impl Display for NodeTestResult {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Test results: ")?;
writeln!(f, "Total score: {:.2}%", self.score())?;
writeln!(f, "Sent packets: {}", self.sent_packets)?;
writeln!(f, "Received (valid) packets: {}", self.received_packets)?;
writeln!(f, "Received (valid) acks: {}", self.received_acks)?;
writeln!(f, "Received duplicate packets: {}", self.duplicate_packets)?;
write!(f, "Received duplicate acks: {}", self.duplicate_acks)
}
}
#[wasm_bindgen]
impl NodeTestResult {
pub fn log_details(&self) {
console_log!("{}", self)
}
pub fn score(&self) -> f32 {
let expected = self.sent_packets * 2;
let actual = (self.received_packets + self.received_acks)
.saturating_sub(self.duplicate_packets + self.duplicate_acks);
actual as f32 / expected as f32 * 100.
}
}
pub(crate) struct TestMarker {
value: Arc<AtomicBool>,
}
impl TestMarker {
pub fn new(value: Arc<AtomicBool>) -> Self {
Self { value }
}
}
impl Drop for TestMarker {
// make sure to clear the test flag when the marker is dropped
fn drop(&mut self) {
self.value.store(false, Ordering::SeqCst)
}
}
+321
View File
@@ -0,0 +1,321 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::WasmClientError;
use crate::helpers::{current_network_topology_async, setup_new_key_manager};
use crate::tester::ephemeral_receiver::EphemeralTestReceiver;
use crate::tester::helpers::{
NodeTestResult, ReceivedReceiverWrapper, TestMarker, WasmTestMessageExt,
};
use crate::topology::WasmNymTopology;
use futures::channel::mpsc;
use js_sys::Promise;
use nym_bandwidth_controller::wasm_mockups::{Client as FakeClient, DirectSigningNyxdClient};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core::client::key_manager::KeyManager;
use nym_client_core::config::GatewayEndpointConfig;
use nym_credential_storage::ephemeral_storage::EphemeralStorage;
use nym_crypto::asymmetric::identity;
use nym_gateway_client::GatewayClient;
use nym_node_tester_utils::receiver::SimpleMessageReceiver;
use nym_node_tester_utils::{NodeTester, TestMessage};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_sphinx::params::PacketSize;
use nym_sphinx::preparer::PreparedFragment;
use nym_task::TaskManager;
use nym_topology::NymTopology;
use rand::rngs::OsRng;
use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex as SyncMutex};
use std::time::Duration;
use tokio::sync::Mutex as AsyncMutex;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_utils::{check_promise_result, console_log, console_warn, PromisableResult};
mod ephemeral_receiver;
pub(crate) mod helpers;
pub type NodeTestMessage = TestMessage<WasmTestMessageExt>;
type LockedGatewayClient =
Arc<AsyncMutex<GatewayClient<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>>;
pub(crate) const DEFAULT_TEST_TIMEOUT: Duration = Duration::from_secs(10);
pub(crate) const DEFAULT_TEST_PACKETS: u32 = 20;
#[wasm_bindgen]
pub struct NymNodeTester {
test_in_progress: Arc<AtomicBool>,
// we need to increment the nonce between tests to distinguish the packets
// but we can't make the tester mutable because of wasm...
// so we're using the atomics
current_test_nonce: AtomicU32,
// blame all those mutexes on being unable to have an async method with internal mutability...
tester: Arc<SyncMutex<NodeTester<OsRng>>>,
gateway_client: LockedGatewayClient,
// we have to put it behind the lock due to wasm limitations and borrowing...
// the mutex acquisition should be instant as there aren't going to be any threads attempting
// to get simultaneous access
processed_receiver: ReceivedReceiverWrapper,
// even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline
_task_manager: TaskManager,
}
#[wasm_bindgen]
pub struct NymNodeTesterBuilder {
gateway_config: GatewayEndpointConfig,
base_topology: NymTopology,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
// unimplemented
bandwidth_controller:
Option<BandwidthController<FakeClient<DirectSigningNyxdClient>, EphemeralStorage>>,
}
fn address(keys: &KeyManager, gateway_identity: NodeIdentity) -> Recipient {
Recipient::new(
*keys.identity_keypair().public_key(),
*keys.encryption_keypair().public_key(),
gateway_identity,
)
}
#[wasm_bindgen]
impl NymNodeTesterBuilder {
#[wasm_bindgen(constructor)]
pub fn new(
gateway_config: GatewayEndpointConfig,
base_topology: WasmNymTopology,
) -> NymNodeTesterBuilder {
NymNodeTesterBuilder {
gateway_config,
base_topology: base_topology.into(),
key_manager: setup_new_key_manager(),
bandwidth_controller: None,
}
}
async fn _new_with_api(
gateway_config: GatewayEndpointConfig,
api_url: String,
) -> Result<Self, WasmClientError> {
let topology = current_network_topology_async(api_url).await?;
Ok(NymNodeTesterBuilder::new(gateway_config, topology))
}
pub fn new_with_api(gateway_config: GatewayEndpointConfig, api_url: String) -> Promise {
future_to_promise(async move {
Self::_new_with_api(gateway_config, api_url)
.await
.into_promise_result()
})
}
async fn _setup_client(mut self) -> Result<NymNodeTester, WasmClientError> {
let rng = OsRng;
let task_manager = TaskManager::default();
let gateway_identity =
identity::PublicKey::from_base58_string(self.gateway_config.gateway_id)
.map_err(|source| WasmClientError::InvalidGatewayIdentity { source })?;
// we **REALLY** need persistence...
let shared_key = if self.key_manager.is_gateway_key_set() {
Some(self.key_manager.gateway_shared_key())
} else {
console_warn!("Gateway key not set - will derive a fresh one.");
None
};
let (mixnet_message_sender, mixnet_message_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded();
let mut gateway_client = GatewayClient::new(
self.gateway_config.gateway_listener,
self.key_manager.identity_keypair(),
gateway_identity,
shared_key,
mixnet_message_sender,
ack_sender,
Duration::from_secs(10),
self.bandwidth_controller.take(),
task_manager.subscribe(),
);
gateway_client.set_disabled_credentials_mode(true);
let shared_keys = gateway_client.authenticate_and_start().await?;
// currently pointless but might as well do it for the future ¯\_(ツ)_/¯
self.key_manager.insert_gateway_shared_key(shared_keys);
// TODO: make those values configurable later
let tester = NodeTester::new(
rng,
self.base_topology,
Some(address(&self.key_manager, gateway_identity)),
PacketSize::default(),
Duration::from_millis(5),
Duration::from_millis(5),
self.key_manager.ack_key(),
);
let (processed_sender, processed_receiver) = mpsc::unbounded();
let mut receiver = SimpleMessageReceiver::new_sphinx_receiver(
self.key_manager.encryption_keypair(),
self.key_manager.ack_key(),
mixnet_message_receiver,
ack_receiver,
processed_sender,
task_manager.subscribe(),
);
nym_task::spawn(async move { receiver.run().await });
Ok(NymNodeTester {
test_in_progress: Arc::new(AtomicBool::new(false)),
current_test_nonce: Default::default(),
tester: Arc::new(SyncMutex::new(tester)),
gateway_client: Arc::new(AsyncMutex::new(gateway_client)),
processed_receiver: ReceivedReceiverWrapper::new(processed_receiver),
_task_manager: task_manager,
})
}
pub fn setup_client(self) -> Promise {
future_to_promise(async move { self._setup_client().await.into_promise_result() })
}
}
async fn test_mixnode(
test_packets: Vec<PreparedFragment>,
gateway_client: LockedGatewayClient,
processed_receiver: ReceivedReceiverWrapper,
_test_marker: TestMarker,
timeout: Duration,
) -> Result<NodeTestResult, WasmClientError> {
let num_test_packets = test_packets.len() as u32;
let expected_ack_ids = test_packets
.iter()
.map(|p| p.fragment_identifier)
.collect::<HashSet<_>>();
let mix_packets = test_packets.into_iter().map(|p| p.mix_packet).collect();
// start by clearing any messages that might have been received between tests
processed_receiver.clear_received_channel().await;
// locking the gateway client so that we could get mutable access to data without having to declare
// self mutable
let mut gateway_permit = gateway_client.lock().await;
gateway_permit.batch_send_mix_packets(mix_packets).await?;
let receiver_permit = processed_receiver.lock().await;
let result =
EphemeralTestReceiver::new(num_test_packets, expected_ack_ids, receiver_permit, timeout)
.perform_test()
.await;
Ok(result)
}
#[wasm_bindgen]
impl NymNodeTester {
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(gateway_config: GatewayEndpointConfig, topology: WasmNymTopology) -> Promise {
console_log!("constructing node tester!");
NymNodeTesterBuilder::new(gateway_config, topology).setup_client()
}
async fn _new_with_api(
gateway_config: GatewayEndpointConfig,
api_url: String,
) -> Result<Self, WasmClientError> {
NymNodeTesterBuilder::_new_with_api(gateway_config, api_url)
.await?
._setup_client()
.await
}
pub fn new_with_api(gateway_config: GatewayEndpointConfig, api_url: String) -> Promise {
future_to_promise(async move {
Self::_new_with_api(gateway_config, api_url)
.await
.into_promise_result()
})
}
fn prepare_test_packets(
&self,
mixnode_identity: String,
test_nonce: u32,
num_test_packets: u32,
) -> Result<Vec<PreparedFragment>, WasmClientError> {
let test_ext = WasmTestMessageExt::new(test_nonce);
let mut tester_permit = self.tester.lock().expect("mutex got poisoned");
tester_permit
.existing_identity_mixnode_test_packets(
mixnode_identity,
test_ext,
num_test_packets,
None,
)
.map_err(Into::into)
}
pub fn test_node(
&self,
mixnode_identity: String,
timeout_millis: Option<u64>,
num_test_packets: Option<u32>,
) -> Promise {
// establish test parameters
let timeout = timeout_millis
.map(Duration::from_millis)
.unwrap_or(DEFAULT_TEST_TIMEOUT);
let num_test_packets = num_test_packets.unwrap_or(DEFAULT_TEST_PACKETS);
// mark start of the test
if self.test_in_progress.swap(true, Ordering::SeqCst) {
return WasmClientError::TestInProgress.into_rejected_promise();
}
// prepare test packets
// (I simultaneously feel both disgusted and amazed by this workaround)
let test_nonce = self.current_test_nonce.fetch_add(1, Ordering::Relaxed);
let test_packets = check_promise_result!(self.prepare_test_packets(
mixnode_identity,
test_nonce,
num_test_packets
));
let processed_receiver_clone = self.processed_receiver.clone();
let gateway_client_clone = Arc::clone(&self.gateway_client);
let tester_marker = TestMarker::new(Arc::clone(&self.test_in_progress));
// start doing async things (send packets and watch for anything coming back)
future_to_promise(async move {
test_mixnode(
test_packets,
gateway_client_clone,
processed_receiver_clone,
tester_marker,
timeout,
)
.await
.into_promise_result()
})
}
}
+262
View File
@@ -0,0 +1,262 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_client_core::config::GatewayEndpointConfig;
use nym_crypto::asymmetric::{encryption, identity};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::{Layer, MixnodeConversionError};
use nym_topology::{gateway, mix, MixLayer, NymTopology};
use nym_validator_client::client::MixId;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use thiserror::Error;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use wasm_utils::{console_log, simple_js_error};
#[derive(Debug, Error)]
pub enum WasmTopologyError {
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
InvalidMixLayer { value: u8 },
#[error(transparent)]
GatewayConversion(#[from] GatewayConversionError),
#[error(transparent)]
MixnodeConversion(#[from] MixnodeConversionError),
#[error("The provided mixnode map was malformed: {source}")]
MalformedMixnodeMap { source: serde_wasm_bindgen::Error },
#[error("The provided gateway list was malformed: {source}")]
MalformedGatewayList { source: serde_wasm_bindgen::Error },
}
impl From<WasmTopologyError> for JsValue {
fn from(value: WasmTopologyError) -> Self {
simple_js_error(value.to_string())
}
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct WasmNymTopology {
inner: NymTopology,
}
#[wasm_bindgen]
impl WasmNymTopology {
#[wasm_bindgen(constructor)]
pub fn new(
// expected: BTreeMap<MixLayer, Vec<WasmMixNode>>,
// HashMap<MixLayer, Vec<WasmMixNode>> will also work because it has the same json representation
mixnodes: JsValue,
// expected: Vec<WasmGateway>
gateways: JsValue,
) -> Result<WasmNymTopology, WasmTopologyError> {
let mixnodes: BTreeMap<MixLayer, Vec<WasmMixNode>> =
serde_wasm_bindgen::from_value(mixnodes)
.map_err(|source| WasmTopologyError::MalformedMixnodeMap { source })?;
let gateways: Vec<WasmGateway> = serde_wasm_bindgen::from_value(gateways)
.map_err(|source| WasmTopologyError::MalformedGatewayList { source })?;
let mut converted_mixes = BTreeMap::new();
for (layer, nodes) in mixnodes {
let layer_nodes = nodes
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
converted_mixes.insert(layer, layer_nodes);
}
let gateways = gateways
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
Ok(WasmNymTopology {
inner: NymTopology::new(converted_mixes, gateways),
})
}
pub(crate) fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
self.inner
.gateways()
.iter()
.any(|g| g.identity_key.to_base58_string() == gateway_config.gateway_id)
}
pub fn print(&self) {
if !self.inner.mixes().is_empty() {
console_log!("mixnodes:");
for (layer, nodes) in self.inner.mixes() {
console_log!("\tlayer {layer}:");
for node in nodes {
console_log!("\t\t{} - {}", node.mix_id, node.identity_key)
}
}
} else {
console_log!("NO MIXNODES")
}
if !self.inner.gateways().is_empty() {
console_log!("gateways:");
for gateway in self.inner.gateways() {
console_log!("\t{}", gateway.identity_key)
}
} else {
console_log!("NO GATEWAYS")
}
}
}
impl From<WasmNymTopology> for NymTopology {
fn from(value: WasmNymTopology) -> Self {
value.inner
}
}
impl From<NymTopology> for WasmNymTopology {
fn from(value: NymTopology) -> Self {
WasmNymTopology { inner: value }
}
}
#[wasm_bindgen]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WasmMixNode {
pub mix_id: MixId,
#[wasm_bindgen(getter_with_clone)]
pub owner: String,
#[wasm_bindgen(getter_with_clone)]
pub host: String,
pub mix_port: u16,
#[wasm_bindgen(getter_with_clone)]
pub identity_key: String,
#[wasm_bindgen(getter_with_clone)]
pub sphinx_key: String,
pub layer: MixLayer,
#[wasm_bindgen(getter_with_clone)]
pub version: String,
}
#[wasm_bindgen]
impl WasmMixNode {
#[wasm_bindgen(constructor)]
#[allow(clippy::too_many_arguments)]
pub fn new(
mix_id: MixId,
owner: String,
host: String,
mix_port: u16,
identity_key: String,
sphinx_key: String,
layer: MixLayer,
version: String,
) -> Self {
Self {
mix_id,
owner,
host,
mix_port,
identity_key,
sphinx_key,
layer,
version,
}
}
}
impl TryFrom<WasmMixNode> for mix::Node {
type Error = WasmTopologyError;
fn try_from(value: WasmMixNode) -> Result<Self, Self::Error> {
let host = mix::Node::parse_host(&value.host)?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = mix::Node::extract_mix_host(&host, value.mix_port)?;
Ok(mix::Node {
mix_id: value.mix_id,
owner: value.owner,
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(MixnodeConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(MixnodeConversionError::from)?,
layer: Layer::try_from(value.layer)
.map_err(|_| WasmTopologyError::InvalidMixLayer { value: value.layer })?,
version: value.version,
})
}
}
#[wasm_bindgen]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct WasmGateway {
#[wasm_bindgen(getter_with_clone)]
pub owner: String,
#[wasm_bindgen(getter_with_clone)]
pub host: String,
pub mix_port: u16,
pub clients_port: u16,
#[wasm_bindgen(getter_with_clone)]
pub identity_key: String,
#[wasm_bindgen(getter_with_clone)]
pub sphinx_key: String,
#[wasm_bindgen(getter_with_clone)]
pub version: String,
}
#[wasm_bindgen]
impl WasmGateway {
#[wasm_bindgen(constructor)]
pub fn new(
owner: String,
host: String,
mix_port: u16,
clients_port: u16,
identity_key: String,
sphinx_key: String,
version: String,
) -> Self {
Self {
owner,
host,
mix_port,
clients_port,
identity_key,
sphinx_key,
version,
}
}
}
impl TryFrom<WasmGateway> for gateway::Node {
type Error = WasmTopologyError;
fn try_from(value: WasmGateway) -> Result<Self, Self::Error> {
let host = gateway::Node::parse_host(&value.host)?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = gateway::Node::extract_mix_host(&host, value.mix_port)?;
Ok(gateway::Node {
owner: value.owner,
host,
mix_host,
clients_port: value.clients_port,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(GatewayConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(GatewayConversionError::from)?,
version: value.version,
})
}
}
@@ -22,7 +22,7 @@ use crate::client::topology_control::{
};
use crate::config::{Config, DebugConfig, GatewayEndpointConfig};
use crate::error::ClientCoreError;
use crate::spawn_future;
use crate::{config, spawn_future};
use futures::channel::mpsc;
use log::{debug, info};
use nym_bandwidth_controller::BandwidthController;
@@ -39,7 +39,6 @@ use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender,
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;
@@ -371,11 +370,12 @@ where
// the current global view of topology
async fn start_topology_refresher(
topology_provider: Box<dyn TopologyProvider>,
refresh_rate: Duration,
topology_config: config::Topology,
topology_accessor: TopologyAccessor,
shutdown: TaskClient,
mut shutdown: TaskClient,
) -> Result<(), ClientCoreError> {
let topology_refresher_config = TopologyRefresherConfig::new(refresh_rate);
let topology_refresher_config =
TopologyRefresherConfig::new(topology_config.topology_refresh_rate);
let mut topology_refresher = TopologyRefresher::new(
topology_refresher_config,
@@ -395,12 +395,21 @@ where
return Err(ClientCoreError::InsufficientNetworkTopology(err));
}
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
if topology_config.disable_refreshing {
// if we're not spawning the refresher, don't cause shutdown immediately
info!("The topology refesher is not going to be started");
shutdown.mark_as_success();
} else {
// don't spawn the refresher if we don't want to be refreshing the topology.
// only use the initial values obtained
info!("Starting topology refresher...");
topology_refresher.start_with_shutdown(shutdown);
}
Ok(())
}
// controller for sending packets to mixnet (either real traffic or cover traffic)
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
// TODO: if we want to send control messages to gateway_client, this CAN'T take the ownership
// over it. Perhaps GatewayClient needs to be thread-shareable or have some channel for
// requests?
@@ -500,7 +509,7 @@ where
);
Self::start_topology_refresher(
topology_provider,
self.debug_config.topology.topology_refresh_rate,
self.debug_config.topology,
shared_topology_accessor.clone(),
task_manager.subscribe(),
)
@@ -515,11 +524,11 @@ where
task_manager.subscribe(),
);
// The message_sender is the transmitter for any component generating sphinx packets
// The sphinx_message_sender is the transmitter for any component generating sphinx packets
// that are to be sent to the mixnet. They are used by cover traffic stream and real
// traffic stream.
// The MixTrafficController then sends the actual traffic
let message_sender =
let sphinx_message_sender =
Self::start_mix_traffic_controller(gateway_client, task_manager.subscribe());
// Channels that the websocket listener can use to signal downstream to the real traffic
@@ -541,7 +550,7 @@ where
shared_topology_accessor.clone(),
ack_receiver,
input_receiver,
message_sender.clone(),
sphinx_message_sender.clone(),
reply_storage,
reply_controller_sender.clone(),
reply_controller_receiver,
@@ -560,7 +569,7 @@ where
self.key_manager.ack_key(),
self_address,
shared_topology_accessor.clone(),
message_sender,
sphinx_message_sender,
task_manager.subscribe(),
);
}
@@ -45,7 +45,7 @@ where
#[cfg(target_arch = "wasm32")]
next_delay: Pin<Box<wasm_timer::Delay>>,
/// Channel used for sending prepared nym packets to `MixTrafficController` that sends them
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: BatchMixMessageSender,
@@ -1,6 +1,9 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_task::connections::TransmissionLane;
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
@@ -8,6 +11,14 @@ pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
#[derive(Debug)]
pub enum InputMessage {
/// Fire an already prepared mix packets into the network.
/// No guarantees are made about it. For example no retransmssion
/// will be attempted if it gets dropped.
Premade {
msgs: Vec<MixPacket>,
lane: TransmissionLane,
},
/// The simplest message variant where no additional information is attached.
/// You're simply sending your `data` to specified `recipient` without any tagging.
///
@@ -42,36 +53,18 @@ pub enum InputMessage {
data: Vec<u8>,
lane: TransmissionLane,
},
MessageWrapper {
message: Box<InputMessage>,
packet_type: PacketType,
},
}
impl InputMessage {
pub fn new_wrapper(message: InputMessage, packet_type: PacketType) -> Self {
InputMessage::MessageWrapper {
message: Box::new(message),
packet_type,
}
pub fn new_premade(msgs: Vec<MixPacket>, lane: TransmissionLane) -> Self {
InputMessage::Premade { msgs, lane }
}
pub fn new_regular(
recipient: Recipient,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
let message = InputMessage::Regular {
pub fn new_regular(recipient: Recipient, data: Vec<u8>, lane: TransmissionLane) -> Self {
InputMessage::Regular {
recipient,
data,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -80,18 +73,12 @@ impl InputMessage {
data: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
let message = InputMessage::Anonymous {
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -99,17 +86,11 @@ impl InputMessage {
recipient_tag: AnonymousSenderTag,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
) -> Self {
let message = InputMessage::Reply {
InputMessage::Reply {
recipient_tag,
data,
lane,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
@@ -117,8 +98,8 @@ impl InputMessage {
match self {
InputMessage::Regular { lane, .. }
| InputMessage::Anonymous { lane, .. }
| InputMessage::Reply { lane, .. } => lane,
InputMessage::MessageWrapper { message, .. } => message.lane(),
| InputMessage::Reply { lane, .. }
| InputMessage::Premade { lane, .. } => lane,
}
}
}
+3 -3
View File
@@ -39,15 +39,15 @@ where
pub fn new(
gateway_client: GatewayClient<C, St>,
) -> (MixTrafficController<C, St>, BatchMixMessageSender) {
let (message_sender, message_receiver) =
let (sphinx_message_sender, sphinx_message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
(
MixTrafficController {
gateway_client,
mix_rx: message_receiver,
mix_rx: sphinx_message_receiver,
consecutive_gateway_failure_count: 0,
},
message_sender,
sphinx_message_sender,
)
}
@@ -3,11 +3,12 @@
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver};
use crate::client::real_messages_control::message_handler::MessageHandler;
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use crate::client::replies::reply_controller::ReplyControllerSender;
use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_sphinx::params::PacketType;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng};
@@ -42,6 +43,18 @@ where
}
}
async fn handle_premade_packets(&mut self, packets: Vec<MixPacket>, lane: TransmissionLane) {
self.message_handler
.send_premade_mix_packets(
packets
.into_iter()
.map(|p| RealMessage::new(p, None))
.collect(),
lane,
)
.await
}
async fn handle_reply(
&mut self,
recipient_tag: AnonymousSenderTag,
@@ -58,11 +71,10 @@ where
recipient: Recipient,
content: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
) {
if let Err(err) = self
.message_handler
.try_send_plain_message(recipient, content, lane, packet_type)
.try_send_plain_message(recipient, content, lane)
.await
{
warn!("failed to send a plain message - {err}")
@@ -75,11 +87,10 @@ where
content: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
) {
if let Err(err) = self
.message_handler
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type)
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane)
.await
{
warn!("failed to send a repliable message - {err}")
@@ -92,17 +103,14 @@ where
recipient,
data,
lane,
} => {
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
.await
}
} => self.handle_plain_message(recipient, data, lane).await,
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
self.handle_repliable_message(recipient, data, reply_surbs, lane)
.await
}
InputMessage::Reply {
@@ -112,37 +120,7 @@ where
} => {
self.handle_reply(recipient_tag, data, lane).await;
}
InputMessage::MessageWrapper {
message,
packet_type,
} => match *message {
InputMessage::Regular {
recipient,
data,
lane,
} => {
self.handle_plain_message(recipient, data, lane, packet_type)
.await
}
InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
.await
}
InputMessage::Reply {
recipient_tag,
data,
lane,
} => {
self.handle_reply(recipient_tag, data, lane).await;
}
// MessageWrappers can't be nested
InputMessage::MessageWrapper { .. } => unimplemented!(),
},
InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
};
}
@@ -11,9 +11,9 @@ use crate::client::real_messages_control::real_traffic_stream::RealMessage;
use crate::client::replies::reply_controller::ReplyControllerSender;
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::chunking::fragment::Fragment;
use nym_sphinx::preparer::PreparedFragment;
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
use nym_task::connections::TransmissionLane;
use rand::{CryptoRng, Rng};
use std::sync::{Arc, Weak};
@@ -51,10 +51,8 @@ where
) -> Result<PreparedFragment, PreparationError> {
debug!("retransmitting normal packet...");
// TODO: Figure out retransmission packet type signaling
self.message_handler
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, PacketType::Mix)
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data)
.await
}
@@ -133,7 +131,10 @@ where
// send to `OutQueueControl` to eventually send to the mix network
self.message_handler
.forward_messages(
vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
vec![RealMessage::new(
prepared_fragment.mix_packet,
Some(frag_id),
)],
TransmissionLane::Retransmission,
)
.await
@@ -15,7 +15,7 @@ use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessa
use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey};
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx::message::NymMessage;
use nym_sphinx::params::{PacketSize, PacketType, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
use nym_sphinx::Delay;
use nym_task::connections::TransmissionLane;
@@ -291,8 +291,10 @@ where
.try_prepare_single_reply_chunk_for_sending(reply_surb, chunk_clone)
.await?;
let real_messages =
RealMessage::new(prepared_fragment.mix_packet, chunk.fragment_identifier());
let real_messages = RealMessage::new(
prepared_fragment.mix_packet,
Some(chunk.fragment_identifier()),
);
let delay = prepared_fragment.total_delay;
let pending_ack =
PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request);
@@ -384,7 +386,8 @@ where
let lane = raw.0;
let fragment = raw.1;
let real_message = RealMessage::new(prepared.mix_packet, prepared.fragment_identifier);
let real_message =
RealMessage::new(prepared.mix_packet, Some(prepared.fragment_identifier));
let delay = prepared.total_delay;
let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false);
@@ -401,15 +404,22 @@ where
Ok(())
}
pub(crate) async fn send_premade_mix_packets(
&mut self,
msgs: Vec<RealMessage>,
lane: TransmissionLane,
) {
self.forward_messages(msgs, lane).await;
}
pub(crate) async fn try_send_plain_message(
&mut self,
recipient: Recipient,
message: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), PreparationError> {
let message = NymMessage::new_plain(message);
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
self.try_split_and_send_non_reply_message(message, recipient, lane)
.await
}
@@ -418,7 +428,6 @@ where
message: NymMessage,
recipient: Recipient,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), PreparationError> {
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
debug_assert!(!matches!(message, NymMessage::Reply(_)));
@@ -444,11 +453,12 @@ where
topology,
&self.config.ack_key,
&recipient,
packet_type,
)?;
let real_message =
RealMessage::new(prepared_fragment.mix_packet, fragment.fragment_identifier());
let real_message = RealMessage::new(
prepared_fragment.mix_packet,
Some(fragment.fragment_identifier()),
);
let delay = prepared_fragment.total_delay;
let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient);
@@ -466,7 +476,6 @@ where
&mut self,
recipient: Recipient,
amount: u32,
packet_type: PacketType,
) -> Result<(), PreparationError> {
let sender_tag = self.get_or_create_sender_tag(&recipient);
let (reply_surbs, reply_keys) =
@@ -481,7 +490,6 @@ where
message,
recipient,
TransmissionLane::AdditionalReplySurbs,
packet_type,
)
.await?;
@@ -497,7 +505,6 @@ where
message: Vec<u8>,
num_reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
) -> Result<(), SurbWrappedPreparationError> {
let sender_tag = self.get_or_create_sender_tag(&recipient);
let (reply_surbs, reply_keys) = self
@@ -507,7 +514,7 @@ where
let message =
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
self.try_split_and_send_non_reply_message(message, recipient, lane)
.await?;
log::trace!("storing {} reply keys", reply_keys.len());
@@ -520,20 +527,13 @@ where
&mut self,
recipient: Recipient,
chunk: Fragment,
packet_type: PacketType,
) -> Result<PreparedFragment, PreparationError> {
let topology_permit = self.topology_access.get_read_permit().await;
let topology = self.get_topology(&topology_permit)?;
let prepared_fragment = self
.message_preparer
.prepare_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
&recipient,
packet_type,
)
.prepare_chunk_for_sending(chunk, topology, &self.config.ack_key, &recipient)
.unwrap();
Ok(prepared_fragment)
@@ -92,7 +92,7 @@ where
// messages.
sending_delay_controller: SendingDelayController,
/// Channel used for sending prepared packets to `MixTrafficController` that sends them
/// Channel used for sending prepared sphinx packets to `MixTrafficController` that sends them
/// out to the network without any further delays.
mix_tx: BatchMixMessageSender,
@@ -121,7 +121,7 @@ where
#[derive(Debug)]
pub(crate) struct RealMessage {
mix_packet: MixPacket,
fragment_id: FragmentIdentifier,
fragment_id: Option<FragmentIdentifier>,
// TODO: add info about it being constructed with reply-surb
}
@@ -129,17 +129,17 @@ impl From<PreparedFragment> for RealMessage {
fn from(fragment: PreparedFragment) -> Self {
RealMessage {
mix_packet: fragment.mix_packet,
fragment_id: fragment.fragment_identifier,
fragment_id: Some(fragment.fragment_identifier),
}
}
}
impl RealMessage {
pub(crate) fn packet_size(&self) -> usize {
self.mix_packet.packet().len()
self.mix_packet.sphinx_packet().len()
}
pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self {
pub(crate) fn new(mix_packet: MixPacket, fragment_id: Option<FragmentIdentifier>) -> Self {
RealMessage {
mix_packet,
fragment_id,
@@ -255,7 +255,7 @@ where
)
}
StreamMessage::Real(real_message) => {
(real_message.mix_packet, Some(real_message.fragment_id))
(real_message.mix_packet, real_message.fragment_id)
}
};
@@ -386,7 +386,7 @@ where
// On every iteration we get new messages from upstream. Given that these come bunched
// in `Vec`, this ensures that on average we will fetch messages faster than we can
// send, which is a condition for being able to multiplex packets from multiple
// send, which is a condition for being able to multiplex sphinx packets from multiple
// data streams.
match Pin::new(&mut self.real_receiver).poll_recv(cx) {
// in the case our real message channel stream was closed, we should also indicate we are closed
@@ -512,11 +512,7 @@ where
let to_send = min(remaining, 100);
if let Err(err) = self
.message_handler
.try_send_additional_reply_surbs(
recipient,
to_send,
nym_sphinx::params::PacketType::Mix,
)
.try_send_additional_reply_surbs(recipient, to_send)
.await
{
warn!("failed to send additional surbs to {recipient} - {err}");
@@ -28,7 +28,7 @@ impl SizedData for RealMessage {
impl SizedData for Fragment {
fn data_size(&self) -> usize {
// note that raw `Fragment` is smaller than packet payload
// note that raw `Fragment` is smaller than sphinx packet payload
// as it doesn't include surb-ack or the [shared] key materials
self.payload_size()
}
+6
View File
@@ -743,6 +743,11 @@ pub struct Topology {
/// did not reach its destination.
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
}
impl Default for Topology {
@@ -750,6 +755,7 @@ impl Default for Topology {
Topology {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
}
}
}
@@ -142,6 +142,7 @@ impl From<OldDebugConfigV1_1_13> for DebugConfig {
topology: Topology {
topology_refresh_rate: value.topology_refresh_rate,
topology_resolution_timeout: value.topology_resolution_timeout,
disable_refreshing: false,
},
reply_surbs: ReplySurbs {
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
@@ -605,7 +605,7 @@ where
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
packets
.iter()
.map(|packet| packet.packet().len())
.map(|packet| packet.sphinx_packet().len())
.sum::<usize>() as i64
}
@@ -686,9 +686,9 @@ where
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if (mix_packet.packet().len() as i64) > self.bandwidth_remaining {
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth(
mix_packet.packet().len() as i64,
mix_packet.sphinx_packet().len() as i64,
self.bandwidth_remaining,
));
}
+18 -15
View File
@@ -4,11 +4,10 @@
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx::framing::codec::NymCodec;
use nym_sphinx::framing::packet::FramedNymPacket;
use nym_sphinx::params::PacketType;
use nym_sphinx::NymPacket;
use nym_sphinx::framing::codec::SphinxCodec;
use nym_sphinx::framing::packet::FramedSphinxPacket;
use nym_sphinx::params::PacketMode;
use nym_sphinx::{addressing::nodes::NymNodeRoutingAddress, SphinxPacket};
use std::collections::HashMap;
use std::io;
use std::net::SocketAddr;
@@ -51,8 +50,8 @@ pub trait SendWithoutResponse {
fn send_without_response(
&mut self,
address: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
packet: SphinxPacket,
packet_mode: PacketMode,
) -> io::Result<()>;
}
@@ -62,12 +61,12 @@ pub struct Client {
}
struct ConnectionSender {
channel: mpsc::Sender<FramedNymPacket>,
channel: mpsc::Sender<FramedSphinxPacket>,
current_reconnection_attempt: Arc<AtomicU32>,
}
impl ConnectionSender {
fn new(channel: mpsc::Sender<FramedNymPacket>) -> Self {
fn new(channel: mpsc::Sender<FramedSphinxPacket>) -> Self {
ConnectionSender {
channel,
current_reconnection_attempt: Arc::new(AtomicU32::new(0)),
@@ -85,7 +84,7 @@ impl Client {
async fn manage_connection(
address: SocketAddr,
receiver: mpsc::Receiver<FramedNymPacket>,
receiver: mpsc::Receiver<FramedSphinxPacket>,
connection_timeout: Duration,
current_reconnection: &AtomicU32,
) {
@@ -97,7 +96,7 @@ impl Client {
debug!("Managed to establish connection to {}", address);
// if we managed to connect, reset the reconnection count (whatever it might have been)
current_reconnection.store(0, Ordering::Release);
Framed::new(stream, NymCodec)
Framed::new(stream, SphinxCodec)
}
Err(err) => {
debug!(
@@ -149,7 +148,11 @@ impl Client {
}
}
fn make_connection(&mut self, address: NymNodeRoutingAddress, pending_packet: FramedNymPacket) {
fn make_connection(
&mut self,
address: NymNodeRoutingAddress,
pending_packet: FramedSphinxPacket,
) {
let (mut sender, receiver) = mpsc::channel(self.config.maximum_connection_buffer_size);
// this CAN'T fail because we just created the channel which has a non-zero capacity
@@ -197,12 +200,12 @@ impl SendWithoutResponse for Client {
fn send_without_response(
&mut self,
address: NymNodeRoutingAddress,
packet: NymPacket,
packet_type: PacketType,
packet: SphinxPacket,
packet_mode: PacketMode,
) -> io::Result<()> {
trace!("Sending packet to {:?}", address);
let framed_packet =
FramedNymPacket::new(packet, packet_type, self.config.use_legacy_version);
FramedSphinxPacket::new(packet, packet_mode, self.config.use_legacy_version);
if let Some(sender) = self.conn_new.get_mut(&address) {
if let Err(err) = sender.channel.try_send(framed_packet) {
@@ -59,14 +59,14 @@ impl PacketForwarder {
trace!("Going to forward packet to {:?}", mix_packet.next_hop());
let next_hop = mix_packet.next_hop();
let packet_type = mix_packet.packet_type();
let packet = mix_packet.into_packet();
let packet_mode = mix_packet.packet_mode();
let sphinx_packet = mix_packet.into_sphinx_packet();
// we don't care about responses, we just want to fire packets
// as quickly as possible
if let Err(err) =
self.mixnet_client
.send_without_response(next_hop, packet, packet_type)
.send_without_response(next_hop, sphinx_packet, packet_mode)
{
debug!("failed to forward the packet - {err}")
}
@@ -18,6 +18,7 @@ nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-c
nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts/service-provider-directory" }
nym-vesting-contract = { path = "../../../contracts/vesting" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
@@ -63,6 +64,10 @@ name = "offline_signing"
# (traits would need to be moved around and refactored themselves)
required-features = ["nyxd-client"]
[[example]]
name = "query_service_provider_directory"
required-features = ["nyxd-client"]
[features]
nyxd-client = [
"async-trait",
@@ -0,0 +1,43 @@
use std::str::FromStr;
use cosmrs::AccountId;
use nym_network_defaults::{setup_env, NymNetworkDetails};
use nym_service_provider_directory_common::NymAddress;
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
#[tokio::main]
async fn main() {
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
let network_details = NymNetworkDetails::new_from_env();
let config =
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
let client = nym_validator_client::Client::new_query(config).unwrap();
let config = client.nyxd.get_service_config().await.unwrap();
println!("config: {config:?}");
let services_paged = client.nyxd.get_services_paged(None, None).await.unwrap();
println!("services (paged): {services_paged:#?}");
let services = client.nyxd.get_all_services().await.unwrap();
println!("services: {services:#?}");
let announcer = AccountId::from_str("n1hmf957kc7arcd39rl7xq8l0a4zyg7kxnv7su87").unwrap();
let services_by_announcer = client
.nyxd
.get_services_by_announcer(announcer)
.await
.unwrap();
println!("services (by announcer): {services_by_announcer:#?}");
let nym_address = NymAddress::new("foo.bar@gateway");
let services_by_nym_address = client
.nyxd
.get_services_by_nym_address(nym_address)
.await
.unwrap();
assert_eq!(services_by_announcer, services_by_nym_address);
let service_info = client.nyxd.get_service_info(1).await;
println!("service info: {service_info:#?}");
}
@@ -117,7 +117,7 @@ async fn test_nyxd_connection(
);
code == 18
}
Ok(Err(error @ NyxdError::NoContractAddressAvailable)) => {
Ok(Err(error @ NyxdError::NoContractAddressAvailable(_))) => {
log::debug!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
false
}
@@ -13,7 +13,8 @@ pub mod nyxd;
pub mod signing;
pub use crate::error::ValidatorClientError;
pub use client::NymApiClient;
pub use nym_api_requests::*;
#[cfg(feature = "nyxd-client")]
pub use client::{Client, CoconutApiClient, Config, NymApiClient};
pub use client::{Client, CoconutApiClient, Config};
@@ -9,6 +9,9 @@ pub enum NymAPIError {
source: reqwest::Error,
},
#[error("Not found")]
NotFound,
#[error("Request failed with error message - {0}")]
GenericRequestFailure(String),
@@ -15,7 +15,8 @@ use nym_api_requests::models::{
};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use reqwest::Response;
use nym_service_provider_directory_common::ServiceInfo;
use reqwest::{Response, StatusCode};
use serde::{Deserialize, Serialize};
use url::Url;
@@ -76,6 +77,8 @@ impl Client {
let res = self.send_get_request(path, params).await?;
if res.status().is_success() {
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(NymAPIError::NotFound)
} else {
Err(NymAPIError::GenericRequestFailure(res.text().await?))
}
@@ -480,6 +483,11 @@ impl Client {
)
.await
}
pub async fn get_service_providers(&self) -> Result<Vec<ServiceInfo>, NymAPIError> {
self.query_nym_api(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
@@ -32,3 +32,5 @@ pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation";
pub const AVG_UPTIME: &str = "avg_uptime";
pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
pub const SERVICE_PROVIDERS: &str = "service-providers";
@@ -21,8 +21,8 @@ use std::{io, time::Duration};
#[derive(Debug, Error)]
pub enum NyxdError {
#[error("No contract address is available to perform the call")]
NoContractAddressAvailable,
#[error("No contract address is available to perform the call: {0}")]
NoContractAddressAvailable(String),
#[error(transparent)]
WalletError(#[from] DirectSecp256k1HdWalletError),
@@ -67,6 +67,7 @@ pub struct Config {
pub(crate) group_contract_address: Option<AccountId>,
pub(crate) multisig_contract_address: Option<AccountId>,
pub(crate) coconut_dkg_contract_address: Option<AccountId>,
pub(crate) service_provider_contract_address: Option<AccountId>,
// TODO: add this in later commits
// pub(crate) gas_price: GasPrice,
}
@@ -131,6 +132,13 @@ impl Config {
details.contracts.coconut_dkg_contract_address.as_ref(),
prefix,
)?,
service_provider_contract_address: Self::parse_optional_account(
details
.contracts
.service_provider_directory_contract_address
.as_ref(),
prefix,
)?,
})
}
}
@@ -246,6 +254,10 @@ impl<C> NyxdClient<C> {
self.config.multisig_contract_address = Some(address);
}
pub fn set_service_provider_contract_address(&mut self, address: AccountId) {
self.config.service_provider_contract_address = Some(address);
}
// TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits
// note: what unwrap is doing here is just moving a failure that would have normally
// occurred in `connect` when attempting to parse an empty address,
@@ -304,6 +316,11 @@ impl<C> NyxdClient<C> {
self.config.coconut_dkg_contract_address.as_ref().unwrap()
}
// The service provider directory contract is optional, so we return an Option not a Result
pub fn service_provider_contract_address(&self) -> Option<&AccountId> {
self.config.service_provider_contract_address.as_ref()
}
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.simulated_gas_multiplier = multiplier;
}
@@ -24,8 +24,9 @@ use nym_mixnet_contract_common::{
MixOwnershipResponse, MixnodeDetailsResponse, NumberOfPendingEventsResponse,
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedFamiliesResponse,
PagedGatewayResponse, PagedMembersResponse, PagedMixNodeDelegationsResponse,
PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventsResponse,
PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg,
PagedMixnodeBondsResponse, PagedRewardedSetResponse, PendingEpochEventResponse,
PendingEpochEventsResponse, PendingIntervalEventResponse, PendingIntervalEventsResponse,
QueryMsg as MixnetQueryMsg,
};
use serde::Deserialize;
@@ -174,6 +175,16 @@ pub trait MixnetQueryClient {
.await
}
async fn get_mixnode_details_by_identity(
&self,
mix_identity: IdentityKey,
) -> Result<Option<MixNodeDetails>, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
mix_identity,
})
.await
}
async fn get_mixnode_rewarding_details(
&self,
mix_id: MixId,
@@ -374,14 +385,20 @@ pub trait MixnetQueryClient {
.await
}
async fn get_mixnode_details_by_identity(
async fn get_pending_epoch_event(
&self,
mix_identity: IdentityKey,
) -> Result<Option<MixNodeDetails>, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
mix_identity,
})
.await
event_id: EpochEventId,
) -> Result<PendingEpochEventResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingEpochEvent { event_id })
.await
}
async fn get_pending_interval_event(
&self,
event_id: IntervalEventId,
) -> Result<PendingIntervalEventResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingIntervalEvent { event_id })
.await
}
async fn get_number_of_pending_events(
@@ -331,6 +331,38 @@ pub trait MixnetSigningClient {
.await
}
async fn decrease_pledge(
&self,
decrease_by: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DecreasePledge {
decrease_by: decrease_by.into(),
},
vec![],
)
.await
}
async fn decrease_pledge_on_behalf(
&self,
owner: AccountId,
decrease_by: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DecreasePledgeOnBehalf {
owner: owner.to_string(),
decrease_by: decrease_by.into(),
},
vec![],
)
.await
}
async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
.await
@@ -16,15 +16,20 @@ mod mixnet_signing_client;
mod multisig_signing_client;
mod vesting_signing_client;
mod sp_directory_query_client;
mod sp_directory_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use dkg_query_client::DkgQueryClient;
pub use group_query_client::GroupQueryClient;
pub use mixnet_query_client::MixnetQueryClient;
pub use multisig_query_client::MultisigQueryClient;
pub use sp_directory_query_client::SpDirectoryQueryClient;
pub use vesting_query_client::VestingQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use dkg_signing_client::DkgSigningClient;
pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use sp_directory_signing_client::SpDirectorySigningClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -0,0 +1,120 @@
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::ContractBuildInformation;
use nym_service_provider_directory_common::{
msg::QueryMsg as SpQueryMsg,
response::{
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
},
NymAddress, ServiceId, ServiceInfo,
};
use serde::Deserialize;
use crate::nyxd::{error::NyxdError, CosmWasmClient, NyxdClient};
#[async_trait]
pub trait SpDirectoryQueryClient {
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>;
async fn get_service_config(&self) -> Result<ConfigResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::Config {})
.await
}
async fn get_service_info(
&self,
service_id: ServiceId,
) -> Result<ServiceInfoResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ServiceId { service_id })
.await
}
async fn get_services_paged(
&self,
start_after: Option<ServiceId>,
limit: Option<u32>,
) -> Result<PagedServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::All { limit, start_after })
.await
}
async fn get_services_by_announcer(
&self,
announcer: AccountId,
) -> Result<ServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ByAnnouncer {
announcer: announcer.to_string(),
})
.await
}
async fn get_services_by_nym_address(
&self,
nym_address: NymAddress,
) -> Result<ServicesListResponse, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::ByNymAddress { nym_address })
.await
}
async fn get_sp_contract_version(&self) -> Result<ContractBuildInformation, NyxdError> {
self.query_service_provider_contract(SpQueryMsg::GetContractVersion {})
.await
}
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, NyxdError> {
let mut services = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self.get_services_paged(start_after.take(), None).await?;
let last_id = paged_response.services.last().map(|serv| serv.service_id);
services.append(&mut paged_response.services);
if let Some(start_after_res) = last_id {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(services)
}
}
#[async_trait]
impl<C> SpDirectoryQueryClient for NyxdClient<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(
self.service_provider_contract_address().ok_or(
NyxdError::NoContractAddressAvailable(
"service provider directory contract".to_string(),
),
)?,
&query,
)
.await
}
}
#[async_trait]
impl<C> SpDirectoryQueryClient for crate::Client<C>
where
C: CosmWasmClient + Send + Sync,
{
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
where
for<'a> T: Deserialize<'a>,
{
self.nyxd.query_service_provider_contract(query).await
}
}
@@ -0,0 +1,111 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_service_provider_directory_common::{
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
};
use crate::nyxd::{
coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient,
SigningCosmWasmClient,
};
#[async_trait]
pub trait SpDirectorySigningClient {
async fn exectute_service_provider_directory_contract(
&self,
fee: Option<Fee>,
msg: SpExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError>;
async fn announce_service_provider(
&self,
nym_address: NymAddress,
service_type: ServiceType,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::Announce {
nym_address,
service_type,
},
vec![deposit],
)
.await
}
async fn delete_service_provider_by_id(
&self,
service_id: ServiceId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::DeleteId { service_id },
vec![],
)
.await
}
async fn delete_service_provider_by_nym_address(
&self,
nym_address: NymAddress,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::DeleteNymAddress { nym_address },
vec![],
)
.await
}
async fn update_deposit_required(
&self,
deposit_required: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.exectute_service_provider_directory_contract(
fee,
SpExecuteMsg::UpdateDepositRequired {
deposit_required: deposit_required.into(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> SpDirectorySigningClient for NyxdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn exectute_service_provider_directory_contract(
&self,
fee: Option<Fee>,
msg: SpExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.service_provider_contract_address().ok_or(
NyxdError::NoContractAddressAvailable(
"service provider directory contract".to_string(),
),
)?,
&msg,
fee,
memo,
funds,
)
.await
}
}
@@ -91,6 +91,21 @@ pub trait VestingSigningClient {
.await
}
async fn vesting_decrease_pledge(
&self,
decrease_by: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::DecreasePledge {
amount: decrease_by.into(),
},
vec![],
)
.await
}
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError>;
async fn vesting_track_unbond_mixnode(
+1
View File
@@ -38,3 +38,4 @@ nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-cont
nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
@@ -6,11 +6,9 @@ use clap::{Args, Subcommand};
pub mod rewards;
pub mod delegate_to_mixnode;
pub mod pledge_more;
pub mod query_for_delegations;
pub mod undelegate_from_mixnode;
pub mod vesting_delegate_to_mixnode;
pub mod vesting_pledge_more;
pub mod vesting_undelegate_from_mixnode;
#[derive(Debug, Args)]
@@ -34,8 +32,4 @@ pub enum MixnetDelegatorsCommands {
DelegateVesting(vesting_delegate_to_mixnode::Args),
/// Undelegate from a mixnode (when originally using locked tokens)
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
/// Pledge more
PledgeMore(pledge_more::Args),
/// Pledge more with locked tokens
PledgeMoreVesting(vesting_pledge_more::Args),
}
@@ -26,7 +26,7 @@ pub struct Args {
pub version: Option<String>,
}
pub async fn vesting_update_config(client: SigningClient, args: Args) {
pub async fn vesting_update_config(args: Args, client: SigningClient) {
info!("Update vesting gateway config!");
let current_details = match client
@@ -45,7 +45,9 @@ pub struct Args {
pub force: bool,
}
pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str) {
pub async fn vesting_bond_gateway(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting vesting gateway bonding!");
// if we're trying to bond less than 1 token
@@ -0,0 +1,29 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use nym_validator_client::nyxd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub decrease_by: u128,
}
pub async fn decrease_pledge(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting to decrease pledge");
let coin = Coin::new(args.decrease_by, denom);
let res = client
.pledge_more(coin.into(), None)
.await
.expect("failed to decrease pledge!");
info!("decreasing pledge: {:?}", res);
}
@@ -4,13 +4,17 @@
use clap::{Args, Subcommand};
pub mod bond_mixnode;
pub mod decrease_pledge;
pub mod families;
pub mod keys;
pub mod mixnode_bonding_sign_payload;
pub mod pledge_more;
pub mod rewards;
pub mod settings;
pub mod unbond_mixnode;
pub mod vesting_bond_mixnode;
pub mod vesting_decrease_pledge;
pub mod vesting_pledge_more;
pub mod vesting_unbond_mixnode;
#[derive(Debug, Args)]
@@ -40,4 +44,12 @@ pub enum MixnetOperatorsMixnodeCommands {
UnbondVesting(vesting_unbond_mixnode::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
CreateMixnodeBondingSignPayload(mixnode_bonding_sign_payload::Args),
/// Pledge more
PledgeMore(pledge_more::Args),
/// Pledge more with locked tokens
PledgeMoreVesting(vesting_pledge_more::Args),
/// Decrease pledge
DecreasePledge(decrease_pledge::Args),
/// Decrease pledge with locked tokens
DecreasePledgeVesting(vesting_decrease_pledge::Args),
}
@@ -0,0 +1,29 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use nym_validator_client::nyxd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub decrease_by: u128,
}
pub async fn vesting_decrease_pledge(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting vesting to decrease pledge");
let coin = Coin::new(args.decrease_by, denom);
let res = client
.vesting_decrease_pledge(coin.into(), None)
.await
.expect("failed to vesting decrease pledge!");
info!("vesting decreasing pledge: {:?}", res);
}
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
pub mod gateway;
pub mod mixnode;
pub mod service;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -19,4 +20,6 @@ pub enum MixnetOperatorsCommands {
Mixnode(mixnode::MixnetOperatorsMixnode),
/// Manage your gateway
Gateway(gateway::MixnetOperatorsGateway),
/// Manage your service
ServiceProvider(service::MixnetOperatorsService),
}
@@ -0,0 +1,33 @@
use clap::Parser;
use log::info;
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceType};
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub nym_address: String,
/// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym')
#[clap(long)]
pub deposit: u128,
}
pub async fn announce(args: Args, client: SigningClient) {
info!("Annoucing service provider");
let nym_address = NymAddress::Address(args.nym_address);
let service_type = ServiceType::NetworkRequester;
let denom = client.current_chain_details().mix_denom.base.as_str();
let deposit = Coin::new(args.deposit, denom);
let res = client
.announce_service_provider(nym_address, service_type, deposit.into(), None)
.await
.expect("Failed to announce service provider");
info!("Announced service provider: {res:?}");
}
@@ -0,0 +1,23 @@
use clap::Parser;
use log::info;
use nym_service_provider_directory_common::ServiceId;
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub id: ServiceId,
}
pub async fn delete(args: Args, client: SigningClient) {
info!("Deleting service provider with id {}", args.id);
let res = client
.delete_service_provider_by_id(args.id, None)
.await
.expect("Failed to delete service provider");
info!("Deleted: {res:?}");
}
@@ -0,0 +1,19 @@
use clap::{Args, Subcommand};
pub mod announce;
pub mod delete;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsService {
#[clap(subcommand)]
pub command: MixnetOperatorsServiceCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsServiceCommands {
/// Announce service provider to the world
Announce(announce::Args),
/// Delete entry for service provider from the directory
Delete(delete::Args),
}
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
pub mod query_all_gateways;
pub mod query_all_mixnodes;
pub mod query_all_service_providers;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -19,4 +20,6 @@ pub enum MixnetQueryCommands {
Mixnodes(query_all_mixnodes::Args),
/// Query gateways
Gateways(query_all_gateways::Args),
/// Query announced service-providers
ServiceProviders(query_all_service_providers::Args),
}
@@ -0,0 +1,55 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::nym_api::error::NymAPIError;
use crate::context::QueryClientWithNyxd;
use crate::utils::show_error;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(value_parser)]
#[clap(help = "Optionally, the service provider to display")]
pub nym_address: Option<String>,
}
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
match client.nym_api.get_service_providers().await {
Ok(res) => {
if let Some(nym_address) = args.nym_address {
let service = res.iter().find(|service| {
service
.service
.nym_address
.to_string()
.eq_ignore_ascii_case(&nym_address)
});
println!(
"{}",
::serde_json::to_string_pretty(&service).expect("json formatting error")
);
} else {
let mut table = Table::new();
table.set_header(vec!["Service Id", "Announcer", "Nym Address"]);
for service in res {
table.add_row(vec![
service.service_id.to_string(),
service.service.announcer.to_string(),
service.service.service_type.to_string(),
service.service.nym_address.to_string(),
]);
}
println!("The service providers in the directory are:");
println!("{table}");
}
}
Err(NymAPIError::NotFound) => {
println!("nym-api reports no service provider endpoint available");
}
Err(e) => show_error(e),
}
}
@@ -1,6 +1,6 @@
[package]
name = "nym-mixnet-contract-common"
version = "0.4.0"
version = "0.5.0"
description = "Common library for the Nym mixnet contract"
rust-version = "1.62"
edition = { workspace = true }
@@ -1,13 +1,16 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{EpochState, IdentityKey, MixId};
use crate::{EpochEventId, EpochState, IdentityKey, MixId};
use contracts_common::signing::verifier::ApiVerifierError;
use cosmwasm_std::{Addr, Coin, Decimal};
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum MixnetContractError {
#[error("could not perform contract migration: {comment}")]
FailedMigration { comment: String },
#[error("{source}")]
StdErr {
#[from]
@@ -26,6 +29,17 @@ pub enum MixnetContractError {
#[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")]
InsufficientPledge { received: Coin, minimum: Coin },
#[error("Attempted to reduce node pledge ({current}{denom} - {decrease_by}{denom}) below the minimum amount: {minimum}{denom}")]
InvalidPledgeReduction {
current: Uint128,
decrease_by: Uint128,
minimum: Uint128,
denom: String,
},
#[error("A pledge change is already pending in this epoch. The event id: {pending_event_id}")]
PendingPledgeChange { pending_event_id: EpochEventId },
#[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")]
InsufficientDelegation { received: Coin, minimum: Coin },
@@ -190,6 +204,9 @@ pub enum MixnetContractError {
#[error("epoch duration must be > 0")]
EpochDurationZero,
#[error("attempted to perform the operation with 0 coins. This is not allowed")]
ZeroCoinAmount,
#[error("this validator ({current_validator}) is not the one responsible for advancing this epoch. It's responsibility of {chosen_validator}.")]
RewardingValidatorMismatch {
current_validator: Addr,
@@ -226,3 +243,11 @@ pub enum MixnetContractError {
source: ApiVerifierError,
},
}
impl MixnetContractError {
pub fn inconsistent_state<S: Into<String>>(comment: S) -> Self {
MixnetContractError::InconsistentState {
comment: comment.into(),
}
}
}
@@ -15,6 +15,8 @@ pub enum MixnetEventType {
MixnodeBonding,
PendingPledgeIncrease,
PledgeIncrease,
PendingPledgeDecrease,
PledgeDecrease,
GatewayBonding,
GatewayUnbonding,
PendingMixnodeUnbonding,
@@ -58,6 +60,8 @@ impl ToString for MixnetEventType {
MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::PendingPledgeIncrease => "pending_pledge_increase",
MixnetEventType::PledgeIncrease => "pledge_increase",
MixnetEventType::PendingPledgeDecrease => "pending_pledge_decrease",
MixnetEventType::PledgeDecrease => "pledge_decrease",
MixnetEventType::GatewayBonding => "gateway_bonding",
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
@@ -354,6 +358,19 @@ pub fn new_pledge_increase_event(created_at: BlockHeight, mix_id: MixId, amount:
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pending_pledge_decrease_event(mix_id: MixId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PendingPledgeDecrease)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pledge_decrease_event(created_at: BlockHeight, mix_id: MixId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PledgeDecrease)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
@@ -3,7 +3,10 @@
use crate::error::MixnetContractError;
use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent};
use crate::{EpochId, IntervalId, MixId};
use crate::{
EpochEventId, EpochId, IntervalEventId, IntervalId, MixId, PendingEpochEventData,
PendingIntervalEventData,
};
use cosmwasm_std::{Addr, Env};
use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject};
@@ -528,6 +531,30 @@ impl PendingIntervalEventsResponse {
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingEpochEventResponse {
pub event_id: EpochEventId,
pub event: Option<PendingEpochEventData>,
}
impl PendingEpochEventResponse {
pub fn new(event_id: EpochEventId, event: Option<PendingEpochEventData>) -> Self {
PendingEpochEventResponse { event_id, event }
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingIntervalEventResponse {
pub event_id: IntervalEventId,
pub event: Option<PendingIntervalEventData>,
}
impl PendingIntervalEventResponse {
pub fn new(event_id: IntervalEventId, event: Option<PendingIntervalEventData>) -> Self {
PendingIntervalEventResponse { event_id, event }
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct NumberOfPendingEventsResponse {
pub epoch_events: u32,
@@ -32,7 +32,8 @@ pub use gateway::{
};
pub use interval::{
CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse,
PendingEpochEventsResponse, PendingIntervalEventsResponse,
PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEventResponse,
PendingIntervalEventsResponse,
};
pub use mixnode::{
Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails,
@@ -10,7 +10,7 @@ use crate::helpers::IntoBaseDecimal;
use crate::reward_params::{NodeRewardParams, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
use crate::{Delegation, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use crate::{Delegation, EpochEventId, EpochId, IdentityKey, MixId, Percent, SphinxKey};
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -37,13 +37,20 @@ impl RewardedSetNodeStatus {
pub struct MixNodeDetails {
pub bond_information: MixNodeBond,
pub rewarding_details: MixNodeRewarding,
#[serde(default)]
pub pending_changes: PendingMixNodeChanges,
}
impl MixNodeDetails {
pub fn new(bond_information: MixNodeBond, rewarding_details: MixNodeRewarding) -> Self {
pub fn new(
bond_information: MixNodeBond,
rewarding_details: MixNodeRewarding,
pending_changes: PendingMixNodeChanges,
) -> Self {
MixNodeDetails {
bond_information,
rewarding_details,
pending_changes,
}
}
@@ -73,6 +80,10 @@ impl MixNodeDetails {
pub fn total_stake(&self) -> Decimal {
self.rewarding_details.node_bond()
}
pub fn pending_pledge_change(&self) -> Option<EpochEventId> {
self.pending_changes.pledge_change
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
@@ -332,6 +343,22 @@ impl MixNodeRewarding {
Ok(())
}
/// Decreases total pledge of operator by the specified amount.
pub fn decrease_operator_uint128(
&mut self,
amount: Uint128,
) -> Result<(), MixnetContractError> {
let amount_decimal = amount.into_base_decimal()?;
if self.operator < amount_decimal {
return Err(MixnetContractError::OverflowDecimalSubtraction {
minuend: self.operator,
subtrahend: amount_decimal,
});
}
self.operator -= amount_decimal;
Ok(())
}
pub fn increase_delegates_uint128(
&mut self,
amount: Uint128,
@@ -601,6 +628,25 @@ impl From<Layer> for u8 {
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/PendingMixnodeChanges.ts")
)]
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PendingMixNodeChanges {
pub pledge_change: Option<EpochEventId>,
// pub cost_params_change: Option<IntervalEventId>,
}
impl PendingMixNodeChanges {
pub fn new_empty() -> PendingMixNodeChanges {
PendingMixNodeChanges {
pledge_change: None,
}
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
@@ -10,10 +10,13 @@ use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
};
use crate::{delegation, ContractStateParams, Layer, LayerAssignment, MixId, Percent};
use crate::{
delegation, ContractStateParams, EpochEventId, IntervalEventId, Layer, LayerAssignment, MixId,
Percent,
};
use crate::{Gateway, IdentityKey, MixNode};
use contracts_common::signing::MessageSignature;
use cosmwasm_std::Decimal;
use cosmwasm_std::{Coin, Decimal};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::time::Duration;
@@ -161,6 +164,13 @@ pub enum ExecuteMsg {
PledgeMoreOnBehalf {
owner: String,
},
DecreasePledge {
decrease_by: Coin,
},
DecreasePledgeOnBehalf {
owner: String,
decrease_by: Coin,
},
UnbondMixnode {},
UnbondMixnodeOnBehalf {
owner: String,
@@ -297,6 +307,10 @@ impl ExecuteMsg {
}
ExecuteMsg::PledgeMore {} => "pledging additional tokens".into(),
ExecuteMsg::PledgeMoreOnBehalf { .. } => "pledging additional tokens on behalf".into(),
ExecuteMsg::DecreasePledge { .. } => "decreasing mixnode pledge".into(),
ExecuteMsg::DecreasePledgeOnBehalf { .. } => {
"decreasing mixnode pledge on behalf".into()
}
ExecuteMsg::UnbondMixnode { .. } => "unbonding mixnode".into(),
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
@@ -506,6 +520,12 @@ pub enum QueryMsg {
limit: Option<u32>,
start_after: Option<u32>,
},
GetPendingEpochEvent {
event_id: EpochEventId,
},
GetPendingIntervalEvent {
event_id: IntervalEventId,
},
GetNumberOfPendingEvents {},
// signing-related
@@ -38,6 +38,10 @@ pub enum PendingEpochEventKind {
mix_id: MixId,
amount: Coin,
},
DecreasePledge {
mix_id: MixId,
decrease_by: Coin,
},
UnbondMixnode {
mix_id: MixId,
},
@@ -66,7 +70,7 @@ impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingIntervalEvent {
pub id: EpochEventId,
pub id: IntervalEventId,
pub event: PendingIntervalEventData,
}
@@ -7,4 +7,5 @@ edition = "2021"
[dependencies]
cosmwasm-std = { workspace = true }
schemars = "0.8"
serde = { workspace = true, default-features = false, features = ["derive"] }
@@ -5,3 +5,5 @@ pub mod types;
// Re-export all types at the top-level
pub use types::*;
pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction};
@@ -40,6 +40,24 @@ impl ExecuteMsg {
pub fn delete_id(service_id: ServiceId) -> Self {
ExecuteMsg::DeleteId { service_id }
}
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::Announce {
nym_address,
service_type,
} => format!("announcing {nym_address} as type {service_type}"),
ExecuteMsg::DeleteId { service_id } => {
format!("deleting service with service id {service_id}")
}
ExecuteMsg::DeleteNymAddress { nym_address } => {
format!("deleting service with nym address {nym_address}")
}
ExecuteMsg::UpdateDepositRequired { deposit_required } => {
format!("updating the deposit required to {deposit_required}")
}
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -1,5 +1,6 @@
use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo};
use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@@ -9,7 +10,7 @@ pub struct ServiceInfoResponse {
pub service: Option<Service>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServicesListResponse {
pub services: Vec<ServiceInfo>,
@@ -26,6 +27,14 @@ impl ServicesListResponse {
}
}
impl From<&[ServiceInfo]> for ServicesListResponse {
fn from(services: &[ServiceInfo]) -> Self {
Self {
services: services.to_vec(),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct PagedServicesListResponse {
@@ -1,12 +1,13 @@
use std::fmt::{Display, Formatter};
use cosmwasm_std::{Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// The directory of services are indexed by [`ServiceId`].
pub type ServiceId = u32;
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
pub struct Service {
/// The address of the service.
pub nym_address: NymAddress,
@@ -21,7 +22,7 @@ pub struct Service {
}
/// The types of addresses supported.
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum NymAddress {
/// String representation of a nym address, which is of the form
@@ -51,7 +52,7 @@ impl Display for NymAddress {
}
/// The type of services provider supported
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug)]
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ServiceType {
NetworkRequester,
@@ -66,7 +67,7 @@ impl std::fmt::Display for ServiceType {
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ServiceInfo {
pub service_id: ServiceId,
@@ -1,6 +1,6 @@
[package]
name = "nym-vesting-contract-common"
version = "0.5.0"
version = "0.6.0"
description = "Common library for the Nym vesting contract"
edition = { workspace = true }
authors = { workspace = true }
@@ -9,7 +9,7 @@ repository = { workspace = true }
[dependencies]
cosmwasm-std = { workspace = true }
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.4.0" }
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.5.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.0" }
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
@@ -15,6 +15,7 @@ pub const VESTING_GATEWAY_BONDING_EVENT_TYPE: &str = "vesting_gateway_bonding";
pub const VESTING_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbonding";
pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
pub const VESTING_PLEDGE_MORE_EVENT_TYPE: &str = "vesting_pledge_more";
pub const VESTING_DECREASE_PLEDGE_EVENT_TYPE: &str = "vesting_pledge_decrease";
pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding";
pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
pub const VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE: &str = "vesting_update_gateway_config";
@@ -22,6 +23,7 @@ pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
"vesting_update_mixnode_cost_params";
pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond";
pub const TRACK_MIXNODE_PLEDGE_DECREASE_EVENT_TYPE: &str = "track_mixnode_pledge_decrease";
pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond";
pub const TRACK_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation";
pub const TRACK_REWARD_EVENT_TYPE: &str = "track_reaward";
@@ -118,6 +120,10 @@ pub fn new_vesting_pledge_more_event() -> Event {
Event::new(VESTING_PLEDGE_MORE_EVENT_TYPE)
}
pub fn new_vesting_decrease_pledge_event() -> Event {
Event::new(VESTING_DECREASE_PLEDGE_EVENT_TYPE)
}
pub fn new_vesting_update_mixnode_config_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
}
@@ -146,6 +152,10 @@ pub fn new_track_mixnode_unbond_event() -> Event {
Event::new(TRACK_MIXNODE_UNBOND_EVENT_TYPE)
}
pub fn new_track_mixnode_pledge_decrease_event() -> Event {
Event::new(TRACK_MIXNODE_PLEDGE_DECREASE_EVENT_TYPE)
}
pub fn new_track_gateway_unbond_event() -> Event {
Event::new(TRACK_GATEWAY_UNBOND_EVENT_TYPE)
}
@@ -123,11 +123,18 @@ pub enum ExecuteMsg {
PledgeMore {
amount: Coin,
},
DecreasePledge {
amount: Coin,
},
UnbondMixnode {},
TrackUnbondMixnode {
owner: String,
amount: Coin,
},
TrackDecreasePledge {
owner: String,
amount: Coin,
},
BondGateway {
gateway: Gateway,
owner_signature: MessageSignature,
@@ -175,8 +182,10 @@ impl ExecuteMsg {
ExecuteMsg::TrackUndelegation { .. } => "VestingExecuteMsg::TrackUndelegation",
ExecuteMsg::BondMixnode { .. } => "VestingExecuteMsg::BondMixnode",
ExecuteMsg::PledgeMore { .. } => "VestingExecuteMsg::PledgeMore",
ExecuteMsg::DecreasePledge { .. } => "VestingExecuteMsg::DecreasePledge",
ExecuteMsg::UnbondMixnode { .. } => "VestingExecuteMsg::UnbondMixnode",
ExecuteMsg::TrackUnbondMixnode { .. } => "VestingExecuteMsg::TrackUnbondMixnode",
ExecuteMsg::TrackDecreasePledge { .. } => "VestingExecuteMsg::TrackDecreasePledge",
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway",
ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway",
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-crypto"
version = "0.2.0"
version = "0.3.0"
description = "Crypto library for the nym mixnet"
edition = { workspace = true }
authors = { workspace = true }
@@ -24,6 +24,7 @@ serde_bytes = { version = "0.11.6", optional = true }
serde_crate = { version = "1.0", optional = true, default_features = false, package = "serde" }
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
thiserror = "1.0.37"
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
# internal
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" }
@@ -3,15 +3,12 @@
use nym_sphinx_acknowledgements::surb_ack::SurbAckRecoveryError;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddressError;
use nym_sphinx_types::{NymPacketError, SphinxError};
use nym_sphinx_types::Error as SphinxError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MixProcessingError {
#[error("failed to process received packet: {0}")]
NymPacketProcessingError(#[from] NymPacketError),
#[error("failed to process received sphinx packet: {0}")]
SphinxProcessingError(#[from] SphinxError),
#[error("the forward hop address was malformed: {0}")]
@@ -7,11 +7,11 @@ use log::*;
use nym_sphinx_acknowledgements::surb_ack::SurbAck;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_framing::packet::FramedNymPacket;
use nym_sphinx_params::{PacketSize, PacketType};
use nym_sphinx_framing::packet::FramedSphinxPacket;
use nym_sphinx_params::{PacketMode, PacketSize};
use nym_sphinx_types::{
Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, NymPacket, Payload,
PrivateKey, ProcessedPacket,
Delay as SphinxDelay, DestinationAddressBytes, NodeAddressBytes, Payload, PrivateKey,
ProcessedPacket, SphinxPacket,
};
use std::convert::TryFrom;
use std::sync::Arc;
@@ -53,14 +53,14 @@ impl SphinxPacketProcessor {
feature = "cpucycles",
instrument(skip(self, packet), fields(cpucycles))
)]
fn perform_initial_packet_processing(
fn perform_initial_sphinx_packet_processing(
&self,
packet: NymPacket,
packet: SphinxPacket,
) -> Result<ProcessedPacket, MixProcessingError> {
measure!({
packet.process(&self.sphinx_key).map_err(|err| {
debug!("Failed to unwrap Sphinx packet: {err}");
MixProcessingError::NymPacketProcessingError(err)
MixProcessingError::SphinxProcessingError(err)
})
})
}
@@ -72,12 +72,17 @@ impl SphinxPacketProcessor {
)]
fn perform_initial_unwrapping(
&self,
received: FramedNymPacket,
received: FramedSphinxPacket,
) -> Result<ProcessedPacket, MixProcessingError> {
measure!({
let packet = received.into_inner();
let packet_mode = received.packet_mode();
let sphinx_packet = received.into_inner();
self.perform_initial_packet_processing(packet)
if packet_mode.is_old_vpn() {
return Err(MixProcessingError::ReceivedOldTypeVpnPacket);
}
self.perform_initial_sphinx_packet_processing(sphinx_packet)
})
}
@@ -85,14 +90,14 @@ impl SphinxPacketProcessor {
/// and packs all the data in a way that can be easily sent to the next hop.
fn process_forward_hop(
&self,
packet: NymPacket,
packet: SphinxPacket,
forward_address: NodeAddressBytes,
delay: SphinxDelay,
packet_type: PacketType,
packet_mode: PacketMode,
) -> Result<MixProcessingResult, MixProcessingError> {
let next_hop_address = NymNodeRoutingAddress::try_from(forward_address)?;
let mix_packet = MixPacket::new(next_hop_address, packet, packet_type);
let mix_packet = MixPacket::new(next_hop_address, packet, packet_mode);
Ok(MixProcessingResult::ForwardHop(mix_packet, Some(delay)))
}
@@ -119,25 +124,21 @@ impl SphinxPacketProcessor {
&self,
data: Vec<u8>,
packet_size: PacketSize,
packet_type: PacketType,
packet_mode: PacketMode,
) -> Result<(Option<MixPacket>, Vec<u8>), MixProcessingError> {
match packet_size {
PacketSize::AckPacket | PacketSize::OutfoxAckPacket => {
PacketSize::AckPacket => {
trace!("received an ack packet!");
Ok((None, data))
}
PacketSize::RegularPacket
| PacketSize::ExtendedPacket8
| PacketSize::ExtendedPacket16
| PacketSize::ExtendedPacket32
| PacketSize::OutfoxRegularPacket
| PacketSize::OutfoxExtendedPacket8
| PacketSize::OutfoxExtendedPacket16
| PacketSize::OutfoxExtendedPacket32 => {
| PacketSize::ExtendedPacket32 => {
trace!("received a normal packet!");
let (ack_data, message) = self.split_hop_data_into_ack_and_message(data)?;
let (ack_first_hop, ack_packet) = SurbAck::try_recover_first_hop_packet(&ack_data)?;
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type);
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_mode);
Ok((Some(forward_ack), message))
}
}
@@ -151,12 +152,12 @@ impl SphinxPacketProcessor {
destination: DestinationAddressBytes,
payload: Payload,
packet_size: PacketSize,
packet_type: PacketType,
packet_mode: PacketMode,
) -> Result<MixProcessingResult, MixProcessingError> {
let packet_message = payload.recover_plaintext()?;
let (forward_ack, message) =
self.split_into_ack_and_message(packet_message, packet_size, packet_type)?;
self.split_into_ack_and_message(packet_message, packet_size, packet_mode)?;
Ok(MixProcessingResult::FinalHop(ProcessedFinalHop {
destination,
@@ -171,16 +172,16 @@ impl SphinxPacketProcessor {
&self,
packet: ProcessedPacket,
packet_size: PacketSize,
packet_type: PacketType,
packet_mode: PacketMode,
) -> Result<MixProcessingResult, MixProcessingError> {
match packet {
ProcessedPacket::ForwardHop(packet, address, delay) => {
self.process_forward_hop(NymPacket::Sphinx(*packet), address, delay, packet_type)
self.process_forward_hop(*packet, address, delay, packet_mode)
}
// right now there's no use for the surb_id included in the header - probably it should get removed from the
// sphinx all together?
ProcessedPacket::FinalHop(destination, _, payload) => {
self.process_final_hop(destination, payload, packet_size, packet_type)
self.process_final_hop(destination, payload, packet_size, packet_mode)
}
}
}
@@ -191,19 +192,19 @@ impl SphinxPacketProcessor {
)]
pub fn process_received(
&self,
received: FramedNymPacket,
received: FramedSphinxPacket,
) -> Result<MixProcessingResult, MixProcessingError> {
// explicit packet size will help to correctly parse final hop
measure!({
let packet_size = received.packet_size();
let packet_type = received.packet_type();
let packet_mode = received.packet_mode();
// unwrap the sphinx packet and if possible and appropriate, cache keys
let processed_packet = self.perform_initial_unwrapping(received)?;
// for forward packets, extract next hop and set delay (but do NOT delay here)
// for final packets, extract SURBAck
self.perform_final_processing(processed_packet, packet_size, packet_type)
self.perform_final_processing(processed_packet, packet_size, packet_mode)
})
}
}
+30 -1
View File
@@ -3,7 +3,12 @@
use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD};
use serde::{Deserialize, Serialize};
use std::{env::var, ops::Not, path::PathBuf};
use std::{
env::{var, VarError},
ffi::OsStr,
ops::Not,
path::PathBuf,
};
use url::Url;
pub mod mainnet;
@@ -28,6 +33,7 @@ pub struct NymContracts {
pub group_contract_address: Option<String>,
pub multisig_contract_address: Option<String>,
pub coconut_dkg_contract_address: Option<String>,
pub service_provider_directory_contract_address: Option<String>,
}
// I wanted to use the simpler `NetworkDetails` name, but there's a clash
@@ -68,6 +74,14 @@ impl NymNetworkDetails {
}
pub fn new_from_env() -> Self {
fn get_optional_env<K: AsRef<OsStr>>(env: K) -> Option<String> {
match var(env) {
Ok(var) => Some(var),
Err(VarError::NotPresent) => None,
err => panic!("Unable to set: {:?}", err),
}
}
NymNetworkDetails::new_empty()
.with_bech32_account_prefix(
var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"),
@@ -117,6 +131,9 @@ impl NymNetworkDetails {
.with_coconut_dkg_contract(Some(
var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"),
))
.with_service_provider_directory_contract(get_optional_env(
var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
))
}
pub fn new_mainnet() -> Self {
@@ -146,6 +163,9 @@ impl NymNetworkDetails {
coconut_dkg_contract_address: parse_optional_str(
mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
),
service_provider_directory_contract_address: parse_optional_str(
mainnet::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
),
},
}
}
@@ -227,6 +247,15 @@ impl NymNetworkDetails {
self.contracts.coconut_dkg_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_service_provider_directory_contract<S: Into<String>>(
mut self,
contract: Option<S>,
) -> Self {
self.contracts.service_provider_directory_contract_address = contract.map(Into::into);
self
}
}
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
+2
View File
@@ -20,6 +20,8 @@ pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =

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