Compare commits

...

122 Commits

Author SHA1 Message Date
Simon Wicky edfb75b9ef remove test client 2023-03-28 16:12:21 +02:00
Simon Wicky 298591b9e0 Merge commit '009b2131f61dc447ad08b5f1df1e2eef44cde1d2' into simon/instrumented 2023-03-28 13:45:37 +00:00
Simon Wicky 009b2131f6 instrumentation 2023-03-28 13:24:45 +00:00
Simon Wicky e5af0f5d5e Merge commit '3cad2dbb341c23592d22856ced5c8020a7ea20b7' into simon/instrumented 2023-03-28 13:21:46 +00:00
Simon Wicky 3cad2dbb34 Merge remote-tracking branch 'origin/simon/instrumented' into simon/instrumented 2023-03-28 13:18:53 +00:00
Simon Wicky e95e33cd70 Merge commit '6d44fe818ea4c74f476cc6d79434cc8619b45c0c' into simon/instrumented 2023-03-28 13:17:09 +00:00
Simon Wicky 5281895d5b mixnode instrumentation 2023-03-28 12:39:06 +00:00
Simon Wicky 263db0dbc3 Merge commit '6d44fe818ea4c74f476cc6d79434cc8619b45c0c' into Simon/1.1.4 2023-03-28 11:55:06 +00:00
Simon Wicky 6d44fe818e Merge commit '2878e9be9d0c397a746a8c942b818ac1168dff6f' into simon 2023-03-28 11:50:17 +02:00
Simon Wicky b8ec48cf07 small changes to warmup phase 2023-03-28 09:41:35 +00:00
Simon Wicky 99227e837c disable some epoch operations 2023-03-23 07:52:39 +00:00
farbanas 2878e9be9d Merge branch 'release/v1.1.13' 2023-03-22 10:15:32 +01:00
farbanas 7b419c2b12 bump version of contracts 2023-03-22 10:12:33 +01:00
Fran Arbanas 0049126a91 Merge pull request #3200 from nymtech/jon/chore/explicit-cosmwasm-versions-across-workspaces
Set cosmwasm versions on workspace and strictly use 1.0.0
2023-03-22 10:06:18 +01:00
Simon Wicky 34cb142595 packet size the end 2023-03-22 08:40:33 +00:00
Jon Häggblad 80c5194d8b Add explicit cosmwasm-crypto 1.0.0 dev-dependency 2023-03-21 16:34:06 +01:00
Jon Häggblad 27a6b99453 Even more workspace version missed earlier 2023-03-21 16:34:06 +01:00
Jon Häggblad 61982de511 A few more workspace versions 2023-03-21 16:34:06 +01:00
Jon Häggblad efd9883197 Use workspace deps for coconut contracts 2023-03-21 16:34:06 +01:00
Jon Häggblad ce4ae8d90c Set cosmwasm versions on workspace and strictly use 1.0.0 2023-03-21 16:34:06 +01:00
farbanas 1d2722f994 update workflows with specific wasm-opt version 2023-03-21 16:30:40 +01:00
farbanas f7a0b305df update common crate version in contracts 2023-03-21 11:10:55 +01:00
farbanas 746ec71a0d update package versions 2023-03-21 10:54:18 +01:00
farbanas cdfa5ee540 chore: update versions for release/v1.1.13 2023-03-15 15:23:55 +01:00
farbanas 71853f69f3 chore: update changelogs for release/v1.1.13 2023-03-15 15:23:15 +01:00
Tommy Verrall bedff1f258 Merge pull request #3153 from nymtech/oak-14
Validating new interval config parameters to prevent division by zero
2023-03-14 12:38:43 +02:00
Fouad 71a10a9a8b add blockstream green to sp list (#3180) 2023-03-13 17:05:21 +01:00
Jon Häggblad 605aed6f20 mock-nym-api: fix .storybook lint error (#3178) 2023-03-13 12:23:17 +01:00
Tommy Verrall 5aee4b1660 Merge pull request #3167 from nymtech/feature/wallet-3132
Feature/wallet 3132
2023-03-10 17:56:09 +02:00
Tommy Verrall 68a7bb67de Merge pull request #3169 from nymtech/feature/explorer-3168
Feature/explorer 3168
2023-03-10 17:32:22 +02:00
Simon Wicky 2988ae4459 packet size in config 2023-03-10 14:16:53 +00:00
Simon Wicky e653b632ba packet sizes support 2023-03-10 13:18:19 +00:00
Simon Wicky eb216a06b3 redirect mixnodes/active to mixnodes 2023-03-10 13:18:15 +00:00
fmtabbara 31e93428cf use new locked rewards and locked coins endpoints 2023-03-09 16:48:05 +00:00
fmtabbara 5f56b3eeea add bond % tooltip 2023-03-09 16:34:22 +00:00
fmtabbara e69b05693a switch avg and routing score table positions 2023-03-09 16:30:58 +00:00
fmtabbara 8ae2451340 add y-axis label 2023-03-09 16:28:30 +00:00
fmtabbara b3b3279345 fix gateway click thorough from gateway version field 2023-03-09 14:45:45 +00:00
farbanas 94a451c79b fix: updated build-and-upload-binaries-ci workflow with explorer-api and the new contracts 2023-03-09 13:41:22 +01:00
Jędrzej Stuczyński ec7b959028 removed source of future clippy complaints 2023-03-09 11:30:30 +00:00
Jędrzej Stuczyński 7061beea6e exposing tauri operations for spendable vested and reward coins 2023-03-09 11:28:50 +00:00
Fran Arbanas e408162e26 Merge pull request #2747 from nymtech/339-net-switch-bttn
339 net switch bttn
2023-03-09 10:52:00 +01:00
Gala 25ae3895cb updating branch 2023-03-09 10:39:30 +01:00
fmtabbara 60296f2a41 update vesting schedule ui 2023-03-08 17:51:30 +00:00
Fouad 3b97844310 Feature/explorer 2979 (#3147)
* additional unfiltered endpoints for nym-api

* add poor performance UI

* display appropriate UI when node is blacklisted

* update explorer api with blacklisted nodes

* add new unfiltered endpoint

add new unfiltered endpoint

* show blacklisted detail even when node description is unavailable

remove console.log

---------

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

* Add nym prefix to coconut-bandwidth

* Add nym prefix to bandwidth-claim-contract

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

* Add nym prefix to coconut-dkg-common

* Add nym prefix to group-contract-common

* Add nym prefix to multisig-contract-common

* Add nym prefix to coconut-interface

* Add nym prefix to credential-storage

* rustfmt

* Mark coconut-test crate as private

* Fix build errors

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

* Update imports

* rustfmt

* Add nym-socks5 prefix to proxy-helpers crate

* rustfmt

* Add nym prefix to ordered-buffer crate

* rustfmt

* Add nym prefix to service-providers-common crate

* rustfmt

* Add nym prefix to dkg crate

* Add nym prefix to credentials crate

* rustfmt

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

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

* Cargo.lock

* rustfmt

* vesting: option_env for git info

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

* make request for well known service providers

* fetch and display service providers

* service provider overview

handle undefined data

fix linting

fix type

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

* fix up table alignment

fix lint errors

* add node_performance to explorer api response for mixnodes

* use mixnode node_performance for avg and lastest values

* move stake sat to top table

fix lint errors

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

* topology provider builder method for base client

* ability to take manual control over topology

* wasm fixes

* added topology injection to nym-sdk API

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

* oak-8

* oak-13

* oak-15

* oak-18

* Minor clippy nit

* 2023-01-13-OAK-6

* 2023-01-13-OAK-3

* 2023-01-13-OAK-13

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

* Removed few instances of password being unecessarily copied

* 2023-01-13-OAK-10

* 2023-01-13-OAK-12

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

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

* Removed further instances of needlessly copying the mnemonic

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

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

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

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

* wip

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

* split epoch operations into multiple files

* epoch operation failure recovery

* sending rewarding transactions in correct order

* tests and fixes due to epoch state progression

* lint

* missed rebasing import changes

* Setting cw2 contract version during first migration run

* calling 'reconcile_epoch_events' at least once

* Made message to BeginEpochTransition more consistent with other variants

* Merge layer assignment updates

---------

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

* reorder gateway columns

* update tooltips

* fix column sorting in explorer lists

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

* fix up tests

fix up linting

* add bundle script

* add build prod script to package json

update tests

* update readme + copy to dist output

update global types

update types and tests!

* update package build

* move types and tests into src

* Squashed clients/validator content from old branch

fix up tests

fix up linting

* add bundle script

* add build prod script to package json

update tests

* update readme + copy to dist output

update global types

update types and tests!

update package build

* move types and tests into src

* build to sub-dir

* Fixing the few broken tests

---------

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

* nym-api: update get mixnode avg_uptime endpoint

* nym-api: mixnode report to use cached data

* nym-api: annotate gateway bond with node performance

* nym-api: gateway report to use cached data

* wip

* Add get_gateway_avg_uptime

* Add comment

* update NR gateways to include node_performance on frontend

* use node_performance values on frontend

* fixup select gateway from list

* fix up lint errors

---------

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

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

* new option to select gateways based on latency

* further changes for wasm-compatibility

* post rebase fixes + clippy

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

* android change

* wasm: the gift that keeps on giving
2023-02-23 17:09:22 +00:00
Bogdan-Ștefan Neacşu 0b0ec075bb Use saturating_sub with an additional 1 second buffer (#3095) 2023-02-23 13:44:54 +01:00
Tommy Verrall cca4d21e7c Merge pull request #3097 from nymtech/feature/update-checker-to-use-master
Feature/update checker to use master
2023-02-23 14:24:59 +02:00
Simon Wicky 921f01a789 Merge commit 'f4d0a120bb7f552591c415ece31d9f75bd6ec33e' into Simon/1.1.5 2023-01-31 09:31:24 +00:00
Simon Wicky 27bf8b2e00 tracing first part 2023-01-31 09:25:15 +00:00
Simon Wicky e82a669cd3 merge 1.1.5 2023-01-11 15:17:15 +00:00
Simon Wicky 2015443a90 set default packetsize back to 2 2023-01-10 10:23:51 +00:00
Gala 4f59678ded center layout 2022-12-22 15:31:35 +01:00
Gala 8a1d2af3cf adding changes 2022-12-22 14:44:58 +01:00
Simon Wicky 2c2823f9e1 test instrumentation 2022-12-21 08:31:16 +00:00
Simon Wicky 52b41a5697 add packet sizes support 2022-12-21 08:06:23 +00:00
Simon Wicky 9ee6ae44e2 remove override config, and active nodes 2022-12-21 08:06:13 +00:00
443 changed files with 14467 additions and 5445 deletions
@@ -79,6 +79,9 @@ jobs:
override: true
components: rustfmt, clippy
- name: Install wasm-opt
run: cargo install --version 0.110.0 wasm-opt
- name: Build release contracts
run: make wasm
@@ -96,9 +99,14 @@ jobs:
cp target/release/nym-network-statistics $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/credential $OUTPUT_DIR
cp target/release/explorer-api $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_bandwidth.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
- name: Deploy branch to CI www
continue-on-error: true
+4 -1
View File
@@ -6,7 +6,7 @@ on:
jobs:
build:
if: ${{ startsWith(github.ref, 'refs/tags/nym-contracts-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-contracts-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
runs-on: [self-hosted, custom-runner-linux]
steps:
- uses: actions/checkout@v2
@@ -19,6 +19,9 @@ jobs:
override: true
components: rustfmt, clippy
- name: Install wasm-opt
run: cargo install --version 0.110.0 wasm-opt
- name: Build release contracts
run: make wasm
@@ -1,57 +0,0 @@
name: CI for Network Explorer API
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym:
if: ${{ startsWith(github.ref, 'refs/tags/nym-explorer-api-') && (github.event_name == 'release' || github.event_name == 'workflow_dispatch') }}
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
continue-on-error: true
- name: Check the release tag starts with `nym-explorer-api-`
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-explorer-api-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build all explorer-api
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path explorer-api/Cargo.toml --workspace --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact
path: |
target/release/explorer-api
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/explorer-api
-50
View File
@@ -1,50 +0,0 @@
name: Publish Nym CLI binaries
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym-cli:
if: ${{ startsWith(github.ref, 'refs/tags/nym-cli-') && (github.event_name == 'release' || github.event_name == 'workflow_dispatch') }}
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04, windows-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Check the release tag starts with `nym-cli-`
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-cli-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build binary
run: make build-nym-cli
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: nym-cli-${{ matrix.platform }}
path: |
target/release/nym-cli*
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/nym-cli
@@ -10,7 +10,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
@@ -10,7 +10,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
+1 -1
View File
@@ -16,7 +16,7 @@ env:
jobs:
publish-nym:
if: ${{ startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
@@ -10,7 +10,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
@@ -9,7 +9,7 @@ defaults:
jobs:
publish-tauri:
if: ${{ startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release' }}
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
-2
View File
@@ -39,7 +39,5 @@ validator-api-config.toml
dist
storybook-static
envs/qwerty.env
Cargo.lock
nym-connect/Cargo.lock
.parcel-cache
**/.DS_Store
+54
View File
@@ -4,6 +4,60 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.13] (2023-03-15)
- NE - instead of throwing a "Mixnode/Gateway not found" error for blacklisted nodes due to bad performance, show their history but tag them as "Having poor performance" ([#2979])
- NE - Upgrade Sandbox and make below changes: ([#2332])
- Explorer - Updates ([#3168])
- Fix contracts and nym-api audit findings ([#3026])
- Website v2 - deploy infrastructure for strapi and CI ([#2213])
- add blockstream green to sp list ([#3180])
- mock-nym-api: fix .storybook lint error ([#3178])
- Validating new interval config parameters to prevent division by zero ([#3153])
[#2979]: https://github.com/nymtech/nym/issues/2979
[#2332]: https://github.com/nymtech/nym/issues/2332
[#3168]: https://github.com/nymtech/nym/issues/3168
[#3026]: https://github.com/nymtech/nym/issues/3026
[#2213]: https://github.com/nymtech/nym/issues/2213
[#3180]: https://github.com/nymtech/nym/pull/3180
[#3178]: https://github.com/nymtech/nym/pull/3178
[#3153]: https://github.com/nymtech/nym/pull/3153
## [v1.1.12] (2023-03-07)
- Fix generated docs for mixnet and vesting contract on docs.rs ([#3093])
- Introduce a way of injecting topology into the client ([#3044])
- Update mixnet TypeScript client methods #1 ([#2783])
- Update tooltips for routing and average score ([#3133])
- update selected service provider description style ([#3128])
[#3093]: https://github.com/nymtech/nym/issues/3093
[#3044]: https://github.com/nymtech/nym/issues/3044
[#2783]: https://github.com/nymtech/nym/issues/2783
[#3133]: https://github.com/nymtech/nym/pull/3133
[#3128]: https://github.com/nymtech/nym/pull/3128
## [v1.1.11] (2023-02-28)
- Fix empty dealer set loop ([#3105])
- The nym-api db.sqlite is broken when trying to run against it it in `enabled-credentials-mode true` there is an ordering issue with migrations when using the credential binary to purchase bandwidth ([#3100])
- Feature/latency based gateway selection ([#3081])
- Fix the credential binary to handle transactions to sleep when in non-inProgress epochs ([#3057])
- Publish mixnet contract to crates.io ([#1919])
- Publish vesting contract to crates.io ([#1920])
- Feature/update checker to use master ([#3097])
- Feature/improve binary checks ([#3094])
[#3105]: https://github.com/nymtech/nym/issues/3105
[#3100]: https://github.com/nymtech/nym/issues/3100
[#3081]: https://github.com/nymtech/nym/pull/3081
[#3057]: https://github.com/nymtech/nym/issues/3057
[#1919]: https://github.com/nymtech/nym/issues/1919
[#1920]: https://github.com/nymtech/nym/issues/1920
[#3097]: https://github.com/nymtech/nym/pull/3097
[#3094]: https://github.com/nymtech/nym/pull/3094
## [v1.1.10] (2023-02-21)
- Verloc listener causing mixnode unexpected shutdown ([#3038])
Generated
+985 -864
View File
File diff suppressed because it is too large Load Diff
+12 -2
View File
@@ -105,9 +105,19 @@ edition = "2021"
license = "Apache-2.0"
[workspace.dependencies]
async-trait = "0.1.63"
async-trait = "0.1.64"
cfg-if = "1.0.0"
dotenv = "0.15.0"
cosmwasm-derive = "=1.0.0"
cosmwasm-schema = "=1.0.0"
cosmwasm-std = "=1.0.0"
cosmwasm-storage = "=1.0.0"
cw-utils = "=0.13.4"
cw-storage-plus = "=0.13.4"
cw2 = { version = "=0.13.4" }
cw3 = { version = "=0.13.4" }
cw3-fixed-multisig = { version = "=0.13.4" }
cw4 = { version = "=0.13.4" }
dotenvy = "0.15.6"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
+16 -5
View File
@@ -1,13 +1,22 @@
test: clippy-all cargo-test wasm fmt
test-no-mobile: clippy-all-no-mobile cargo-test-no-mobile wasm fmt-no-mobile
test-all: test cargo-test-expensive
test-all-no-mobile: test-no-mobile cargo-test-expensive
no-clippy: build cargo-test wasm fmt
no-clippy-no-mobile: build-no-mobile cargo-test-no-mobile wasm fmt-no-mobile
happy: fmt clippy-happy test
clippy-all: clippy-main clippy-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-connect-mobile clippy-all-wasm-client
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect clippy-happy-connect-mobile
cargo-test: test-main test-contracts test-wallet test-connect test-connect-mobile
happy-no-mobile: fmt-no-mobile clippy-happy-no-mobile test-no-mobile
clippy-all: clippy-all-no-mobile clippy-all-connect-mobile
clippy-all-no-mobile: clippy-main clippy-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-wasm-client
clippy-happy: clippy-happy-no-mobile clippy-happy-connect-mobile
clippy-happy-no-mobile: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect
cargo-test: cargo-test-no-mobile test-connect-mobile
cargo-test-no-mobile: test-main test-contracts test-wallet test-connect
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive
build: build-contracts build-wallet build-main build-main-examples build-connect build-connect-mobile build-wasm-client
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-connect-mobile fmt-wasm-client
build: build-no-mobile build-connect-mobile
build-no-mobile: build-contracts build-wallet build-main build-main-examples build-connect build-wasm-client
fmt: fmt-no-mobile fmt-connect-mobile
fmt-no-mobile: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-wasm-client
clippy-happy-main:
cargo clippy
@@ -126,6 +135,8 @@ fmt-wasm-client:
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
wasm-opt -Os contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
wasm-opt -Os contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm
mixnet-opt: wasm
cd contracts/mixnet && make opt
+1 -1
View File
@@ -16,7 +16,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)
[![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
[![Build Status](https://img.shields.io/github/actions/workflow/status/nymtech/nym/build.yml?branch=develop&style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
### Building
+11 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "1.1.10"
version = "1.1.13"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.66"
@@ -8,7 +8,7 @@ rust-version = "1.66"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { version = "0.1.58" }
async-trait = { workspace = true }
dirs = "4.0"
dashmap = "5.4.0"
futures = "0.3"
@@ -20,6 +20,7 @@ serde_json = "1.0.89"
tap = "1.0.1"
thiserror = "1.0.34"
url = { version ="2.2", features = ["serde"] }
tungstenite = { version = "0.13.0", default-features = false }
tokio = { version = "1.24.1", features = ["macros"]}
time = "0.3.17"
@@ -36,6 +37,10 @@ nym-topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
nym-task = { path = "../../common/task" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.validator-client]
path = "../../common/client-libs/validator-client"
features = ["nyxd-client"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
version = "0.1.11"
features = ["time"]
@@ -44,6 +49,9 @@ features = ["time"]
version = "1.24.1"
features = ["time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
version = "0.6.2"
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
@@ -65,6 +73,7 @@ features = ["futures"]
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
path = "../../common/wasm-utils"
features = ["websocket"]
[target."cfg(target_arch = \"wasm32\")".dependencies.time]
version = "0.3.17"
@@ -15,6 +15,7 @@ use crate::client::replies::reply_controller::{ReplyControllerReceiver, ReplyCon
use crate::client::replies::reply_storage::{
CombinedReplyStorage, PersistentReplyStorage, ReplyStorageBackend, SentReplyKeys,
};
use crate::client::topology_control::nym_api_provider::NymApiTopologyProvider;
use crate::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
@@ -37,10 +38,12 @@ use nym_sphinx::addressing::nodes::NodeIdentity;
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
use nym_task::{TaskClient, TaskManager};
use nym_topology::provider_trait::TopologyProvider;
use std::sync::Arc;
use std::time::Duration;
use tap::TapFallible;
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::CosmWasmClient;
@@ -90,6 +93,7 @@ impl ClientOutput {
pub struct ClientState {
pub shared_lane_queue_lengths: LaneQueueLengths,
pub reply_controller_sender: ReplyControllerSender,
pub topology_accessor: TopologyAccessor,
}
pub enum ClientInputStatus {
@@ -154,6 +158,7 @@ pub struct BaseClientBuilder<'a, B, C: Clone> {
nym_api_endpoints: Vec<Url>,
reply_storage_backend: B,
custom_topology_provider: Option<Box<dyn TopologyProvider>>,
bandwidth_controller: Option<BandwidthController<C>>,
key_manager: KeyManager,
}
@@ -177,6 +182,7 @@ where
bandwidth_controller,
reply_storage_backend,
key_manager,
custom_topology_provider: None,
}
}
@@ -195,11 +201,17 @@ where
disabled_credentials: credentials_toggle.is_disabled(),
nym_api_endpoints,
reply_storage_backend,
custom_topology_provider: None,
bandwidth_controller,
key_manager,
}
}
pub fn with_topology_provider(mut self, provider: Box<dyn TopologyProvider>) -> Self {
self.custom_topology_provider = Some(provider);
self
}
pub fn as_mix_recipient(&self) -> Recipient {
Recipient::new(
*self.key_manager.identity_keypair().public_key(),
@@ -304,7 +316,7 @@ where
}
let gateway_address = self.gateway_config.gateway_listener.clone();
if gateway_address.is_empty() {
return Err(ClientCoreError::GatwayAddressUnknown);
return Err(ClientCoreError::GatewayAddressUnknown);
}
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
@@ -341,25 +353,38 @@ where
Ok(gateway_client)
}
fn setup_topology_provider(
custom_provider: Option<Box<dyn TopologyProvider>>,
nym_api_urls: Vec<Url>,
) -> Box<dyn TopologyProvider> {
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| {
Box::new(NymApiTopologyProvider::new(
nym_api_urls,
env!("CARGO_PKG_VERSION").to_string(),
))
})
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(
nym_api_urls: Vec<Url>,
topology_provider: Box<dyn TopologyProvider>,
refresh_rate: Duration,
topology_accessor: TopologyAccessor,
shutdown: TaskClient,
) -> Result<(), ClientCoreError> {
let topology_refresher_config = TopologyRefresherConfig::new(
nym_api_urls,
refresh_rate,
env!("CARGO_PKG_VERSION").to_string(),
let topology_refresher_config = TopologyRefresherConfig::new(refresh_rate);
let mut topology_refresher = TopologyRefresher::new(
topology_refresher_config,
topology_accessor,
topology_provider,
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
topology_refresher.try_refresh().await;
if let Err(err) = topology_refresher.ensure_topology_is_routable().await {
log::error!(
@@ -468,8 +493,12 @@ where
)
.await?;
let topology_provider = Self::setup_topology_provider(
self.custom_topology_provider.take(),
self.nym_api_endpoints,
);
Self::start_topology_refresher(
self.nym_api_endpoints.clone(),
topology_provider,
self.debug_config.topology_refresh_rate,
shared_topology_accessor.clone(),
task_manager.subscribe(),
@@ -530,7 +559,7 @@ where
self.debug_config,
self.key_manager.ack_key(),
self_address,
shared_topology_accessor,
shared_topology_accessor.clone(),
sphinx_message_sender,
task_manager.subscribe(),
);
@@ -554,6 +583,7 @@ where
client_state: ClientState {
shared_lane_queue_lengths,
reply_controller_sender,
topology_accessor: shared_topology_accessor,
},
task_manager,
})
@@ -333,7 +333,7 @@ where
fn poll_poisson(&mut self, cx: &mut Context<'_>) -> Poll<Option<StreamMessage>> {
// The average delay could change depending on if backpressure in the downstream channel
// (mix_tx) was detected.
self.adjust_current_average_message_sending_delay();
//self.adjust_current_average_message_sending_delay();
let avg_delay = self.current_average_message_sending_delay();
// Start by checking if we have any incoming messages about closed connections
@@ -155,18 +155,21 @@ impl Backend {
// (assuming no key rotation has happened)
// but the way it's currently coded, everyone will purge old data
let since_last_flush = OffsetDateTime::now_utc() - last_flush;
if since_last_flush.whole_days() > 0 {
info!("it's been over {} days and {} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
let days = since_last_flush.whole_days();
let hours = since_last_flush.whole_hours() % 24;
if days > 0 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now.");
manager.delete_all_reply_surb_data().await?;
}
if since_last_flush.whole_days() > 1 {
info!("it's been over {} days and {} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
if days > 1 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now.");
manager.delete_all_reply_keys().await?;
}
if since_last_flush.whole_days() > 2 {
info!("it's been over {} days and {} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
if days > 2 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.");
manager.delete_all_tags().await?;
}
@@ -1,336 +0,0 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
use futures::StreamExt;
use log::*;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::DEFAULT_NUM_MIX_HOPS;
use nym_topology::{nym_topology_from_detailed, NymTopology, NymTopologyError};
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{RwLock, RwLockReadGuard};
use url::Url;
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
#[derive(Debug)]
pub struct TopologyAccessorInner(Option<NymTopology>);
impl AsRef<Option<NymTopology>> for TopologyAccessorInner {
fn as_ref(&self) -> &Option<NymTopology> {
&self.0
}
}
impl TopologyAccessorInner {
fn new() -> Self {
TopologyAccessorInner(None)
}
fn update(&mut self, new: Option<NymTopology>) {
self.0 = new;
}
}
pub struct TopologyReadPermit<'a> {
permit: RwLockReadGuard<'a, TopologyAccessorInner>,
}
impl<'a> Deref for TopologyReadPermit<'a> {
type Target = TopologyAccessorInner;
fn deref(&self) -> &Self::Target {
&self.permit
}
}
impl<'a> TopologyReadPermit<'a> {
/// Using provided topology read permit, tries to get an immutable reference to the underlying
/// topology. For obvious reasons the lifetime of the topology reference is bound to the permit.
pub(super) fn try_get_valid_topology_ref(
&'a self,
ack_recipient: &Recipient,
packet_recipient: Option<&Recipient>,
) -> Result<&'a NymTopology, NymTopologyError> {
// 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through?
let topology = self
.permit
.as_ref()
.as_ref()
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
// 2. does it have any mixnode at all?
// 3. does it have any gateways at all?
// 4. does it have a mixnode on each layer?
topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?;
// 5. does it contain OUR gateway (so that we could create an ack packet)?
if !topology.gateway_exists(ack_recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: ack_recipient.gateway().to_base58_string(),
});
}
// 6. for our target recipient, does it contain THEIR gateway (so that we could create
if let Some(recipient) = packet_recipient {
if !topology.gateway_exists(recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: recipient.gateway().to_base58_string(),
});
}
}
Ok(topology)
}
}
impl<'a> From<RwLockReadGuard<'a, TopologyAccessorInner>> for TopologyReadPermit<'a> {
fn from(read_permit: RwLockReadGuard<'a, TopologyAccessorInner>) -> Self {
TopologyReadPermit {
permit: read_permit,
}
}
}
#[derive(Clone, Debug)]
pub struct TopologyAccessor {
// `RwLock` *seems to* be the better approach for this as write access is only requested every
// few seconds, while reads are needed every single packet generated.
// However, proper benchmarks will be needed to determine if `RwLock` is indeed a better
// approach than a `Mutex`
inner: Arc<RwLock<TopologyAccessorInner>>,
}
impl TopologyAccessor {
pub fn new() -> Self {
TopologyAccessor {
inner: Arc::new(RwLock::new(TopologyAccessorInner::new())),
}
}
pub async fn get_read_permit(&self) -> TopologyReadPermit<'_> {
self.inner.read().await.into()
}
async fn update_global_topology(&self, new_topology: Option<NymTopology>) {
self.inner.write().await.update(new_topology);
}
// only used by the client at startup to get a slightly more reasonable error message
// (currently displays as unused because health checker is disabled due to required changes)
pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> {
match &self.inner.read().await.0 {
None => Err(NymTopologyError::EmptyNetworkTopology),
Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
}
}
}
impl Default for TopologyAccessor {
fn default() -> Self {
TopologyAccessor::new()
}
}
pub struct TopologyRefresherConfig {
nym_api_urls: Vec<Url>,
refresh_rate: Duration,
client_version: String,
}
impl TopologyRefresherConfig {
pub fn new(nym_api_urls: Vec<Url>, refresh_rate: Duration, client_version: String) -> Self {
TopologyRefresherConfig {
nym_api_urls,
refresh_rate,
client_version,
}
}
}
pub struct TopologyRefresher {
validator_client: validator_client::client::NymApiClient,
client_version: String,
nym_api_urls: Vec<Url>,
topology_accessor: TopologyAccessor,
refresh_rate: Duration,
currently_used_api: usize,
was_latest_valid: bool,
}
impl TopologyRefresher {
pub fn new(mut cfg: TopologyRefresherConfig, topology_accessor: TopologyAccessor) -> Self {
cfg.nym_api_urls.shuffle(&mut thread_rng());
TopologyRefresher {
validator_client: validator_client::client::NymApiClient::new(
cfg.nym_api_urls[0].clone(),
),
client_version: cfg.client_version,
nym_api_urls: cfg.nym_api_urls,
topology_accessor,
refresh_rate: cfg.refresh_rate,
currently_used_api: 0,
was_latest_valid: true,
}
}
fn use_next_nym_api(&mut self) {
if self.nym_api_urls.len() == 1 {
warn!("There's only a single nym API available - it won't be possible to use a different one");
return;
}
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
self.validator_client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
}
/// Verifies whether nodes a reasonably distributed among all mix layers.
///
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
/// However, this is a rather unrealistic expectation, instead we check whether there exists
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
///
/// # Arguments
///
/// * `topology`: active topology constructed from validator api data
fn check_layer_distribution(&self, active_topology: &NymTopology) -> bool {
let mixes = active_topology.mixes();
let mixnodes_count = active_topology.num_mixnodes();
if active_topology.gateways().is_empty() {
return false;
}
// trivial check to see if have at least a single node on each layer (regardless of active set size)
if mixes.get(&1).is_none() || mixes.get(&2).is_none() || mixes.get(&3).is_none() {
return false;
}
let upper_bound = (mixnodes_count as f32 * 0.66) as usize;
let lower_bound = (mixnodes_count as f32 * 0.15) as usize;
let layer1 = mixes.get(&1).unwrap().len();
let layer2 = mixes.get(&2).unwrap().len();
let layer3 = mixes.get(&3).unwrap().len();
if layer1 < lower_bound || layer1 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
if layer2 < lower_bound || layer2 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
if layer3 < lower_bound || layer3 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
true
}
async fn get_current_compatible_topology(&self) -> Option<NymTopology> {
// TODO: optimization for the future:
// only refresh mixnodes on timer and refresh gateways only when
// we have to send to a new, unknown, gateway
let mixnodes = match self.validator_client.get_cached_active_mixnodes().await {
Err(err) => {
error!("failed to get network mixnodes - {err}");
return None;
}
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
}
Ok(gateways) => gateways,
};
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
if !self.check_layer_distribution(&topology) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
None
} else {
Some(topology)
}
}
pub async fn refresh(&mut self) {
trace!("Refreshing the topology");
let new_topology = self.get_current_compatible_topology().await;
if new_topology.is_none() {
self.use_next_nym_api();
}
if new_topology.is_none() && self.was_latest_valid {
// if we failed to grab this topology, but the one before it was alright, let's assume
// validator had a tiny hiccup and use the old data
warn!("we're going to keep on using the old topology for this iteration");
self.was_latest_valid = false;
return;
} else if new_topology.is_some() {
self.was_latest_valid = true;
}
self.topology_accessor
.update_global_topology(new_topology)
.await;
}
pub async fn ensure_topology_is_routable(&self) -> Result<(), NymTopologyError> {
self.topology_accessor.ensure_is_routable().await
}
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
spawn_future(async move {
debug!("Started TopologyRefresher with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while !shutdown.is_shutdown() {
tokio::select! {
_ = interval.next() => {
self.refresh().await;
},
_ = shutdown.recv() => {
log::trace!("TopologyRefresher: Received shutdown");
},
}
}
shutdown.recv_timeout().await;
log::debug!("TopologyRefresher: Exiting");
})
}
}
@@ -0,0 +1,154 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::params::DEFAULT_NUM_MIX_HOPS;
use nym_topology::{NymTopology, NymTopologyError};
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::{Notify, RwLock, RwLockReadGuard};
#[derive(Debug)]
pub struct TopologyAccessorInner {
controlled_manually: AtomicBool,
released_manual_control: Notify,
// `RwLock` *seems to* be the better approach for this as write access is only requested every
// few seconds, while reads are needed every single packet generated.
// However, proper benchmarks will be needed to determine if `RwLock` is indeed a better
// approach than a `Mutex`
topology: RwLock<Option<NymTopology>>,
}
impl TopologyAccessorInner {
fn new() -> Self {
TopologyAccessorInner {
controlled_manually: AtomicBool::new(false),
released_manual_control: Notify::new(),
topology: RwLock::new(None),
}
}
async fn update(&self, new: Option<NymTopology>) {
*self.topology.write().await = new;
}
}
pub struct TopologyReadPermit<'a> {
permit: RwLockReadGuard<'a, Option<NymTopology>>,
}
impl<'a> Deref for TopologyReadPermit<'a> {
type Target = Option<NymTopology>;
fn deref(&self) -> &Self::Target {
&self.permit
}
}
impl<'a> TopologyReadPermit<'a> {
/// Using provided topology read permit, tries to get an immutable reference to the underlying
/// topology. For obvious reasons the lifetime of the topology reference is bound to the permit.
pub(crate) fn try_get_valid_topology_ref(
&'a self,
ack_recipient: &Recipient,
packet_recipient: Option<&Recipient>,
) -> Result<&'a NymTopology, NymTopologyError> {
// 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through?
let topology = self
.permit
.as_ref()
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
// 2. does it have any mixnode at all?
// 3. does it have any gateways at all?
// 4. does it have a mixnode on each layer?
topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?;
// 5. does it contain OUR gateway (so that we could create an ack packet)?
if !topology.gateway_exists(ack_recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: ack_recipient.gateway().to_base58_string(),
});
}
// 6. for our target recipient, does it contain THEIR gateway (so that we could create
if let Some(recipient) = packet_recipient {
if !topology.gateway_exists(recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: recipient.gateway().to_base58_string(),
});
}
}
Ok(topology)
}
}
impl<'a> From<RwLockReadGuard<'a, Option<NymTopology>>> for TopologyReadPermit<'a> {
fn from(read_permit: RwLockReadGuard<'a, Option<NymTopology>>) -> Self {
TopologyReadPermit {
permit: read_permit,
}
}
}
#[derive(Clone, Debug)]
pub struct TopologyAccessor {
inner: Arc<TopologyAccessorInner>,
}
impl TopologyAccessor {
pub fn new() -> Self {
TopologyAccessor {
inner: Arc::new(TopologyAccessorInner::new()),
}
}
pub fn controlled_manually(&self) -> bool {
self.inner.controlled_manually.load(Ordering::SeqCst)
}
pub async fn get_read_permit(&self) -> TopologyReadPermit<'_> {
self.inner.topology.read().await.into()
}
pub(crate) async fn update_global_topology(&self, new_topology: Option<NymTopology>) {
self.inner.update(new_topology).await;
}
pub(crate) async fn wait_for_released_manual_control(&self) {
self.inner.released_manual_control.notified().await
}
pub async fn current_topology(&self) -> Option<NymTopology> {
self.inner.topology.read().await.clone()
}
pub async fn manually_change_topology(&self, new_topology: NymTopology) {
self.inner.controlled_manually.store(true, Ordering::SeqCst);
self.inner.update(Some(new_topology)).await;
}
pub fn release_manual_control(&self) {
self.inner
.controlled_manually
.store(false, Ordering::SeqCst);
self.inner.released_manual_control.notify_waiters();
}
// only used by the client at startup to get a slightly more reasonable error message
// (currently displays as unused because health checker is disabled due to required changes)
pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> {
match self.inner.topology.read().await.deref() {
None => Err(NymTopologyError::EmptyNetworkTopology),
Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
}
}
}
impl Default for TopologyAccessor {
fn default() -> Self {
TopologyAccessor::new()
}
}
@@ -0,0 +1,115 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::spawn_future;
pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit};
use futures::StreamExt;
use log::*;
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::NymTopologyError;
use std::time::Duration;
mod accessor;
pub(crate) mod nym_api_provider;
// TODO: move it to config later
const MAX_FAILURE_COUNT: usize = 10;
pub struct TopologyRefresherConfig {
refresh_rate: Duration,
}
impl TopologyRefresherConfig {
pub fn new(refresh_rate: Duration) -> Self {
TopologyRefresherConfig { refresh_rate }
}
}
pub struct TopologyRefresher {
topology_provider: Box<dyn TopologyProvider>,
topology_accessor: TopologyAccessor,
refresh_rate: Duration,
consecutive_failure_count: usize,
}
impl TopologyRefresher {
pub fn new(
cfg: TopologyRefresherConfig,
topology_accessor: TopologyAccessor,
topology_provider: Box<dyn TopologyProvider>,
) -> Self {
TopologyRefresher {
topology_provider,
topology_accessor,
refresh_rate: cfg.refresh_rate,
consecutive_failure_count: 0,
}
}
pub fn change_topology_provider(&mut self, provider: Box<dyn TopologyProvider>) {
self.topology_provider = provider;
}
pub async fn try_refresh(&mut self) {
trace!("Refreshing the topology");
if self.topology_accessor.controlled_manually() {
info!("topology is being controlled manually - we're going to wait until the control is released...");
self.topology_accessor
.wait_for_released_manual_control()
.await;
}
let new_topology = self.topology_provider.get_new_topology().await;
if new_topology.is_none() {
warn!("failed to obtain new network topology");
}
if new_topology.is_none() && self.consecutive_failure_count < MAX_FAILURE_COUNT {
// if we failed to grab this topology, but the one before it was alright, let's assume
// validator had a tiny hiccup and use the old data
warn!("we're going to keep on using the old topology for this iteration");
self.consecutive_failure_count += 1;
return;
} else if new_topology.is_some() {
self.consecutive_failure_count = 0;
}
self.topology_accessor
.update_global_topology(new_topology)
.await;
}
pub async fn ensure_topology_is_routable(&self) -> Result<(), NymTopologyError> {
self.topology_accessor.ensure_is_routable().await
}
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
spawn_future(async move {
debug!("Started TopologyRefresher with graceful shutdown support");
#[cfg(not(target_arch = "wasm32"))]
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
self.refresh_rate,
));
#[cfg(target_arch = "wasm32")]
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
while !shutdown.is_shutdown() {
tokio::select! {
_ = interval.next() => {
self.try_refresh().await;
},
_ = shutdown.recv() => {
log::trace!("TopologyRefresher: Received shutdown");
},
}
}
shutdown.recv_timeout().await;
log::debug!("TopologyRefresher: Exiting");
})
}
}
@@ -0,0 +1,106 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use log::{error, warn};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::{nym_topology_from_detailed, NymTopology, NymTopologyError};
use rand::prelude::SliceRandom;
use rand::thread_rng;
use url::Url;
pub(crate) struct NymApiTopologyProvider {
validator_client: validator_client::client::NymApiClient,
nym_api_urls: Vec<Url>,
client_version: String,
currently_used_api: usize,
}
impl NymApiTopologyProvider {
pub(crate) fn new(mut nym_api_urls: Vec<Url>, client_version: String) -> Self {
nym_api_urls.shuffle(&mut thread_rng());
NymApiTopologyProvider {
validator_client: validator_client::client::NymApiClient::new(nym_api_urls[0].clone()),
nym_api_urls,
client_version,
currently_used_api: 0,
}
}
fn use_next_nym_api(&mut self) {
if self.nym_api_urls.len() == 1 {
warn!("There's only a single nym API available - it won't be possible to use a different one");
return;
}
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
self.validator_client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
}
/// Verifies whether nodes a reasonably distributed among all mix layers.
///
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
/// However, this is a rather unrealistic expectation, instead we check whether there exists
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
///
/// # Arguments
///
/// * `topology`: active topology constructed from validator api data
fn check_layer_distribution(
&self,
active_topology: &NymTopology,
) -> Result<(), NymTopologyError> {
let lower_threshold = 0.15;
let upper_threshold = 0.66;
active_topology.ensure_even_layer_distribution(lower_threshold, upper_threshold)
}
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
let mixnodes = match self.validator_client.get_cached_active_mixnodes().await {
Err(err) => {
error!("failed to get network mixnodes - {err}");
return None;
}
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
}
Ok(gateways) => gateways,
};
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
if let Err(err) = self.check_layer_distribution(&topology) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used: {err}");
self.use_next_nym_api();
None
} else {
Some(topology)
}
}
}
// hehe, wasm
#[cfg(not(target_arch = "wasm32"))]
#[async_trait]
impl TopologyProvider for NymApiTopologyProvider {
async fn get_new_topology(&mut self) -> Option<NymTopology> {
self.get_current_compatible_topology().await
}
}
#[cfg(target_arch = "wasm32")]
#[async_trait(?Send)]
impl TopologyProvider for NymApiTopologyProvider {
async fn get_new_topology(&mut self) -> Option<NymTopology> {
self.get_current_compatible_topology().await
}
}
+24 -2
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use nym_config::defaults::NymNetworkDetails;
use nym_config::{NymConfig, OptionalSet, DB_FILE_NAME};
use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME};
use nym_sphinx::params::PacketSize;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
@@ -579,7 +579,7 @@ impl<T: NymConfig> Client<T> {
}
fn default_database_path(id: &str) -> PathBuf {
T::default_data_directory(id).join(DB_FILE_NAME)
T::default_data_directory(id).join(CRED_DB_FILE_NAME)
}
}
@@ -697,6 +697,17 @@ pub enum ExtendedPacketSize {
Extended8,
Extended16,
Extended32,
Extended10,
Extended15,
Extended20,
Extended25,
Extended50,
Extended100,
Extended150,
Extended200,
Extended250,
Extended500,
}
impl Default for DebugConfig {
@@ -734,6 +745,17 @@ impl From<ExtendedPacketSize> for PacketSize {
ExtendedPacketSize::Extended8 => PacketSize::ExtendedPacket8,
ExtendedPacketSize::Extended16 => PacketSize::ExtendedPacket16,
ExtendedPacketSize::Extended32 => PacketSize::ExtendedPacket32,
ExtendedPacketSize::Extended10 => PacketSize::ExtendedPacket10,
ExtendedPacketSize::Extended15 => PacketSize::ExtendedPacket15,
ExtendedPacketSize::Extended20 => PacketSize::ExtendedPacket20,
ExtendedPacketSize::Extended25 => PacketSize::ExtendedPacket25,
ExtendedPacketSize::Extended50 => PacketSize::ExtendedPacket50,
ExtendedPacketSize::Extended100 => PacketSize::ExtendedPacket100,
ExtendedPacketSize::Extended150 => PacketSize::ExtendedPacket150,
ExtendedPacketSize::Extended200 => PacketSize::ExtendedPacket200,
ExtendedPacketSize::Extended250 => PacketSize::ExtendedPacket250,
ExtendedPacketSize::Extended500 => PacketSize::ExtendedPacket500,
}
}
}
+27 -1
View File
@@ -3,6 +3,7 @@
use gateway_client::error::GatewayClientError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_topology::gateway::GatewayConversionError;
use nym_topology::NymTopologyError;
use validator_client::ValidatorClientError;
@@ -53,7 +54,32 @@ pub enum ClientCoreError {
GatewayOwnerUnknown,
#[error("The address of the gateway is unknown - did you run init?")]
GatwayAddressUnknown,
GatewayAddressUnknown,
#[error("The gateway is malformed: {source}")]
MalformedGateway {
#[from]
source: GatewayConversionError,
},
#[error("failed to establish connection to gateway: {source}")]
GatewayConnectionFailure {
#[from]
source: tungstenite::Error,
},
#[cfg(target_arch = "wasm32")]
#[error("failed to establish gateway connection (wasm)")]
GatewayJsConnectionFailure,
#[error("Gateway connection was abruptly closed")]
GatewayConnectionAbruptlyClosed,
#[error("Timed out while trying to establish gateway connection")]
GatewayConnectionTimeout,
#[error("No ping measurements for the gateway ({identity}) performed")]
NoGatewayMeasurements { identity: String },
#[error("failed to register receiver for reconstructed mixnet messages")]
FailedToRegisterReceiver,
+195 -24
View File
@@ -6,52 +6,223 @@ use crate::{
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
error::ClientCoreError,
};
#[cfg(target_arch = "wasm32")]
use gateway_client::wasm_mockups::SigningNyxdClient;
use futures::{SinkExt, StreamExt};
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
use log::{debug, info, trace, warn};
use nym_config::NymConfig;
use nym_crypto::asymmetric::identity;
use nym_topology::{filter::VersionFilterable, gateway};
use rand::{seq::SliceRandom, thread_rng};
use rand::{seq::SliceRandom, thread_rng, Rng};
use std::{sync::Arc, time::Duration};
use tap::TapFallible;
use tungstenite::Message;
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(not(target_arch = "wasm32"))]
use validator_client::nyxd::SigningNyxdClient;
pub(super) async fn query_gateway_details(
validator_servers: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
) -> Result<gateway::Node, ClientCoreError> {
let nym_api = validator_servers
.choose(&mut thread_rng())
#[cfg(not(target_arch = "wasm32"))]
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
#[cfg(target_arch = "wasm32")]
use gateway_client::wasm_mockups::SigningNyxdClient;
#[cfg(target_arch = "wasm32")]
use wasm_timer::Instant;
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
#[cfg(target_arch = "wasm32")]
type WsConn = JSWebsocket;
const MEASUREMENTS: usize = 3;
#[cfg(not(target_arch = "wasm32"))]
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
const PING_TIMEOUT: Duration = Duration::from_millis(1000);
struct GatewayWithLatency {
gateway: gateway::Node,
latency: Duration,
}
impl GatewayWithLatency {
fn new(gateway: gateway::Node, latency: Duration) -> Self {
GatewayWithLatency { gateway, latency }
}
}
async fn current_gateways<R: Rng>(
rng: &mut R,
nym_apis: Vec<Url>,
) -> Result<Vec<gateway::Node>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let validator_client = validator_client::client::NymApiClient::new(nym_api.clone());
let client = validator_client::client::NymApiClient::new(nym_api.clone());
log::trace!("Fetching list of gateways from: {}", nym_api);
let gateways = validator_client.get_cached_gateways().await?;
let gateways = client.get_cached_gateways().await?;
let valid_gateways = gateways
.into_iter()
.filter_map(|gateway| gateway.try_into().ok())
.collect::<Vec<gateway::Node>>();
// we were always filtering by version so I'm not removing that 'feature'
let filtered_gateways = valid_gateways.filter_by_version(env!("CARGO_PKG_VERSION"));
Ok(filtered_gateways)
}
// if we have chosen particular gateway - use it, otherwise choose a random one.
// (remember that in active topology all gateways have at least 100 reputation so should
// be working correctly)
if let Some(gateway_id) = chosen_gateway_id {
filtered_gateways
.iter()
.find(|gateway| gateway.identity_key == gateway_id)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_id.to_string()))
.cloned()
#[cfg(not(target_arch = "wasm32"))]
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
Ok(Err(conn_failure)) => Err(conn_failure.into()),
Ok(Ok((stream, _))) => Ok(stream),
}
}
#[cfg(target_arch = "wasm32")]
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure)
}
async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, ClientCoreError> {
let addr = gateway.clients_address();
trace!(
"establishing connection to {} ({addr})...",
gateway.identity_key,
);
let mut stream = connect(&addr).await?;
let mut results = Vec::new();
for _ in 0..MEASUREMENTS {
let measurement_future = async {
let ping_content = vec![1, 2, 3];
let start = Instant::now();
stream.send(Message::Ping(ping_content.clone())).await?;
match stream.next().await {
Some(Ok(Message::Pong(content))) => {
if content == ping_content {
let elapsed = Instant::now().duration_since(start);
trace!("current ping time: {elapsed:?}");
results.push(elapsed);
} else {
warn!("received a pong message with different content? wtf.")
}
}
Some(Ok(_)) => warn!("received a message that's not a pong!"),
Some(Err(err)) => return Err(err.into()),
None => return Err(ClientCoreError::GatewayConnectionAbruptlyClosed),
}
Ok::<(), ClientCoreError>(())
};
// thanks to wasm we can't use tokio::time::timeout : (
#[cfg(not(target_arch = "wasm32"))]
let timeout = tokio::time::sleep(PING_TIMEOUT);
#[cfg(not(target_arch = "wasm32"))]
tokio::pin!(timeout);
#[cfg(target_arch = "wasm32")]
let mut timeout = wasm_timer::Delay::new(PING_TIMEOUT);
tokio::select! {
_ = &mut timeout => {
warn!("timed out while trying to perform measurement...")
}
res = measurement_future => res?,
}
}
let count = results.len() as u64;
if count == 0 {
return Err(ClientCoreError::NoGatewayMeasurements {
identity: gateway.identity_key.to_base58_string(),
});
}
let sum: Duration = results.into_iter().sum();
let avg = Duration::from_nanos(sum.as_nanos() as u64 / count);
Ok(GatewayWithLatency::new(gateway, avg))
}
async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
info!("choosing gateway by latency...");
let mut gateways_with_latency = Vec::new();
for gateway in gateways {
let id = *gateway.identity();
trace!("measuring latency to {id}...");
let with_latency = match measure_latency(gateway).await {
Ok(res) => res,
Err(err) => {
warn!("failed to measure {id}: {err}");
continue;
}
};
debug!(
"{id} ({}): {:?}",
with_latency.gateway.location, with_latency.latency
);
gateways_with_latency.push(with_latency)
}
let chosen = gateways_with_latency
.choose_weighted(rng, |item| 1. / item.latency.as_secs_f32())
.expect("invalid selection weight!");
info!(
"chose gateway {} (located at {}) with average latency of {:?}",
chosen.gateway.identity_key, chosen.gateway.location, chosen.latency
);
Ok(chosen.gateway.clone())
}
fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: Vec<gateway::Node>,
) -> Result<gateway::Node, ClientCoreError> {
gateways
.choose(rng)
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
}
pub(super) async fn query_gateway_details(
validator_servers: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<gateway::Node, ClientCoreError> {
let mut rng = thread_rng();
let gateways = current_gateways(&mut rng, validator_servers).await?;
// if we set an explicit gateway, use that one and nothing else
if let Some(explicitly_chosen) = chosen_gateway_id {
gateways
.into_iter()
.find(|gateway| gateway.identity_key == explicitly_chosen)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(explicitly_chosen.to_string()))
} else if by_latency {
choose_gateway_by_latency(&mut rng, gateways).await
} else {
filtered_gateways
.choose(&mut rand::thread_rng())
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
uniformly_random_gateway(&mut rng, gateways)
}
}
+10 -4
View File
@@ -77,9 +77,11 @@ pub async fn register_with_gateway(
key_manager: &mut KeyManager,
nym_api_endpoints: Vec<Url>,
chosen_gateway_id: Option<identity::PublicKey>,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError> {
// Get the gateway details of the gateway we will use
let gateway = helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id).await?;
let gateway =
helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id, by_latency).await?;
log::debug!("Querying gateway gives: {}", gateway);
let our_identity = key_manager.identity_keypair();
@@ -102,6 +104,7 @@ pub async fn setup_gateway_from_config<C, T>(
register_gateway: bool,
user_chosen_gateway_id: Option<identity::PublicKey>,
config: &Config<T>,
by_latency: bool,
) -> Result<GatewayEndpointConfig, ClientCoreError>
where
C: NymConfig + ClientCoreConfigTrait,
@@ -117,9 +120,12 @@ where
}
// Else, we preceed by querying the nym-api
let gateway =
helpers::query_gateway_details(config.get_nym_api_endpoints(), user_chosen_gateway_id)
.await?;
let gateway = helpers::query_gateway_details(
config.get_nym_api_endpoints(),
user_chosen_gateway_id,
by_latency,
)
.await?;
log::debug!("Querying gateway gives: {}", gateway);
// If we are not registering, just return this and assume the caller has the keys already and
+3 -3
View File
@@ -15,10 +15,10 @@ thiserror = "1.0"
url = "2.2"
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
coconut-interface = { path = "../../common/coconut-interface" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
nym-credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
nym-bin-common = { path = "../../common/bin-common"}
nym-network-defaults = { path = "../../common/network-defaults" }
+5 -5
View File
@@ -7,11 +7,11 @@ use nym_bin_common::completions::ArgShell;
use rand::rngs::OsRng;
use std::str::FromStr;
use coconut_interface::{Base58, Parameters};
use credential_storage::storage::Storage;
use credential_storage::PersistentStorage;
use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use credentials::coconut::utils::obtain_aggregate_signature;
use nym_coconut_interface::{Base58, Parameters};
use nym_credential_storage::storage::Storage;
use nym_credential_storage::PersistentStorage;
use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use nym_credentials::coconut::utils::obtain_aggregate_signature;
use nym_crypto::asymmetric::{encryption, identity};
use nym_network_defaults::VOUCHER_INFO;
use validator_client::nyxd::traits::DkgQueryClient;
+2 -2
View File
@@ -4,8 +4,8 @@
use std::time::SystemTimeError;
use thiserror::Error;
use credential_storage::error::StorageError;
use credentials::error::Error as CredentialError;
use nym_credential_storage::error::StorageError;
use nym_credentials::error::Error as CredentialError;
use nym_crypto::asymmetric::encryption::KeyRecoveryError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use validator_client::nyxd::error::NyxdError;
+11 -5
View File
@@ -11,7 +11,7 @@ use commands::*;
use error::Result;
use log::*;
use nym_bin_common::completions::fig_generate;
use nym_config::{DATA_DIR, DB_FILE_NAME};
use nym_config::{CRED_DB_FILE_NAME, DATA_DIR};
use nym_network_defaults::{setup_env, NymNetworkDetails};
use std::process::exit;
use std::time::{Duration, SystemTime};
@@ -51,8 +51,11 @@ async fn block_until_coconut_is_available<C: Clone + CosmWasmClient + Send + Syn
break;
} else {
// Use 20 additional seconds to avoid the exact moment of going into the final epoch state
let secs_until_final = epoch.final_timestamp_secs() + 20 - current_timestamp_secs;
// Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries
let secs_until_final = epoch
.final_timestamp_secs()
.saturating_sub(current_timestamp_secs)
+ 1;
info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final);
std::thread::sleep(Duration::from_secs(secs_until_final));
}
@@ -70,8 +73,11 @@ async fn main() -> Result<()> {
match args.command {
Command::Run(r) => {
let db_path = r.client_home_directory.join(DATA_DIR).join(DB_FILE_NAME);
let shared_storage = credential_storage::initialise_storage(db_path).await;
let db_path = r
.client_home_directory
.join(DATA_DIR)
.join(CRED_DB_FILE_NAME);
let shared_storage = nym_credential_storage::initialise_storage(db_path).await;
let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?;
let network_details = NymNetworkDetails::new_from_env();
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use credentials::coconut::bandwidth::BandwidthVoucher;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use std::fs::{create_dir_all, read_dir, File};
use std::io::{Read, Write};
use std::path::PathBuf;
+2 -2
View File
@@ -1,8 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_interface::Parameters;
use credentials::coconut::bandwidth::BandwidthVoucher;
use nym_coconut_interface::Parameters;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_crypto::asymmetric::{encryption, identity};
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.10"
version = "1.1.13"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -36,10 +36,10 @@ tokio-tungstenite = "0.14" # websocket
## internal
nym-bin-common = { path = "../../common/bin-common" }
client-core = { path = "../client-core", features = ["fs-surb-storage"] }
coconut-interface = { path = "../../common/coconut-interface" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials" }
nym-credential-storage = { path = "../../common/credential-storage" }
nym-credentials = { path = "../../common/credentials" }
nym-crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
+2 -1
View File
@@ -74,7 +74,7 @@ impl SocketClient {
let client = validator_client::Client::new_query(client_config)
.expect("Could not construct query client");
BandwidthController::new(
credential_storage::initialise_storage(config.get_base().get_database_path()).await,
nym_credential_storage::initialise_storage(config.get_base().get_database_path()).await,
client,
)
}
@@ -101,6 +101,7 @@ impl SocketClient {
let ClientState {
shared_lane_queue_lengths,
reply_controller_sender,
..
} = client_state;
let websocket_handler = websocket::HandlerBuilder::new(
+6
View File
@@ -25,6 +25,11 @@ pub(crate) struct Init {
#[clap(long)]
gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
@@ -143,6 +148,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
register_gateway,
user_chosen_gateway_id,
config.get_base(),
args.latency_based_selection,
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
+9 -9
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.10"
version = "1.1.13"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -29,26 +29,26 @@ url = "2.2"
# internal
nym-bin-common = { path = "../../common/bin-common" }
client-core = { path = "../client-core", features = ["fs-surb-storage"] }
coconut-interface = { path = "../../common/coconut-interface" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-config = { path = "../../common/config" }
credential-storage = { path = "../../common/credential-storage", optional = true }
nym-credential-storage = { path = "../../common/credential-storage", optional = true }
mobile-storage = { path = "../../common/mobile-storage", optional = true }
credentials = { path = "../../common/credentials" }
nym-credentials = { path = "../../common/credentials" }
nym-crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
nym-network-defaults = { path = "../../common/network-defaults" }
nym-sphinx = { path = "../../common/nymsphinx" }
ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
nym-ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
nym-pemstore = { path = "../../common/pemstore" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
service-providers-common = { path = "../../service-providers/common" }
socks5-requests = { path = "../../common/socks5/requests" }
nym-socks5-proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
nym-service-providers-common = { path = "../../service-providers/common" }
nym-socks5-requests = { path = "../../common/socks5/requests" }
nym-task = { path = "../../common/task" }
nym-topology = { path = "../../common/topology" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] }
[features]
default = ["credential-storage"]
default = ["nym-credential-storage"]
eth = []
mobile = ["mobile-storage", "gateway-client/mobile"]
+2 -2
View File
@@ -7,10 +7,10 @@ pub use client_core::config::MISSING_VALUE;
use client_core::config::{ClientCoreConfigTrait, DebugConfig};
use nym_config::defaults::DEFAULT_SOCKS5_LISTENING_PORT;
use nym_config::{NymConfig, OptionalSet};
use nym_service_providers_common::interface::ProviderInterfaceVersion;
use nym_socks5_requests::Socks5ProtocolVersion;
use nym_sphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
use service_providers_common::interface::ProviderInterfaceVersion;
use socks5_requests::Socks5ProtocolVersion;
use std::fmt::Debug;
use std::path::PathBuf;
use std::str::FromStr;
+2 -2
View File
@@ -92,7 +92,7 @@ impl NymClient {
#[cfg(not(feature = "mobile"))]
let storage =
credential_storage::initialise_storage(config.get_base().get_database_path()).await;
nym_credential_storage::initialise_storage(config.get_base().get_database_path()).await;
#[cfg(feature = "mobile")]
let storage = mobile_storage::PersistentStorage {};
@@ -123,7 +123,7 @@ impl NymClient {
let ClientState {
shared_lane_queue_lengths,
reply_controller_sender: _,
..
} = client_status;
let authenticator = Authenticator::new(auth_methods, allowed_users);
+6
View File
@@ -37,6 +37,11 @@ pub(crate) struct Init {
#[clap(long)]
gateway: Option<identity::PublicKey>,
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
/// uniformly.
#[clap(long, conflicts_with = "gateway")]
latency_based_selection: bool,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
@@ -149,6 +154,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
register_gateway,
user_chosen_gateway_id,
config.get_base(),
args.latency_based_selection,
)
.await
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
+1 -1
View File
@@ -1,6 +1,6 @@
use crate::socks::types::SocksProxyError;
use client_core::error::ClientCoreError;
use socks5_requests::{ConnectionError, ConnectionId};
use nym_socks5_requests::{ConnectionError, ConnectionId};
#[derive(thiserror::Error, Debug)]
pub enum Socks5ClientError {
+8 -8
View File
@@ -8,19 +8,19 @@ use client_core::client::inbound_messages::{InputMessage, InputMessageSender};
use futures::channel::mpsc;
use futures::task::{Context, Poll};
use log::*;
use nym_service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion};
use nym_socks5_proxy_helpers::connection_controller::{
ConnectionReceiver, ControllerCommand, ControllerSender,
};
use nym_socks5_proxy_helpers::proxy_runner::ProxyRunner;
use nym_socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
};
use nym_sphinx::addressing::clients::Recipient;
use nym_task::connections::{LaneQueueLengths, TransmissionLane};
use nym_task::TaskClient;
use pin_project::pin_project;
use proxy_helpers::connection_controller::{
ConnectionReceiver, ControllerCommand, ControllerSender,
};
use proxy_helpers::proxy_runner::ProxyRunner;
use rand::RngCore;
use service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion};
use socks5_requests::{
ConnectionId, RemoteAddress, Socks5ProtocolVersion, Socks5ProviderRequest, Socks5Request,
};
use std::io;
use std::net::SocketAddr;
use std::pin::Pin;
+3 -3
View File
@@ -4,11 +4,11 @@ use log::*;
use client_core::client::received_buffer::ReconstructedMessagesReceiver;
use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBufferRequestSender};
use nym_service_providers_common::interface::{ControlResponse, ResponseContent};
use nym_socks5_proxy_helpers::connection_controller::ControllerSender;
use nym_socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::TaskClient;
use proxy_helpers::connection_controller::ControllerSender;
use service_providers_common::interface::{ControlResponse, ResponseContent};
use socks5_requests::{Socks5ProviderResponse, Socks5Response, Socks5ResponseContent};
use crate::error::Socks5ClientError;
+1 -1
View File
@@ -8,10 +8,10 @@ use client_core::client::{
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
};
use log::*;
use nym_socks5_proxy_helpers::connection_controller::Controller;
use nym_sphinx::addressing::clients::Recipient;
use nym_task::connections::{ConnectionCommandSender, LaneQueueLengths};
use nym_task::TaskClient;
use proxy_helpers::connection_controller::Controller;
use std::net::SocketAddr;
use tap::TapFallible;
use tokio::net::TcpListener;
+1 -1
View File
@@ -1,4 +1,4 @@
use socks5_requests::Socks5RequestError;
use nym_socks5_requests::Socks5RequestError;
use std::string::FromUtf8Error;
use thiserror::Error;
+68 -80
View File
@@ -1,83 +1,71 @@
{
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 2019,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["prettier", "mocha"],
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"prettier"],
"rules": {
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.[jt]s",
"**/*.spec.[jt]s"
]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"js": "never"
}
]
},
"overrides": [
{
"files": "**/*.ts",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.ts",
"**/*.spec.ts"
]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
"root": true,
"env": {
"browser": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 2019,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["prettier", "mocha"],
"extends": ["airbnb-base", "airbnb-typescript/base", "prettier"],
"rules": {
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": ["**/*.test.[jt]s", "**/*.spec.[jt]s"]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"js": "never"
}
]
},
"overrides": [
{
"files": "**/*.ts",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": ["plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": ["**/*.test.ts", "**/*.spec.ts"]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
],
"ignorePatterns": ["tsconfig.json", "*.d.ts", "dist/**/*", "dist", "node_modules"]
}
+2 -1
View File
@@ -4,4 +4,5 @@ coverage
dist
docs
examples/accounts
node_modules
node_modules
.env
+3 -1
View File
@@ -1,3 +1,5 @@
coverage
node_modules
tests
tests
src
type
+24 -25
View File
@@ -1,40 +1,39 @@
Nym Validator Client
====================
# Nym Validator Client (Typescript)
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
Running examples
-----------------
With the code checked out, `cd examples`. This folder contains runnable example code that will set up a blockchain and allow you to interact with it through the client.
Running tests
-------------
Include the Nym Validator in your project:
```
npm test
yarn add @nymproject/nym-validator-client
```
You can also trigger test execution with a test watcher. I don't have the centuries of life left to me that are needed to fight through the arcana of wiring up a working TypeScript mocha triggered execution setup, so for now my Cargo-based hack is:
Connect to validator and make queries
```
cargo watch -s "cd clients/validator && npm test"
import Validator from '@nymproject/nym-validator-client'
const main = async () => {
const client = await Validator.connectForQuery(rpcAddress, validatorAddress, prefix, mixnetContractAddress, vestingContractAddress, denom)
client.getBalance(address)
}
```
It's ugly but works fine if you have Cargo installed. TypeScript setup help happily accepted here.
Connect to validator for performing actions
Generating Documentation
------------------------
```
import Validator from '@nymproject/nym-validator-client'
You can generate docs by running `npm run docs`. Generated output will appear in the `docs` directory.
const main = async () => {
Packaging
------------------------
const client = await Validator.connect(mnemonic, rpcAddress, validatorAddress, prefix, mixnetContractAddress, vestingContractAddress, denom)
If you're a Nym platform developer who's made changes to the client and wants to re-publish the package to NPM, here's how you do it:
const res = await client.send(address, [{ amount: '10000000', denom: 'unym' }]);
1. Bump the version number (use SemVer)
1. `npm run build`
1. `npm login` (if you haven't already)
1. `npm publish`
}
```
+34 -18
View File
@@ -4,15 +4,23 @@
"description": "A TypeScript client for interacting with smart contracts in Nym validators",
"repository": "https://github.com/nymtech/nym",
"main": "./dist/index.js",
"types": "dist/index.d.ts",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "ts-mocha tests/**/*.test.ts",
"build": "rollup -c ./rollup.config.mjs",
"build:types": "rollup-type-bundler --dist ./dist/nym-validator-client",
"build:prod": "sh ./scripts/build-prod.sh",
"test": "ts-mocha -p ./tsconfig.test.json ./src/tests/**/*.test.ts",
"testmock": "ts-mocha -p ./tsconfig.test.json ./src/tests/mock/*.test.ts",
"coverage": "nyc npm test",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"clean": "rm -rf ./dist",
"lint": "eslint",
"lint:fix": "eslint --fix",
"lint:tsc": "tsc --noEmit",
"docs": "typedoc --out docs src/index.ts"
},
"files": [
"./dist/*"
],
"keywords": [],
"author": "Nym Technologies SA (https://nymtech.net)",
"contributors": [
@@ -21,6 +29,14 @@
],
"license": "Apache-2.0",
"devDependencies": {
"@favware/rollup-type-bundler": "^2.0.0",
"@nymproject/types": "^1.0.0",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-typescript": "^11.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"rollup": "^3.17.2",
"rollup-plugin-dts": "^5.2.0",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.7.0",
"eslint": "^7.18.0",
@@ -31,21 +47,21 @@
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-mocha": "^10.0.3",
"eslint-plugin-prettier": "^4.0.0",
"expect": "^28.1.3",
"mocha": "^10.0.0",
"prettier": "^2.5.1",
"typedoc": "^0.22.13",
"ts-mocha": "^10.0.0",
"typescript": "^4.6.2"
},
"dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.28.0",
"@cosmjs/crypto": "^0.28.0",
"@cosmjs/math": "^0.28.0",
"@cosmjs/proto-signing": "^0.28.0",
"@cosmjs/stargate": "^0.28.0",
"@cosmjs/tendermint-rpc": "^0.28.0",
"axios": "^0.26.1",
"cosmjs-types": "^0.4.1"
"typedoc": "^0.22.13",
"typescript": "^4.6.2",
"cosmjs-types": "^0.4.1",
"dotenv": "^16.0.3",
"expect": "^28.1.3",
"moq.ts": "^7.3.4",
"@cosmjs/cosmwasm-stargate": "^0.29.5",
"@cosmjs/crypto": "^0.29.5",
"@cosmjs/math": "^0.29.5",
"@cosmjs/proto-signing": "^0.29.5",
"@cosmjs/stargate": "^0.29.5",
"@cosmjs/tendermint-rpc": "^0.29.5",
"axios": "^1.3.3"
}
}
+15
View File
@@ -0,0 +1,15 @@
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import json from '@rollup/plugin-json';
import commonjs from '@rollup/plugin-commonjs';
export default [
{
input: './src/index.ts',
output: {
dir: 'dist/nym-validator-client',
format: 'cjs',
},
plugins: [resolve(), typescript(), commonjs(), json()],
},
];
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset
rm -rf ./dist || true
rm -rf ../../dist || true
# Bundle application
yarn build
# Bundle types
yarn build:types
# Build package.json for bundle
node ./scripts/buildPackageJson.mjs
# Copy README
cp README.md dist/nym-validator-client
# move the output outside of the yarn/npm workspaces
mv ./dist ../../
echo "Output can be found in:"
realpath ../../dist
@@ -0,0 +1,20 @@
import * as fs from 'fs';
// parse the package.json from the SDK, so we can keep fields like the name and version
const json = JSON.parse(fs.readFileSync('./package.json').toString());
// defaults (NB: these are in the output file locations)
const main = 'index.js';
const types = 'index.d.ts';
// make a package.json for the bundle
const packageJson = {
name: json.name,
version: json.version,
license: json.license,
author: json.author,
main,
types,
};
fs.writeFileSync('./dist/nym-validator-client/package.json', JSON.stringify(packageJson, null, 2));
+1 -9
View File
@@ -1,5 +1,6 @@
import { Decimal } from '@cosmjs/math';
import { Coin } from '@cosmjs/stargate';
import { CoinMap } from './types/shared';
// NARROW NO-BREAK SPACE (U+202F)
const thinSpace = '\u202F';
@@ -34,15 +35,6 @@ export function nativeToPrintable(nativeValue: string): string {
return Decimal.fromAtomics(nativeValue, 6).toString();
}
export interface MappedCoin {
readonly denom: string;
readonly fractionalDigits: number;
}
export interface CoinMap {
readonly [key: string]: MappedCoin;
}
export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin {
if (!coinMap) return coin;
+133 -75
View File
@@ -1,6 +1,3 @@
import { Bip39, Random } from '@cosmjs/crypto';
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
import { coin as cosmosCoin, Coin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate';
import {
ExecuteResult,
InstantiateOptions,
@@ -8,45 +5,37 @@ import {
MigrateResult,
UploadResult,
} from '@cosmjs/cosmwasm-stargate';
import SigningClient, { ISigningClient } from './signing-client';
import { Bip39, Random } from '@cosmjs/crypto';
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
import { Coin, coin as cosmosCoin, DeliverTxResponse, isDeliverTxFailure, StdFee } from '@cosmjs/stargate';
import {
ContractStateParams,
Delegation,
Gateway,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNode,
MixNodeBond,
MixNodeCostParams,
MixNodeDetails,
MixNodeRewarding,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
} from './types';
import {
CoinMap,
displayAmountToNative,
MappedCoin,
nativeCoinToDisplay,
nativeToPrintable,
printableBalance,
printableCoin,
} from './currency';
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
RewardingParams,
StakeSaturationResponse,
UnbondedMixnodeResponse,
} from '@nymproject/types';
import QueryClient from './query-client';
import { nymGasPrice } from './stargate-helper';
export { coins, coin } from '@cosmjs/stargate';
export { Coin };
export {
displayAmountToNative,
nativeCoinToDisplay,
printableCoin,
printableBalance,
nativeToPrintable,
MappedCoin,
CoinMap,
};
export { nymGasPrice };
import SigningClient, { ISigningClient } from './signing-client';
import { ContractState } from './types/shared';
export interface INymClient {
readonly mixnetContract: string;
@@ -147,7 +136,7 @@ export default class ValidatorClient implements INymClient {
return DirectSecp256k1HdWallet.fromMnemonic(mnemonic, signerOptions);
}
getBalance(address: string): Promise<Coin> {
async getBalance(address: string): Promise<Coin> {
return this.client.getBalance(address, this.denom);
}
@@ -159,15 +148,39 @@ export default class ValidatorClient implements INymClient {
return this.client.getCachedMixnodes();
}
async getActiveMixnodes(): Promise<MixNodeBond[]> {
async getStakeSaturation(mixId: number): Promise<StakeSaturationResponse> {
return this.client.getStakeSaturation(this.mixnetContract, mixId);
}
async getActiveMixnodes(): Promise<MixNodeDetails[]> {
return this.client.getActiveMixnodes();
}
async getUnbondedMixNodeInformation(mixId: number): Promise<UnbondedMixnodeResponse> {
return this.client.getUnbondedMixNodeInformation(this.mixnetContract, mixId);
}
async getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.client.getRewardedMixnodes();
}
public async getMixnetContractSettings(): Promise<ContractStateParams> {
async getMixnodeRewardingDetails(mixId: number): Promise<MixNodeRewarding> {
return this.client.getMixnodeRewardingDetails(this.mixnetContract, mixId);
}
async getOwnedMixnode(address: string): Promise<MixOwnershipResponse> {
return this.client.getOwnedMixnode(this.mixnetContract, address);
}
async ownsGateway(address: string): Promise<GatewayOwnershipResponse> {
return this.client.ownsGateway(this.mixnetContract, address);
}
async getLayerDistribution(): Promise<LayerDistribution> {
return this.client.getLayerDistribution(this.mixnetContract);
}
public async getMixnetContractSettings(): Promise<ContractState> {
return this.client.getStateParams(this.mixnetContract);
}
@@ -175,29 +188,74 @@ export default class ValidatorClient implements INymClient {
return this.client.getContractVersion(this.mixnetContract);
}
public async getRewardPool(): Promise<string> {
return this.client.getRewardPool(this.mixnetContract);
public async getVestingContractVersion(): Promise<MixnetContractVersion> {
return this.client.getContractVersion(this.vestingContract);
}
public async getCirculatingSupply(): Promise<string> {
return this.client.getCirculatingSupply(this.mixnetContract);
public async getSpendableCoins(vestingAccountAddress: string): Promise<MixnetContractVersion> {
return this.client.getSpendableCoins(this.vestingContract, vestingAccountAddress);
}
public async getSybilResistancePercent(): Promise<number> {
return this.client.getSybilResistancePercent(this.mixnetContract);
public async getRewardParams(): Promise<RewardingParams> {
return this.client.getRewardParams(this.mixnetContract);
}
public async getIntervalRewardPercent(): Promise<number> {
return this.client.getIntervalRewardPercent(this.mixnetContract);
async getUnbondedMixNodes(): Promise<UnbondedMixnodeResponse[]> {
let mixNodes: UnbondedMixnodeResponse[] = [];
const limit = 50;
let startAfter;
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedUnbondedMixnodesResponse = await this.client.getUnbondedMixNodes(
this.mixnetContract,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return mixNodes;
}
public async getAllNyxdMixnodes(): Promise<MixNodeBond[]> {
public async getMixNodeBonds(): Promise<MixNodeBond[]> {
let mixNodes: MixNodeBond[] = [];
const limit = 50;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixnodeResponse = await this.client.getMixNodesPaged(this.mixnetContract, limit);
const pagedResponse: PagedMixNodeBondResponse = await this.client.getMixNodeBonds(
this.mixnetContract,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return mixNodes;
}
public async getMixNodesDetailed(): Promise<MixNodeDetails[]> {
let mixNodes: MixNodeDetails[] = [];
const limit = 50;
let startAfter;
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixNodeDetailsResponse = await this.client.getMixNodesDetailed(
this.mixnetContract,
limit,
startAfter,
);
mixNodes = mixNodes.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
@@ -210,37 +268,24 @@ export default class ValidatorClient implements INymClient {
}
public async getAllNyxdGateways(): Promise<GatewayBond[]> {
let gateways: GatewayBond[] = [];
const limit = 50;
let startAfter;
for (; ;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract, limit);
gateways = gateways.concat(pagedResponse.nodes);
startAfter = pagedResponse.start_next_after;
// if `start_next_after` is not set, we're done
if (!startAfter) {
break;
}
}
return gateways;
const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract);
return pagedResponse.nodes;
}
/**
* Gets list of all delegations towards particular mixnode.
*
* @param mixIdentity identity of the node to which the delegation was sent
* @param mix_id identity of the node to which the delegation was sent
*/
public async getAllNyxdSingleMixnodeDelegations(mixIdentity: string): Promise<Delegation[]> {
public async getAllNyxdSingleMixnodeDelegations(mix_id: number): Promise<Delegation[]> {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedMixDelegationsResponse = await this.client.getMixNodeDelegationsPaged(
this.mixnetContract,
mixIdentity,
mix_id,
limit,
startAfter,
);
@@ -259,7 +304,7 @@ export default class ValidatorClient implements INymClient {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedDelegatorDelegationsResponse = await this.client.getDelegatorDelegationsPaged(
this.mixnetContract,
@@ -278,13 +323,13 @@ export default class ValidatorClient implements INymClient {
return delegations;
}
public async getAllNyxdNetworkDelegations(): Promise<Delegation[]> {
public async getAllNyxdDelegations(): Promise<Delegation[]> {
let delegations: Delegation[] = [];
const limit = 250;
let startAfter;
for (; ;) {
for (;;) {
// eslint-disable-next-line no-await-in-loop
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllNetworkDelegationsPaged(
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllDelegationsPaged(
this.mixnetContract,
limit,
startAfter,
@@ -300,6 +345,10 @@ export default class ValidatorClient implements INymClient {
return delegations;
}
public async getDelegationDetails(mix_id: number, delegator: string): Promise<Delegation> {
return this.client.getDelegationDetails(this.mixnetContract, mix_id, delegator);
}
/**
* Generate a minimum gateway bond required to create a fresh mixnode.
*
@@ -308,7 +357,7 @@ export default class ValidatorClient implements INymClient {
public async minimumMixnodePledge(): Promise<Coin> {
const stateParams = await this.getMixnetContractSettings();
// we trust the contract to return a valid number
return cosmosCoin(stateParams.minimum_mixnode_pledge, this.prefix);
return cosmosCoin(stateParams.params.minimum_mixnode_pledge, this.prefix);
}
/**
@@ -319,7 +368,7 @@ export default class ValidatorClient implements INymClient {
public async minimumGatewayPledge(): Promise<Coin> {
const stateParams = await this.getMixnetContractSettings();
// we trust the contract to return a valid number
return cosmosCoin(stateParams.minimum_gateway_pledge, this.prefix);
return cosmosCoin(stateParams.params.minimum_gateway_pledge, this.prefix);
}
public async send(
@@ -393,12 +442,21 @@ export default class ValidatorClient implements INymClient {
public async bondMixNode(
mixNode: MixNode,
ownerSignature: string,
costParams: MixNodeCostParams,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).bondMixNode(this.mixnetContract, mixNode, ownerSignature, pledge, fee, memo);
return (this.client as ISigningClient).bondMixNode(
this.mixnetContract,
mixNode,
costParams,
ownerSignature,
pledge,
fee,
memo,
);
}
public async unbondMixNode(fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult> {
@@ -423,29 +481,29 @@ export default class ValidatorClient implements INymClient {
}
public async delegateToMixNode(
mixIdentity: string,
mixId: number,
amount: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
this.assertSigning();
return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixIdentity, amount, fee, memo);
return (this.client as ISigningClient).delegateToMixNode(this.mixnetContract, mixId, amount, fee, memo);
}
public async undelegateFromMixNode(
mixIdentity: string,
mixId: number,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult> {
return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixIdentity, fee, memo);
return (this.client as ISigningClient).undelegateFromMixNode(this.mixnetContract, mixId, fee, memo);
}
public async updateMixnodeConfig(
mixIdentity: string,
mixId: number,
fee: StdFee | 'auto' | number,
profitPercentage: number,
): Promise<ExecuteResult> {
return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixIdentity, profitPercentage, fee);
return (this.client as ISigningClient).updateMixnodeConfig(this.mixnetContract, mixId, profitPercentage, fee);
}
public async updateContractStateParams(
+3 -4
View File
@@ -2,9 +2,8 @@
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import axios from 'axios';
import { GatewayBond, MixNodeBond } from './types';
import { GatewayBond, MixNodeBond, MixNodeDetails } from '@nymproject/types';
export const NYM_API_VERSION = '/v1';
export const NYM_API_GATEWAYS_PATH = `${NYM_API_VERSION}/gateways`;
@@ -17,7 +16,7 @@ export interface INymApiQuery {
getCachedGateways(): Promise<GatewayBond[]>;
getActiveMixnodes(): Promise<MixNodeBond[]>;
getActiveMixnodes(): Promise<MixNodeDetails[]>;
getRewardedMixnodes(): Promise<MixNodeBond[]>;
}
@@ -51,7 +50,7 @@ export default class NymApiQuerier implements INymApiQuery {
throw new Error('None of the provided validator APIs seem to be alive');
}
async getActiveMixnodes(): Promise<MixNodeBond[]> {
async getActiveMixnodes(): Promise<MixNodeDetails[]> {
const url = new URL(this.nymApiUrl);
url.pathname += NYM_API_ACTIVE_MIXNODES_PATH;
+72 -56
View File
@@ -2,28 +2,24 @@
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import { JsonObject } from '@cosmjs/cosmwasm-stargate';
// eslint-disable-next-line import/no-cycle
import { INyxdQuery } from './query-client';
import { Delegation, RewardingParams, StakeSaturationResponse } from '@nymproject/types';
import {
ContractStateParams,
Delegation,
UnbondedMixnodeResponse,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingStatus,
} from './types';
interface SmartContractQuery {
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
LayerDistribution,
} from '@nymproject/types';
import { ContractState, SmartContractQuery } from './types/shared';
export default class NyxdQuerier implements INyxdQuery {
client: SmartContractQuery;
@@ -38,15 +34,44 @@ export default class NyxdQuerier implements INyxdQuery {
});
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mix_nodes: {
get_mix_node_bonds: {
limit,
start_after: startAfter,
},
});
}
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mix_nodes_detailed: {
limit,
start_after: startAfter,
},
});
}
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_stake_saturation: { mix_id: mixId },
});
}
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<any> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mixnode_rewarding_details: { mix_id: mixId },
});
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_gateways: {
@@ -56,35 +81,51 @@ export default class NyxdQuerier implements INyxdQuery {
});
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
owns_mixnode: {
get_owned_mixnode: {
address,
},
});
}
getUnbondedMixNodes(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedUnbondedMixnodesResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_unbonded_mix_nodes: { limit, start_after: startAfter },
});
}
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_unbonded_mix_node_information: { mix_id: mixId },
});
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
owns_gateway: {
get_owned_gateway: {
address,
},
});
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
getStateParams(mixnetContractAddress: string): Promise<ContractState> {
return this.client.queryContractSmart(mixnetContractAddress, {
state_params: {},
get_state: {},
});
}
getAllNetworkDelegationsPaged(
getAllDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_all_network_delegations: {
get_all_delegations: {
start_after: startAfter,
limit,
},
@@ -93,13 +134,13 @@ export default class NyxdQuerier implements INyxdQuery {
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mixnode_delegations: {
mix_identity: mixIdentity,
mix_id: mix_id,
start_after: startAfter,
limit,
},
@@ -121,10 +162,10 @@ export default class NyxdQuerier implements INyxdQuery {
});
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_delegation_details: {
mix_identity: mixIdentity,
mix_id: mix_id,
delegator,
},
});
@@ -132,44 +173,19 @@ export default class NyxdQuerier implements INyxdQuery {
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.client.queryContractSmart(mixnetContractAddress, {
layer_distribution: {},
get_layer_distribution: {},
});
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_reward_pool: {},
get_rewarding_params: {},
});
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_circulating_supply: {},
});
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_interval_reward_percent: {},
});
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_sybil_resistance_percent: {},
});
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_rewarding_status: {
mix_identity: mixIdentity,
rewarding_interval_nonce: rewardingIntervalNonce,
},
getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise<any> {
return this.client.queryContractSmart(vestingContractAddress, {
vesting_account_address: vestingAccountAddress,
});
}
}
+84 -93
View File
@@ -1,75 +1,56 @@
import { CosmWasmClient, JsonObject } from '@cosmjs/cosmwasm-stargate';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
import {
Account,
Block,
Coin,
DeliverTxResponse,
IndexedTx,
SearchTxFilter,
SearchTxQuery,
SequenceResponse,
} from '@cosmjs/stargate';
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
// eslint-disable-next-line import/no-cycle
import NyxdQuerier from './nyxd-querier';
import {
ContractStateParams,
Delegation,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNodeBond,
MixNodeDetails,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingStatus,
} from './types';
import NymApiQuerier, { INymApiQuery as INymApiQuery } from './nym-api-querier';
export interface ICosmWasmQuery {
// methods exposed by `CosmWasmClient`
getChainId(): Promise<string>;
getHeight(): Promise<number>;
getAccount(searchAddress: string): Promise<Account | null>;
getSequence(address: string): Promise<SequenceResponse>;
getBlock(height?: number): Promise<Block>;
getBalance(address: string, searchDenom: string): Promise<Coin>;
getTx(id: string): Promise<IndexedTx | null>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
disconnect(): void;
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>;
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>;
getContracts(codeId: number): Promise<readonly string[]>;
getContract(address: string): Promise<Contract>;
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
StakeSaturationResponse,
UnbondedMixnodeResponse,
MixNodeBond,
MixNodeRewarding,
} from '@nymproject/types';
import NymApiQuerier, { INymApiQuery } from './nym-api-querier';
import { ContractState, ICosmWasmQuery } from './types/shared';
import { RewardingParams } from '@nymproject/types';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
export interface INyxdQuery {
// nym-specific implemented inside NymQuerier
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse>;
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse>;
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse>;
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>;
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams>;
getAllNetworkDelegationsPaged(
getStateParams(mixnetContractAddress: string): Promise<ContractState>;
getAllDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse>;
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse>;
@@ -79,21 +60,15 @@ export interface INyxdQuery {
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse>;
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation>;
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation>;
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>;
getRewardPool(mixnetContractAddress: string): Promise<string>;
getCirculatingSupply(mixnetContractAddress: string): Promise<string>;
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number>;
getSybilResistancePercent(mixnetContractAddress: string): Promise<number>;
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus>;
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams>;
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse>;
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse>;
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding>;
}
export interface IQueryClient extends ICosmWasmQuery, INyxdQuery, INymApiQuery { }
export interface IQueryClient extends ICosmWasmQuery, INyxdQuery, INymApiQuery {}
export default class QueryClient extends CosmWasmClient implements IQueryClient {
private nyxdQuerier: NyxdQuerier;
@@ -115,41 +90,73 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
return this.nyxdQuerier.getContractVersion(mixnetContractAddress);
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nyxdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse> {
return this.nyxdQuerier.getMixNodeBonds(mixnetContractAddress, limit, startAfter);
}
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse> {
return this.nyxdQuerier.getMixNodesDetailed(mixnetContractAddress, limit, startAfter);
}
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse> {
return this.nyxdQuerier.getStakeSaturation(mixnetContractAddress, mixId);
}
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding> {
return this.nyxdQuerier.getMixnodeRewardingDetails(mixnetContractAddress, mixId);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.ownsMixNode(mixnetContractAddress, address);
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.getOwnedMixnode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address);
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
getUnbondedMixNodes(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedUnbondedMixnodesResponse> {
return this.nyxdQuerier.getUnbondedMixNodes(mixnetContractAddress, limit, startAfter);
}
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse> {
return this.nyxdQuerier.getUnbondedMixNodeInformation(mixnetContractAddress, mixId);
}
getStateParams(mixnetContractAddress: string): Promise<ContractState> {
return this.nyxdQuerier.getStateParams(mixnetContractAddress);
}
getAllNetworkDelegationsPaged(
getAllDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.nyxdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
return this.nyxdQuerier.getAllDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mix_id, limit, startAfter);
}
getDelegatorDelegationsPaged(
@@ -161,36 +168,16 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mix_id, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getIntervalRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nyxdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams> {
return this.nyxdQuerier.getRewardParams(mixnetContractAddress);
}
getCachedGateways(): Promise<GatewayBond[]> {
@@ -201,11 +188,15 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
return this.nymApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeBond[]> {
getActiveMixnodes(): Promise<MixNodeDetails[]> {
return this.nymApiQuerier.getActiveMixnodes();
}
getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.nymApiQuerier.getRewardedMixnodes();
}
getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise<any> {
return this.nyxdQuerier.getSpendableCoins(vestingContractAddress, vestingAccountAddress);
}
}
+73 -47
View File
@@ -25,15 +25,22 @@ import {
MixnetContractVersion,
MixNode,
MixNodeBond,
MixNodeCostParams,
MixNodeDetails,
MixNodeRewarding,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingStatus,
} from './types';
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
PagedUnbondedMixnodesResponse,
RewardingParams,
UnbondedMixnodeResponse,
} from '@nymproject/types';
import NymApiQuerier from './nym-api-querier';
import { ContractState } from './types/shared';
// methods exposed by `SigningCosmWasmClient`
export interface ICosmWasmSigning {
@@ -143,6 +150,7 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
costParams: MixNodeCostParams,
ownerSignature: string,
pledge: Coin,
fee?: StdFee | 'auto' | number,
@@ -164,7 +172,7 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign
delegateToMixNode(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
amount: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
@@ -172,14 +180,14 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign
undelegateFromMixNode(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
updateMixnodeConfig(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
profitMarginPercent: number,
fee: StdFee | 'auto' | number,
): Promise<ExecuteResult>;
@@ -235,44 +243,76 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
// query related:
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.nyxdQuerier.getContractVersion(mixnetContractAddress);
return this.getContractVersion(mixnetContractAddress);
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nyxdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse> {
return this.nyxdQuerier.getMixNodeBonds(mixnetContractAddress, limit, startAfter);
}
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse> {
return this.nyxdQuerier.getMixNodesDetailed(mixnetContractAddress, limit, startAfter);
}
getStakeSaturation(mixnetContractAddress: string, mixId: number) {
return this.nyxdQuerier.getStakeSaturation(mixnetContractAddress, mixId);
}
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse> {
return this.nyxdQuerier.getUnbondedMixNodeInformation(mixnetContractAddress, mixId);
}
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding> {
return this.nyxdQuerier.getMixnodeRewardingDetails(mixnetContractAddress, mixId);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.ownsMixNode(mixnetContractAddress, address);
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nyxdQuerier.getOwnedMixnode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address);
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
getStateParams(mixnetContractAddress: string): Promise<ContractState> {
return this.nyxdQuerier.getStateParams(mixnetContractAddress);
}
getAllNetworkDelegationsPaged(
getAllDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.nyxdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
return this.getAllDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getUnbondedMixNodes(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedUnbondedMixnodesResponse> {
return this.nyxdQuerier.getUnbondedMixNodes(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mix_id, limit, startAfter);
}
getDelegatorDelegationsPaged(
@@ -284,36 +324,16 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation> {
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mix_id, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nyxdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getIntervalRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nyxdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nyxdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
getRewardParams(mixnetContractAddress: string): Promise<RewardingParams> {
return this.nyxdQuerier.getRewardParams(mixnetContractAddress);
}
getCachedGateways(): Promise<GatewayBond[]> {
@@ -324,7 +344,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nymApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeBond[]> {
getActiveMixnodes(): Promise<MixNodeDetails[]> {
return this.nymApiQuerier.getActiveMixnodes();
}
@@ -332,11 +352,16 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
return this.nymApiQuerier.getRewardedMixnodes();
}
getSpendableCoins(vestingContractAddress: string, vestingAccountAddress: string): Promise<any> {
return this.nyxdQuerier.getSpendableCoins(vestingContractAddress, vestingAccountAddress);
}
// signing related:
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
costParams: MixNodeCostParams,
ownerSignature: string,
pledge: Coin,
fee: StdFee | 'auto' | number = 'auto',
@@ -348,6 +373,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
{
bond_mixnode: {
mix_node: mixNode,
cost_params: costParams,
owner_signature: ownerSignature,
},
},
@@ -414,7 +440,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
delegateToMixNode(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
amount: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Delegation from Typescript',
@@ -424,7 +450,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
mixnetContractAddress,
{
delegate_to_mixnode: {
mix_identity: mixIdentity,
mix_id: mixId,
},
},
fee,
@@ -435,7 +461,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
undelegateFromMixNode(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Undelegation from Typescript',
): Promise<ExecuteResult> {
@@ -444,7 +470,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
mixnetContractAddress,
{
undelegate_from_mixnode: {
mix_identity: mixIdentity,
mix_id: mixId,
},
},
fee,
@@ -454,14 +480,14 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
updateMixnodeConfig(
mixnetContractAddress: string,
mixIdentity: string,
mixId: number,
profitMarginPercent: number,
fee: StdFee | 'auto' | number,
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{ update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_identity: mixIdentity } },
{ update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_id: mixId } },
fee,
);
}
@@ -0,0 +1,178 @@
import expect from 'expect';
export const amountDemon = {
amount: expect.any(String),
denom: expect.any(String)
}
export const delegation = {
owner: expect.any(String),
mix_id: expect.any(Number),
cumulative_reward_ratio: expect.any(String),
amount: amountDemon,
height: expect.any(Number || BigInt),
proxy: expect.any(String || null)
}
export const detailedDelegation = {
delegation: delegation,
mixnode_still_bonded: expect.any(Boolean)
}
export const gateway = {
pledge_amount: amountDemon,
owner: expect.any(String),
block_height: expect.any(Number || BigInt),
gateway: {
host: expect.any(String),
mix_port: expect.any(Number),
clients_port: expect.any(Number),
location: expect.any(String),
sphinx_key: expect.any(String),
identity_key: expect.any(String),
version: expect.any(String),
},
proxy: expect.any(String || null)
}
export const pagedGateway = {
nodes: gateway,
per_page: expect.any(Number),
start_next_after: expect.any(Number)
}
export const ownGateway = {
address: expect.any(String),
gateway: gateway
}
export const rewardingdetails = {
cost_params: {
profit_margin_percent: expect.any(String),
interval_operating_cost: {
denom: expect.any(String),
amount: expect.any(String)
}
},
operator: expect.any(String),
delegates: expect.any(String),
total_unit_reward: expect.any(String),
unit_delegation: expect.any(String),
last_rewarded_epoch: expect.any(Number),
unique_delegations: expect.any(Number)
}
export const mix_node = {
host: expect.any(String),
mix_port: expect.any(Number),
verloc_port: expect.any(Number),
http_api_port: expect.any(Number),
sphinx_key: expect.any(String),
identity_key: expect.any(String),
version: expect.any(String)
}
export const mixnodebond = {
mix_id: expect.any(Number),
owner: expect.any(String),
original_pledge: {
denom: expect.any(String),
amount: expect.any(String)
},
layer: expect.any(String),
mix_node: mix_node,
proxy: expect.any(String) || null,
bonding_height: expect.any(Number || BigInt),
is_unbonding: expect.any(Boolean)
}
export const mixnode = {
bond_information: mixnodebond,
rewarding_details: rewardingdetails
}
export const ownedNode = {
address: expect.any(String),
mixnode_details: {
bond_information: mixnodebond,
rewarding_details: rewardingdetails
}
}
export const saturation = {
mix_id: expect.any(Number),
current_saturation: expect.any(String),
uncapped_saturation: expect.any(String)
}
export const contractVersion = {
build_timestamp: expect.any(String),
build_version: expect.any(String),
commit_sha: expect.any(String),
commit_timestamp: expect.any(String),
commit_branch: expect.any(String),
rustc_version: expect.any(String)
};
export const stateParams = {
minimum_gateway_pledge: amountDemon,
minimum_mixnode_pledge: expect.any(String),
mixnode_rewarded_set_size: expect.any(Number),
mixnode_active_set_size: expect.any(Number)
}
export const contract = {
owner: expect.any(Number),
rewarding_validator_address: expect.any(Number),
vesting_contract_address: expect.any(Number),
rewarding_denom: expect.any(String),
params: stateParams
}
export const rewardingnode = {
mix_id: expect.any(Number),
rewarding_details: rewardingdetails
}
export const unbondednode = {
mix_id: expect.any(Number),
unbonded_info: {
identity_key: expect.any(String),
owner: expect.any(String),
proxy: expect.any(String) || null,
unbonding_height: expect.any(Number)
}
}
export const allunbondednodes = [
expect.any(Number), {
identity_key: expect.any(String),
owner: expect.any(String),
proxy: expect.any(String) || null,
unbonding_height: expect.any(Number)
}
]
export const layerDistribution = {
layer1: expect.any(Number),
layer2: expect.any(Number),
layer3: expect.any(Number)
}
export const intervalRewardParams = {
reward_pool: expect.any(Number),
staking_supply: expect.any(Number),
staking_supply_scale_factor: expect.any(Number),
epoch_reward_budget: expect.any(Number),
stake_saturation_point: expect.any(Number),
sybil_resistance: expect.any(Number),
active_set_work_factor: expect.any(Number),
interval_pool_emission: expect.any(Number)
}
export const rewardingParams = {
interval: intervalRewardParams,
rewarded_set_size: expect.any(Number),
active_set_size: expect.any(Number)
}
@@ -0,0 +1,19 @@
import { Mock, Times } from 'moq.ts';
import expect from 'expect';
import { INyxdQuery } from '../../query-client';
export class TestHelper {
buildMethod = async <T>(methodName: string, args: any[], expectedResult: any): Promise<T> => {
const client = new Mock<INyxdQuery>()
.setup((nym) => nym[methodName](...args))
.returns(Promise.resolve(expectedResult));
const obj = client.object();
const actualDetails = await obj[methodName](...args);
client.verify((nym) => nym[methodName](...args), Times.Exactly(1));
expect(Object.keys([actualDetails])).toEqual(Object.keys(expectedResult));
expect(actualDetails).toBeDefined();
return actualDetails;
};
}
@@ -0,0 +1,40 @@
import expect from 'expect';
import { Delegation } from '@nymproject/types';
import { PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse } from '../../types/shared-types';
import { TestHelper } from './client';
import { mixnet, mixnodeowneraddress, mix_id } from './testData';
describe('Delegation mock tests', () => {
const testHelper = new TestHelper();
it('get Delegation Details', () => {
const execute = testHelper.buildMethod('getDelegationDetails', [mixnet, mix_id, mixnodeowneraddress], <Delegation>{
owner: mixnodeowneraddress,
mix_id: 0,
cumulative_reward_ratio: '',
amount: {
denom: 'nym',
amount: '10',
},
height: 1314134144132n,
proxy: 'null',
});
expect(execute).toBeTruthy();
});
it('get All Delegations Paged', () => {
const execute = testHelper.buildMethod('getAllDelegationsPaged', [mixnet], <PagedAllDelegationsResponse>{
delegations: [],
});
expect(execute).toBeTruthy();
});
it('get Delegator Delegations Paged', () => {
const execute = testHelper.buildMethod('getDelegatorDelegationsPaged', [mixnet, mixnodeowneraddress], <
PagedDelegatorDelegationsResponse
>{
delegations: [],
});
expect(execute).toBeTruthy();
});
});
@@ -0,0 +1,24 @@
import expect from 'expect';
import { GatewayOwnershipResponse, PagedGatewayResponse } from '../../types/shared-types';
import { TestHelper } from './client';
import { gatewayowneraddress, mixnet } from './testData';
describe('Gateway mock tests', () => {
const testHelper = new TestHelper();
it('get Gateways Paged', () => {
const execute = testHelper.buildMethod('getGatewaysPaged', [mixnet], <PagedGatewayResponse>{
nodes: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('owns Gateway', () => {
const execute = testHelper.buildMethod('ownsGateway', [mixnet, gatewayowneraddress], <GatewayOwnershipResponse>{
address: gatewayowneraddress,
gateway: {},
});
expect(execute).toBeTruthy();
});
});
@@ -0,0 +1,65 @@
import expect from 'expect';
import { LayerDistribution, MixnetContractVersion, StakeSaturationResponse } from '@nymproject/types';
import { TestHelper } from './client';
import { mixnet, mix_id } from './testData';
import { RewardingParams } from '@nymproject/types';
import { ContractState } from '../../types/shared';
describe('Mixnet mock tests', () => {
const testHelper = new TestHelper();
it('get Layer Distribution', () => {
const execute = testHelper.buildMethod('getLayerDistribution', [mixnet], <LayerDistribution>{
layer1: 2,
layer2: 2,
layer3: 5,
});
expect(execute).toBeTruthy();
});
it('get Reward Params', () => {
const execute = testHelper.buildMethod('getRewardParams', [mixnet], <RewardingParams>{
interval: {},
rewarded_set_size: 0,
active_set_size: 0,
});
expect(execute).toBeTruthy();
});
it('get Stake Saturation', () => {
const execute = testHelper.buildMethod('getStakeSaturation', [mixnet, mix_id], <StakeSaturationResponse>{
mix_id: 0,
current_saturation: '',
uncapped_saturation: '',
});
expect(execute).toBeTruthy();
});
it('get State Params', () => {
const execute = testHelper.buildMethod('getStateParams', [mixnet], <ContractState>{
owner: '',
rewarding_validator_address: '',
vesting_contract_address: '',
rewarding_denom: 'unym',
params: {
minimum_mixnode_pledge: '',
minimum_gateway_pledge: '',
mixnode_rewarded_set_size: 240,
mixnode_active_set_size: 240,
},
});
expect(execute).toBeTruthy();
});
it('get Contract Version', () => {
const execute = testHelper.buildMethod('getContractVersion', [mixnet], <MixnetContractVersion>{
build_timestamp: 'test',
commit_branch: 'test',
build_version: 'test',
rustc_version: 'test',
commit_sha: 'test',
commit_timestamp: 'test',
});
expect(execute).toBeTruthy();
});
});
@@ -0,0 +1,71 @@
import expect from 'expect';
import {
MixNodeRewarding,
MixOwnershipResponse,
PagedMixDelegationsResponse,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
UnbondedMixnodeResponse,
} from '@nymproject/types';
import { TestHelper } from './client';
import { mixnet, mixnodeowneraddress, mix_id } from './testData';
describe('Mixnode mock tests', () => {
const testHelper = new TestHelper();
it('get Mixnode Bonds', () => {
const execute = testHelper.buildMethod('getMixNodeBonds', [mixnet], <PagedMixNodeBondResponse>{
nodes: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('get Mixnode Delegations Paged', () => {
const execute = testHelper.buildMethod('getMixNodeDelegationsPaged', [mixnet, mix_id], <
PagedMixDelegationsResponse
>{
delegations: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('get Mixnodes Detailed', () => {
const execute = testHelper.buildMethod('getMixNodesDetailed', [mixnet], <PagedMixNodeDetailsResponse>{
nodes: [],
per_page: 25,
});
expect(execute).toBeTruthy();
});
it('get Mixnode Rewarding Details', () => {
const execute = testHelper.buildMethod('getMixnodeRewardingDetails', [mixnet, mix_id], <MixNodeRewarding>{
cost_params: {},
operator: '',
delegates: '',
total_unit_reward: '',
unit_delegation: '',
last_rewarded_epoch: 1,
unique_delegations: 1,
});
expect(execute).toBeTruthy();
});
it('get Owned Mixnode', () => {
const execute = testHelper.buildMethod('getOwnedMixnode', [mixnet, mixnodeowneraddress], <MixOwnershipResponse>{
address: '',
mixnode: {},
});
expect(execute).toBeTruthy();
});
it('get Unbonded Mixnode Information', () => {
const execute = testHelper.buildMethod(
'getUnbondedMixNodeInformation',
[mixnet, mix_id],
<UnbondedMixnodeResponse>{},
);
expect(execute).toBeTruthy();
});
});
@@ -0,0 +1,8 @@
export const mixnet = 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g';
export const gatewayowneraddress = 'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts';
// export const mixId = 436207616;
export const mix_id = 26;
export const nodeIdentityKey = 'ATmVJknZarDF6Yj53M7h8NS9LLCUvWuToXpk3pDvYUH1';
export const mixnodeowneraddress = 'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47';
export const delegatorAddress = 'n1lemst75va9700tsrxq58adzujrh6h9s5x60h9h';
export const rewardingIntervalNonce = 1;
@@ -0,0 +1,180 @@
import expect from 'expect';
import ValidatorClient from '../../index';
import {
allunbondednodes,
contract,
delegation,
gateway,
layerDistribution,
mixnode,
mixnodebond,
ownedNode,
ownGateway,
rewardingnode,
saturation,
unbondednode,
} from '../expectedResponses';
import { delegatorAddress, gatewayowneraddress } from '../mock/testData';
const dotenv = require('dotenv');
dotenv.config();
describe('Mixnet queries', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connectForQuery(
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
//
// CONTRACT
//
it('can query for an account balance', async () => {
const balance = await client.getBalance('n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77');
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
});
it('can query for stake saturation', async () => {
const stakeSaturation = await client.getStakeSaturation(7);
expect(Object.keys(stakeSaturation)).toEqual(Object.keys(saturation));
expect(stakeSaturation).toBeTruthy();
expect(stakeSaturation?.current_saturation).toBeTruthy();
});
it('can query for contract version', async () => {
const contractV = await client.getMixnetContractVersion();
expect(contractV).toBeTruthy();
});
it('can query for mixnet contract settings', async () => {
const settings = await client.getMixnetContractSettings();
expect(Object.keys(settings)).toEqual(Object.keys(contract));
expect(settings).toBeTruthy();
});
it('can query for reward pool', async () => {
const rewardPool = await client.getRewardParams();
// TODO add velidation here
expect(rewardPool).toBeTruthy();
});
it('can query for layer distribution', async () => {
const layer = await client.getLayerDistribution();
expect(Object.keys(layer)).toEqual(Object.keys(layerDistribution));
expect(layer).toBeTruthy();
});
//
// MIXNODES
//
it('can query for unbonded mixnodes', async () => {
const unbondedNodes = await client.getUnbondedMixNodes();
for (let i = 0; i < unbondedNodes.length; i++) {
expect(Object.keys(unbondedNodes[0])).toEqual(Object.keys(allunbondednodes));
expect(unbondedNodes).toBeTruthy();
}
});
it('can query for unbonded mixnode information', async () => {
const unbondedMixnodeInfo = await client.getUnbondedMixNodeInformation(1);
expect(Object.keys(unbondedMixnodeInfo)).toEqual(Object.keys(unbondednode));
expect(unbondedMixnodeInfo).toBeTruthy();
});
it('can query for mixnode rewarding details', async () => {
const rewardingDetails = await client.getMixnodeRewardingDetails(1);
expect(Object.keys(rewardingDetails)).toEqual(Object.keys(rewardingnode));
expect(rewardingDetails).toBeTruthy();
});
it('can query for owned mixnode', async () => {
const ownedMixnode = await client.getOwnedMixnode('n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77');
expect(Object.keys(ownedMixnode)).toEqual(Object.keys(ownedNode));
expect(ownedMixnode).toBeTruthy();
});
it('can query for all mixnode bonds', async () => {
const mixnodeBonds = await client.getMixNodeBonds();
expect(Object.keys(mixnodeBonds[0])).toEqual(Object.keys(mixnodebond));
expect(mixnodeBonds).toBeTruthy();
expect(Array.isArray(mixnodeBonds)).toBeTruthy();
});
it('can query for all mixnode details', async () => {
const mixnodeDetails = await client.getMixNodesDetailed();
expect(Object.keys(mixnodeDetails[0])).toEqual(Object.keys(mixnode));
expect(mixnodeDetails).toBeTruthy();
expect(Array.isArray(mixnodeDetails)).toBeTruthy();
});
it('can query for all active mixnodes', async () => {
const activeNodes = await client.getActiveMixnodes();
expect(Object.keys(activeNodes[0])).toEqual(Object.keys(mixnode));
expect(activeNodes).toBeTruthy();
expect(Array.isArray(activeNodes)).toBeTruthy();
});
it('can query for rewarded mixnodes', async () => {
const rewardNodes = await client.getRewardedMixnodes();
expect(Object.keys(rewardNodes[0])).toEqual(Object.keys(mixnode));
expect(rewardNodes).toBeTruthy();
});
//
// DELEGATIONS
//
it('can query for account delegations', async () => {
const delegations = await client.getAllNyxdDelegatorDelegations('n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47');
expect(Object.keys(delegations[0])).toEqual(Object.keys(delegation));
expect(delegations).toBeTruthy();
expect(Array.isArray(delegations)).toBeTruthy();
});
it('can query for all delegations', async () => {
const allDelegations = await client.getAllNyxdDelegations();
expect(Object.keys(allDelegations[0])).toEqual(Object.keys(delegation));
expect(allDelegations).toBeTruthy();
expect(Array.isArray(allDelegations)).toBeTruthy();
});
it('can query for all delegations on a node', async () => {
const mixnodeDelegations = await client.getAllNyxdSingleMixnodeDelegations(1);
expect(Object.keys(mixnodeDelegations[0])).toEqual(Object.keys(delegation));
expect(mixnodeDelegations).toBeTruthy();
});
it('can query for detailed delegations', async () => {
const detailedDelegation = await client.getDelegationDetails(7, delegatorAddress);
expect(Object.keys(detailedDelegation)).toEqual(Object.keys(detailedDelegation));
expect(detailedDelegation).toBeTruthy();
});
//
// GATEWAYS
//
it('can query for all gateways', async () => {
const gateways = await client.getAllNyxdGateways();
expect(Object.keys(gateways[0])).toEqual(Object.keys(gateway));
expect(gateways).toBeTruthy();
expect(Array.isArray(gateways)).toBeTruthy();
}).timeout(10000);
it('can query for owned gateway', async () => {
const gateway = await client.ownsGateway(gatewayowneraddress);
expect(Object.keys(gateway)).toEqual(Object.keys(ownGateway));
expect(gateway).toBeTruthy();
}).timeout(10000);
});
@@ -0,0 +1,28 @@
import expect from 'expect';
import ValidatorClient from '../../index';
const dotenv = require('dotenv');
dotenv.config();
describe('Vesting queries', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connectForQuery(
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
it('can query for contract version', async () => {
const contract = await client.getVestingContractVersion();
expect(contract).toBeTruthy();
});
it('can get the balance on the account', () => {});
});
@@ -0,0 +1,107 @@
import expect from 'expect';
import ValidatorClient from '../../';
const dotenv = require('dotenv');
dotenv.config();
// TODO: implement for QA with .env for mnemonics
describe('Mixnet actions', () => {
let client: ValidatorClient;
beforeEach(async () => {
client = await ValidatorClient.connect(
process.env.mnemonic || '',
process.env.rpcAddress || '',
process.env.validatorAddress || '',
process.env.prefix || '',
process.env.mixnetContractAddress || '',
process.env.vestingContractAddress || '',
process.env.denom || '',
);
});
it('can send tokens', async () => {
const res = await client.send(client.address, [{ amount: '10000000', denom: 'unym' }]);
expect(res.transactionHash).toBeDefined();
}).timeout(10000);
it.skip('can delegate tokens', async () => {
const [_, secondMixnode] = await client.getActiveMixnodes();
if (secondMixnode) {
const res = await client.delegateToMixNode(
secondMixnode.bond_information.mix_id,
{
amount: '15000000',
denom: 'unym',
},
{ gas: '1000000', amount: [{ amount: '100000', denom: 'unym' }] },
);
expect(res.transactionHash).toBeDefined();
}
}).timeout(10000);
// Need to provide a mix id that can be undelegated from
it.skip('can undelegate from a mixnode', async () => {
const mixId = 8;
const res = await client.undelegateFromMixNode(mixId);
expect(res.transactionHash).toBeDefined();
});
it.skip('Can unbond a mixnode', async () => {
const res = await client.unbondMixNode();
expect(res.transactionHash).toBeDefined();
}).timeout(10000);
it.skip('Can bond a mixnode', async () => {
const res = await client.bondMixNode(
{
identity_key: '3P6pAcF2p3pYMqWdpHqhbavu3ifyaBs5Qw5UmmCGwimx',
sphinx_key: 'GQMQKwUThaggatA6oZteSWTsCQoUfmLtamJ7o9YkP9aE',
host: '109.74.195.67',
mix_port: 1789,
verloc_port: 1790,
http_api_port: 8000,
version: '1.1.4',
},
'3rXWCQBUj5JQB3UBUkZcXhCk9Zh3cjduMF8aFHPTG7KTkkhZzDJTNmE2p2Ph1g6iQW5vRGTpQzjXF33WDwvhzHk6',
{ profit_margin_percent: '0.1', interval_operating_cost: { amount: '40', denom: 'nym' } },
{ amount: '100_000_000', denom: 'unym' },
{ gas: '1000000', amount: [{ amount: '100000', denom: 'unym' }] },
);
expect(res.transactionHash).toBeDefined();
}).timeout(10000);
it.skip('can unbond a gateway', async () => {
const res = await client.unbondGateway();
expect(res.transactionHash).toBeDefined();
});
it.skip('can bond a gateway', async () => {
const res = await client.bondGateway(
{
identity_key: '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL',
sphinx_key: 'G65Fwc2JNAotuHQFqmDKhQNQL5rn3r9pupUdmxMygNUZ',
host: '151.236.220.82',
version: '1.1.4',
mix_port: 1789,
clients_port: 9000,
location: 'Cuba',
},
'3ipSJksWHehZm1YfuH5Ahtg7b22NnrP9hEs6iEDXfUS5uiUhpWmCjGR3b3NDHuxeGjpZYJNYJ52D8WCPK5ZR7Szj',
{ amount: '100_000_000', denom: 'unym' },
);
expect(res.transactionHash).toBeDefined();
});
it.skip('can update contract state params', async () => {
const res = await client.updateContractStateParams({
minimum_mixnode_pledge: '',
minimum_gateway_pledge: '',
mixnode_rewarded_set_size: 2,
mixnode_active_set_size: 2,
});
expect(res.transactionHash).toBeDefined();
});
});
-15
View File
@@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./dist"
},
"exclude": [
"tests",
"dist",
"node_modules"
]
}
-157
View File
@@ -1,157 +0,0 @@
import { Coin } from '@cosmjs/stargate';
// TODO: ideally we'd have re-exported those using that fancy crate that builds ts types from rust
export type MixnetContractVersion = {
build_timestamp: string;
build_version: string;
commit_sha: string;
commit_timestamp: string;
commit_branch: string;
rustc_version: string;
};
export type PagedMixnodeResponse = {
nodes: MixNodeBond[];
per_page: number;
start_next_after?: string;
};
export type PagedGatewayResponse = {
nodes: GatewayBond[];
per_page: number;
start_next_after?: string;
};
export type MixOwnershipResponse = {
address: string;
mixnode?: MixNodeBond;
};
export type GatewayOwnershipResponse = {
address: string;
gateway?: GatewayBond;
};
export type ContractStateParams = {
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_pledge: string;
minimum_gateway_pledge: string;
mixnode_rewarded_set_size: number;
mixnode_active_set_size: number;
};
export type LayerDistribution = {
gateways: number;
layer1: number;
layer2: number;
layer3: number;
};
export type Delegation = {
owner: string;
node_identity: string;
amount: Coin;
block_height: number;
proxy?: string;
};
export type PagedMixDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedDelegatorDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedAllDelegationsResponse = {
delegations: Delegation[];
start_next_after?: [string, string];
};
export type RewardingResult = {
operator_reward: string;
total_delegator_reward: string;
};
export type NodeRewardParams = {
period_reward_pool: string;
k: string;
reward_blockstamp: number;
circulating_supply: string;
uptime: string;
sybil_resistance_percent: number;
};
export type DelegatorRewardParams = {
node_reward_params: NodeRewardParams;
sigma: number;
profit_margin: number;
node_profit: number;
};
export type PendingDelegatorRewarding = {
running_results: RewardingResult;
next_start: string;
rewarding_params: DelegatorRewardParams;
};
export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding };
export type MixnodeRewardingStatusResponse = {
status?: RewardingStatus;
};
export enum Layer {
Gateway,
One,
Two,
Three,
}
export type MixNodeBond = {
owner: string;
mix_node: MixNode;
layer: Layer;
bond_amount: Coin;
total_delegation: Coin;
};
export type MixNode = {
host: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
sphinx_key: string;
identity_key: string;
version: string;
profit_margin_percent: number;
};
export type GatewayBond = {
owner: string;
gateway: Gateway;
bond_amount: Coin;
total_delegation: Coin;
};
export type Gateway = {
host: string;
mix_port: number;
clients_port: number;
location: string;
sphinx_key: string;
identity_key: string;
version: string;
};
export type SendRequest = {
senderAddress: string;
recipientAddress: string;
transferAmount: readonly Coin[];
};
+11
View File
@@ -0,0 +1,11 @@
declare namespace NodeJS {
interface ProcessEnv {
rpcAddress: string;
validatorAddress: string;
prefix: string;
mixnetContractAddress: string;
vestingContractAddress: string;
denom: string;
mnemonic: string;
}
}
+259
View File
@@ -0,0 +1,259 @@
import { JsonObject } from '@cosmjs/cosmwasm-stargate';
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
import {
Account,
Block,
Coin,
DeliverTxResponse,
IndexedTx,
SearchTxFilter,
SearchTxQuery,
SequenceResponse,
} from '@cosmjs/stargate';
import {
MixNodeRewarding,
PagedMixNodeBondResponse,
PagedMixNodeDetailsResponse,
StakeSaturationResponse,
UnbondedMixnodeResponse,
} from '@nymproject/types';
export type MixnetContractVersion = {
build_timestamp: string;
build_version: string;
commit_sha: string;
commit_timestamp: string;
commit_branch: string;
rustc_version: string;
};
export type PagedMixnodeResponse = {
nodes: MixNodeBond[];
per_page: number;
start_next_after?: string;
};
export type PagedGatewayResponse = {
nodes: GatewayBond[];
per_page: number;
start_next_after?: string;
};
export type MixOwnershipResponse = {
address: string;
mixnode?: MixNodeBond;
};
export type GatewayOwnershipResponse = {
address: string;
gateway?: GatewayBond;
};
export type ContractStateParams = {
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_pledge: string;
minimum_gateway_pledge: string;
mixnode_rewarded_set_size: number;
mixnode_active_set_size: number;
};
export type LayerDistribution = {
gateways: number;
layer1: number;
layer2: number;
layer3: number;
};
export type Delegation = {
owner: string;
node_identity: string;
amount: Coin;
block_height: number;
proxy?: string;
};
export type PagedMixDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedDelegatorDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedAllDelegationsResponse = {
delegations: Delegation[];
start_next_after?: [string, string];
};
export type RewardingResult = {
operator_reward: string;
total_delegator_reward: string;
};
export type NodeRewardParams = {
period_reward_pool: string;
k: string;
reward_blockstamp: number;
circulating_supply: string;
uptime: string;
sybil_resistance_percent: number;
};
export type DelegatorRewardParams = {
node_reward_params: NodeRewardParams;
sigma: number;
profit_margin: number;
node_profit: number;
};
export type PendingDelegatorRewarding = {
running_results: RewardingResult;
next_start: string;
rewarding_params: DelegatorRewardParams;
};
export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding };
export type MixnodeRewardingStatusResponse = {
status?: RewardingStatus;
};
export enum Layer {
Gateway,
One,
Two,
Three,
}
export type MixNodeBond = {
owner: string;
mix_node: MixNode;
layer: Layer;
bond_amount: Coin;
total_delegation: Coin;
};
export type MixNode = {
host: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
sphinx_key: string;
identity_key: string;
version: string;
profit_margin_percent: number;
};
export type GatewayBond = {
owner: string;
gateway: Gateway;
bond_amount: Coin;
total_delegation: Coin;
};
export type Gateway = {
host: string;
mix_port: number;
clients_port: number;
location: string;
sphinx_key: string;
identity_key: string;
version: string;
};
export type SendRequest = {
senderAddress: string;
recipientAddress: string;
transferAmount: readonly Coin[];
};
export interface SmartContractQuery {
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export interface ICosmWasmQuery {
// methods exposed by `CosmWasmClient`
getChainId(): Promise<string>;
getHeight(): Promise<number>;
getAccount(searchAddress: string): Promise<Account | null>;
getSequence(address: string): Promise<SequenceResponse>;
getBlock(height?: number): Promise<Block>;
getBalance(address: string, searchDenom: string): Promise<Coin>;
getTx(id: string): Promise<IndexedTx | null>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
disconnect(): void;
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>;
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>;
getContracts(codeId: number): Promise<readonly string[]>;
getContract(address: string): Promise<Contract>;
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export interface INymdQuery {
// nym-specific implemented inside NymQuerier
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
getMixNodeBonds(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeBondResponse>;
getMixNodesDetailed(
mixnetContractAddress: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixNodeDetailsResponse>;
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>;
getOwnedMixnode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(mixnetContractAddress: string): Promise<ContractState>;
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse>;
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mix_id: number,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse>;
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse>;
getDelegationDetails(mixnetContractAddress: string, mix_id: number, delegator: string): Promise<Delegation>;
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>;
getStakeSaturation(mixnetContractAddress: string, mixId: number): Promise<StakeSaturationResponse>;
getUnbondedMixNodeInformation(mixnetContractAddress: string, mixId: number): Promise<UnbondedMixnodeResponse>;
getMixnodeRewardingDetails(mixnetContractAddress: string, mixId: number): Promise<MixNodeRewarding>;
}
export interface IVestingQuerier {
getVestingContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
}
export interface MappedCoin {
readonly denom: string;
readonly fractionalDigits: number;
}
export interface CoinMap {
readonly [key: string]: MappedCoin;
}
export interface ContractState {
owner: string;
rewarding_validator_address: string;
vesting_contract_address: string;
rewarding_denom: string;
params: ContractStateParams;
}
@@ -1,11 +0,0 @@
import ValidatorClient from '../../validator/index';
import expect from 'expect';
describe('Query: balances', () => {
it('can query for an account balance', async () => {
const client = await ValidatorClient.connectForQuery(
'https://rpc.nymtech.net/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
}).timeout(5000);
})
-14
View File
@@ -1,14 +0,0 @@
import ValidatorClient from '../../dist';
import expect from 'expect';
// TODO: implement for QA with .env for mnemonics
// describe('Sign: send', () => {
// it('can send tokens', async () => {
// const client = await ValidatorClient.connect(
// '<ADD MNEMONIC HERE>',
// 'https://rpc.nyx.nodes.guru/', 'https://validator.nymtech.net/api/', 'n', 'n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g', 'n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw', 'nym');
// await client.send('<ADD ADDRESS HERE>')
// const balance = await client.getBalance('n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy');
// expect(Number.parseFloat(balance.amount)).toBeGreaterThan(0);
// }).timeout(5000);
// })
+21 -22
View File
@@ -1,23 +1,22 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./dist",
"skipLibCheck": true
},
"typedocOptions": {
"entryPoints": [
"src/index.ts"
],
"out": "docs"
},
"exclude": [
"dist",
"examples",
"tests",
"node_modules"
]
}
"compilerOptions": {
"outDir": "./dist/nym-validator-client",
"module": "ES2020",
"target": "es2021",
"allowJs": false,
"strict": true,
"lib": ["es2021", "dom", "dom.iterable", "esnext"],
"moduleResolution": "node",
"skipDefaultLibCheck": true,
"esModuleInterop": true,
"declaration": true,
"skipLibCheck": true,
"noImplicitAny": true,
"typeRoots": ["./src/types/global.d.ts"]
},
"typedocOptions": {
"entryPoints": ["./src/index.ts"],
"out": "docs"
},
"exclude": ["dist", "./src/tests/**/*", "node_module"]
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS"
}
}
+2 -2
View File
@@ -31,8 +31,8 @@ wasm-bindgen-futures = "0.4"
# internal
client-core = { path = "../client-core", default-features = false, features = ["wasm"] }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials" }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-credentials = { path = "../../common/credentials" }
nym-crypto = { path = "../../common/crypto" }
nym-sphinx = { path = "../../common/nymsphinx" }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
+1 -1
View File
@@ -1,5 +1,5 @@
[package]
name = "bandwidth-claim-contract"
name = "nym-bandwidth-claim-contract"
version = "0.1.0"
edition = "2021"
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-bin-common"
version = "0.1.0"
version = "0.3.0"
description = "Common code for nym binaries"
edition = { workspace = true }
authors = { workspace = true }
@@ -17,7 +17,7 @@ semver = "0.11"
serde = { workspace = true, features = ["derive"], optional = true }
[build-dependencies]
vergen = { version = "7", default-features = false, features = ["build", "git", "rustc", "cargo"] }
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc", "cargo"] }
[features]
default = []
+6 -1
View File
@@ -4,5 +4,10 @@
use vergen::{vergen, Config};
fn main() {
vergen(Config::default()).expect("failed to extract build metadata")
let mut config = Config::default();
if std::env::var("DOCS_RS").is_ok() {
// If we don't have access to git information, such as in a docs.rs build, don't error
*config.git_mut().skip_if_error_mut() = true;
}
vergen(config).expect("failed to extract build metadata");
}
View File
+4 -4
View File
@@ -14,12 +14,12 @@ log = { workspace = true }
thiserror = "1.0"
url = "2.2"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
async-trait = { version = "0.1.51" }
async-trait = { workspace = true }
tokio = { version = "1.24.1", features = ["macros"] }
# internal
coconut-interface = { path = "../../coconut-interface" }
credentials = { path = "../../credentials" }
nym-coconut-interface = { path = "../../coconut-interface" }
nym-credentials = { path = "../../credentials" }
nym-crypto = { path = "../../crypto" }
gateway-requests = { path = "../../../gateway/gateway-requests" }
nym-network-defaults = { path = "../../network-defaults" }
@@ -47,7 +47,7 @@ features = ["net", "sync", "time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.credential-storage]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-credential-storage]
path = "../../credential-storage"
# wasm-only dependencies
@@ -7,7 +7,7 @@ use crate::error::GatewayClientError;
use crate::wasm_mockups::Storage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "mobile"))]
use credential_storage::storage::Storage;
use nym_credential_storage::storage::Storage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "mobile")]
@@ -22,7 +22,7 @@ use crate::wasm_mockups::StorageError;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "mobile"))]
use credential_storage::error::StorageError;
use nym_credential_storage::error::StorageError;
#[cfg(target_arch = "wasm32")]
use crate::wasm_mockups::{Client, CosmWasmClient};
@@ -30,8 +30,8 @@ use std::str::FromStr;
#[cfg(not(target_arch = "wasm32"))]
use validator_client::{nyxd::CosmWasmClient, Client};
use {
coconut_interface::Base58,
credentials::coconut::{
nym_coconut_interface::Base58,
nym_credentials::coconut::{
bandwidth::prepare_for_spending, utils::obtain_aggregate_verification_key,
},
};
@@ -42,7 +42,7 @@ use crate::wasm_mockups::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "mobile"))]
use credential_storage::PersistentStorage;
use nym_credential_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "mobile")]
@@ -69,17 +69,17 @@ where
pub async fn prepare_coconut_credential(
&self,
) -> Result<(coconut_interface::Credential, i64), GatewayClientError> {
) -> Result<(nym_coconut_interface::Credential, i64), GatewayClientError> {
let bandwidth_credential = self.storage.get_next_coconut_credential().await?;
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
.map_err(|_| StorageError::InconsistentData)?;
let voucher_info = bandwidth_credential.voucher_info.clone();
let serial_number =
coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?;
nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?;
let binding_number =
coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?;
nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?;
let signature =
coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?;
nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?;
let epoch_id = u64::from_str(&bandwidth_credential.epoch_id)
.map_err(|_| StorageError::InconsistentData)?;
@@ -9,13 +9,13 @@ pub use crate::packet_router::{
};
use crate::socket_state::{PartiallyDelegated, SocketState};
use crate::{cleanup_socket_message, try_decrypt_binary_message};
use coconut_interface::Credential;
use futures::{SinkExt, StreamExt};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use gateway_requests::iv::IV;
use gateway_requests::registration::handshake::{client_handshake, SharedKeys};
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, PROTOCOL_VERSION};
use log::*;
use nym_coconut_interface::Credential;
use nym_crypto::asymmetric::identity;
use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN};
use nym_sphinx::forwarding::packet::MixPacket;
@@ -33,7 +33,7 @@ use validator_client::nyxd::CosmWasmClient;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(feature = "mobile"))]
use credential_storage::PersistentStorage;
use nym_credential_storage::PersistentStorage;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "mobile")]
@@ -3,13 +3,13 @@
#[cfg(target_arch = "wasm32")]
use crate::wasm_mockups::StorageError;
#[cfg(not(feature = "mobile"))]
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::error::StorageError;
use gateway_requests::registration::handshake::error::HandshakeError;
#[cfg(feature = "mobile")]
#[cfg(not(target_arch = "wasm32"))]
use mobile_storage::StorageError;
#[cfg(not(feature = "mobile"))]
#[cfg(not(target_arch = "wasm32"))]
use nym_credential_storage::error::StorageError;
use std::io;
use thiserror::Error;
use tungstenite::Error as WsError;
@@ -31,7 +31,7 @@ pub enum GatewayClientError {
CredentialStorageError(#[from] StorageError),
#[error("Coconut error - {0}")]
CoconutError(#[from] coconut_interface::CoconutError),
CoconutError(#[from] nym_coconut_interface::CoconutError),
// TODO: see if `JsValue` is a reasonable type for this
#[cfg(target_arch = "wasm32")]
@@ -48,7 +48,7 @@ pub enum GatewayClientError {
NoBandwidthControllerAvailable,
#[error("Credential error - {0}")]
CredentialError(#[from] credentials::error::Error),
CredentialError(#[from] nym_credentials::error::Error),
#[error("Connection was abruptly closed")]
ConnectionAbruptlyClosed,
@@ -74,7 +74,7 @@ impl PacketRouter {
received_messages.push(received_packet);
} else {
// this can happen if other clients are not padding their messages
warn!("Received message of unexpected size. Probably from an outdated client... len: {}", received_packet.len());
//warn!("Received message of unexpected size. Probably from an outdated client... len: {}", received_packet.len());
received_messages.push(received_packet);
}
}
@@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
futures = "0.3"
tracing = "0.1.37"
log = { workspace = true }
tokio = { version = "1.24.1", features = ["time", "net", "rt"] }
tokio-util = { version = "0.7.4", features = ["codec"] }
@@ -3,11 +3,12 @@
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use tracing::*;
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 nym_sphinx::params::packet_sizes::PacketSize;
use std::collections::HashMap;
use std::io;
use std::net::SocketAddr;
@@ -197,6 +198,7 @@ impl Client {
}
impl SendWithoutResponse for Client {
#[instrument(level="info", skip(self, packet), "Sending packet to mixnet", fields(packet_size))]
fn send_without_response(
&mut self,
address: NymNodeRoutingAddress,
@@ -204,13 +206,15 @@ impl SendWithoutResponse for Client {
packet_mode: PacketMode,
) -> io::Result<()> {
trace!("Sending packet to {:?}", address);
let packet_size = PacketSize::get_type(packet.len()).unwrap();
Span::current().record("packet_size", field::debug(packet_size));
let framed_packet =
FramedSphinxPacket::new(packet, packet_mode, self.config.use_legacy_version);
if let Some(sender) = self.conn_new.get_mut(&address) {
if let Err(err) = sender.channel.try_send(framed_packet) {
if err.is_full() {
debug!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address);
info!("Connection to {} seems to not be able to handle all the traffic - dropping the current packet", address);
// it's not a 'big' error, but we did not manage to send the packet
// if the queue is full, we can't really do anything but to drop the packet
Err(io::Error::new(
@@ -4,8 +4,8 @@
use crate::client::{Client, Config, SendWithoutResponse};
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use nym_sphinx::forwarding::packet::MixPacket;
use tracing::*;
use std::time::Duration;
pub type MixForwardingSender = mpsc::UnboundedSender<MixPacket>;
@@ -53,10 +53,10 @@ impl PacketForwarder {
tokio::select! {
biased;
_ = self.shutdown.recv() => {
log::trace!("PacketForwarder: Received shutdown");
trace!("PacketForwarder: Received shutdown");
}
Some(mix_packet) = self.packet_receiver.next() => {
trace!("Going to forward packet to {:?}", mix_packet.next_hop());
trace!("Going to forward packet to {:?}", mix_packet.next_hop());
let next_hop = mix_packet.next_hop();
let packet_mode = mix_packet.packet_mode();
+12 -10
View File
@@ -11,13 +11,13 @@ rust-version = "1.56"
base64 = "0.13"
colored = "2.0"
coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" }
nym-coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" }
nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
nym-mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
nym-vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
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-vesting-contract = { path = "../../../contracts/vesting" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -28,25 +28,26 @@ url = { version = "2.2", features = ["serde"] }
tokio = { version = "1.24.1", features = ["sync", "time"] }
futures = "0.3"
coconut-interface = { path = "../../coconut-interface" }
nym-coconut-interface = { path = "../../coconut-interface" }
nym-network-defaults = { path = "../../network-defaults" }
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
nym-execute = { path = "../../execute" }
# required for nyxd-client
# at some point it might be possible to make it wasm-compatible
# perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated)
async-trait = { version = "0.1.51", optional = true }
async-trait = { workspace = true, optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
nym-config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
cw3 = { version = "0.13.4", optional = true }
cw4 = { version = "0.13.4", optional = true }
cw3 = { workspace = true, optional = true }
cw4 = { workspace = true, optional = true }
prost = { version = "0.10", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { version = "1.0.0", optional = true }
nym-execute = { path = "../../execute" }
cosmwasm-std = { workspace = true, optional = true }
zeroize = { version = "1.5.7", optional = true }
[dev-dependencies]
ts-rs = "6.1.2"
@@ -64,6 +65,7 @@ nyxd-client = [
"sha2",
"itertools",
"cosmwasm-std",
"zeroize"
]
generate-ts = []
@@ -2,8 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{nym_api, ValidatorClientError};
use coconut_dkg_common::types::NodeIndex;
use coconut_interface::VerificationKey;
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
@@ -11,26 +9,26 @@ use nym_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::MixId;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef};
use nym_coconut_dkg_common::types::NodeIndex;
use nym_coconut_interface::VerificationKey;
pub use nym_mixnet_contract_common::{mixnode::MixNodeDetails, GatewayBond, IdentityKeyRef, MixId};
#[cfg(feature = "nyxd-client")]
use crate::nyxd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient};
#[cfg(feature = "nyxd-client")]
use crate::nyxd::{self, CosmWasmClient, NyxdClient, QueryNyxdClient, SigningNyxdClient};
#[cfg(feature = "nyxd-client")]
use coconut_dkg_common::{
use cw3::ProposalResponse;
#[cfg(feature = "nyxd-client")]
use nym_api_requests::models::MixNodeBondAnnotated;
#[cfg(feature = "nyxd-client")]
use nym_coconut_dkg_common::{
dealer::ContractDealing,
types::{DealerDetails, EpochId},
verification_key::ContractVKShare,
};
#[cfg(feature = "nyxd-client")]
use coconut_interface::Base58;
#[cfg(feature = "nyxd-client")]
use cw3::ProposalResponse;
#[cfg(feature = "nyxd-client")]
use nym_api_requests::models::MixNodeBondAnnotated;
use nym_coconut_interface::Base58;
#[cfg(feature = "nyxd-client")]
use nym_mixnet_contract_common::{
families::{Family, FamilyHead},
@@ -132,10 +130,9 @@ impl Config {
#[cfg(feature = "nyxd-client")]
#[derive(Clone)]
pub struct Client<C: Clone> {
// TODO: we really shouldn't be storing a mnemonic here, but removing it would be
// non-trivial amount of work and it's out of scope of the current branch
mnemonic: Option<bip39::Mnemonic>,
// // TODO: we really shouldn't be storing a mnemonic here, but removing it would be
// // non-trivial amount of work and it's out of scope of the current branch
// mnemonic: Option<bip39::Mnemonic>,
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
@@ -159,12 +156,11 @@ impl Client<SigningNyxdClient> {
let nyxd_client = NyxdClient::connect_with_mnemonic(
config.nyxd_config.clone(),
config.nyxd_url.as_str(),
mnemonic.clone(),
mnemonic,
None,
)?;
Ok(Client {
mnemonic: Some(mnemonic),
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
@@ -178,12 +174,7 @@ impl Client<SigningNyxdClient> {
}
pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
self.nyxd = NyxdClient::connect_with_mnemonic(
self.nyxd.current_config().clone(),
new_endpoint.as_ref(),
self.mnemonic.clone().unwrap(),
None,
)?;
self.nyxd.change_endpoint(new_endpoint.as_ref())?;
Ok(())
}
@@ -200,7 +191,6 @@ impl Client<QueryNyxdClient> {
NyxdClient::connect(config.nyxd_config.clone(), config.nyxd_url.as_str())?;
Ok(Client {
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
@@ -759,6 +749,12 @@ where
Ok(self.nym_api.get_mixnodes_detailed().await?)
}
pub async fn get_cached_mixnodes_detailed_unfiltered(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
@@ -144,6 +144,21 @@ impl Client {
.await
}
pub async fn get_mixnodes_detailed_unfiltered(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
&[
routes::API_VERSION,
routes::STATUS,
routes::MIXNODES,
routes::DETAILED_UNFILTERED,
],
NO_PARAMS,
)
.await
}
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
.await
@@ -8,6 +8,7 @@ pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const DETAILED: &str = "detailed";
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
pub const ACTIVE: &str = "active";
pub const REWARDED: &str = "rewarded";
pub const COCONUT_ROUTES: &str = "coconut";
@@ -1,10 +1,13 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::fmt;
use crate::nyxd::{Gas, GasPrice};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmwasm_std::Coin as CosmWasmCoin;
use cosmwasm_std::{Fraction, Uint128};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::Div;
#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct MismatchedDenoms;
@@ -19,6 +22,40 @@ pub struct Coin {
pub denom: String,
}
impl Div<GasPrice> for Coin {
type Output = Gas;
fn div(self, rhs: GasPrice) -> Self::Output {
&self / rhs
}
}
impl<'a> Div<GasPrice> for &'a Coin {
type Output = Gas;
fn div(self, rhs: GasPrice) -> Self::Output {
if self.denom != rhs.denom {
panic!(
"attempted to use two different denoms for gas calculation ({} and {})",
self.denom, rhs.denom
);
}
// tsk, tsk. somebody tried to divide by zero here!
let Some(gas_price_inv) = rhs.amount.inv() else {
panic!("attempted to divide by zero!")
};
let implicit_gas_limit = gas_price_inv * Uint128::new(self.amount);
if implicit_gas_limit.u128() >= u64::MAX as u128 {
u64::MAX
} else {
implicit_gas_limit.u128() as u64
}
.into()
}
}
impl Coin {
pub fn new<S: Into<String>>(amount: u128, denom: S) -> Self {
Coin {
@@ -128,3 +165,67 @@ impl CoinConverter for CosmWasmCoin {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn division_by_zero_gas_price() {
let gas_price: GasPrice = "0unym".parse().unwrap();
let amount = Coin::new(123, "unym");
let _res = amount / gas_price;
}
#[test]
#[should_panic]
fn division_by_gas_price_of_different_denom() {
let gas_price: GasPrice = "0.025unyx".parse().unwrap();
let amount = Coin::new(123, "unym");
let _res = amount / gas_price;
}
#[test]
fn gas_price_division() {
let amount = Coin::new(3938, "unym");
let gas_price = "0.025unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(157520, res.value());
let amount = Coin::new(1234567890, "unym");
let gas_price = "0.025unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(49382715600, res.value());
let amount = Coin::new(1, "unym");
let gas_price = "0.025unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(40, res.value());
let amount = Coin::new(150_000_000, "unym");
let gas_price = "0.001234unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(121555915721, res.value());
let amount = Coin::new(150_000_000, "unym");
let gas_price = "1unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(150_000_000, res.value());
let amount = Coin::new(150_000_000, "unym");
let gas_price = "1234.56unym".parse().unwrap();
let res = amount / gas_price;
assert_eq!(121500, res.value());
}
#[test]
fn gas_price_division_identity() {
let amount = Coin::new(1234567890, "unym");
let gas_price: GasPrice = "0.025unym".parse().unwrap();
let res1 = (&amount) / gas_price.clone();
let res2 = &gas_price * res1;
assert_eq!(amount, Coin::from(res2));
}
}
@@ -6,8 +6,8 @@ use cosmrs::tendermint::abci;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
pub use coconut_bandwidth_contract_common::event_attributes::*;
pub use coconut_dkg_common::event_attributes::*;
pub use nym_coconut_bandwidth_contract_common::event_attributes::*;
pub use nym_coconut_dkg_common::event_attributes::*;
// it seems that currently validators just emit stringified events (which are also returned as part of deliverTx response)
// as theirs logs
@@ -756,6 +756,19 @@ impl Client {
})
}
pub fn change_endpoint<U>(&mut self, new_endpoint: U) -> Result<(), NyxdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let new_rpc_client = HttpClient::new(new_endpoint)?;
self.rpc_client = new_rpc_client;
Ok(())
}
pub fn into_signer(self) -> DirectSecp256k1HdWallet {
self.signer
}
pub fn set_broadcast_polling_rate(&mut self, broadcast_polling_rate: Duration) {
self.broadcast_polling_rate = broadcast_polling_rate
}
@@ -140,7 +140,7 @@ pub enum NyxdError {
CosmwasmStdError(#[from] cosmwasm_std::StdError),
#[error("Coconut interface error: {0}")]
CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError),
CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError),
#[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")]
UnexpectedBech32Prefix { got: String, expected: String },
@@ -1,8 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nyxd::Coin;
use crate::nyxd::Gas;
use crate::nyxd::{Coin, GasPrice};
use cosmrs::{tx, AccountId};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
@@ -64,6 +64,12 @@ impl Display for Fee {
}
impl Fee {
pub fn manual_with_gas_price(fee: Coin, gas_price: GasPrice) -> Self {
let gas_limit = &fee / gas_price;
Fee::Manual(tx::Fee::from_amount_and_gas(fee.into(), gas_limit))
}
pub fn new_payer_granter_auto(
gas_adjustment: Option<GasAdjustment>,
payer: Option<AccountId>,
@@ -197,7 +197,7 @@ impl NyxdClient<SigningNyxdClient> {
{
let prefix = &config.chain_details.bech32_account_prefix;
let denom = &config.chain_details.mix_denom.base;
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)?;
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
let client_address = wallet
.try_derive_accounts()?
.into_iter()
@@ -212,6 +212,17 @@ impl NyxdClient<SigningNyxdClient> {
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
})
}
pub fn change_endpoint<U>(&mut self, new_endpoint: U) -> Result<(), NyxdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
self.client.change_endpoint(new_endpoint)
}
pub fn into_signer(self) -> DirectSecp256k1HdWallet {
self.client.into_signer()
}
}
impl<C> NyxdClient<C>

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