Compare commits

..

149 Commits

Author SHA1 Message Date
fmtabbara 5cc1060693 add app notifications 2021-10-04 20:49:04 +01:00
fmtabbara 2417e0565f start purchase page 2021-09-29 21:32:24 +01:00
fmtabbara ea3f70a2ac initial ui 2021-09-27 15:41:07 +01:00
Fouad 80664b911f Merge pull request #783 from nymtech/tauri-wallet-frontend
tauri wallet front-end
2021-09-23 20:59:27 +01:00
fmtabbara 46149012bd PR updates 2021-09-23 17:42:34 +01:00
fmtabbara de601c319a PR updates 2021-09-23 17:10:22 +01:00
max 7318de23f2 added info on nym-wallet (tauri) in readme 2021-09-22 18:07:29 +02:00
max 56e07753ea added minimal readme for wallet 2021-09-22 14:44:22 +02:00
fmtabbara 21b008fae9 use wrapper functions for send and delegate
form updates

update working

finish bonding and unbonding setup

funds allocation check when bonding/sending/delegating

update title
2021-09-21 13:09:57 +01:00
fmtabbara 27a202cbe8 integrate admin functions
updates

integrate admin form update function
2021-09-17 15:37:57 +01:00
Drazen Urch 085538582b Admin functions, reorganize code 2021-09-17 15:37:46 +01:00
Drazen Urch f66ea05929 Admin functions, reorganize code 2021-09-17 10:43:26 +02:00
fmtabbara 052c7188ec merge develop 2021-09-17 09:38:19 +01:00
Drazen Urch f6c316eea9 fix typo 2021-09-16 18:02:15 +02:00
Drazen Urch f33defc645 Squashed commit of the following:
commit 976dd7aae2
Author: Drazen Urch <drazen@urch.eu>
Date:   Wed Sep 15 17:28:49 2021 +0200

    Add block_height method to Delegation (#778)

    Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>

commit 0d21f4e937
Merge: e84af4f6 1403449a
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Wed Sep 15 12:41:29 2021 +0100

    Merge pull request #776 from nymtech/update/re-enable-bonding

    re-enable bonding

commit 1403449ad5
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Tue Sep 14 16:00:21 2021 +0100

    enable bonding

commit e84af4f601
Author: Drazen Urch <drazen@urch.eu>
Date:   Tue Sep 14 15:15:26 2021 +0200

    Migrate legacy delegation data (#771)

    * Skip ReadOnlyBucket deserialization errors

    * empty migration

    * clippy

    * cargo schema

    * Drop invalid delegation data

    * Dont drop old data

    * Add todo

    * Unify on type param

    * gateways are different

    * cargo fmt

    Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>

commit 26b032c15c
Merge: e1ddaff0 cba36253
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Sep 14 10:09:14 2021 +0100

    Merge pull request #774 from nymtech/feature/explorer-api-delegations

    Explorer-api: add API resource to show the delegations for each mix node

commit cba3625394
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Mon Sep 13 10:33:41 2021 +0100

    explorer-api: add API resource to show the delegations for each mix node

commit e1ddaff04d
Merge: 0b9c03ca 66ab5de4
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 10 17:17:14 2021 +0100

    Merge pull request #772 from nymtech/update/disable-bonding

    add app alert

commit 66ab5de442
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 10 16:16:58 2021 +0100

    add app alert

commit 0b9c03ca90
Author: Dave Hrycyszyn <futurechimp@users.noreply.github.com>
Date:   Fri Sep 10 11:23:21 2021 +0300

    Adding deps for building the Tauri wallet under Ubuntu (#770)

commit c9dce0c1da
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Sep 9 11:21:45 2021 +0200

    Feature/consumable bandwidth (#766)

    * Set actual value for bandwidth

    Also put it as a public attribute, such that it can be actively used
    by the credential consumer

    * Switch from sending Attribute structs to sending the actual attribute bytes over the wire

    * Add atomic bandwidth value to gateway

    * Consume bandwidth based on the mix packet size

    * Use Bandwidth struct for specific functionality

    * Move bandwidth code outside the dependency path of wasm client

    * Use u64 instead of AtomicU64, as the handling is not parallel

commit e00e77db15
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Wed Sep 8 15:07:24 2021 +0200

    Feature/bond blockstamp (#760)

    * Add block_height to MixNode/GatewayBond

    * Reward based on blockstamp of bonded node or of delegation

    * Add specific tests

    * Add migration code

    * Apply doc nit

commit 1074449f91
Merge: 08276e6e 9a3d824a
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Tue Sep 7 23:45:10 2021 +0100

    Merge pull request #767 from nymtech/update/remove-app-alert

    remove alert

commit 08276e6e42
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Sep 7 16:33:30 2021 +0200

    Remove migration code (#759)

commit 9a3d824a4a
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Mon Sep 6 20:39:24 2021 +0100

    remove alert

commit 2789ee8f18
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Fri Sep 3 15:30:45 2021 +0300

    Update coconut-rs and use hash_to_scalar from there (#765)

    Failed tests are due to some nightly issue, not related to this PR

commit a7ba643c35
Merge: 28be53ee c42f3c68
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 3 09:14:50 2021 +0100

    Merge pull request #762 from nymtech/feature/app-alert

    add app alert banner

commit 28be53eefb
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Sep 2 18:26:40 2021 +0300

    Add block_height in the Delegation structure as well (#757)

commit 219c45a352
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Thu Sep 2 15:48:29 2021 +0100

    Updated cosmos-sdk (#761)

    * Updated cosmos-sdk

    * Re-exposing more things

commit 1a3b83752e
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Sep 2 15:45:56 2021 +0100

    Bump next from 11.1.0 to 11.1.1 in /wallet-web (#758)

    Bumps [next](https://github.com/vercel/next.js) from 11.1.0 to 11.1.1.
    - [Release notes](https://github.com/vercel/next.js/releases)
    - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
    - [Commits](https://github.com/vercel/next.js/compare/v11.1.0...v11.1.1)

    ---
    updated-dependencies:
    - dependency-name: next
      dependency-type: direct:production
    ...

    Signed-off-by: dependabot[bot] <support@github.com>

    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit c42f3c6844
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Thu Sep 2 12:29:47 2021 +0100

    add app alert banner

commit a5d3ba3900
Merge: 92e13a5d cdf0d443
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Aug 31 15:59:11 2021 +0100

    Merge pull request #755 from nymtech/bugfix/explorer-api-ping

    Explorer API: port test now split out address resolution and add units tests

commit cdf0d44341
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Aug 31 14:37:28 2021 +0100

    explorer-api: turned down logging from `error` to `warn`

commit 92f976a45d
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Fri Aug 27 11:34:21 2021 +0100

    explorer-api: sanitize hostname before running checks to avoid leading or trailing spaces that are known to exist in the current test net

commit 2bc858cde3
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Fri Aug 27 09:32:26 2021 +0100

    explorer-api: port test: split out address resolution and add units tests

commit 92e13a5d00
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Aug 31 14:51:15 2021 +0300

    Feature/add blockstamp (#756)

    * Add RawDelegationData

    * Fix current tests for the new stored data

    * Added migration commit. Will be reverted after doing the migration

    * New tests for block height

    * Use current blockstamp instead of 24h old one

    * Put _alot_ of migration stuff in the migrate function scope

commit 122f5d9f2e
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Mon Aug 30 10:27:20 2021 +0300

    Feature/cred after handshake (#745)

    * Call perform_initial_authentication instead of register in clients

    * Refactor the register/authenticate functions a bit

    * Introduce Bandwidth request type

    * Add encryption layer to cred

    * Remove cred pass and check from handshake

    * Replaced unreachable!  with error

    * Changed decrypt_tagged signature to not take mutable ownership of data

    * Put handle_bandwidth work inside a function

    * Add check before unwrap

    * Remove unnecessary async

    * Decouple bandwidth credential from authentication

    * Use new_error for ServerResponse:Error

    * Send a fresh IV each time the BandwidthCredential request is sent

    * Remove unwrap of bincode::serialize

    * Add comment regarding Bandwidth response

    * Remove _mut from naming

    * Leave Debug trait alone, as the initial error doesn't reproduce anymore

    * Pass iv as Vec<u8> instead of base58 string

    * Renamed AuthenticationIV to IV, as it is now used for more the just authentication

    * Did some IV refactorization

commit 982ee0266c
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Fri Aug 27 16:02:34 2021 +0300

    Feature/get own delegations (#748)

    * Introduce reverse delegation bucket

    * Add client command

    * Fix clippy error

    * Added tests in queries

    * Add tests in transactions

    * Migration code. Will be reverted after it's called on testnet

    * Replace unwrap with expect

    * Move some test code in the right file...

    ... to remove unnecessary auxiliary function.

    * Reduce the scope to migration auxiliary functions

    * Rename everything from [node]reverse to reverse[node]

    * Fix fmt

commit 5f42a9bd05
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Fri Aug 27 13:52:18 2021 +0100

    NetworkMonitorBuilder - starting the monitor after rocket has launched (#754)

    * NetworkMonitorBuilder - starting the monitor after rocket has launched

    * Removed unused import

commit 1811df9ddb
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Fri Aug 27 13:52:10 2021 +0100

    Enabled validators api argument (#753)

commit 6bdfe7f895
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Thu Aug 26 11:21:01 2021 +0100

    Correctly bounding nominator of uptime calculation (#752)

commit c6b286a1db
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Wed Aug 25 14:50:57 2021 +0100

    Fixed argument parsing for ipv6 'good' topology (#751)

commit b3568a26f5
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Aug 24 11:25:05 2021 +0300

    Revert "Migration commit, will be reverted after the testnet contract is updated" (#749)

    This reverts commit 38d868bcce.

commit 15ae0f521e
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Mon Aug 23 10:26:51 2021 +0100

    Feature/more reliable uptime calculation (#747)

    * New database table holding monitor run info

    * SQL interface for new table

    * Updated uptime calculation to instead rely on number of monitor test runs

commit 2923d4b872
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Aug 19 22:03:07 2021 +0300

    Update template toml key (#746)
2021-09-16 17:36:24 +02:00
Drazen Urch 424230c3bb Squashed commit of the following:
commit cddd9e8e4c
Merge: 40fbdff0 976dd7aa
Author: Drazen Urch <durch@users.noreply.guthub.com>
Date:   Thu Sep 16 17:27:27 2021 +0200

    Merge branch 'develop' into tauri-wallet

commit 976dd7aae2
Author: Drazen Urch <drazen@urch.eu>
Date:   Wed Sep 15 17:28:49 2021 +0200

    Add block_height method to Delegation (#778)

    Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>

commit 0d21f4e937
Merge: e84af4f6 1403449a
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Wed Sep 15 12:41:29 2021 +0100

    Merge pull request #776 from nymtech/update/re-enable-bonding

    re-enable bonding

commit 1403449ad5
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Tue Sep 14 16:00:21 2021 +0100

    enable bonding

commit e84af4f601
Author: Drazen Urch <drazen@urch.eu>
Date:   Tue Sep 14 15:15:26 2021 +0200

    Migrate legacy delegation data (#771)

    * Skip ReadOnlyBucket deserialization errors

    * empty migration

    * clippy

    * cargo schema

    * Drop invalid delegation data

    * Dont drop old data

    * Add todo

    * Unify on type param

    * gateways are different

    * cargo fmt

    Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>

commit 26b032c15c
Merge: e1ddaff0 cba36253
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Sep 14 10:09:14 2021 +0100

    Merge pull request #774 from nymtech/feature/explorer-api-delegations

    Explorer-api: add API resource to show the delegations for each mix node

commit cba3625394
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Mon Sep 13 10:33:41 2021 +0100

    explorer-api: add API resource to show the delegations for each mix node

commit e1ddaff04d
Merge: 0b9c03ca 66ab5de4
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 10 17:17:14 2021 +0100

    Merge pull request #772 from nymtech/update/disable-bonding

    add app alert

commit 66ab5de442
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 10 16:16:58 2021 +0100

    add app alert

commit 0b9c03ca90
Author: Dave Hrycyszyn <futurechimp@users.noreply.github.com>
Date:   Fri Sep 10 11:23:21 2021 +0300

    Adding deps for building the Tauri wallet under Ubuntu (#770)

commit c9dce0c1da
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Sep 9 11:21:45 2021 +0200

    Feature/consumable bandwidth (#766)

    * Set actual value for bandwidth

    Also put it as a public attribute, such that it can be actively used
    by the credential consumer

    * Switch from sending Attribute structs to sending the actual attribute bytes over the wire

    * Add atomic bandwidth value to gateway

    * Consume bandwidth based on the mix packet size

    * Use Bandwidth struct for specific functionality

    * Move bandwidth code outside the dependency path of wasm client

    * Use u64 instead of AtomicU64, as the handling is not parallel

commit e00e77db15
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Wed Sep 8 15:07:24 2021 +0200

    Feature/bond blockstamp (#760)

    * Add block_height to MixNode/GatewayBond

    * Reward based on blockstamp of bonded node or of delegation

    * Add specific tests

    * Add migration code

    * Apply doc nit

commit 1074449f91
Merge: 08276e6e 9a3d824a
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Tue Sep 7 23:45:10 2021 +0100

    Merge pull request #767 from nymtech/update/remove-app-alert

    remove alert

commit 08276e6e42
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Sep 7 16:33:30 2021 +0200

    Remove migration code (#759)

commit 9a3d824a4a
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Mon Sep 6 20:39:24 2021 +0100

    remove alert

commit 2789ee8f18
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Fri Sep 3 15:30:45 2021 +0300

    Update coconut-rs and use hash_to_scalar from there (#765)

    Failed tests are due to some nightly issue, not related to this PR

commit a7ba643c35
Merge: 28be53ee c42f3c68
Author: Fouad <fmtabbara@hotmail.co.uk>
Date:   Fri Sep 3 09:14:50 2021 +0100

    Merge pull request #762 from nymtech/feature/app-alert

    add app alert banner

commit 28be53eefb
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Sep 2 18:26:40 2021 +0300

    Add block_height in the Delegation structure as well (#757)

commit 219c45a352
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Thu Sep 2 15:48:29 2021 +0100

    Updated cosmos-sdk (#761)

    * Updated cosmos-sdk

    * Re-exposing more things

commit 1a3b83752e
Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Date:   Thu Sep 2 15:45:56 2021 +0100

    Bump next from 11.1.0 to 11.1.1 in /wallet-web (#758)

    Bumps [next](https://github.com/vercel/next.js) from 11.1.0 to 11.1.1.
    - [Release notes](https://github.com/vercel/next.js/releases)
    - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
    - [Commits](https://github.com/vercel/next.js/compare/v11.1.0...v11.1.1)

    ---
    updated-dependencies:
    - dependency-name: next
      dependency-type: direct:production
    ...

    Signed-off-by: dependabot[bot] <support@github.com>

    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

commit c42f3c6844
Author: fmtabbara <fmtabbara@hotmail.co.uk>
Date:   Thu Sep 2 12:29:47 2021 +0100

    add app alert banner

commit a5d3ba3900
Merge: 92e13a5d cdf0d443
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Aug 31 15:59:11 2021 +0100

    Merge pull request #755 from nymtech/bugfix/explorer-api-ping

    Explorer API: port test now split out address resolution and add units tests

commit cdf0d44341
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Tue Aug 31 14:37:28 2021 +0100

    explorer-api: turned down logging from `error` to `warn`

commit 92f976a45d
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Fri Aug 27 11:34:21 2021 +0100

    explorer-api: sanitize hostname before running checks to avoid leading or trailing spaces that are known to exist in the current test net

commit 2bc858cde3
Author: Mark Sinclair <mmsinclair@gmail.com>
Date:   Fri Aug 27 09:32:26 2021 +0100

    explorer-api: port test: split out address resolution and add units tests

commit 92e13a5d00
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Aug 31 14:51:15 2021 +0300

    Feature/add blockstamp (#756)

    * Add RawDelegationData

    * Fix current tests for the new stored data

    * Added migration commit. Will be reverted after doing the migration

    * New tests for block height

    * Use current blockstamp instead of 24h old one

    * Put _alot_ of migration stuff in the migrate function scope

commit 122f5d9f2e
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Mon Aug 30 10:27:20 2021 +0300

    Feature/cred after handshake (#745)

    * Call perform_initial_authentication instead of register in clients

    * Refactor the register/authenticate functions a bit

    * Introduce Bandwidth request type

    * Add encryption layer to cred

    * Remove cred pass and check from handshake

    * Replaced unreachable!  with error

    * Changed decrypt_tagged signature to not take mutable ownership of data

    * Put handle_bandwidth work inside a function

    * Add check before unwrap

    * Remove unnecessary async

    * Decouple bandwidth credential from authentication

    * Use new_error for ServerResponse:Error

    * Send a fresh IV each time the BandwidthCredential request is sent

    * Remove unwrap of bincode::serialize

    * Add comment regarding Bandwidth response

    * Remove _mut from naming

    * Leave Debug trait alone, as the initial error doesn't reproduce anymore

    * Pass iv as Vec<u8> instead of base58 string

    * Renamed AuthenticationIV to IV, as it is now used for more the just authentication

    * Did some IV refactorization

commit 982ee0266c
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Fri Aug 27 16:02:34 2021 +0300

    Feature/get own delegations (#748)

    * Introduce reverse delegation bucket

    * Add client command

    * Fix clippy error

    * Added tests in queries

    * Add tests in transactions

    * Migration code. Will be reverted after it's called on testnet

    * Replace unwrap with expect

    * Move some test code in the right file...

    ... to remove unnecessary auxiliary function.

    * Reduce the scope to migration auxiliary functions

    * Rename everything from [node]reverse to reverse[node]

    * Fix fmt

commit 5f42a9bd05
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Fri Aug 27 13:52:18 2021 +0100

    NetworkMonitorBuilder - starting the monitor after rocket has launched (#754)

    * NetworkMonitorBuilder - starting the monitor after rocket has launched

    * Removed unused import

commit 1811df9ddb
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Fri Aug 27 13:52:10 2021 +0100

    Enabled validators api argument (#753)

commit 6bdfe7f895
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Thu Aug 26 11:21:01 2021 +0100

    Correctly bounding nominator of uptime calculation (#752)

commit c6b286a1db
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Wed Aug 25 14:50:57 2021 +0100

    Fixed argument parsing for ipv6 'good' topology (#751)

commit b3568a26f5
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Tue Aug 24 11:25:05 2021 +0300

    Revert "Migration commit, will be reverted after the testnet contract is updated" (#749)

    This reverts commit 38d868bcce.

commit 15ae0f521e
Author: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Date:   Mon Aug 23 10:26:51 2021 +0100

    Feature/more reliable uptime calculation (#747)

    * New database table holding monitor run info

    * SQL interface for new table

    * Updated uptime calculation to instead rely on number of monitor test runs

commit 2923d4b872
Author: Bogdan-Ștefan Neacşu <bogdan@nymtech.net>
Date:   Thu Aug 19 22:03:07 2021 +0300

    Update template toml key (#746)
2021-09-16 17:33:20 +02:00
Drazen Urch cddd9e8e4c Merge branch 'develop' into tauri-wallet 2021-09-16 17:27:27 +02:00
fmtabbara 63bc42cd5f update title 2021-09-16 10:01:53 +01:00
fmtabbara 9dc8ba7b77 funds allocation check when bonding/sending/delegating 2021-09-15 21:39:18 +01:00
fmtabbara e130131f16 finish bonding and unbonding setup 2021-09-15 20:37:24 +01:00
fmtabbara 64a5b4b593 Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-14 12:08:38 +01:00
Drazen Urch 40fbdff05a Coin and denom tests 2021-09-14 10:29:31 +02:00
fmtabbara 9934b9bc8a update working 2021-09-13 15:20:56 +01:00
fmtabbara 6954b383a7 form updates 2021-09-13 14:26:21 +01:00
fmtabbara 4b5276e816 Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-13 13:32:24 +01:00
Drazen Urch 0278bd2c26 Fix coin to cosmwasm coin 2021-09-13 14:31:33 +02:00
fmtabbara 56a9527497 use wrapper functions for send and delegate 2021-09-13 13:30:05 +01:00
Drazen Urch a601c28a20 Return useful info from bond/unbond 2021-09-13 13:07:57 +02:00
Drazen Urch 005dd7513b Merge branch 'tauri-wallet-frontend' into tauri-wallet 2021-09-13 12:58:01 +02:00
Drazen Urch 3ebdc55847 extract Coin and Denom 2021-09-13 12:50:26 +02:00
fmtabbara aaf5d18692 update admin component 2021-09-13 11:19:28 +01:00
fmtabbara bcbec1f3e6 admin form style updates 2021-09-12 21:49:39 +01:00
fmtabbara c95005265d button updates 2021-09-12 01:26:57 +01:00
fmtabbara b299c9e4b5 add qrcode to receive page 2021-09-12 01:03:21 +01:00
fmtabbara bacbd3dfce fix bug 2021-09-12 00:40:53 +01:00
fmtabbara 55561fe1f7 add admin page 2021-09-12 00:26:15 +01:00
fmtabbara 0d01500b87 remove login deets 2021-09-11 00:35:14 +01:00
fmtabbara 74e34567b4 form updates 2021-09-11 00:32:54 +01:00
fmtabbara b77025bfd5 form updates 2021-09-11 00:10:03 +01:00
fmtabbara 4d831efcd6 more fees work 2021-09-10 22:33:00 +01:00
fmtabbara d227d20385 add gas fees to bond form 2021-09-10 17:31:04 +01:00
fmtabbara fb2d3bae3c use new getFee api 2021-09-10 14:17:24 +01:00
fmtabbara 967d74eb19 add nvmrc file 2021-09-10 11:15:52 +01:00
fmtabbara 228df278d9 update tsconfig 2021-09-10 11:13:18 +01:00
fmtabbara 37da23ab1c set up global error handling 2021-09-10 09:56:04 +01:00
fmtabbara 0c9cf7b5d9 finish create account 2021-09-10 09:16:45 +01:00
fmtabbara 262149078c rust updates 2021-09-09 19:21:38 +01:00
fmtabbara 08fd1c1b47 make getBalance global 2021-09-09 19:00:30 +01:00
Drazen Urch 3bcbb90127 fix docs 2021-09-09 17:47:11 +02:00
Drazen Urch 8156ed0029 get_fee, create_new_account 2021-09-09 17:43:56 +02:00
Drazen Urch 265f7a7c2e Dedicated workspace, random_mnemonic, gas_limits 2021-09-09 13:21:27 +02:00
fmtabbara 5de8c9d1ed style updates 2021-09-09 11:35:49 +01:00
fmtabbara 7658eec9b9 create account page 2021-09-09 09:16:38 +01:00
fmtabbara 99c49581df Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-08 20:49:27 +01:00
Drazen Urch 926689da1d Merge branch 'tauri-wallet' of https://github.com/nymtech/nym into tauri-wallet 2021-09-08 11:54:49 +02:00
Drazen Urch 714171f4e5 redundant into 2021-09-08 11:54:36 +02:00
fmtabbara 7a8ad1387d add eslint file 2021-09-08 10:30:51 +01:00
fmtabbara bd72426280 start adding gas fees 2021-09-07 23:44:02 +01:00
fmtabbara 48d0f31d7e send updates 2021-09-07 23:13:54 +01:00
fmtabbara 4522c18a55 Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-07 21:53:38 +01:00
fmtabbara b9389f1235 send updates 2021-09-07 21:53:05 +01:00
durch b40be179ae [ci skip] Generate TS types 2021-09-07 12:37:24 +00:00
Drazen Urch 32ef9e019e More verbose send response 2021-09-07 14:27:49 +02:00
fmtabbara 45a56a7088 update logo 2021-09-06 20:27:15 +01:00
Drazen Urch d50afd6113 Add coconut creds 2021-09-06 20:00:47 +02:00
fmtabbara 3205b1e0e6 start work on send form 2021-09-06 16:42:56 +01:00
Drazen Urch 53ea8486f8 get_gas_price, get_gas_limits 2021-09-06 17:36:46 +02:00
Drazen Urch 43ababf8d4 Rework client errors 2021-09-06 17:31:08 +02:00
fmtabbara 5461574023 small refactors 2021-09-06 13:23:20 +01:00
fmtabbara 01d2df7bb7 set up form validation for undelegation 2021-09-06 11:12:39 +01:00
fmtabbara 7cff72757b set up delgate request 2021-09-05 00:07:46 +01:00
fmtabbara 5bcbf45d16 onerror onsuccess added to bond form 2021-09-04 22:52:02 +01:00
fmtabbara 4295d75e0f format bonding data pre request 2021-09-03 16:53:32 +01:00
fmtabbara 018666a614 layout updates 2021-09-03 16:37:51 +01:00
fmtabbara 15048524a7 layout updates 2021-09-03 16:25:15 +01:00
fmtabbara 2b792945cc update type roots 2021-09-03 15:07:38 +01:00
fmtabbara b6193270a6 Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-03 15:02:56 +01:00
fmtabbara 0b4a8fe657 update type roots 2021-09-03 15:02:09 +01:00
Drazen Urch 2997337948 printable balance should be major 2021-09-03 15:29:20 +02:00
Drazen Urch e1bea43ff4 Autogenerate types (#763)
* Generate TS types on push

* run shell

* fix typo

* pango

* libpango-dev

* hopefully all deps are in now :)

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-09-03 15:22:48 +02:00
fmtabbara 27ef28da8d Merge branch 'tauri-wallet' into tauri-wallet-frontend
rust updates
2021-09-03 12:13:28 +01:00
fmtabbara 42bf139ebb update args collection function 2021-09-03 12:12:52 +01:00
fmtabbara 352862e4d0 delegation form validation 2021-09-03 11:42:31 +01:00
fmtabbara 9f9ab010d8 integrate rust generated types 2021-09-03 11:39:17 +01:00
Drazen Urch 87a0b05d1a Send 2021-09-03 07:43:29 +02:00
Drazen Urch dbf82da9b6 Delegation docs 2021-09-03 07:36:02 +02:00
Drazen Urch 01a4305883 Delegation 2021-09-03 07:33:11 +02:00
Drazen Urch 704b3241ee Gateway bonding 2021-09-03 06:58:57 +02:00
fmtabbara 4513edae46 rust updates 2021-09-02 21:38:19 +01:00
Drazen Urch f020b21106 node unbond 2021-09-02 21:08:19 +02:00
Drazen Urch 2587906473 TS exports, rework internals 2021-09-02 20:57:10 +02:00
fmtabbara 5d3f1b86e8 delegate form validation 2021-09-02 17:29:03 +01:00
fmtabbara 5a17e48581 finish bond form validation 2021-09-02 16:11:54 +01:00
fmtabbara 41356f2181 Merge branch 'tauri-wallet' into tauri-wallet-frontend
merge rust updates
2021-09-02 10:10:55 +01:00
fmtabbara bd7d788741 more validation work 2021-09-02 10:10:18 +01:00
Drazen Urch 63107c2bca WIP node bonding 2021-09-02 10:27:49 +02:00
fmtabbara f043639ad2 Merge branch 'tauri-wallet' into tauri-wallet-frontend
merge rust update
2021-09-01 19:48:22 +01:00
fmtabbara 1d2a1b2635 start form validation 2021-09-01 19:47:41 +01:00
Drazen Urch 6fb15fff8b printable_balance 2021-09-01 16:33:29 +02:00
fmtabbara c69d7fa46f remove unused imports 2021-09-01 15:12:50 +01:00
fmtabbara c2ee02a2cf use printable balance 2021-09-01 15:12:25 +01:00
fmtabbara 6f5f0c00b5 use printable balance 2021-09-01 15:11:24 +01:00
fmtabbara 12707c5f1e merge rust updates 2021-09-01 14:59:27 +01:00
Drazen Urch 0daf89eeb4 Utility native <> printable functions 2021-09-01 15:14:50 +02:00
fmtabbara 2aa7fa0426 move balance function call to global state 2021-09-01 13:41:42 +01:00
fmtabbara f5aa6e2db2 style updates 2021-09-01 09:39:22 +01:00
fmtabbara ee5d1c3419 fix nav styling 2021-08-31 20:57:21 +01:00
fmtabbara e68c261162 fix ts error 2021-08-31 20:48:30 +01:00
fmtabbara c7fe4cd24e update imports 2021-08-31 15:28:46 +01:00
fmtabbara 1c690fc3d0 Merge branch 'tauri-wallet' into tauri-wallet-frontend
merge rust updates
2021-08-31 15:24:35 +01:00
Drazen Urch f56edb825a Fix client address 2021-08-31 15:22:10 +02:00
fmtabbara d8e6a5fb2e make typescript happy 2021-08-31 12:55:13 +01:00
fmtabbara b95893bb02 update balance page 2021-08-31 11:20:12 +01:00
fmtabbara 6bdff701b4 merge backend updates 2021-08-31 11:05:51 +01:00
fmtabbara ccdb5911c6 update routing and use new sign in function 2021-08-31 09:35:24 +01:00
Drazen Urch eff38c8466 some cleanup, get blockchain stuff working 2021-08-31 07:38:25 +02:00
Drazen Urch 5f4fabc0b8 Add internal documentation scaffolding 2021-08-27 15:17:15 +02:00
fmtabbara e2dd1cc9ae Merge branch 'tauri-wallet' into tauri-wallet-frontend
connect with mnemonic update
2021-08-25 14:51:29 +01:00
Drazen Urch 42f75028bc Resolve state deadlock 2021-08-25 15:44:08 +02:00
fmtabbara c7e622f284 begin sign in rust integration 2021-08-25 14:06:37 +01:00
fmtabbara 248da351c6 use color palette object 2021-08-25 14:05:00 +01:00
fmtabbara fe1c8a3b08 Merge branch 'tauri-wallet' into tauri-wallet-frontend
merge rust api updates
2021-08-25 10:36:11 +01:00
fmtabbara 84af923389 update receive to use state value 2021-08-25 10:35:42 +01:00
Drazen Urch 1bc17abbaa Add connect_with_mnemonic and get_balance tauri functions 2021-08-25 11:18:16 +02:00
fmtabbara b8c2735520 cargo lock update 2021-08-24 16:12:08 +01:00
fmtabbara 67fd4367ef global theme update 2021-08-24 16:08:55 +01:00
fmtabbara 4540d2c447 Merge branch 'feature/tauri-wallet-ui' into tauri-wallet
merge with rust updates
2021-08-24 12:56:59 +01:00
fmtabbara 4ad25114c3 update nav cards 2021-08-24 12:46:34 +01:00
fmtabbara 3ce7888c07 address and balance cards 2021-08-24 11:59:10 +01:00
Drazen Urch ce75769703 Bootstrap nymd client with config 2021-08-24 12:54:49 +02:00
fmtabbara 66210658cb add favicon 2021-08-24 10:35:54 +01:00
fmtabbara 728da763b3 fix padding issue 2021-08-23 22:54:30 +01:00
fmtabbara 3da08ee33c style updates 2021-08-23 22:49:39 +01:00
fmtabbara 55977185fd add unbond and undelegte pages 2021-08-23 22:04:26 +01:00
fmtabbara 1c8c0a47bc receive page adjust margin 2021-08-23 21:34:14 +01:00
fmtabbara c161b2fe71 send wizard updates 2021-08-23 21:32:29 +01:00
fmtabbara eb91c1180d update balance page 2021-08-23 17:23:25 +01:00
fmtabbara 13e357637b style updates 2021-08-23 17:18:07 +01:00
fmtabbara 54c4bdb7d2 send wizard update 2021-08-23 17:12:40 +01:00
fmtabbara 9b5f50913f add send wizard 2021-08-23 14:56:52 +01:00
fmtabbara 7cfa35b542 style updates 2021-08-23 11:18:13 +01:00
fmtabbara 581a6b0a6f update nav bar 2021-08-23 10:38:56 +01:00
fmtabbara 85f23eb3d1 create delegation page 2021-08-23 10:38:42 +01:00
fmtabbara 17100fa7da create node type selector component 2021-08-23 10:38:17 +01:00
fmtabbara 24af0c94bf add bond form 2021-08-20 22:26:14 +01:00
fmtabbara 8ec9e3121f side bar updates 2021-08-20 17:35:59 +01:00
fmtabbara 498dbb8ba3 add balance page 2021-08-20 12:24:04 +01:00
fmtabbara 7345bd8148 update navigation style 2021-08-20 11:51:27 +01:00
fmtabbara b405d2e4af set up tauri app with React frontend 2021-08-20 09:03:21 +01:00
468 changed files with 13495 additions and 111392 deletions
+3 -40
View File
@@ -1,40 +1,3 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners.
# More details are here: https://help.github.com/articles/about-codeowners/
# The '*' pattern is global owners.
# Order is important. The last matching pattern has the most precedence.
# The folders are ordered as follows:
# In each subsection folders are ordered first by depth, then alphabetically.
# This should make it easy to add new rules without breaking existing ones.
# Something weird not covered by anything else
* @futurechimp @mmsinclair
# Rust rules:
*.rs @durch @futurechimp @jstuczyn @neacsu
Cargo.* @durch @futurechimp @jstuczyn @neacsu
# JS rules:
*.js @mmsinclair @fmtabbara @Aid19801
*.ts @mmsinclair @fmtabbara @Aid19801
*.tsx @mmsinclair @fmtabbara @Aid19801
*.jsx @mmsinclair @fmtabbara @Aid19801
# Something looking like possible documentation rules:
*.md @mfahampshire
# our docker scripts
/docker/ @neacsu
# if there are any changes in the core crypto, I feel like Ania should take a look:
/common/crypto/ @aniampio
/common/nymsphinx/ @aniampio
# Explorer and wallet should probably get looked by the product team
/explorer/ @nymtech/product
/nym-wallet/ @nymtech/product
/wallet-web/ @nymtech/product
# Global owners. If you want to set more specific (per-directory, per-language) owners, see the write-up at
# https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners
* @durch @futurechimp @jstuczyn @neacsu
-32
View File
@@ -1,32 +0,0 @@
---
name: Feature request
about: Suggest an enhancement to the product
title: "[Feature Request]"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is...
**Is your request a feature not related to an existing problem? A new feature.**
For example.
- Given I am using the nym wallet
- When I transfer nym tokens across the network
- Then I want to have an url link in the wallet which navigates outside the application to the nym-explorer
**Where does the feature fit in the Nym real estate?**
- Application / UI
**What is this solving?**
How will this improve the product...
**Is this an update to packages or libraries?**
If so, please list them. If not, please ignore this section.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-43
View File
@@ -1,43 +0,0 @@
---
name: Report
about: To help identify and reproduce issues
title: "[Issue]"
labels: bug, bug-needs-triage, qa
assignees: tommyv1987
---
**Describe the issue**
A clear and concise description of what the issue is...
**Expected behaviour**
A clear and concise description of what you expected to happen...
**Stack Traces**
If there are stack traces or logs, please provide them here...
**Steps to Reproduce**
Steps to reproduce the behaviour, if you're familiar with BDD syntax, please write it in this style:
- Given I was doing X
- And I installed Y
- When I actioned Y
- Then I expect Z
*An example:*
- Given I was setting up a mix-node following the instructions in the docs
- And I successfully bonded my node via the the wallet
- When I went to start my mixnode
- Then I was presented with an error
**Screenshots**
If applicable, add screenshots to help explain your problem...
**Which area of Nym were you using?**
- UI: [e.g. Websites - network-explorer, nym-website]
- Application: [e.g Gateway, Client, Wallet]
- OS: [e.g. Ubuntu 20.x, MacOs Big Sur, Windows 10]
- Browser: [e.g Chrome (if applicable)]
- Version: [e.g. nym binary(0.11.0), browser(94.0)]
**Additional context**
Please provide any other information
+35 -67
View File
@@ -1,102 +1,70 @@
name: Continuous integration
on:
push:
paths-ignore:
- 'explorer/**'
pull_request:
paths-ignore:
- 'explorer/**'
on: [push, pull_request]
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/build_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
build:
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.os == 'windows-latest' }}
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' }}
strategy:
matrix:
rust: [stable, beta, nightly]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code
uses: actions/checkout@v2
- uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
# Exclude validator API on Windows
- uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: build
args: --all --exclude nym-validator-api
- uses: actions-rs/cargo@v1
if: ${{ matrix.os != 'windows-latest' }}
with:
command: build
args: --all
- name: Run all tests
uses: actions-rs/cargo@v1
# Exclude validator API on Windows
- uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: test
args: --all --exclude nym-validator-api
- uses: actions-rs/cargo@v1
if: ${{ matrix.os != 'windows-latest' }}
with:
command: test
args: --all
- name: Check formatting
uses: actions-rs/cargo@v1
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
# if: matrix.os == 'ubuntu-latest' && matrix.rust == 'stable'
# Exclude validator API on Windows
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' && matrix.os == 'windows-latest' }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
command: clippy
args: --all --exclude nym-validator-api -- -D warnings
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' && matrix.os != 'windows-latest' }}
with:
command: clippy
args: -- -D warnings
# COCONUT stuff
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --all --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --all --features=coconut
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --features=coconut -- -D warnings
@@ -1,50 +0,0 @@
[
{
"os":"ubuntu-latest",
"rust":"stable",
"runOnEvent":"always"
},
{
"os":"windows-latest",
"rust":"stable",
"runOnEvent":"pull_request"
},
{
"os":"macos-latest",
"rust":"stable",
"runOnEvent":"pull_request"
},
{
"os":"ubuntu-latest",
"rust":"beta",
"runOnEvent":"pull_request"
},
{
"os":"windows-latest",
"rust":"beta",
"runOnEvent":"pull_request"
},
{
"os":"macos-latest",
"rust":"beta",
"runOnEvent":"pull_request"
},
{
"os":"ubuntu-latest",
"rust":"nightly",
"runOnEvent":"pull_request"
},
{
"os":"windows-latest",
"rust":"nightly",
"runOnEvent":"pull_request"
},
{
"os":"macos-latest",
"rust":"nightly",
"runOnEvent":"pull_request"
}
]
+14
View File
@@ -0,0 +1,14 @@
name: Clippy check
on: push
jobs:
clippy_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: rustup component add clippy
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
@@ -1,14 +0,0 @@
[
{
"rust":"stable",
"runOnEvent":"always"
},
{
"rust":"beta",
"runOnEvent":"pull_request"
},
{
"rust":"nightly",
"runOnEvent":"pull_request"
}
]
@@ -1,58 +0,0 @@
name: ERC20 Bridge Contract
on: [ push, pull_request ]
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
erc20-bridge-contract:
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
env:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/erc20-bridge/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/erc20-bridge/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- -D warnings
+3 -23
View File
@@ -1,34 +1,16 @@
name: Mixnet Contract
on:
push:
paths-ignore:
- 'explorer/**'
pull_request:
paths-ignore:
- 'explorer/**'
on: [push, pull_request]
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
mixnet-contract:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
matrix:
rust: [ stable, beta, nightly ]
steps:
- uses: actions/checkout@v2
@@ -41,8 +23,6 @@ jobs:
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
env:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/mixnet/Cargo.toml --target wasm32-unknown-unknown
@@ -1,23 +0,0 @@
name: Linting for Network Explorer (eslint/prettier)
on:
pull_request:
paths:
- 'explorer/**'
defaults:
run:
working-directory: explorer
jobs:
build:
runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install
- name: Run ESLint
# GitHub should automatically annotate the PR
run: npm run lint
-57
View File
@@ -1,57 +0,0 @@
name: CI for Network Explorer
on:
push:
paths:
- 'explorer/**'
defaults:
run:
working-directory: explorer
jobs:
build:
runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
run: sudo apt-get install rsync
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install
continue-on-error: true
- run: npm run test
continue-on-error: true
- run: npm run build
continue-on-error: true
- name: Deploy branch to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "explorer/dist/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/network-explorer-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files/messages
- name: Keybase - Send Notification
env:
NYM_PROJECT_NAME: "Network Explorer"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "network-explorer-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-network-explorer"
IS_SUCCESS: "${{ job.status == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/messages/entry_point_notifications.sh
-77
View File
@@ -1,77 +0,0 @@
name: Webdriverio tests for nym wallet
on:
push:
paths:
- "nym-wallet/**"
defaults:
run:
working-directory: nym-wallet
jobs:
test:
name: wallet tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Tauri dependencies
run: >
sudo apt-get update &&
sudo apt-get install -y
libgtk-3-dev
libgtksourceview-3.0-dev
webkit2gtk-4.0
libappindicator3-dev
webkit2gtk-driver
xvfb
- name: Install minimal stable
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
- name: Node v16
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Install yarn for building application
run: yarn install
- name: Build application
run: yarn run webpack:build & yarn run tauri:build
- name: Check binary exists
run: |
cd target/release/
(test -f nym-wallet && echo nym binary exists) || echo wallet does not exist
- name: Install dependencies
run: yarn install
working-directory: nym-wallet/webdriver
- name: Remove existing user datafile
uses: JesseTG/rm@v1.0.2
with:
path: nym-wallet/webdriver/common/data/user-data.json
- name: Create user data json file
id: create-json
uses: jsdaniell/create-json@1.1.2
with:
name: "user-data.json"
json: ${{ secrets.WALLET_USERDATA }}
dir: "nym-wallet/webdriver/common/data/"
- name: Install tauri-driver
uses: actions-rs/cargo@v1
with:
command: install
args: tauri-driver
- name: Launch tests
run: xvfb-run yarn test:runall
working-directory: nym-wallet/webdriver
@@ -1,2 +0,0 @@
node_modules
.idea
@@ -1,6 +0,0 @@
{
"trailingComma": "all",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2
}
@@ -1,10 +0,0 @@
#!/usr/bin/env bash
# pass exit codes out to GitHub Actions
set -euxo pipefail
# change to the directory that contains this script
cd "${0%/*}"
# run the node script
node send_message.js
@@ -1,11 +0,0 @@
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
> :rocket: {{ env.NYM_PROJECT_NAME }}
> 🔴 **FAILURE** :cry:
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
Commit message:
```
{{ env.GIT_COMMIT_MESSAGE }}
```
@@ -1,16 +0,0 @@
{
"name": "send-keybase-message",
"description": "Sends a notification message with the keybase package that fails when piped into the keybase CLI",
"version": "1.0.0",
"private": true,
"scripts": {
"format": "prettier --write send_message.js"
},
"dependencies": {
"handlebars": "^4.7.7",
"keybase-bot": "^3.6.1"
},
"devDependencies": {
"prettier": "2.3.2"
}
}
@@ -1,69 +0,0 @@
const Bot = require('keybase-bot');
const Handlebars = require('handlebars');
const fs = require('fs');
async function main() {
const data = { env: process.env };
// const data = { ...PASTE TEST DATA HERE ... }; // -- DEV: uncomment to set test data
// validation of environment
if(!(process.env.NYM_PROJECT_NAME || data.env.NYM_PROJECT_NAME)) {
throw new Error('Please set env var NYM_PROJECT_NAME with the project name for displaying in notification messages');
}
const keybaseChannel = process.env.KEYBASE_NYM_CHANNEL || data.env.KEYBASE_NYM_CHANNEL;
if(!keybaseChannel) {
throw new Error('Please set env var KEYBASE_NYM_CHANNEL with the channel name for the notification message');
}
// extract the git branch name
const GIT_BRANCH_NAME = (process.env.GITHUB_REF || data.env.GITHUB_REF).split('/').slice(2).join('/');
data.env.GIT_BRANCH_NAME = GIT_BRANCH_NAME;
const source = fs
.readFileSync(process.env.IS_SUCCESS === 'true' ? 'success' : 'failure')
.toString();
const template = Handlebars.compile(source);
const result = template(data);
// -- DEV: uncomment to show what is available in the handlebars template / show the result
// console.dir({ data }, { depth: null });
// console.log(result);
const bot = new Bot();
try {
const username = process.env.KEYBASE_NYMBOT_USERNAME;
const paperkey = process.env.KEYBASE_NYMBOT_PAPERKEY;
if(!username) {
throw new Error('Username is not defined. Please set env var KEYBASE_NYMBOT_USERNAME');
}
if(!paperkey) {
throw new Error('Paperkey is not defined. Please set env var KEYBASE_NYMBOT_PAPERKEY');
}
console.log(`Initialising keybase with user "${username}" and key: "${'*'.repeat(paperkey.length)}"...`);
await bot.init(username, paperkey, { verbose: false });
const channel = {
name: 'nymtech_bot',
membersType: 'team',
topicName: keybaseChannel,
topic_type: 'CHAT',
};
const message = {
body: result,
};
console.log(`Sending to ${channel.name}#${channel.topicName}...`);
await bot.chat.send(channel, message);
console.log('Message sent!');
} catch (error) {
console.error(error);
process.exitCode = -1;
} finally {
await bot.deinit();
}
}
main();
@@ -1,11 +0,0 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/
> ✅ **SUCCESS**
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
Commit message:
```
{{ env.GIT_COMMIT_MESSAGE }}
```
@@ -1,12 +0,0 @@
name: Publish Tauri Wallet
on:
push:
tags:
- nym-wallet-*
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Run a one-line script
run: echo Hello, world!
+6 -13
View File
@@ -1,17 +1,10 @@
name: Generate TS types
on:
push:
paths-ignore:
- "explorer/**"
pull_request:
paths-ignore:
- "explorer/**"
on: push
jobs:
nym-wallet-types:
tauri-wallet-types:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Prepare
run: sudo apt-get update && sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgdk-pixbuf2.0-dev libsoup2.4-dev librust-gdk-dev libwebkit2gtk-4.0-dev
@@ -20,10 +13,10 @@ jobs:
with:
toolchain: stable
- name: Generate TS
run: cd nym-wallet/src-tauri && cargo test
run: cd tauri-wallet/src-tauri && cargo test
- uses: EndBug/add-and-commit@v7.2.1 # https://github.com/marketplace/actions/add-commit
with:
add: '["nym-wallet"]'
message: "[ci skip] Generate TS types"
add: '["tauri-wallet"]'
message: '[ci skip] Generate TS types'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3 -12
View File
@@ -1,9 +1,6 @@
name: Wasm Client
on:
pull_request:
paths-ignore:
- 'explorer/**'
on: [push, pull_request]
jobs:
wasm:
@@ -19,16 +16,10 @@ jobs:
override: true
components: rustfmt, clippy
# token credentials (non-coconut) don't work for wasm right now
# - uses: actions-rs/cargo@v1
# with:
# command: build
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --features=coconut
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
# for some reason this does not seem to work correctly, leave it for later, building is good enough for now
# - uses: actions-rs/cargo@v1
@@ -45,4 +36,4 @@ jobs:
# - uses: actions-rs/cargo@v1
# with:
# command: clippy
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
+1 -5
View File
@@ -11,6 +11,7 @@ target
/.vscode/settings.json
validator/.vscode
sample-configs/validator-config.toml
.vscode
scripts/deploy_qa.sh
scripts/run_gate.sh
scripts/run_mix.sh
@@ -29,8 +30,3 @@ validator-api/v4.json
validator-api/v6.json
**/node_modules
validator-api/keypair
contracts/mixnet/code_id
contracts/mixnet/Justfile
contracts/mixnet/Makefile
validator-config
*.patch
Generated
+273 -533
View File
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -4,7 +4,6 @@
[profile.release]
panic = "abort"
opt-level = "s"
overflow-checks = true
[profile.dev]
panic = "abort"
@@ -25,7 +24,6 @@ members = [
"common/config",
"common/credentials",
"common/crypto",
"common/erc20-bridge-contract",
"common/mixnet-contract",
"common/mixnode-common",
"common/network-defaults",
@@ -63,4 +61,4 @@ default-members = [
"validator-api",
]
exclude = ["explorer", "contracts", "tokenomics-py"]
exclude = ["explorer", "contracts"]
+3 -40
View File
@@ -5,6 +5,8 @@ SPDX-License-Identifier: Apache-2.0
## The Nym Privacy Platform
This repository contains the Nym mixnet.
The platform is composed of multiple Rust crates. Top-level executable binary crates include:
* nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
@@ -21,7 +23,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
### Building
Platform build instructions are available on [our docs site](https://nymtech.net/docs/0.11.0/overview/index/).
Platform build instructions are available on [our docs site](https://nymtech.net/docs).
### Developing
@@ -31,45 +33,6 @@ There's a `.env.sample-dev` file provided which you can rename to `.env` if you
You can chat to us in [Keybase](https://keybase.io). Download their chat app, then click **Teams -> Join a team**. Type **nymtech.friends** into the team name and hit **continue**. For general chat, hang out in the **#general** channel. Our development takes places in the **#dev** channel. Node operators should be in the **#node-operators** channel.
### Rewards
Node, node operator and delegator rewards are determined according to the principles laid out in the section 6 of [Nym Whitepaper](https://nymtech.net/nym-whitepaper.pdf). Below is a TLDR of the variables and formulas involved in calculating the epoch rewards. Initial reward pool is set to 250 million Nym, making the circulating supply 750 million Nym.
|Symbol|Definition|
|---|---|
|<img src="https://render.githubusercontent.com/render/math?math=R">|global share of rewards available, starts at 2% of the reward pool.
|<img src="https://render.githubusercontent.com/render/math?math=R_{i}">|node reward for mixnode `i`.
|<img src="https://render.githubusercontent.com/render/math?math=\sigma_{i}">|ratio of total node stake (node bond + all delegations) to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}">|ratio of stake operator has plaged to their node to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}">|fraction of total effort undertaken by node `i`, set to `1/k` in testnet Milhon.
|<img src="https://render.githubusercontent.com/render/math?math=k">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `active set` size, and set to 5000 in testnet Milhon.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}">|declared profit margin of operator `i`, defaults to 10% in testnet Milhon.
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}">|cost of operating node `i` for the duration of the rewarding eopoch, set to 40 Nym for testnet Milhon.
Node reward for node `i` is determined as:
<img src="https://render.githubusercontent.com/render/math?math=R_{i}=PF_{i} \cdot R \cdot (\sigma^'_{i} \cdot \omega_{i} \cdot k %2b \alpha \cdot \lambda^'_{i} \cdot \sigma^'_{i} \cdot k)/(1 %2b \alpha)">
where:
<img src="https://render.githubusercontent.com/render/math?math=\sigma^'_{i} = min\{\sigma_{i}, 1/k\}">
and
<img src="https://render.githubusercontent.com/render/math?math=\lambda^'_{i} = min\{\lambda_{i}, 1/k\}">
Operator of node `i` is credited with the following amount:
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}">
Delegate with stake `s` recieves:
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}">
where `s'` is stake `s` scaled over total token circulating supply.
### Licensing and copyright information
This program is available as open source under the terms of the Apache 2.0 license. However, some elements are being licensed under CC0-1.0 and MIT. For accurate information, please check individual files.
-3
View File
@@ -30,6 +30,3 @@ validator-client = { path = "../../common/client-libs/validator-client" }
[dev-dependencies]
tempfile = "3.1.0"
[features]
coconut = []
@@ -40,7 +40,7 @@ impl MixTrafficController {
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
debug_assert!(!mix_packets.is_empty());
let result = if mix_packets.len() == 1 {
let success = if mix_packets.len() == 1 {
let mix_packet = mix_packets.pop().unwrap();
self.gateway_client.send_mix_packet(mix_packet).await
} else {
@@ -49,7 +49,7 @@ impl MixTrafficController {
.await
};
match result {
match success {
Err(e) => {
error!("Failed to send sphinx packet(s) to the gateway! - {:?}", e);
self.consecutive_gateway_failure_count += 1;
@@ -18,7 +18,7 @@ pub enum ReplyKeyStorageError {
/// Permanent storage for keys in all sent [`ReplySURB`]
///
/// Each sent out [`ReplySURB`] has a new key associated with it that is going to be used for
/// payload encryption. In order to -decrypt whatever reply we receive, we need to know which
/// payload encryption. In order to decrypt whatever reply we receive, we need to know which
/// key to use for that purpose. We do it based on received `H(t)` which has to be included
/// with each reply.
/// Moreover, there is no restriction when the [`ReplySURB`] might get used so we need to
@@ -107,7 +107,7 @@ impl TopologyAccessor {
self.inner.read().await.into()
}
async fn update_global_topology(&self, new_topology: Option<NymTopology>) {
async fn update_global_topology(&mut self, new_topology: Option<NymTopology>) {
self.inner.write().await.update(new_topology);
}
@@ -130,26 +130,19 @@ impl Default for TopologyAccessor {
pub struct TopologyRefresherConfig {
validator_api_urls: Vec<Url>,
refresh_rate: time::Duration,
client_version: String,
}
impl TopologyRefresherConfig {
pub fn new(
validator_api_urls: Vec<Url>,
refresh_rate: time::Duration,
client_version: String,
) -> Self {
pub fn new(validator_api_urls: Vec<Url>, refresh_rate: time::Duration) -> Self {
TopologyRefresherConfig {
validator_api_urls,
refresh_rate,
client_version,
}
}
}
pub struct TopologyRefresher {
validator_client: validator_client::ApiClient,
client_version: String,
validator_api_urls: Vec<Url>,
topology_accessor: TopologyAccessor,
@@ -165,7 +158,6 @@ impl TopologyRefresher {
TopologyRefresher {
validator_client: validator_client::ApiClient::new(cfg.validator_api_urls[0].clone()),
client_version: cfg.client_version,
validator_api_urls: cfg.validator_api_urls,
topology_accessor,
refresh_rate: cfg.refresh_rate,
@@ -185,71 +177,12 @@ impl TopologyRefresher {
.change_validator_api(self.validator_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
/// * `mixnodes_count`: total number of active mixnodes
fn check_layer_distribution(
&self,
active_topology: &NymTopology,
mixnodes_count: usize,
) -> bool {
let mixes = active_topology.mixes();
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> {
async fn get_current_compatible_topology(&mut 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 {
let mixnodes = match self.validator_client.get_cached_mixnodes().await {
Err(err) => {
error!("failed to get network mixnodes - {}", err);
return None;
@@ -265,16 +198,11 @@ impl TopologyRefresher {
Ok(gateways) => gateways,
};
let mixnodes_count = mixnodes.len();
let topology =
nym_topology_from_bonds(mixnodes, gateways).filter_system_version(&self.client_version);
let topology = nym_topology_from_bonds(mixnodes, gateways);
if !self.check_layer_distribution(&topology, mixnodes_count) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
None
} else {
Some(topology)
}
// TODO: I didn't want to change it now, but the expected system version should rather be put in config
// rather than pulled from package version of `client_core`
Some(topology.filter_system_version(env!("CARGO_PKG_VERSION")))
}
pub async fn refresh(&mut self) {
+8 -66
View File
@@ -22,10 +22,7 @@ const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20)
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
@@ -103,17 +100,6 @@ impl<T: NymConfig> Config<T> {
self::Client::<T>::default_reply_encryption_key_store_path(&id);
}
#[cfg(not(feature = "coconut"))]
if self
.client
.backup_bandwidth_token_keys_dir
.as_os_str()
.is_empty()
{
self.client.backup_bandwidth_token_keys_dir =
self::Client::<T>::default_backup_bandwidth_token_keys_dir(&id);
}
self.client.id = id;
}
@@ -125,16 +111,6 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_listener = gateway_listener.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_private_key<S: Into<String>>(&mut self, eth_private_key: S) {
self.client.eth_private_key = eth_private_key.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_endpoint<S: Into<String>>(&mut self, eth_endpoint: S) {
self.client.eth_endpoint = eth_endpoint.into();
}
pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec<Url>) {
self.client.validator_api_urls = validator_api_urls;
}
@@ -197,21 +173,6 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_listener.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_backup_bandwidth_token_keys_dir(&self) -> PathBuf {
self.client.backup_bandwidth_token_keys_dir.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_endpoint(&self) -> String {
self.client.eth_endpoint.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_private_key(&self) -> String {
self.client.eth_private_key.clone()
}
// Debug getters
pub fn get_average_packet_delay(&self) -> Duration {
self.debug.average_packet_delay
@@ -307,20 +268,6 @@ pub struct Client<T> {
/// Address of the gateway listener to which all client requests should be sent.
gateway_listener: String,
/// Path to directory containing public/private keys used for bandwidth token purchase.
/// Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
/// The public key is the name of the file, while the private key is the content.
#[cfg(not(feature = "coconut"))]
backup_bandwidth_token_keys_dir: PathBuf,
/// Ethereum private key.
#[cfg(not(feature = "coconut"))]
eth_private_key: String,
/// Address to an Ethereum full node.
#[cfg(not(feature = "coconut"))]
eth_endpoint: String,
/// nym_home_directory specifies absolute path to the home nym Clients directory.
/// It is expected to use default value and hence .toml file should not redefine this field.
nym_root_directory: PathBuf,
@@ -345,12 +292,6 @@ impl<T: NymConfig> Default for Client<T> {
reply_encryption_key_store_path: Default::default(),
gateway_id: "".to_string(),
gateway_listener: "".to_string(),
#[cfg(not(feature = "coconut"))]
backup_bandwidth_token_keys_dir: Default::default(),
#[cfg(not(feature = "coconut"))]
eth_private_key: "".to_string(),
#[cfg(not(feature = "coconut"))]
eth_endpoint: "".to_string(),
nym_root_directory: T::default_root_directory(),
super_struct: Default::default(),
}
@@ -385,17 +326,18 @@ impl<T: NymConfig> Client<T> {
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("reply_key_store")
}
#[cfg(not(feature = "coconut"))]
fn default_backup_bandwidth_token_keys_dir(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("backup_bandwidth_token_keys")
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Logging {}
impl Default for Logging {
fn default() -> Self {
Logging {}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct Debug {
+2 -6
View File
@@ -3,7 +3,6 @@ name = "nym-client"
version = "0.11.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
rust-version = "1.56"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -32,8 +31,8 @@ tokio-tungstenite = "0.14" # websocket
## internal
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials" }
config = { path = "../../common/config" }
crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
@@ -45,8 +44,5 @@ websocket-requests = { path = "websocket-requests" }
validator-client = { path = "../../common/client-libs/validator-client" }
version-checker = { path = "../../common/version-checker" }
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
[dev-dependencies]
serde_json = "1.0" # for the "textsend" example
@@ -42,17 +42,6 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
# sent but not received back.
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to directory containing public/private keys used for bandwidth token purchase.
# Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
# The public key is the name of the file, while the private key is the content.
backup_bandwidth_token_keys_dir = '{{ client.backup_bandwidth_token_keys_dir }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options #####
# ID of the gateway from which the client should be fetching messages.
+29 -16
View File
@@ -22,6 +22,9 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use coconut_interface::Credential;
use credentials::bandwidth::prepare_for_spending;
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::{
@@ -35,8 +38,6 @@ use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use tokio::runtime::Runtime;
use gateway_client::bandwidth::BandwidthController;
pub(crate) mod config;
pub struct NymClient {
@@ -165,6 +166,30 @@ impl NymClient {
.start(self.runtime.handle())
}
async fn prepare_credential(&self) -> Credential {
let verification_key = obtain_aggregate_verification_key(
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain aggregate verification key of validators");
let bandwidth_credential = credentials::bandwidth::obtain_signature(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain bandwidth credential");
// the above would presumably be loaded from a file
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
prepare_for_spending(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&bandwidth_credential,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
}
fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
@@ -183,18 +208,7 @@ impl NymClient {
.expect("provided gateway id is invalid!");
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let coconut_credential = self.prepare_credential().await;
let mut gateway_client = GatewayClient::new(
gateway_address,
@@ -204,7 +218,7 @@ impl NymClient {
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
coconut_credential,
);
gateway_client
@@ -222,7 +236,6 @@ impl NymClient {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
+30 -17
View File
@@ -6,7 +6,10 @@ use crate::commands::override_config;
use clap::{App, Arg, ArgMatches};
use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use coconut_interface::Credential;
use config::NymConfig;
use credentials::bandwidth::prepare_for_spending;
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::{encryption, identity};
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
@@ -22,7 +25,7 @@ use topology::{filter::VersionFilterable, gateway};
use url::Url;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init")
App::new("init")
.about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id")
.long("id")
@@ -54,32 +57,37 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("fastmode")
.hidden(true) // this will prevent this flag from being displayed in `--help`
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(not(feature = "coconut"))]
let app = app
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true)
.required(true))
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true)
.required(true));
)
}
app
// this behaviour should definitely be changed, we shouldn't
// need to get bandwidth credential for registration
async fn prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
let verification_key = obtain_aggregate_verification_key(validators)
.await
.expect("could not obtain aggregate verification key of validators");
let bandwidth_credential = credentials::bandwidth::obtain_signature(raw_identity, validators)
.await
.expect("could not obtain bandwidth credential");
prepare_for_spending(raw_identity, &bandwidth_credential, &verification_key)
.expect("could not prepare out bandwidth credential for spending")
}
async fn register_with_gateway(
gateway: &gateway::Node,
our_identity: Arc<identity::KeyPair>,
validator_urls: Vec<Url>,
) -> Arc<SharedKeys> {
let timeout = Duration::from_millis(1500);
let coconut_credential =
prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await;
let mut gateway_client = GatewayClient::new_init(
gateway.clients_address(),
gateway.identity_key,
our_identity.clone(),
coconut_credential,
timeout,
);
gateway_client
@@ -202,8 +210,13 @@ pub fn execute(matches: &ArgMatches) {
config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
let validator_urls = config.get_base().get_validator_api_endpoints();
let shared_keys = register_with_gateway(
&gate_details,
key_manager.identity_keypair(),
validator_urls,
)
.await;
(shared_keys, gate_details.clients_address())
};
-9
View File
@@ -43,14 +43,5 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
config = config.with_port(port.unwrap());
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of("eth_endpoint") {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of("eth_private_key") {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
config
}
+2 -14
View File
@@ -10,7 +10,7 @@ use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("run")
App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id")
.long("id")
@@ -38,19 +38,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("port")
.help("Port for the socket (if applicable) to listen on")
.takes_value(true)
);
#[cfg(not(feature = "coconut"))]
let app = app
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true))
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true));
app
)
}
// this only checks compatibility between config the binary. It does not take into consideration
+2 -6
View File
@@ -3,7 +3,6 @@ name = "nym-socks5-client"
version = "0.11.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2018"
rust-version = "1.56"
[lib]
name = "nym_socks5"
@@ -25,8 +24,8 @@ url = "2.2"
# internal
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials" }
config = { path = "../../common/config" }
crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
@@ -39,6 +38,3 @@ pemstore = { path = "../../common/pemstore" }
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
validator-client = { path = "../../common/client-libs/validator-client" }
version-checker = { path = "../../common/version-checker" }
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
@@ -42,17 +42,6 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
# sent but not received back.
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to directory containing public/private keys used for bandwidth token purchase.
# Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
# The public key is the name of the file, while the private key is the content.
backup_bandwidth_token_keys_dir = '{{ client.backup_bandwidth_token_keys_dir }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options #####
# ID of the gateway from which the client should be fetching messages.
+29 -16
View File
@@ -23,6 +23,9 @@ use client_core::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use coconut_interface::Credential;
use credentials::bandwidth::prepare_for_spending;
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::{
@@ -34,8 +37,6 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use tokio::runtime::Runtime;
use gateway_client::bandwidth::BandwidthController;
pub(crate) mod config;
pub struct NymClient {
@@ -153,6 +154,30 @@ impl NymClient {
.start(self.runtime.handle())
}
async fn prepare_credential(&self) -> Credential {
let verification_key = obtain_aggregate_verification_key(
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain aggregate verification key of validators");
let bandwidth_credential = credentials::bandwidth::obtain_signature(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&self.config.get_base().get_validator_api_endpoints(),
)
.await
.expect("could not obtain bandwidth credential");
// the above would presumably be loaded from a file
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
prepare_for_spending(
&self.key_manager.identity_keypair().public_key().to_bytes(),
&bandwidth_credential,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
}
fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
@@ -171,18 +196,7 @@ impl NymClient {
.expect("provided gateway id is invalid!");
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let coconut_credential = self.prepare_credential().await;
let mut gateway_client = GatewayClient::new(
gateway_address,
@@ -192,7 +206,7 @@ impl NymClient {
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
coconut_credential,
);
gateway_client
@@ -210,7 +224,6 @@ impl NymClient {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
+30 -17
View File
@@ -6,7 +6,10 @@ use crate::commands::override_config;
use clap::{App, Arg, ArgMatches};
use client_core::client::key_manager::KeyManager;
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use coconut_interface::Credential;
use config::NymConfig;
use credentials::bandwidth::prepare_for_spending;
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::{encryption, identity};
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
@@ -20,7 +23,7 @@ use topology::{filter::VersionFilterable, gateway};
use url::Url;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init")
App::new("init")
.about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id")
.long("id")
@@ -54,32 +57,37 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("fastmode")
.hidden(true) // this will prevent this flag from being displayed in `--help`
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(not(feature = "coconut"))]
let app = app
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true)
.required(true))
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true)
.required(true));
)
}
app
// this behaviour should definitely be changed, we shouldn't
// need to get bandwidth credential for registration
async fn prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential {
let verification_key = obtain_aggregate_verification_key(validators)
.await
.expect("could not obtain aggregate verification key of validators");
let bandwidth_credential = credentials::bandwidth::obtain_signature(raw_identity, validators)
.await
.expect("could not obtain bandwidth credential");
prepare_for_spending(raw_identity, &bandwidth_credential, &verification_key)
.expect("could not prepare out bandwidth credential for spending")
}
async fn register_with_gateway(
gateway: &gateway::Node,
our_identity: Arc<identity::KeyPair>,
validator_urls: Vec<Url>,
) -> Arc<SharedKeys> {
let timeout = Duration::from_millis(1500);
let coconut_credential =
prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await;
let mut gateway_client = GatewayClient::new_init(
gateway.clients_address(),
gateway.identity_key,
our_identity.clone(),
coconut_credential,
timeout,
);
gateway_client
@@ -203,8 +211,13 @@ pub fn execute(matches: &ArgMatches) {
config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
let validator_urls = config.get_base().get_validator_api_endpoints();
let shared_keys = register_with_gateway(
&gate_details,
key_manager.identity_keypair(),
validator_urls,
)
.await;
(shared_keys, gate_details.clients_address())
};
-9
View File
@@ -39,14 +39,5 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
config = config.with_port(port.unwrap());
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of("eth_endpoint") {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of("eth_private_key") {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
config
}
+2 -14
View File
@@ -10,7 +10,7 @@ use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("run")
App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id")
.long("id")
@@ -44,19 +44,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.long("port")
.help("Port for the socket to listen on")
.takes_value(true)
);
#[cfg(not(feature = "coconut"))]
let app = app
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true))
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true));
app
)
}
// this only checks compatibility between config the binary. It does not take into consideration
+45 -5600
View File
File diff suppressed because it is too large Load Diff
+6 -9
View File
@@ -1,13 +1,12 @@
{
"name": "@nymproject/nym-validator-client",
"version": "0.18.0",
"version": "0.17.0",
"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",
"scripts": {
"build": "tsc",
"run_cli": "clear && ts-node src/cli.ts",
"test": "ts-mocha tests/**/*.test.ts",
"coverage": "nyc npm test",
"lint": "eslint \"**/*.ts\"",
@@ -23,7 +22,6 @@
"devDependencies": {
"@types/chai": "^4.2.15",
"@types/expect": "^24.3.0",
"@types/inquirer": "^8.1.3",
"@types/mocha": "^8.2.1",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
@@ -37,11 +35,10 @@
"typescript": "^4.1.3"
},
"dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.25.5",
"@cosmjs/math": "^0.25.5",
"@cosmjs/proto-signing": "^0.25.5",
"@cosmjs/stargate": "^0.25.5",
"axios": "^0.21.1",
"inquirer": "^8.2.0"
"@cosmjs/cosmwasm-stargate": "^0.25.5",
"@cosmjs/stargate": "^0.25.5",
"@cosmjs/math": "^0.25.5",
"@cosmjs/proto-signing": "^0.25.5"
}
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
import {GatewayBond, PagedGatewayResponse} from "../types";
import { GatewayBond } from "../types";
import {INetClient} from "../net-client"
import {IQueryClient} from "../query-client";
import {VALIDATOR_API_GATEWAYS, VALIDATOR_API_PORT} from "../index";
import {PagedGatewayResponse, VALIDATOR_API_GATEWAYS, VALIDATOR_API_PORT} from "../index";
import axios from "axios";
+45 -49
View File
@@ -1,64 +1,60 @@
import { MixNodeBond, PagedMixnodeResponse } from "../types";
import { INetClient } from "../net-client";
import { IQueryClient } from "../query-client";
import { VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT } from "../index";
import { MixNodeBond } from "../types";
import { INetClient } from "../net-client"
import {IQueryClient} from "../query-client";
import {PagedMixnodeResponse, VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
import axios from "axios";
export { MixnodesCache };
/**
* There are serious limits in smart contract systems, but we need to keep track of
* There are serious limits in smart contract systems, but we need to keep track of
* potentially thousands of nodes. MixnodeCache instances repeatedly make requests for
* paged data about what mixnodes exist, and keep them locally in memory so that they're
* available for querying.
* */
export default class MixnodesCache {
mixNodes: MixNodeBond[];
client: INetClient | IQueryClient;
perPage: number;
mixNodes: MixNodeBond[]
client: INetClient | IQueryClient
perPage: number
constructor(client: INetClient | IQueryClient, perPage: number) {
this.client = client;
this.mixNodes = [];
this.perPage = perPage;
}
/// Makes repeated requests to assemble a full list of nodes.
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
// returns true.
async refreshMixNodes(contractAddress: string): Promise<MixNodeBond[]> {
let newMixnodes: MixNodeBond[] = [];
let response: PagedMixnodeResponse;
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getMixNodes(
contractAddress,
this.perPage,
next
);
newMixnodes = newMixnodes.concat(response.nodes);
next = response.start_next_after;
// if `start_next_after` is not set, we're done
if (!next) {
break;
}
constructor(client: INetClient | IQueryClient, perPage: number) {
this.client = client;
this.mixNodes = [];
this.perPage = perPage;
}
this.mixNodes = newMixnodes;
return this.mixNodes;
}
/// Makes repeated requests to assemble a full list of nodes.
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
// returns true.
async refreshMixNodes(contractAddress: string): Promise<MixNodeBond[]> {
let newMixnodes: MixNodeBond[] = [];
let response: PagedMixnodeResponse;
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getMixNodes(contractAddress, this.perPage, next);
newMixnodes = newMixnodes.concat(response.nodes)
next = response.start_next_after;
// if `start_next_after` is not set, we're done
if (!next) {
break
}
}
/// Makes requests to assemble a full list of mixnodes from validator-api
async refreshValidatorAPIMixNodes(urls: string[]): Promise<MixNodeBond[]> {
for (const url of urls) {
const validator_api_url = new URL(url);
validator_api_url.port = VALIDATOR_API_PORT;
validator_api_url.pathname += VALIDATOR_API_MIXNODES;
const response = await axios.get(validator_api_url.toString());
if (response.status == 200) {
return response.data;
}
this.mixNodes = newMixnodes
return this.mixNodes;
}
throw new Error("None of the provided validators seem to be alive");
}
}
/// Makes requests to assemble a full list of mixnodes from validator-api
async refreshValidatorAPIMixNodes(urls: string[]): Promise<MixNodeBond[]> {
for (const url of urls) {
const validator_api_url = new URL(url);
validator_api_url.port = VALIDATOR_API_PORT;
validator_api_url.pathname += VALIDATOR_API_MIXNODES;
const response = await axios.get(validator_api_url.toString());
if (response.status == 200) {
return response.data;
}
}
throw new Error("None of the provided validators seem to be alive")
}
}
-317
View File
@@ -1,317 +0,0 @@
import ValidatorClient from "./index";
import inquirer from "inquirer";
// This script runs a CLI to consume the Validator and provide mixnet information to the user
const VALIDATOR_URLS: string[] = [
"https://testnet-milhon-validator1.nymtech.net",
// "https://testnet-milhon-validator2.nymtech.net", // <-- val 2 doesnt work apparently.
];
const DENOM = "punk";
const MOCK_MNEMONIC =
"vault risk throw flat garlic pretty clay senior birth correct panic floor around pen horror mail entry arrest zoo devote message evoke street total";
// ^^ addr: punk10dxwmqjy72s9nkm9x9pluyn6pyx0gkptjhs4k9
// curr balance: 899999747
// const MOCK_MNEMONIC =
// "oil once motion cute crawl patch happy wave donkey zoo retreat matrix emerge adult very universe aware error snap credit actress couple upset engine";
// ^^ addr: punk1yzr7gtmtlfd0s7s9wpexhteeu05y4xlcvh65eh
// curr balance: 5045 UPUNK
// const MOCK_MNEMONIC =
// "sample menu edit midnight guard review call record horn antenna stairs awkward fringe document during amazing twelve wise wide escape matter betray staff someone";
// ^^ addr: punk1wn8lwxe5hvdtx60c6p7ekskmu75agwfrslf0qs
// curr balance:
type AccountType = {
addr: string;
client: any;
mnemonic?: string;
};
function validatorCli() {
// define funcs to be used in CLI switch-case
let state: AccountType = {
addr: "",
client: null,
mnemonic: "",
};
function restartApp() {
setTimeout(() => {
validatorCli();
}, 300);
}
function generateNewAccount() {
const mnemonic = ValidatorClient.randomMnemonic();
ValidatorClient.mnemonicToAddress(mnemonic, "punk")
.then((address) => {
console.log("Your address is: ", address);
console.log("Your mnemonic is: ", mnemonic);
return address;
})
.catch((err) => {
console.log("err", err);
});
restartApp();
}
function sendFundsMenu() {
inquirer
.prompt([
{
name: "recipient",
type: "input",
message: "please enter the receipient:",
},
{
name: "amount",
type: "input",
message: "please enter the amount (UPUNK):",
},
])
.then(async ({ recipient, amount }) => {
const { addr, client } = state;
console.log(
`🔥 Hold Tight - Sending ${amount}UPUNK to ${recipient} 🚀`
);
const res = await client.send(addr, recipient, [
{
denom: "upunk",
amount: amount,
},
]);
console.log("Funds Transfer Response:", res);
restartApp();
});
}
async function delegateGateway() {
console.log(
"unfortunately - gateway delegation is switched off at the moment."
);
startTransactionMenu();
// const id = "punk1yzr7gtmtlfd0s7s9wpexhteeu05y4xlcvh65eh";
// const gatewayID = "EQhjPpUuy4i1u87nfQMW21WiBT5mJk4dcq4ju7Vct7cB";
// const coin = {
// denom: "upunk",
// amount: "101",
// };
// const res = await state.client.delegateToMixnode(gatewayID, coin);
// console.log("delegateMixnode ==> ", res);
}
async function delegateMixnode() {
const mixNodeID = "2cFpCe7yP79CcuRpf6JBRdJaSp7JF5YcA5SHi8JVm1d2";
// const mixNodeID = "2Vrr7s2peGiWsPh6xY3ZFEMDRmMNv8xLBUtV5XMyQLSB";
const coin = {
denom: "upunk",
amount: "1001",
};
const res = await state.client.delegateToMixnode(mixNodeID, coin);
console.log("delegate to mixnode response: ", res);
}
async function findMinimumMixnodeBond() {
const res = await state.client.minimumMixnodeBond();
console.log("res is back ", res);
}
async function bondMixnode() {
state.client.bondMixnode();
}
async function checkOwnsMixnodes() {
const res = await state.client.ownsMixNode();
console.log("owns mixnode? ", res);
}
function startTransactionMenu() {
inquirer
.prompt([
{
type: "list",
name: "task",
message: "What now?",
choices: [
"send_funds",
"get_mixnodes",
"refresh_mixnodes",
"refresh_val_api_mixnodes",
"min_mixn_bond",
"bond_mixnode",
"delegate_mixnode",
"delegate_gateway",
"check_owns_mixnode",
],
},
])
.then(({ task }) => {
switch (task) {
case "send_funds":
sendFundsMenu();
break;
case "get_mixnodes":
getMixnodes();
break;
case "refresh_mixnodes":
refreshMixnodes();
break;
case "refresh_val_api_mixnodes":
refreshValApiMixnodes();
break;
case "min_mixn_bond":
findMinimumMixnodeBond();
break;
case "bond_mixnode":
bondMixnode();
break;
case "delegate_gateway":
delegateGateway();
break;
case "delegate_mixnode":
delegateMixnode();
break;
case "check_owns_mixnode":
checkOwnsMixnodes();
break;
default:
return null;
}
});
}
function queryUserAccount() {
inquirer
.prompt([
{
type: "input",
name: "query_user",
message: "Please enter the public address of user you wish to query",
},
])
.then(async ({ query_user }) => {
let response = "";
try {
const client = await ValidatorClient.connectForQuery(
query_user,
VALIDATOR_URLS,
DENOM
);
const balance = await client.getBalance(query_user);
response = `User ${query_user} has a balance of ${balance?.amount}${balance?.denom}`;
console.log(response);
return validatorCli();
} catch (error) {
console.log("error back ", error);
return validatorCli();
}
});
}
async function refreshMixnodes() {
const res = await state.client.refreshMixNodes(
"punk1yksauczytk60x5cejaras8w6nwf7r772n3kwkp"
);
console.log("done:", res);
}
function connectAccount() {
inquirer
.prompt([
{
name: "user_mnemonic",
type: "input",
message: "please enter your mnemonic:",
},
])
.then(async ({ user_mnemonic }) => {
console.log("Connecting...");
const addr = await ValidatorClient.mnemonicToAddress(
MOCK_MNEMONIC,
// user_mnemonic,
"punk"
);
const client = await ValidatorClient.connect(
addr,
MOCK_MNEMONIC,
VALIDATOR_URLS,
DENOM
);
state = {
addr,
mnemonic: MOCK_MNEMONIC,
client,
};
const balance = await client.getBalance(addr);
console.log(`connected to validator, our address is ${client.address}`);
console.log("connected to validator", client.urls[0]);
console.log(
`💰 Your balance is ${balance?.amount}${balance?.denom.toUpperCase()}`
);
startTransactionMenu();
})
.catch((err) => {
console.log("error: ", err);
});
}
function buildAWallet() {
inquirer
.prompt([
{
message: "enter your mnemonic to build wallet:",
type: "input",
name: "mnemonic",
},
])
.then(async ({ mnemonic }) => {
const res = await ValidatorClient.buildWallet(mnemonic, DENOM);
console.log("Build_Wallet Response: ", res);
});
}
async function refreshValApiMixnodes() {
const res = await state.client.refreshValidatorAPIMixNodes();
console.log("res is back: ", res);
}
function getMixnodes() {
const res = state.client.mixNodesCache;
console.log("Mixnodes", res);
}
// app provides a list of possible tasks
inquirer
.prompt([
{
type: "list",
name: "task",
message: "Yo, What would you like to do today?",
choices: [
"create_account",
"connect_account",
"build_wallet",
"query_user",
],
},
])
.then(({ task }) => {
switch (task) {
case "create_account":
generateNewAccount();
break;
case "connect_account":
connectAccount();
break;
case "build_wallet":
buildAWallet();
break;
case "query_user":
queryUserAccount();
break;
default:
return null;
}
});
}
validatorCli();
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@ import {
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
StateParams
} from "./types";
} from "./index";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
import { Coin, StdFee } from "@cosmjs/stargate";
import { BroadcastTxResponse } from "@cosmjs/stargate"
+1 -1
View File
@@ -7,7 +7,7 @@ import {
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
StateParams
} from "./types";
} from "./index";
export interface IQueryClient {
getBalance(address: string, stakeDenom: string): Promise<Coin | null>;
-75
View File
@@ -1,80 +1,5 @@
import { Coin } from "@cosmjs/stargate";
/// One page of a possible multi-page set of mixnodes. The paging interface is quite
/// inconvenient, as we don't have the two pieces of information we need to know
/// in order to do paging nicely (namely `currentPage` and `totalPages` parameters).
///
/// Instead, we have only `start_next_page_after`, i.e. the key of the last record
/// on this page. In order to get the *next* page, CosmWasm looks at that value,
/// finds the next record, and builds the next page starting there. This happens
/// **in series** rather than **in parallel** (!).
///
/// So we have some consistency problems:
///
/// * we can't make requests at a given block height, so the result set
/// which we assemble over time may change while requests are being made.
/// * at some point we will make a request for a `start_next_page_after` key
/// which has just been deleted from the database.
///
/// TODO: more robust error handling on the "deleted key" case.
export type PagedMixnodeResponse = {
nodes: MixNodeBond[],
per_page: number, // TODO: camelCase
start_next_after: string, // TODO: camelCase
}
// a temporary way of achieving the same paging behaviour for the gateways
// the same points made for `PagedResponse` stand here.
export type PagedGatewayResponse = {
nodes: GatewayBond[],
per_page: number, // TODO: camelCase
start_next_after: string, // TODO: camelCase
}
export type MixOwnershipResponse = {
address: string,
has_node: boolean,
}
export type GatewayOwnershipResponse = {
address: string,
has_gateway: boolean,
}
export type StateParams = {
epoch_length: number,
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and Decimal that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_bond: string,
minimum_gateway_bond: string,
mixnode_bond_reward_rate: string,
gateway_bond_reward_rate: string,
mixnode_delegation_reward_rate: string,
gateway_delegation_reward_rate: string,
mixnode_active_set_size: number,
gateway_active_set_size: number,
}
export type Delegation = {
owner: string,
amount: Coin,
}
export type PagedMixDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
export type PagedGatewayDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
export enum Layer {
Gateway,
One,
+2259 -2517
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -7,7 +7,6 @@ keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
repository = "https://github.com/nymtech/nym"
description = "A webassembly client which can be used to interact with the the Nym privacy platform. Wasm is used for Sphinx packet generation."
rust-version = "1.56"
[lib]
crate-type = ["cdylib", "rlib"]
@@ -15,7 +14,6 @@ crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
offline-test = []
coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
[dependencies]
futures = "0.3"
@@ -27,8 +25,8 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] }
url = "2.2"
# internal
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials" }
crypto = { path = "../../common/crypto" }
nymsphinx = { path = "../../common/nymsphinx" }
topology = { path = "../../common/topology" }
+26 -12
View File
@@ -1,6 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_interface::Credential;
use credentials::bandwidth::prepare_for_spending;
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::{encryption, identity};
use futures::channel::mpsc;
use gateway_client::GatewayClient;
@@ -17,8 +20,6 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::{console_log, console_warn};
use gateway_client::bandwidth::BandwidthController;
pub(crate) mod received_processor;
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
@@ -100,22 +101,35 @@ impl NymClient {
self.self_recipient().to_string()
}
async fn prepare_credential(validators: &[Url], identity_bytes: &[u8]) -> Credential {
let verification_key = obtain_aggregate_verification_key(validators)
.await
.expect("could not obtain aggregate verification key of validators");
let bandwidth_credential =
credentials::bandwidth::obtain_signature(identity_bytes, validators)
.await
.expect("could not obtain bandwidth credential");
// the above would presumably be loaded from a file
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
prepare_for_spending(identity_bytes, &bandwidth_credential, &verification_key)
.expect("could not prepare out bandwidth credential for spending")
}
// Right now it's impossible to have async exported functions to take `&self` rather than self
pub async fn initial_setup(self) -> Self {
#[cfg(feature = "coconut")]
let bandwidth_controller = Some(BandwidthController::new(
vec![self.validator_server.clone()],
*self.identity.public_key(),
));
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = None;
let validator_server = self.validator_server.clone();
let identity_public_key = self.identity.public_key().clone();
let mut client = self.get_and_update_topology().await;
let gateway = client.choose_gateway();
let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded();
let (ack_sender, ack_receiver) = mpsc::unbounded();
let coconut_credential =
Self::prepare_credential(&vec![validator_server], &identity_public_key.to_bytes())
.await;
let mut gateway_client = GatewayClient::new(
gateway.clients_address(),
Arc::clone(&client.identity),
@@ -124,7 +138,7 @@ impl NymClient {
mixnet_messages_sender,
ack_sender,
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
bandwidth_controller,
coconut_credential,
);
gateway_client
@@ -249,7 +263,7 @@ impl NymClient {
pub(crate) async fn get_nym_topology(&self) -> NymTopology {
let validator_client = validator_client::ApiClient::new(self.validator_server.clone());
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
let mixnodes = match validator_client.get_cached_mixnodes().await {
Err(err) => panic!("{}", err),
Ok(mixes) => mixes,
};
+1 -15
View File
@@ -10,19 +10,14 @@ edition = "2018"
# TODO: (for this and other crates), similarly to 'tokio', import only required "futures" modules rather than
# the entire crate
futures = "0.3"
json = "0.12.4"
log = "0.4"
thiserror = "1.0"
url = "2.2"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
# internal
credentials = { path = "../../credentials" }
crypto = { path = "../../crypto" }
gateway-requests = { path = "../../../gateway/gateway-requests" }
nymsphinx = { path = "../../nymsphinx" }
coconut-interface = { path = "../../coconut-interface", optional = true }
network-defaults = { path = "../../network-defaults" }
coconut-interface = { path = "../../coconut-interface" }
[dependencies.tungstenite]
version = "0.13"
@@ -36,12 +31,6 @@ features = ["macros", "rt", "net", "sync", "time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.secp256k1]
version = "0.20.3"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.web3]
version = "0.17.0"
# wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2"
@@ -68,6 +57,3 @@ features = ["js"]
[dev-dependencies]
# for tests
#url = "2.1"
[features]
coconut = ["gateway-requests/coconut", "coconut-interface"]
@@ -1,226 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::GatewayClientError;
#[cfg(feature = "coconut")]
use credentials::coconut::{
bandwidth::{obtain_signature, prepare_for_spending},
utils::obtain_aggregate_verification_key,
};
#[cfg(not(feature = "coconut"))]
use credentials::token::bandwidth::TokenCredential;
#[cfg(not(feature = "coconut"))]
use crypto::asymmetric::identity;
use crypto::asymmetric::identity::PublicKey;
#[cfg(not(feature = "coconut"))]
use network_defaults::{
eth_contract::ETH_JSON_ABI, BANDWIDTH_VALUE, ETH_BURN_FUNCTION_NAME, ETH_CONTRACT_ADDRESS,
ETH_MIN_BLOCK_DEPTH, TOKENS_TO_BURN,
};
#[cfg(not(feature = "coconut"))]
use rand::rngs::OsRng;
#[cfg(not(feature = "coconut"))]
use secp256k1::SecretKey;
#[cfg(not(feature = "coconut"))]
use std::io::Write;
#[cfg(not(feature = "coconut"))]
use std::str::FromStr;
#[cfg(not(feature = "coconut"))]
use web3::{
contract::{Contract, Options},
transports::Http,
types::{Address, Bytes, U256, U64},
Web3,
};
#[cfg(not(feature = "coconut"))]
pub fn eth_contract(web3: Web3<Http>) -> Contract<Http> {
Contract::from_json(
web3.eth(),
Address::from(ETH_CONTRACT_ADDRESS),
json::parse(ETH_JSON_ABI)
.expect("Invalid json abi")
.dump()
.as_bytes(),
)
.expect("Invalid json abi")
}
#[derive(Clone)]
pub struct BandwidthController {
#[cfg(feature = "coconut")]
validator_endpoints: Vec<url::Url>,
#[cfg(feature = "coconut")]
identity: PublicKey,
#[cfg(not(feature = "coconut"))]
contract: Contract<Http>,
#[cfg(not(feature = "coconut"))]
eth_private_key: SecretKey,
#[cfg(not(feature = "coconut"))]
backup_bandwidth_token_keys_dir: std::path::PathBuf,
}
impl BandwidthController {
#[cfg(feature = "coconut")]
pub fn new(validator_endpoints: Vec<url::Url>, identity: PublicKey) -> Self {
BandwidthController {
validator_endpoints,
identity,
}
}
#[cfg(not(feature = "coconut"))]
pub fn new(
eth_endpoint: String,
eth_private_key: String,
backup_bandwidth_token_keys_dir: std::path::PathBuf,
) -> Result<Self, GatewayClientError> {
// Fail early, on invalid url
let transport =
Http::new(&eth_endpoint).map_err(|_| GatewayClientError::InvalidURL(eth_endpoint))?;
let web3 = web3::Web3::new(transport);
// Fail early, on invalid abi
let contract = eth_contract(web3);
let eth_private_key = secp256k1::SecretKey::from_str(&eth_private_key)
.map_err(|_| GatewayClientError::InvalidEthereumPrivateKey)?;
Ok(BandwidthController {
contract,
eth_private_key,
backup_bandwidth_token_keys_dir,
})
}
#[cfg(not(feature = "coconut"))]
fn backup_keypair(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
std::fs::create_dir_all(&self.backup_bandwidth_token_keys_dir)?;
let file_path = self
.backup_bandwidth_token_keys_dir
.join(keypair.public_key().to_base58_string());
let mut file = std::fs::File::create(file_path)?;
file.write_all(&keypair.private_key().to_bytes())?;
Ok(())
}
#[cfg(feature = "coconut")]
pub async fn prepare_coconut_credential(
&self,
) -> Result<coconut_interface::Credential, GatewayClientError> {
let verification_key = obtain_aggregate_verification_key(&self.validator_endpoints).await?;
let bandwidth_credential =
obtain_signature(&self.identity.to_bytes(), &self.validator_endpoints).await?;
// the above would presumably be loaded from a file
// the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff)
Ok(prepare_for_spending(
&self.identity.to_bytes(),
&bandwidth_credential,
&verification_key,
)?)
}
#[cfg(not(feature = "coconut"))]
pub async fn prepare_token_credential(
&self,
gateway_identity: PublicKey,
) -> Result<TokenCredential, GatewayClientError> {
let mut rng = OsRng;
let kp = identity::KeyPair::new(&mut rng);
self.backup_keypair(&kp)?;
let verification_key = *kp.public_key();
let signed_verification_key = kp.private_key().sign(&verification_key.to_bytes());
self.buy_token_credential(verification_key, signed_verification_key)
.await?;
let message: Vec<u8> = verification_key
.to_bytes()
.iter()
.chain(gateway_identity.to_bytes().iter())
.copied()
.collect();
let signature = kp.private_key().sign(&message);
Ok(TokenCredential::new(
verification_key,
gateway_identity,
BANDWIDTH_VALUE,
signature,
))
}
#[cfg(not(feature = "coconut"))]
pub async fn buy_token_credential(
&self,
verification_key: PublicKey,
signed_verification_key: identity::Signature,
) -> Result<(), GatewayClientError> {
// 0 means a transaction failure, 1 means success
let confirmations = if cfg!(debug_assertions) {
1
} else {
ETH_MIN_BLOCK_DEPTH
};
// 15 seconds per confirmation block + 10 seconds of network overhead
log::info!(
"Waiting for Ethereum transaction. This should take about {} seconds",
confirmations * 15 + 10
);
let recipt = self
.contract
.signed_call_with_confirmations(
ETH_BURN_FUNCTION_NAME,
(
U256::from(TOKENS_TO_BURN),
U256::from(&verification_key.to_bytes()),
Bytes(signed_verification_key.to_bytes().to_vec()),
),
Options::default(),
confirmations,
&self.eth_private_key,
)
.await?;
if Some(U64::from(0)) == recipt.status {
Err(GatewayClientError::BurnTokenError(
web3::Error::InvalidResponse(format!(
"Transaction status is 0 (failure): {:?}",
recipt.logs,
)),
))
} else {
log::info!(
"Bought bandwidth on Ethereum: {} MB",
BANDWIDTH_VALUE / 1024 / 1024
);
Ok(())
}
}
}
#[cfg(not(feature = "coconut"))]
#[cfg(test)]
mod tests {
use super::*;
use network_defaults::ETH_EVENT_NAME;
#[test]
fn parse_contract() {
let transport =
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
let web3 = web3::Web3::new(transport);
// test no panic occurs
eth_contract(web3);
}
#[test]
fn check_event_name_constant_against_abi() {
let transport =
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
let web3 = web3::Web3::new(transport);
let contract = eth_contract(web3);
assert!(contract.abi().event(ETH_EVENT_NAME).is_ok());
}
}
+59 -139
View File
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::bandwidth::BandwidthController;
use crate::cleanup_socket_message;
use crate::error::GatewayClientError;
use crate::packet_router::PacketRouter;
@@ -9,10 +8,7 @@ pub use crate::packet_router::{
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
};
use crate::socket_state::{PartiallyDelegated, SocketState};
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(not(feature = "coconut"))]
use credentials::token::bandwidth::TokenCredential;
use crypto::asymmetric::identity;
use futures::{FutureExt, SinkExt, StreamExt};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
@@ -40,7 +36,8 @@ const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
pub struct GatewayClient {
authenticated: bool,
bandwidth_remaining: i64,
// TODO: This should be replaced by an actual bandwidth value, with 0 meaning no bandwidth
has_bandwidth: bool,
gateway_address: String,
gateway_identity: identity::PublicKey,
local_identity: Arc<identity::KeyPair>,
@@ -48,7 +45,6 @@ pub struct GatewayClient {
connection: SocketState,
packet_router: PacketRouter,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController>,
// reconnection related variables
/// Specifies whether client should try to reconnect to gateway on connection failure.
@@ -58,6 +54,7 @@ pub struct GatewayClient {
reconnection_attempts: usize,
/// Delay between each subsequent reconnection attempt.
reconnection_backoff: Duration,
coconut_credential: Credential,
}
impl GatewayClient {
@@ -71,11 +68,11 @@ impl GatewayClient {
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController>,
coconut_credential: Credential,
) -> Self {
GatewayClient {
authenticated: false,
bandwidth_remaining: 0,
has_bandwidth: false,
gateway_address,
gateway_identity,
local_identity,
@@ -83,10 +80,10 @@ impl GatewayClient {
connection: SocketState::NotConnected,
packet_router: PacketRouter::new(ack_sender, mixnet_message_sender),
response_timeout_duration,
bandwidth_controller,
should_reconnect_on_failure: true,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
coconut_credential,
}
}
@@ -107,6 +104,7 @@ impl GatewayClient {
gateway_address: String,
gateway_identity: identity::PublicKey,
local_identity: Arc<identity::KeyPair>,
coconut_credential: Credential,
response_timeout_duration: Duration,
) -> Self {
use futures::channel::mpsc;
@@ -119,7 +117,7 @@ impl GatewayClient {
GatewayClient {
authenticated: false,
bandwidth_remaining: 0,
has_bandwidth: false,
gateway_address,
gateway_identity,
local_identity,
@@ -127,10 +125,10 @@ impl GatewayClient {
connection: SocketState::NotConnected,
packet_router,
response_timeout_duration,
bandwidth_controller: None,
should_reconnect_on_failure: false,
reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS,
reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF,
coconut_credential,
}
}
@@ -138,14 +136,10 @@ impl GatewayClient {
self.gateway_identity
}
pub fn remaining_bandwidth(&self) -> i64 {
self.bandwidth_remaining
}
#[cfg(not(target_arch = "wasm32"))]
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
SocketState::Available(mut socket) => Ok((*socket).close(None).await?),
SocketState::Available(mut socket) => Ok(socket.close(None).await?),
SocketState::PartiallyDelegated(_) => {
unreachable!("this branch should have never been reached!")
}
@@ -157,7 +151,7 @@ impl GatewayClient {
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
SocketState::Available(mut socket) => {
(*socket).close(None).await;
socket.close(None).await;
Ok(())
}
SocketState::PartiallyDelegated(_) => {
@@ -182,7 +176,7 @@ impl GatewayClient {
Err(e) => return Err(GatewayClientError::NetworkError(e)),
};
self.connection = SocketState::Available(Box::new(ws_stream));
self.connection = SocketState::Available(ws_stream);
Ok(())
}
@@ -193,7 +187,7 @@ impl GatewayClient {
Err(e) => return Err(GatewayClientError::NetworkErrorWasm(e)),
};
self.connection = SocketState::Available(Box::new(ws_stream));
self.connection = SocketState::Available(ws_stream);
Ok(())
}
@@ -436,12 +430,8 @@ impl GatewayClient {
ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into();
match self.send_websocket_message(msg).await? {
ServerResponse::Authenticate {
status,
bandwidth_remaining,
} => {
ServerResponse::Authenticate { status } => {
self.authenticated = status;
self.bandwidth_remaining = bandwidth_remaining;
Ok(())
}
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
@@ -466,53 +456,6 @@ impl GatewayClient {
}
}
#[cfg(feature = "coconut")]
async fn claim_coconut_bandwidth(
&mut self,
credential: Credential,
) -> Result<(), GatewayClientError> {
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
&credential,
self.shared_key.as_ref().unwrap(),
iv,
)
.ok_or(GatewayClientError::SerializeCredential)?
.into();
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
ServerResponse::Bandwidth { available_total } => Ok(available_total),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
Ok(())
}
#[cfg(not(feature = "coconut"))]
async fn claim_token_bandwidth(
&mut self,
credential: TokenCredential,
) -> Result<(), GatewayClientError> {
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
let msg = ClientControlRequest::new_enc_token_bandwidth_credential(
&credential,
self.shared_key.as_ref().unwrap(),
iv,
)
.into();
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
ServerResponse::Bandwidth { available_total } => Ok(available_total),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
Ok(())
}
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
@@ -520,38 +463,23 @@ impl GatewayClient {
if self.shared_key.is_none() {
return Err(GatewayClientError::NoSharedKeyAvailable);
}
if self.bandwidth_controller.is_none() {
return Err(GatewayClientError::NoBandwidthControllerAvailable);
}
warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while");
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
#[cfg(feature = "coconut")]
let credential = self
.bandwidth_controller
.as_ref()
.unwrap()
.prepare_coconut_credential()
.await?;
#[cfg(not(feature = "coconut"))]
let credential = self
.bandwidth_controller
.as_ref()
.unwrap()
.prepare_token_credential(self.gateway_identity)
.await?;
#[cfg(feature = "coconut")]
return self.claim_coconut_bandwidth(credential).await;
#[cfg(not(feature = "coconut"))]
return self.claim_token_bandwidth(credential).await;
}
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
packets
.iter()
.map(|packet| packet.sphinx_packet().len())
.sum::<usize>() as i64
let msg = ClientControlRequest::new_enc_bandwidth_credential(
&self.coconut_credential,
self.shared_key.as_ref().unwrap(),
iv,
)
.ok_or(GatewayClientError::SerializeCredential)?
.into();
self.has_bandwidth = match self.send_websocket_message(msg).await? {
ServerResponse::Bandwidth { status } => Ok(status),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
Ok(())
}
pub async fn batch_send_mix_packets(
@@ -561,16 +489,8 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
// Try to claim more bandwidth first, and return an error only if that is still not
// enough (the current granularity for bandwidth should be sufficient)
self.claim_bandwidth().await?;
if self.estimate_required_bandwidth(&packets) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth(
self.estimate_required_bandwidth(&packets),
self.bandwidth_remaining,
));
}
if !self.has_bandwidth {
return Err(GatewayClientError::NotEnoughBandwidth);
}
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -601,10 +521,15 @@ impl GatewayClient {
}
}
async fn send_with_reconnection_on_failure(
&mut self,
msg: Message,
) -> Result<(), GatewayClientError> {
pub async fn send_ping_message(&mut self) -> Result<(), GatewayClientError> {
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
// as per RFC6455 section 5.5.2, `Ping frame MAY include "Application data".`
// so we don't need to include any here.
let msg = Message::Ping(Vec::new());
if let Err(err) = self.send_websocket_message_without_response(msg).await {
if err.is_closed_connection() && self.should_reconnect_on_failure {
info!("Going to attempt a reconnection");
@@ -617,17 +542,6 @@ impl GatewayClient {
}
}
pub async fn send_ping_message(&mut self) -> Result<(), GatewayClientError> {
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
// as per RFC6455 section 5.5.2, `Ping frame MAY include "Application data".`
// so we don't need to include any here.
let msg = Message::Ping(Vec::new());
self.send_with_reconnection_on_failure(msg).await
}
// TODO: possibly make responses optional
pub async fn send_mix_packet(
&mut self,
@@ -636,16 +550,8 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
// Try to claim more bandwidth first, and return an error only if that is still not
// enough
self.claim_bandwidth().await?;
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth(
mix_packet.sphinx_packet().len() as i64,
self.bandwidth_remaining,
));
}
if !self.has_bandwidth {
return Err(GatewayClientError::NotEnoughBandwidth);
}
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -657,7 +563,17 @@ impl GatewayClient {
.as_ref()
.expect("no shared key present even though we're authenticated!"),
);
self.send_with_reconnection_on_failure(msg).await
if let Err(err) = self.send_websocket_message_without_response(msg).await {
if err.is_closed_connection() && self.should_reconnect_on_failure {
info!("Going to attempt a reconnection");
self.attempt_reconnection().await
} else {
Err(err)
}
} else {
Ok(())
}
}
async fn recover_socket_connection(&mut self) -> Result<(), GatewayClientError> {
@@ -673,7 +589,7 @@ impl GatewayClient {
_ => unreachable!(),
};
self.connection = SocketState::Available(Box::new(conn));
self.connection = SocketState::Available(conn);
Ok(())
}
@@ -682,6 +598,9 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if !self.has_bandwidth {
return Err(GatewayClientError::NotEnoughBandwidth);
}
if self.connection.is_partially_delegated() {
return Ok(());
}
@@ -693,7 +612,7 @@ impl GatewayClient {
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
SocketState::Available(conn) => {
PartiallyDelegated::split_and_listen_for_mixnet_messages(
*conn,
conn,
self.packet_router.clone(),
Arc::clone(
self.shared_key
@@ -714,6 +633,7 @@ impl GatewayClient {
self.establish_connection().await?;
}
let shared_key = self.perform_initial_authentication().await?;
self.claim_bandwidth().await?;
// this call is NON-blocking
self.start_listening_for_mixnet_messages()?;
+67 -54
View File
@@ -2,85 +2,41 @@
// SPDX-License-Identifier: Apache-2.0
use gateway_requests::registration::handshake::error::HandshakeError;
use std::fmt::{self, Error, Formatter};
use std::io;
use thiserror::Error;
use tungstenite::Error as WsError;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsValue;
#[cfg(not(feature = "coconut"))]
use web3::Error as Web3Error;
#[derive(Debug, Error)]
#[derive(Debug)]
pub enum GatewayClientError {
#[error("Connection to the gateway is not established")]
ConnectionNotEstablished,
#[error("Gateway returned an error response - {0}")]
GatewayError(String),
#[error("There was a network error - {0}")]
NetworkError(#[from] WsError),
NetworkError(WsError),
// TODO: see if `JsValue` is a reasonable type for this
#[cfg(target_arch = "wasm32")]
#[error("There was a network error")]
NetworkErrorWasm(JsValue),
#[cfg(not(feature = "coconut"))]
#[error("Could not backup keypair - {0}")]
IOError(#[from] std::io::Error),
#[cfg(not(feature = "coconut"))]
#[error("Could not burn ERC20 token in Ethereum smart contract - {0}")]
BurnTokenError(#[from] Web3Error),
#[cfg(not(feature = "coconut"))]
#[error("Invalid Ethereum private key")]
InvalidEthereumPrivateKey,
#[error("Invalid URL - {0}")]
InvalidURL(String),
#[error("No shared key was provided or obtained")]
NoSharedKeyAvailable,
#[error("No bandwidth controller provided")]
NoBandwidthControllerAvailable,
#[error("Credential error - {0}")]
CredentialError(#[from] credentials::error::Error),
#[error("Connection was abruptly closed")]
ConnectionAbruptlyClosed,
#[error("Received response was malformed")]
MalformedResponse,
#[error("Credential could not be serialized")]
SerializeCredential,
#[error("Client is not authenticated")]
NotAuthenticated,
#[error("Client does not have enough bandwidth: estimated {0}, remaining: {1}")]
NotEnoughBandwidth(i64, i64),
#[error("Received an unexpected response")]
NotEnoughBandwidth,
UnexpectedResponse,
#[error("Connection is in an invalid state - please send a bug report")]
ConnectionInInvalidState,
#[error("Failed to finish registration handshake - {0}")]
RegistrationFailure(HandshakeError),
#[error("Authentication failure")]
AuthenticationFailure,
#[error("Timed out")]
Timeout,
}
impl From<WsError> for GatewayClientError {
fn from(err: WsError) -> Self {
GatewayClientError::NetworkError(err)
}
}
impl GatewayClientError {
pub fn is_closed_connection(&self) -> bool {
match self {
@@ -98,3 +54,60 @@ impl GatewayClientError {
}
}
}
#[cfg(target_arch = "wasm32")]
impl From<JsValue> for GatewayClientError {
fn from(err: JsValue) -> Self {
GatewayClientError::NetworkErrorWasm(err)
}
}
// better human readable representation of the error, mostly so that GatewayClientError
// would implement std::error::Error
impl fmt::Display for GatewayClientError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
match self {
GatewayClientError::ConnectionNotEstablished => {
write!(f, "connection to the gateway is not established")
}
GatewayClientError::NoSharedKeyAvailable => {
write!(f, "no shared key was provided or obtained")
}
GatewayClientError::NotAuthenticated => write!(f, "client is not authenticated"),
GatewayClientError::NetworkError(err) => {
write!(f, "there was a network error - {}", err)
}
#[cfg(target_arch = "wasm32")]
GatewayClientError::NetworkErrorWasm(err) => {
write!(f, "there was a network error - {:?}", err)
}
GatewayClientError::ConnectionAbruptlyClosed => {
write!(f, "connection was abruptly closed")
}
GatewayClientError::Timeout => write!(f, "timed out"),
GatewayClientError::MalformedResponse => write!(f, "received response was malformed"),
GatewayClientError::ConnectionInInvalidState => write!(
f,
"connection is in an invalid state - please send a bug report"
),
GatewayClientError::RegistrationFailure(handshake_err) => write!(
f,
"failed to finish registration handshake - {}",
handshake_err
),
GatewayClientError::AuthenticationFailure => write!(f, "authentication failure"),
GatewayClientError::GatewayError(err) => {
write!(f, "gateway returned an error response - {}", err)
}
GatewayClientError::UnexpectedResponse => write!(f, "received an unexpected response"),
GatewayClientError::NotEnoughBandwidth => {
write!(f, "client does not have enough bandwidth")
}
GatewayClientError::SerializeCredential => {
write!(f, "credential could not be serialized")
}
}
}
}
@@ -8,7 +8,6 @@ pub use packet_router::{
};
use tungstenite::{protocol::Message, Error as WsError};
pub mod bandwidth;
pub mod client;
pub mod error;
pub mod packet_router;
@@ -119,7 +119,7 @@ impl PartiallyDelegated {
}
.is_err()
{
warn!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
panic!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
}
};
@@ -173,9 +173,9 @@ impl PartiallyDelegated {
// this call failing is incredibly unlikely, but not impossible.
// basically the gateway connection must have failed after executing previous line but
// before starting execution of this one.
notify
.send(())
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
if notify.send(()).is_err() {
return Err(GatewayClientError::ConnectionAbruptlyClosed);
}
let stream_results: Result<_, GatewayClientError> = stream_receiver
.await
@@ -193,7 +193,7 @@ impl PartiallyDelegated {
// by notifying the future owning it to finish the execution and awaiting the result
// which should be almost immediate (or an invalid state which should never, ever happen)
pub(crate) enum SocketState {
Available(Box<WsConn>),
Available(WsConn),
PartiallyDelegated(PartiallyDelegated),
NotConnected,
Invalid,
@@ -3,7 +3,6 @@ name = "validator-client"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
rust-version = "1.56"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -26,13 +25,13 @@ network-defaults = { path = "../../network-defaults" }
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true }
cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
prost = { version = "0.9", default-features = false, optional = true }
cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"], optional = true }
prost = { version = "0.7", 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 = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true }
ts-rs = {version = "5.1", optional = true}
ts-rs = "3.0"
[features]
nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
@@ -5,14 +5,9 @@
use crate::nymd::{
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
#[cfg(feature = "nymd-client")]
use mixnet_contract::StateParams;
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract::{GatewayBond, MixNodeBond};
#[cfg(feature = "nymd-client")]
use mixnet_contract::{RawDelegationData, RewardingIntervalResponse};
use url::Url;
#[cfg(feature = "nymd-client")]
@@ -24,6 +19,7 @@ pub struct Config {
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
gateway_delegations_page_limit: Option<u32>,
}
#[cfg(feature = "nymd-client")]
@@ -40,6 +36,7 @@ impl Config {
mixnode_page_limit: None,
gateway_page_limit: None,
mixnode_delegations_page_limit: None,
gateway_delegations_page_limit: None,
}
}
@@ -57,6 +54,11 @@ impl Config {
self.mixnode_delegations_page_limit = limit;
self
}
pub fn with_gateway_delegations_page_limit(mut self, limit: Option<u32>) -> Config {
self.gateway_delegations_page_limit = limit;
self
}
}
#[cfg(feature = "nymd-client")]
@@ -67,6 +69,7 @@ pub struct Client<C> {
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
gateway_delegations_page_limit: Option<u32>,
// ideally they would have been read-only, but unfortunately rust doesn't have such features
pub validator_api: validator_api::Client,
@@ -92,6 +95,7 @@ impl Client<SigningNymdClient> {
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
gateway_delegations_page_limit: config.gateway_delegations_page_limit,
validator_api: validator_api_client,
nymd: nymd_client,
})
@@ -127,6 +131,7 @@ impl Client<QueryNymdClient> {
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
gateway_delegations_page_limit: config.gateway_delegations_page_limit,
validator_api: validator_api_client,
nymd: nymd_client,
})
@@ -153,10 +158,6 @@ impl<C> Client<C> {
self.mixnet_contract_address = Some(mixnet_contract_address)
}
pub fn get_mixnet_contract_address(&self) -> Option<cosmrs::AccountId> {
self.mixnet_contract_address.clone()
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
@@ -165,50 +166,6 @@ impl<C> Client<C> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_state_params(&self) -> Result<StateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_state_params().await?)
}
pub async fn get_current_rewarding_interval(
&self,
) -> Result<RewardingIntervalResponse, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_rewarding_interval().await?)
}
pub async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_reward_pool().await?.u128())
}
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_circulating_supply().await?.u128())
}
pub async fn get_sybil_resistance_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_sybil_resistance_percent().await?)
}
pub async fn get_epoch_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_epoch_reward_percent().await?)
}
// basically handles paging for us
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
@@ -256,7 +213,7 @@ impl<C> Client<C> {
Ok(gateways)
}
pub async fn get_all_nymd_single_mixnode_delegations(
pub async fn get_all_nymd_mixnode_delegations(
&self,
identity: mixnet_contract::IdentityKey,
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
@@ -286,34 +243,6 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_mixnode_delegations(
&self,
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_all_mix_delegations_paged(
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_reverse_mixnode_delegations(
&self,
delegation_owner: &cosmrs::AccountId,
@@ -366,6 +295,88 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_gateway_delegations(
&self,
identity: mixnet_contract::IdentityKey,
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_gateway_delegations(
identity.clone(),
start_after.take(),
self.gateway_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_reverse_gateway_delegations(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_reverse_gateway_delegations_paged(
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegated_nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_gateway_delegations_of_owner(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
for node_identity in self
.get_all_nymd_reverse_gateway_delegations(delegation_owner)
.await?
{
let delegation = self
.nymd
.get_gateway_delegation(node_identity, delegation_owner)
.await?;
delegations.push(delegation);
}
Ok(delegations)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -399,12 +410,6 @@ impl ApiClient {
self.validator_api.change_url(new_endpoint);
}
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
@@ -22,7 +22,7 @@ use cosmrs::rpc::query::Query;
use cosmrs::rpc::{self, HttpClient, Order};
use cosmrs::tendermint::abci::Transaction;
use cosmrs::tendermint::{abci, block, chain};
use cosmrs::{tx, AccountId, Coin, Denom};
use cosmrs::{AccountId, Coin, Denom};
use prost::Message;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
@@ -153,9 +153,12 @@ pub trait CosmWasmClient: rpc::Client {
.map_err(|_| NymdError::SerializationError("Coins".to_owned()))
}
async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError> {
Ok(self.tx(id, false).await?)
}
// disabled until https://github.com/tendermint/tendermint/issues/6802
// and consequently https://github.com/informalsystems/tendermint-rs/issues/942 is resolved
//
// async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError> {
// Ok(self.tx(id, false).await?)
// }
async fn search_tx(&self, query: Query) -> Result<Vec<TxResponse>, NymdError> {
// according to https://docs.tendermint.com/master/rpc/#/Info/tx_search
@@ -13,9 +13,8 @@ use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{Fee, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, tx, AccountId, Any, Coin};
use log::debug;
use cosmrs::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, tx, AccountId, Coin};
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
@@ -52,7 +51,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.unwrap_or_default(),
instantiate_permission: Default::default(),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgStoreCode".to_owned()))?;
let tx_res = self
@@ -114,7 +113,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
init_msg: serde_json::to_vec(msg)?,
funds: options.map(|options| options.funds).unwrap_or_default(),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgInstantiateContract".to_owned()))?;
let tx_res = self
@@ -154,7 +153,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
new_admin: new_admin.clone(),
contract: contract_address.clone(),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgUpdateAdmin".to_owned()))?;
let tx_res = self
@@ -179,7 +178,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
sender: sender_address.clone(),
contract: contract_address.clone(),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgClearAdmin".to_owned()))?;
let tx_res = self
@@ -211,7 +210,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
code_id,
migrate_msg: serde_json::to_vec(msg)?,
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
let tx_res = self
@@ -243,7 +242,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
msg: serde_json::to_vec(msg)?,
funds,
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?;
let tx_res = self
@@ -257,48 +256,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
})
}
async fn execute_multiple<I, M>(
&self,
sender_address: &AccountId,
contract_address: &AccountId,
msgs: I,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<ExecuteResult, NymdError>
where
I: IntoIterator<Item = (M, Vec<Coin>)> + Send,
M: Serialize,
{
let messages = msgs
.into_iter()
.map(|(msg, funds)| {
cosmwasm::MsgExecuteContract {
sender: sender_address.clone(),
contract: contract_address.clone(),
msg: serde_json::to_vec(&msg)?,
funds,
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
})
.collect::<Result<_, _>>()?;
let tx_res = self
.sign_and_broadcast_commit(sender_address, messages, fee, memo)
.await?
.check_response()?;
debug!(
"gas wanted: {:?}, gas used: {:?}",
tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used
);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
})
}
async fn send_tokens(
&self,
sender_address: &AccountId,
@@ -312,7 +269,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
to_address: recipient_address.clone(),
amount,
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
@@ -332,7 +289,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
validator_address: validator_address.to_owned(),
amount,
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
@@ -352,7 +309,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
validator_address: validator_address.to_owned(),
amount: Some(amount),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
@@ -370,7 +327,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
delegator_address: delegator_address.to_owned(),
validator_address: validator_address.to_owned(),
}
.to_any()
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
@@ -381,7 +338,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
async fn sign_and_broadcast_async(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
messages: Vec<Msg>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_async::Response, NymdError> {
@@ -397,7 +354,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
async fn sign_and_broadcast_sync(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
messages: Vec<Msg>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_sync::Response, NymdError> {
@@ -413,7 +370,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
async fn sign_and_broadcast_commit(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
messages: Vec<Msg>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError> {
@@ -428,7 +385,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fn sign_direct(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
messages: Vec<Msg>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
signer_data: SignerData,
@@ -466,7 +423,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
async fn sign(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
messages: Vec<Msg>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<tx::Raw, NymdError> {
@@ -506,7 +463,7 @@ impl Client {
#[async_trait]
impl rpc::Client for Client {
async fn perform<R>(&self, request: R) -> Result<R::Response, rpc::Error>
async fn perform<R>(&self, request: R) -> rpc::Result<R::Response>
where
R: SimpleRequest,
{
@@ -3,15 +3,10 @@
use crate::nymd::cosmwasm_client::types::ContractCodeId;
use cosmrs::tendermint::block;
use cosmrs::{bip32, tx, AccountId};
use cosmrs::{bip32, rpc, tx, AccountId};
use std::io;
use thiserror::Error;
pub use cosmrs::rpc::error::{
Error as TendermintRpcError, ErrorDetail as TendermintRpcErrorDetail,
};
pub use cosmrs::rpc::response_error::{Code, ResponseError};
#[derive(Debug, Error)]
pub enum NymdError {
#[error("No contract address is available to perform the call")]
@@ -36,7 +31,7 @@ pub enum NymdError {
InvalidTxHash(String),
#[error("There was an issue with a tendermint RPC request - {0}")]
TendermintError(#[from] TendermintRpcError),
TendermintError(#[from] rpc::Error),
#[error("There was an issue when attempting to serialize data")]
SerializationError(String),
@@ -103,56 +98,3 @@ pub enum NymdError {
#[error("The provided gas price is malformed")]
MalformedGasPrice,
}
impl NymdError {
pub fn is_tendermint_response_timeout(&self) -> bool {
match &self {
NymdError::TendermintError(TendermintRpcError(
TendermintRpcErrorDetail::Response(err),
_,
)) => {
let response = &err.source;
if response.code() == Code::InternalError {
// 0.34 (and earlier) versions of tendermint seemed to be using phrase "timed out waiting ..."
// (https://github.com/tendermint/tendermint/blob/v0.34.13/rpc/core/mempool.go#L124)
// while 0.35+ has "timeout waiting for ..."
// https://github.com/tendermint/tendermint/blob/v0.35.0-rc3/internal/rpc/core/mempool.go#L99
// note that as of the time of writing this comment (08.10.2021), the most recent version
// of cosmos-sdk (v0.44.1) uses tendermint 0.34.13
if let Some(data) = response.data() {
data.contains("timed out") || data.contains("timeout")
} else {
false
}
} else {
false
}
}
_ => false,
}
}
pub fn is_tendermint_response_duplicate(&self) -> bool {
match &self {
NymdError::TendermintError(TendermintRpcError(
TendermintRpcErrorDetail::Response(err),
_,
)) => {
let response = &err.source;
if response.code() == Code::InternalError {
// this particular error message seems to be unchanged between 0.34 and newer versions
// https://github.com/tendermint/tendermint/blob/v0.34.13/mempool/errors.go#L10
// https://github.com/tendermint/tendermint/blob/v0.35.0-rc3/types/mempool.go#L10
if let Some(data) = response.data() {
data.contains("tx already exists in cache")
} else {
false
}
} else {
false
}
}
_ => false,
}
}
}
@@ -4,11 +4,12 @@
use crate::nymd::GasPrice;
use cosmrs::tx::{Fee, Gas};
use cosmrs::Coin;
use cosmwasm_std::Uint128;
use serde::{Deserialize, Serialize};
use std::fmt;
use ts_rs::TS;
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, TS)]
pub enum Operation {
Upload,
Init,
@@ -23,15 +24,20 @@ pub enum Operation {
BondGateway,
UnbondGateway,
DelegateToGateway,
UndelegateFromGateway,
UpdateStateParams,
BeginMixnodeRewarding,
FinishMixnodeRewarding,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
gas_price * gas_limit
let limit_uint128 = Uint128::from(gas_limit.value());
let amount = gas_price.amount * limit_uint128;
assert!(amount.u128() <= u64::MAX as u128);
Coin {
denom: gas_price.denom.clone(),
amount: (amount.u128() as u64).into(),
}
}
impl fmt::Display for Operation {
@@ -44,13 +50,13 @@ impl fmt::Display for Operation {
Operation::Send => f.write_str("Send"),
Operation::BondMixnode => f.write_str("BondMixnode"),
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::UnbondGateway => f.write_str("UnbondGateway"),
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::UnbondGateway => f.write_str("UnbondGateway"),
Operation::DelegateToGateway => f.write_str("DelegateToGateway"),
Operation::UndelegateFromGateway => f.write_str("UndelegateFromGateway"),
Operation::UpdateStateParams => f.write_str("UpdateStateParams"),
Operation::BeginMixnodeRewarding => f.write_str("BeginMixnodeRewarding"),
Operation::FinishMixnodeRewarding => f.write_str("FinishMixnodeRewarding"),
}
}
}
@@ -72,27 +78,23 @@ impl Operation {
Operation::BondGateway => 175_000u64.into(),
Operation::UnbondGateway => 175_000u64.into(),
Operation::DelegateToGateway => 175_000u64.into(),
Operation::UndelegateFromGateway => 175_000u64.into(),
Operation::UpdateStateParams => 175_000u64.into(),
Operation::BeginMixnodeRewarding => 175_000u64.into(),
Operation::FinishMixnodeRewarding => 175_000u64.into(),
}
}
pub(crate) fn determine_custom_fee(gas_price: &GasPrice, gas_limit: Gas) -> Fee {
pub(crate) fn determine_fee(&self, gas_price: &GasPrice, gas_limit: Option<Gas>) -> Fee {
// we need to know 2 of the following 3 parameters (the third one is being implicit) in order to construct Fee:
// (source: https://docs.cosmos.network/v0.42/basics/gas-fees.html)
// - gas price
// - gas limit
// - fees
let gas_limit = gas_limit.unwrap_or_else(|| self.default_gas_limit());
let fee = calculate_fee(gas_price, gas_limit);
Fee::from_amount_and_gas(fee, gas_limit)
}
pub(crate) fn determine_fee(&self, gas_price: &GasPrice, gas_limit: Option<Gas>) -> Fee {
let gas_limit = gas_limit.unwrap_or_else(|| self.default_gas_limit());
Self::determine_custom_fee(gas_price, gas_limit)
}
}
#[cfg(test)]
@@ -3,10 +3,8 @@
use crate::nymd::error::NymdError;
use config::defaults;
use cosmrs::tx::Gas;
use cosmrs::{Coin, Denom};
use cosmwasm_std::{Decimal, Fraction, Uint128};
use std::ops::Mul;
use cosmrs::Denom;
use cosmwasm_std::Decimal;
use std::str::FromStr;
/// A gas price, i.e. the price of a single unit of gas. This is typically a fraction of
@@ -20,36 +18,6 @@ pub struct GasPrice {
pub denom: Denom,
}
impl<'a> Mul<Gas> for &'a GasPrice {
type Output = Coin;
fn mul(self, gas_limit: Gas) -> Self::Output {
let limit_uint128 = Uint128::from(gas_limit.value());
let mut amount = self.amount * limit_uint128;
let gas_price_numerator = self.amount.numerator();
let gas_price_denominator = self.amount.denominator();
// gas price is a fraction of the smallest fee token unit, so we must ensure that
// for any multiplication, we have rounded up
//
// I don't really like the this solution as it has a theoretical chance of
// overflowing (internally cosmwasm uses U256 to avoid that)
// however, realistically that is impossible to happen as the resultant value
// would have to be way higher than our token limit of 10^15 (1 billion of tokens * 1 million for denomination)
// and max value of u128 is approximately 10^38
if limit_uint128.u128() * gas_price_numerator > amount.u128() * gas_price_denominator {
amount += Uint128::new(1);
}
assert!(amount.u128() <= u64::MAX as u128);
Coin {
denom: self.denom.clone(),
amount: (amount.u128() as u64).into(),
}
}
}
impl FromStr for GasPrice {
type Err = NymdError;
@@ -110,15 +78,4 @@ mod tests {
assert!("0.025 upunk".parse::<GasPrice>().is_err());
assert!("0.025UPUNK".parse::<GasPrice>().is_err());
}
#[test]
fn gas_limit_multiplication() {
// real world example that caused an issue when the result was rounded down
let gas_price: GasPrice = "0.025upunk".parse().unwrap();
let gas_limit: Gas = 157500u64.into();
let fee = &gas_price * gas_limit;
// the failing behaviour was result value of 3937
assert_eq!(fee.amount, 3938u64.into());
}
}
@@ -4,19 +4,21 @@
use crate::nymd::cosmwasm_client::signing_client;
use crate::nymd::cosmwasm_client::types::{
ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions, InstantiateResult,
MigrateResult, SequenceResponse, UploadMeta, UploadResult,
MigrateResult, UploadMeta, UploadResult,
};
use crate::nymd::error::NymdError;
use crate::nymd::fee_helpers::Operation;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
use cosmwasm_std::{Coin, Uint128};
use cosmrs::tx::{Fee, Gas};
use cosmwasm_std::Coin;
use mixnet_contract::{
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
LayerDistribution, MixNode, MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, RewardingIntervalResponse,
PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, QueryMsg,
StateParams,
};
use serde::Serialize;
@@ -27,12 +29,8 @@ pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
pub use crate::nymd::gas_price::GasPrice;
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
pub use cosmrs::tendermint::block::Height;
pub use cosmrs::tendermint::hash;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::{Fee, Gas};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{AccountId, Decimal, Denom};
pub use cosmrs::{AccountId, Denom};
pub use signing_client::Client as SigningNymdClient;
pub mod cosmwasm_client;
@@ -156,51 +154,11 @@ impl<C> NymdClient<C> {
&self.client_address.as_ref().unwrap()[0]
}
pub async fn account_sequence(&self) -> Result<SequenceResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
self.client.get_sequence(self.address()).await
}
pub fn get_fee(&self, operation: Operation) -> Fee {
let gas_limit = self.custom_gas_limits.get(&operation).cloned();
operation.determine_fee(&self.gas_price, gas_limit)
}
pub fn calculate_custom_fee(&self, gas_limit: impl Into<Gas>) -> Fee {
Operation::determine_custom_fee(&self.gas_price, gas_limit.into())
}
pub async fn get_current_block_timestamp(&self) -> Result<TendermintTime, NymdError>
where
C: CosmWasmClient + Sync,
{
Ok(self.client.get_block(None).await?.block.header.time)
}
pub async fn get_current_block_height(&self) -> Result<Height, NymdError>
where
C: CosmWasmClient + Sync,
{
self.client.get_height().await
}
/// Obtains the hash of a block specified by the provided height.
///
/// # Arguments
///
/// * `height`: height of the block for which we want to obtain the hash.
pub async fn get_block_hash(&self, height: u32) -> Result<hash::Hash, NymdError>
where
C: CosmWasmClient + Sync,
{
self.client
.get_block(Some(height))
.await
.map(|block| block.block_id.hash)
}
pub async fn get_balance(&self, address: &AccountId) -> Result<Option<CosmosCoin>, NymdError>
where
C: CosmWasmClient + Sync,
@@ -218,18 +176,6 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_current_rewarding_interval(
&self,
) -> Result<RewardingIntervalResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::CurrentRewardingInterval {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
pub async fn get_layer_distribution(&self) -> Result<LayerDistribution, NymdError>
where
C: CosmWasmClient + Sync,
@@ -240,46 +186,6 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_reward_pool(&self) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetRewardPool {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
pub async fn get_circulating_supply(&self) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetCirculatingSupply {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
pub async fn get_sybil_resistance_percent(&self) -> Result<u8, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetSybilResistancePercent {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
pub async fn get_epoch_reward_percent(&self) -> Result<u8, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetEpochRewardPercent {};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Checks whether there is a bonded mixnode associated with the provided client's address
pub async fn owns_mixnode(&self, address: &AccountId) -> Result<bool, NymdError>
where
@@ -365,25 +271,6 @@ impl<C> NymdClient<C> {
.await
}
/// Gets list of all mixnode delegations on particular page.
pub async fn get_all_mix_delegations_paged(
&self,
// I really hate mixing cosmwasm and cosmos-sdk types here...
start_after: Option<Vec<u8>>,
page_limit: Option<u32>,
) -> Result<PagedAllDelegationsResponse<RawDelegationData>, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetAllMixDelegations {
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Gets list of all the mixnodes on which a particular address delegated.
pub async fn get_reverse_mix_delegations_paged(
&self,
@@ -422,6 +309,64 @@ impl<C> NymdClient<C> {
.await
}
/// Gets list of all delegations towards particular mixnode on particular page.
pub async fn get_gateway_delegations(
&self,
gateway_identity: IdentityKey,
start_after: Option<Addr>,
page_limit: Option<u32>,
) -> Result<PagedGatewayDelegationsResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetGatewayDelegations {
gateway_identity,
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Gets list of all the gateways on which a particular address delegated.
pub async fn get_reverse_gateway_delegations_paged(
&self,
delegation_owner: Addr,
start_after: Option<IdentityKey>,
page_limit: Option<u32>,
) -> Result<PagedReverseGatewayDelegationsResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetReverseGatewayDelegations {
delegation_owner,
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Checks value of delegation of given client towards particular gateway.
pub async fn get_gateway_delegation(
&self,
gateway_identity: IdentityKey,
delegator: &AccountId,
) -> Result<Delegation, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetGatewayDelegation {
gateway_identity,
address: Addr::unchecked(delegator.as_ref()),
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Send funds from one address to another
pub async fn send(
&self,
@@ -455,23 +400,6 @@ impl<C> NymdClient<C> {
.await
}
pub async fn execute_multiple<I, M>(
&self,
contract_address: &AccountId,
msgs: I,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
I: IntoIterator<Item = (M, Vec<CosmosCoin>)> + Send,
M: Serialize,
{
self.client
.execute_multiple(self.address(), contract_address, msgs, fee, memo)
.await
}
pub async fn upload(
&self,
wasm_code: Vec<u8>,
@@ -690,6 +618,57 @@ impl<C> NymdClient<C> {
.await
}
/// Delegates specified amount of stake to particular gateway.
pub async fn delegate_to_gateway(
&self,
gateway_identity: &str,
amount: &Coin,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.get_fee(Operation::DelegateToGateway);
let req = ExecuteMsg::DelegateToGateway {
gateway_identity: gateway_identity.to_string(),
};
self.client
.execute(
self.address(),
self.contract_address()?,
&req,
fee,
"Delegating to gateway from rust!",
vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
)
.await
}
/// Removes stake delegation from a particular gateway.
pub async fn remove_gateway_delegation(
&self,
gateway_identity: &str,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.get_fee(Operation::UndelegateFromGateway);
let req = ExecuteMsg::UndelegateFromGateway {
gateway_identity: gateway_identity.to_string(),
};
self.client
.execute(
self.address(),
self.contract_address()?,
&req,
fee,
"Removing gateway delegation from rust!",
Vec::new(),
)
.await
}
pub async fn update_state_params(
&self,
new_params: StateParams,
@@ -711,54 +690,6 @@ impl<C> NymdClient<C> {
)
.await
}
pub async fn begin_mixnode_rewarding(
&self,
rewarding_interval_nonce: u32,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.get_fee(Operation::BeginMixnodeRewarding);
let req = ExecuteMsg::BeginMixnodeRewarding {
rewarding_interval_nonce,
};
self.client
.execute(
self.address(),
self.contract_address()?,
&req,
fee,
"Beginning mixnode rewarding procedure",
Vec::new(),
)
.await
}
pub async fn finish_mixnode_rewarding(
&self,
rewarding_interval_nonce: u32,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.get_fee(Operation::FinishMixnodeRewarding);
let req = ExecuteMsg::FinishMixnodeRewarding {
rewarding_interval_nonce,
};
self.client
.execute(
self.address(),
self.contract_address()?,
&req,
fee,
"Finishing mixnode rewarding procedure",
Vec::new(),
)
.await
}
}
fn cosmwasm_coin_to_cosmos_coin(coin: Coin) -> CosmosCoin {
@@ -24,20 +24,6 @@ pub struct AccountData {
pub(crate) private_key: SigningKey,
}
impl AccountData {
pub fn address(&self) -> &AccountId {
&self.address
}
pub fn public_key(&self) -> PublicKey {
self.public_key
}
pub fn private_key(&self) -> &SigningKey {
&self.private_key
}
}
type Secp256k1Keypair = (SigningKey, PublicKey);
#[derive(Debug)]
@@ -68,11 +68,6 @@ impl Client {
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE])
.await
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -7,7 +7,5 @@ pub const API_VERSION: &str = VALIDATOR_API_VERSION;
pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const ACTIVE: &str = "active";
pub const COCONUT_BLIND_SIGN: &str = "blind_sign";
pub const COCONUT_VERIFICATION_KEY: &str = "verification_key";
-2
View File
@@ -11,6 +11,4 @@ url = "2.2"
# I guess temporarily until we get serde support in coconut up and running
coconut-interface = { path = "../coconut-interface" }
crypto = { path = "../crypto" }
network-defaults = { path = "../network-defaults" }
validator-client = { path = "../client-libs/validator-client" }
@@ -8,10 +8,11 @@
use url::Url;
use super::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
use crate::error::Error;
use crate::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
use coconut_interface::{hash_to_scalar, Credential, Parameters, Signature, VerificationKey};
use network_defaults::BANDWIDTH_VALUE;
const BANDWIDTH_VALUE: u64 = 1024 * 1024; // 1 MB
pub const PUBLIC_ATTRIBUTES: u32 = 1;
pub const PRIVATE_ATTRIBUTES: u32 = 1;
-5
View File
@@ -1,5 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod bandwidth;
pub mod utils;
+3 -3
View File
@@ -1,8 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod coconut;
pub mod bandwidth;
pub mod error;
pub mod token;
mod utils;
pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
pub use utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
-140
View File
@@ -1,140 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crypto::asymmetric::identity::{PublicKey, Signature, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH};
use crate::error::Error;
use std::convert::TryInto;
#[cfg(not(feature = "coconut"))]
pub struct TokenCredential {
verification_key: PublicKey,
gateway_identity: PublicKey,
bandwidth: u64,
signature: Signature,
}
#[cfg(not(feature = "coconut"))]
impl TokenCredential {
pub fn new(
verification_key: PublicKey,
gateway_identity: PublicKey,
bandwidth: u64,
signature: Signature,
) -> Self {
TokenCredential {
verification_key,
gateway_identity,
bandwidth,
signature,
}
}
pub fn verification_key(&self) -> PublicKey {
self.verification_key
}
pub fn gateway_identity(&self) -> PublicKey {
self.gateway_identity
}
pub fn bandwidth(&self) -> u64 {
self.bandwidth
}
pub fn signature_bytes(&self) -> [u8; 64] {
self.signature.to_bytes()
}
pub fn verify_signature(&self) -> bool {
let message: Vec<u8> = self
.verification_key
.to_bytes()
.iter()
.chain(self.gateway_identity.to_bytes().iter())
.copied()
.collect();
self.verification_key
.verify(&message, &self.signature)
.is_ok()
}
pub fn to_bytes(&self) -> Vec<u8> {
self.verification_key
.to_bytes()
.iter()
.chain(self.gateway_identity.to_bytes().iter())
.chain(self.bandwidth.to_be_bytes().iter())
.chain(self.signature.to_bytes().iter())
.copied()
.collect()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, Error> {
if b.len() != 2 * PUBLIC_KEY_LENGTH + 8 + SIGNATURE_LENGTH {
return Err(Error::BandwidthCredentialError);
}
let verification_key = PublicKey::from_bytes(&b[..PUBLIC_KEY_LENGTH])
.map_err(|_| Error::BandwidthCredentialError)?;
let gateway_identity = PublicKey::from_bytes(&b[PUBLIC_KEY_LENGTH..2 * PUBLIC_KEY_LENGTH])
.map_err(|_| Error::BandwidthCredentialError)?;
let bandwidth = u64::from_be_bytes(
b[2 * PUBLIC_KEY_LENGTH..2 * PUBLIC_KEY_LENGTH + 8]
.try_into()
// unwrapping is safe because we know we have 8 bytes
.unwrap(),
);
let signature = Signature::from_bytes(&b[2 * PUBLIC_KEY_LENGTH + 8..])
.map_err(|_| Error::BandwidthCredentialError)?;
Ok(TokenCredential {
verification_key,
gateway_identity,
bandwidth,
signature,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "coconut"))]
#[test]
fn token_serde() {
// pre-generated, valid values
let verification_key = PublicKey::from_bytes(&[
103, 105, 71, 177, 149, 245, 26, 32, 73, 121, 76, 50, 94, 88, 119, 231, 91, 229, 167,
56, 39, 62, 185, 39, 83, 246, 153, 27, 17, 155, 109, 73,
])
.unwrap();
let gateway_identity = PublicKey::from_bytes(&[
37, 113, 137, 189, 157, 82, 35, 2, 187, 136, 61, 119, 98, 5, 245, 82, 46, 124, 67, 45,
165, 255, 53, 222, 185, 252, 6, 148, 128, 15, 206, 19,
])
.unwrap();
let signature = Signature::from_bytes(&[
117, 251, 162, 217, 57, 2, 50, 210, 206, 81, 236, 90, 74, 201, 69, 237, 240, 247, 214,
158, 220, 89, 235, 222, 85, 134, 73, 73, 8, 60, 25, 39, 183, 28, 83, 193, 31, 174, 25,
24, 38, 215, 205, 228, 159, 135, 35, 4, 171, 59, 100, 157, 12, 249, 77, 52, 143, 4, 32,
28, 147, 70, 182, 14,
])
.unwrap();
let credential = TokenCredential::new(verification_key, gateway_identity, 1024, signature);
let serialized_credential = credential.to_bytes();
let deserialized_credential = TokenCredential::from_bytes(&serialized_credential).unwrap();
assert_eq!(
credential.verification_key,
deserialized_credential.verification_key
);
assert_eq!(
credential.gateway_identity,
deserialized_credential.gateway_identity
);
assert_eq!(credential.bandwidth, deserialized_credential.bandwidth);
assert_eq!(
credential.signature.to_bytes(),
deserialized_credential.signature.to_bytes()
);
}
}
-4
View File
@@ -1,4 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod bandwidth;
+8 -7
View File
@@ -7,14 +7,15 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
aes = { version = "0.7.4", features = ["ctr"] }
bs58 = "0.4.0"
blake3 = { version = "1.0.0", features = ["traits-preview"] }
digest = "0.9.0"
aes-ctr = "0.6.0"
bs58 = "0.4"
blake3 = "0.3"
#blake3 = { version = "0.3", features = ["traits-preview"]}
digest = "0.9"
generic-array = "0.14"
hkdf = "0.11.0"
hmac = "0.11.0"
cipher = "0.3.0"
hkdf = "0.10"
hmac = "0.8"
cipher = "0.2"
x25519-dalek = "1.1"
ed25519-dalek = "1.0"
log = "0.4"
+9 -2
View File
@@ -5,13 +5,18 @@ use digest::{BlockInput, FixedOutput, Reset, Update};
use generic_array::ArrayLength;
use hkdf::Hkdf;
#[derive(Debug)]
pub enum HkdfError {
InvalidOkmLength,
}
/// Perform HKDF `extract` then `expand` as a single step.
pub fn extract_then_expand<D>(
salt: Option<&[u8]>,
ikm: &[u8],
info: Option<&[u8]>,
okm_length: usize,
) -> Result<Vec<u8>, hkdf::InvalidLength>
) -> Result<Vec<u8>, HkdfError>
where
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
D::BlockSize: ArrayLength<u8>,
@@ -22,7 +27,9 @@ where
let hkdf = Hkdf::<D>::new(salt, ikm);
let mut okm = vec![0u8; okm_length];
hkdf.expand(info.unwrap_or_else(|| &[]), &mut okm)?;
if hkdf.expand(info.unwrap_or_else(|| &[]), &mut okm).is_err() {
return Err(HkdfError::InvalidOkmLength);
}
Ok(okm)
}
+2 -2
View File
@@ -18,7 +18,7 @@ where
D::OutputSize: ArrayLength<u8>,
{
let mut hmac =
Hmac::<D>::new_from_slice(key).expect("HMAC should be able to take key of any size!");
Hmac::<D>::new_varkey(key).expect("HMAC should be able to take key of any size!");
hmac.update(data);
hmac.finalize()
}
@@ -31,7 +31,7 @@ where
D::OutputSize: ArrayLength<u8>,
{
let mut hmac =
Hmac::<D>::new_from_slice(key).expect("HMAC should be able to take key of any size!");
Hmac::<D>::new_varkey(key).expect("HMAC should be able to take key of any size!");
hmac.update(data);
// note, under the hood ct_eq is called
hmac.verify(tag).is_ok()
+1 -1
View File
@@ -13,7 +13,7 @@ pub use generic_array;
// with the below my idea was to try to introduce having a single place of importing all hashing, encryption,
// etc. algorithms and import them elsewhere as needed via common/crypto
pub use aes;
pub use aes_ctr;
pub use blake3;
// TODO: this function uses all three modules: asymmetric crypto, symmetric crypto and derives key...,
+7 -7
View File
@@ -3,7 +3,7 @@
use crate::asymmetric::encryption;
use crate::hkdf;
use cipher::{CipherKey, NewCipher, StreamCipher};
use cipher::stream::{Key, NewStreamCipher, SyncStreamCipher};
use digest::{BlockInput, FixedOutput, Reset, Update};
use generic_array::{typenum::Unsigned, ArrayLength};
use rand::{CryptoRng, RngCore};
@@ -13,9 +13,9 @@ use rand::{CryptoRng, RngCore};
pub fn new_ephemeral_shared_key<C, D, R>(
rng: &mut R,
remote_key: &encryption::PublicKey,
) -> (encryption::KeyPair, CipherKey<C>)
) -> (encryption::KeyPair, Key<C>)
where
C: StreamCipher + NewCipher,
C: SyncStreamCipher + NewStreamCipher,
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
D::BlockSize: ArrayLength<u8>,
D::OutputSize: ArrayLength<u8>,
@@ -31,7 +31,7 @@ where
.expect("somehow too long okm was provided");
let derived_shared_key =
CipherKey::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!");
(ephemeral_keypair, derived_shared_key)
}
@@ -40,9 +40,9 @@ where
pub fn recompute_shared_key<C, D>(
remote_key: &encryption::PublicKey,
local_key: &encryption::PrivateKey,
) -> CipherKey<C>
) -> Key<C>
where
C: StreamCipher + NewCipher,
C: SyncStreamCipher + NewStreamCipher,
D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
D::BlockSize: ArrayLength<u8>,
D::OutputSize: ArrayLength<u8>,
@@ -53,5 +53,5 @@ where
let okm = hkdf::extract_then_expand::<D>(None, &dh_result, None, C::KeySize::to_usize())
.expect("somehow too long okm was provided");
CipherKey::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
Key::<C>::from_exact_iter(okm).expect("okm was expanded to incorrect length!")
}
+18 -23
View File
@@ -1,14 +1,13 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cipher::{Nonce, StreamCipher};
use cipher::stream::{Nonce, StreamCipher, SyncStreamCipher};
use generic_array::{typenum::Unsigned, GenericArray};
use rand::{CryptoRng, RngCore};
// re-export this for ease of use
pub use cipher::{CipherKey, NewCipher};
pub use cipher::stream::{Key, NewStreamCipher};
// SECURITY:
// TODO: note that this is not the most secure approach here
// we are not using nonces properly but instead "kinda" thinking of them as IVs.
// Nonce require, as the name suggest, being only seen once. Ever.
@@ -21,9 +20,9 @@ pub use cipher::{CipherKey, NewCipher};
#[allow(clippy::upper_case_acronyms)]
pub type IV<C> = Nonce<C>;
pub fn generate_key<C, R>(rng: &mut R) -> CipherKey<C>
pub fn generate_key<C, R>(rng: &mut R) -> Key<C>
where
C: NewCipher,
C: NewStreamCipher,
R: RngCore + CryptoRng,
{
let mut key = GenericArray::default();
@@ -33,7 +32,7 @@ where
pub fn random_iv<C, R>(rng: &mut R) -> IV<C>
where
C: NewCipher,
C: NewStreamCipher,
R: RngCore + CryptoRng,
{
let mut iv = GenericArray::default();
@@ -43,14 +42,14 @@ where
pub fn zero_iv<C>() -> IV<C>
where
C: NewCipher,
C: NewStreamCipher,
{
GenericArray::default()
}
pub fn iv_from_slice<C>(b: &[u8]) -> &IV<C>
where
C: NewCipher,
C: NewStreamCipher,
{
if b.len() != C::NonceSize::to_usize() {
// `from_slice` would have caused a panic about this issue anyway.
@@ -67,42 +66,38 @@ where
// TODO: there's really no way to use more parts of the keystream if it was required at some point.
// However, do we really expect to ever need it?
#[inline]
pub fn encrypt<C>(key: &CipherKey<C>, iv: &IV<C>, data: &[u8]) -> Vec<u8>
pub fn encrypt<C>(key: &Key<C>, iv: &IV<C>, data: &[u8]) -> Vec<u8>
where
C: StreamCipher + NewCipher,
C: SyncStreamCipher + NewStreamCipher,
{
let mut ciphertext = data.to_vec();
encrypt_in_place::<C>(key, iv, &mut ciphertext);
ciphertext
}
#[inline]
pub fn encrypt_in_place<C>(key: &CipherKey<C>, iv: &IV<C>, data: &mut [u8])
pub fn encrypt_in_place<C>(key: &Key<C>, iv: &IV<C>, data: &mut [u8])
where
C: StreamCipher + NewCipher,
C: SyncStreamCipher + NewStreamCipher,
{
let mut cipher = C::new(key, iv);
cipher.apply_keystream(data)
cipher.encrypt(data)
}
#[inline]
pub fn decrypt<C>(key: &CipherKey<C>, iv: &IV<C>, ciphertext: &[u8]) -> Vec<u8>
pub fn decrypt<C>(key: &Key<C>, iv: &IV<C>, ciphertext: &[u8]) -> Vec<u8>
where
C: StreamCipher + NewCipher,
C: SyncStreamCipher + NewStreamCipher,
{
let mut data = ciphertext.to_vec();
decrypt_in_place::<C>(key, iv, &mut data);
data
}
#[inline]
pub fn decrypt_in_place<C>(key: &CipherKey<C>, iv: &IV<C>, data: &mut [u8])
pub fn decrypt_in_place<C>(key: &Key<C>, iv: &IV<C>, data: &mut [u8])
where
C: StreamCipher + NewCipher,
C: SyncStreamCipher + NewStreamCipher,
{
let mut cipher = C::new(key, iv);
cipher.apply_keystream(data)
cipher.decrypt(data)
}
#[cfg(test)]
@@ -113,7 +108,7 @@ mod tests {
#[cfg(test)]
mod aes_ctr128 {
use super::*;
use aes::Aes128Ctr;
use aes_ctr::Aes128Ctr;
#[test]
fn zero_iv_is_actually_zero() {
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "erc20-bridge-contract"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
-45
View File
@@ -1,45 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
// Serializable structures for what we find in common/crypto
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, JsonSchema)]
pub struct PublicKey([u8; 32]);
impl PublicKey {
pub fn new(bytes: [u8; 32]) -> Self {
PublicKey(bytes)
}
pub fn to_bytes(&self) -> [u8; 32] {
self.0
}
}
impl AsRef<[u8]> for PublicKey {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Signature([u8; 32], [u8; 32]);
impl Signature {
pub fn new(bytes: [u8; 64]) -> Self {
let mut sig1 = [0u8; 32];
let mut sig2 = [0u8; 32];
sig1.copy_from_slice(&bytes[..32]);
sig2.copy_from_slice(&bytes[32..]);
Signature(sig1, sig2)
}
pub fn to_bytes(&self) -> [u8; 64] {
let mut res = [0u8; 64];
res[..32].copy_from_slice(&self.0);
res[32..].copy_from_slice(&self.1);
res
}
}
-3
View File
@@ -1,3 +0,0 @@
pub mod keys;
pub mod msg;
pub mod payment;
-30
View File
@@ -1,30 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::keys::PublicKey;
use crate::payment::LinkPaymentData;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
LinkPayment { data: LinkPaymentData },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetPayments {
limit: Option<u32>,
start_after: Option<PublicKey>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
@@ -1,73 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::keys::{PublicKey, Signature};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct Payment {
verification_key: PublicKey,
gateway_identity: PublicKey,
bandwidth: u64,
}
impl Payment {
pub fn new(verification_key: PublicKey, gateway_identity: PublicKey, bandwidth: u64) -> Self {
Payment {
verification_key,
gateway_identity,
bandwidth,
}
}
pub fn verification_key(&self) -> PublicKey {
self.verification_key
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct LinkPaymentData {
pub verification_key: PublicKey,
pub gateway_identity: PublicKey,
pub bandwidth: u64,
pub signature: Signature,
}
impl LinkPaymentData {
pub fn new(
verification_key: [u8; 32],
gateway_identity: [u8; 32],
bandwidth: u64,
signature: [u8; 64],
) -> Self {
LinkPaymentData {
verification_key: PublicKey::new(verification_key),
gateway_identity: PublicKey::new(gateway_identity),
bandwidth,
signature: Signature::new(signature),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedPaymentResponse {
pub payments: Vec<Payment>,
pub per_page: usize,
pub start_next_after: Option<PublicKey>,
}
impl PagedPaymentResponse {
pub fn new(
payments: Vec<Payment>,
per_page: usize,
start_next_after: Option<PublicKey>,
) -> Self {
PagedPaymentResponse {
payments,
per_page,
start_next_after,
}
}
}
+2 -10
View File
@@ -8,18 +8,10 @@ edition = "2018"
[dependencies]
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" }
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256" }
#cosmwasm-std = { version = "0.14.1" }
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
schemars = "0.8"
ts-rs = { version = "5.1", optional = true }
thiserror = "1.0"
network-defaults = { path = "../network-defaults" }
fixed = "1.1"
az = "1.1"
log = "0.4.14"
[features]
default = []
ts-rs = "3.0"
+34 -24
View File
@@ -7,24 +7,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct UnpackedDelegation<T> {
pub owner: Addr,
pub node_identity: IdentityKey,
pub delegation_data: T,
}
impl<T> UnpackedDelegation<T> {
pub fn new(owner: Addr, node_identity: IdentityKey, delegation_data: T) -> Self {
UnpackedDelegation {
owner,
node_identity,
delegation_data,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct RawDelegationData {
pub amount: Uint128,
pub block_height: u64,
@@ -121,16 +104,43 @@ impl PagedReverseMixDelegationsResponse {
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse<T> {
pub delegations: Vec<UnpackedDelegation<T>>,
pub start_next_after: Option<Vec<u8>>,
pub struct PagedGatewayDelegationsResponse {
pub node_identity: IdentityKey,
pub delegations: Vec<Delegation>,
pub start_next_after: Option<Addr>,
}
impl<T> PagedAllDelegationsResponse<T> {
pub fn new(delegations: Vec<UnpackedDelegation<T>>, start_next_after: Option<Vec<u8>>) -> Self {
PagedAllDelegationsResponse {
impl PagedGatewayDelegationsResponse {
pub fn new(
node_identity: IdentityKey,
delegations: Vec<Delegation>,
start_next_after: Option<Addr>,
) -> Self {
PagedGatewayDelegationsResponse {
node_identity,
delegations,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedReverseGatewayDelegationsResponse {
pub delegation_owner: Addr,
pub delegated_nodes: Vec<IdentityKey>,
pub start_next_after: Option<IdentityKey>,
}
impl PagedReverseGatewayDelegationsResponse {
pub fn new(
delegation_owner: Addr,
delegated_nodes: Vec<IdentityKey>,
start_next_after: Option<IdentityKey>,
) -> Self {
PagedReverseGatewayDelegationsResponse {
delegation_owner,
delegated_nodes,
start_next_after,
}
}
}
-9
View File
@@ -1,9 +0,0 @@
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum MixnetContractError {
#[error("Overflow Error")]
OverflowError(#[from] cosmwasm_std::OverflowError),
#[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
BlockstampNotSet,
}
+8 -119
View File
@@ -2,14 +2,15 @@
#![allow(clippy::field_reassign_with_default)]
use crate::{IdentityKey, SphinxKey};
use cosmwasm_std::{Addr, Coin};
use cosmwasm_std::{coin, Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::Display;
use ts_rs::TS;
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
use crate::current_block_height;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema, TS)]
pub struct Gateway {
pub host: String,
pub mix_port: u16,
@@ -24,7 +25,9 @@ pub struct Gateway {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct GatewayBond {
pub bond_amount: Coin,
pub total_delegation: Coin,
pub owner: Addr,
#[serde(default = "current_block_height")]
pub block_height: u64,
pub gateway: Gateway,
}
@@ -32,6 +35,7 @@ pub struct GatewayBond {
impl GatewayBond {
pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
GatewayBond {
total_delegation: coin(0, &bond_amount.denom),
bond_amount,
owner,
block_height,
@@ -56,39 +60,6 @@ impl GatewayBond {
}
}
impl PartialOrd for GatewayBond {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// first remove invalid cases
if self.bond_amount.denom != other.bond_amount.denom {
return None;
}
// try to order by total bond
let bond_cmp = self
.bond_amount
.amount
.partial_cmp(&other.bond_amount.amount)?;
if bond_cmp != Ordering::Equal {
return Some(bond_cmp);
}
// then check block height
let height_cmp = self.block_height.partial_cmp(&other.block_height)?;
if height_cmp != Ordering::Equal {
return Some(height_cmp);
}
// finally go by the rest of the fields in order. It doesn't really matter at this point
// but we should be deterministic.
let owner_cmp = self.owner.partial_cmp(&other.owner)?;
if owner_cmp != Ordering::Equal {
return Some(owner_cmp);
}
self.gateway.partial_cmp(&other.gateway)
}
}
impl Display for GatewayBond {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
@@ -125,85 +96,3 @@ pub struct GatewayOwnershipResponse {
pub address: Addr,
pub has_gateway: bool,
}
#[cfg(test)]
mod tests {
use super::*;
fn gateway_fixture() -> Gateway {
Gateway {
host: "1.1.1.1".to_string(),
mix_port: 123,
clients_port: 456,
location: "foomplandia".to_string(),
sphinx_key: "sphinxkey".to_string(),
identity_key: "identitykey".to_string(),
version: "0.11.0".to_string(),
}
}
#[test]
fn gateway_bond_partial_ord() {
let _150foos = Coin::new(150, "foo");
let _140foos = Coin::new(140, "foo");
let _50foos = Coin::new(50, "foo");
let _0foos = Coin::new(0, "foo");
let gate1 = GatewayBond {
bond_amount: _150foos.clone(),
owner: Addr::unchecked("foo1"),
block_height: 100,
gateway: gateway_fixture(),
};
let gate2 = GatewayBond {
bond_amount: _150foos,
owner: Addr::unchecked("foo2"),
block_height: 120,
gateway: gateway_fixture(),
};
let gate3 = GatewayBond {
bond_amount: _50foos,
owner: Addr::unchecked("foo3"),
block_height: 120,
gateway: gateway_fixture(),
};
let gate4 = GatewayBond {
bond_amount: _140foos,
owner: Addr::unchecked("foo4"),
block_height: 120,
gateway: gateway_fixture(),
};
let gate5 = GatewayBond {
bond_amount: _0foos,
owner: Addr::unchecked("foo5"),
block_height: 120,
gateway: gateway_fixture(),
};
// summary:
// gate1: 150bond, foo1, 100
// gate2: 150bond, foo2, 120
// gate3: 50bond, foo3, 120
// gate4: 140bond, foo4, 120
// gate5: 0bond, foo5, 120
// highest total bond is used
// finally just the rest of the fields
// gate1 has higher total than gate4 or gate5
assert!(gate1 > gate4);
assert!(gate1 > gate5);
// gate1 has the same total as gate3, however, gate1 has more tokens in bond
assert!(gate1 > gate3);
// same case for gate4 and gate5
assert!(gate4 > gate5);
// same bond and delegation, so it's just ordered by height
assert!(gate1 < gate2);
}
}
+11 -8
View File
@@ -2,21 +2,24 @@
// SPDX-License-Identifier: Apache-2.0
mod delegation;
pub mod error;
mod gateway;
pub mod mixnode;
mod mixnode;
mod msg;
mod types;
pub use cosmwasm_std::{Addr, Coin};
pub use delegation::{
Delegation, PagedAllDelegationsResponse, PagedMixDelegationsResponse,
PagedReverseMixDelegationsResponse, RawDelegationData, UnpackedDelegation,
Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse,
PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, RawDelegationData,
};
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
pub use types::{
IdentityKey, IdentityKeyRef, LayerDistribution, RewardingIntervalResponse, SphinxKey,
StateParams,
};
pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams};
use std::sync::atomic::{AtomicU64, Ordering};
pub static CURRENT_BLOCK_HEIGHT: AtomicU64 = AtomicU64::new(0);
pub fn current_block_height() -> u64 {
CURRENT_BLOCK_HEIGHT.load(Ordering::Relaxed)
}
+6 -398
View File
@@ -2,20 +2,16 @@
#![allow(clippy::field_reassign_with_default)]
use crate::{IdentityKey, SphinxKey};
use az::CheckedCast;
use cosmwasm_std::{coin, Addr, Coin, Uint128};
use log::error;
use network_defaults::{DEFAULT_OPERATOR_EPOCH_COST, DEFAULT_PROFIT_MARGIN};
use cosmwasm_std::{coin, Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::cmp::Ordering;
use std::fmt::Display;
use ts_rs::TS;
type U128 = fixed::types::U75F53; // u128 with 18 significant digits
use crate::current_block_height;
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema, TS)]
pub struct MixNode {
pub host: String,
pub mix_port: u16,
@@ -27,19 +23,7 @@ pub struct MixNode {
pub version: String,
}
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize_repr,
Deserialize_repr,
JsonSchema,
)]
#[derive(Copy, Clone, Debug, Serialize_repr, PartialEq, Deserialize_repr, JsonSchema)]
#[repr(u8)]
pub enum Layer {
Gateway = 0,
@@ -48,106 +32,15 @@ pub enum Layer {
Three = 3,
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeRewardParams {
period_reward_pool: Uint128,
k: Uint128,
reward_blockstamp: u64,
circulating_supply: Uint128,
uptime: Uint128,
sybil_resistance_percent: u8,
}
impl NodeRewardParams {
pub fn new(
period_reward_pool: u128,
k: u128,
reward_blockstamp: u64,
circulating_supply: u128,
uptime: u128,
sybil_resistance_percent: u8,
) -> NodeRewardParams {
NodeRewardParams {
period_reward_pool: Uint128(period_reward_pool),
k: Uint128(k),
reward_blockstamp,
circulating_supply: Uint128(circulating_supply),
uptime: Uint128(uptime),
sybil_resistance_percent,
}
}
pub fn performance(&self) -> U128 {
U128::from_num(self.uptime.u128()) / U128::from_num(100)
}
pub fn operator_cost(&self) -> U128 {
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_EPOCH_COST as u128)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.reward_blockstamp = blockstamp;
}
pub fn period_reward_pool(&self) -> u128 {
self.period_reward_pool.u128()
}
pub fn k(&self) -> u128 {
self.k.u128()
}
pub fn circulating_supply(&self) -> u128 {
self.circulating_supply.u128()
}
pub fn reward_blockstamp(&self) -> u64 {
self.reward_blockstamp
}
pub fn uptime(&self) -> u128 {
self.uptime.u128()
}
pub fn one_over_k(&self) -> U128 {
U128::from_num(1) / U128::from_num(self.k.u128())
}
pub fn alpha(&self) -> U128 {
U128::from_num(self.sybil_resistance_percent) / U128::from_num(100)
}
}
#[derive(Debug)]
pub struct NodeRewardResult {
reward: U128,
lambda: U128,
sigma: U128,
}
impl NodeRewardResult {
pub fn reward(&self) -> U128 {
self.reward
}
pub fn lambda(&self) -> U128 {
self.lambda
}
pub fn sigma(&self) -> U128 {
self.sigma
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeBond {
pub bond_amount: Coin,
pub total_delegation: Coin,
pub owner: Addr,
pub layer: Layer,
#[serde(default = "current_block_height")]
pub block_height: u64,
pub mix_node: MixNode,
pub profit_margin_percent: Option<u8>,
}
impl MixNodeBond {
@@ -157,7 +50,6 @@ impl MixNodeBond {
layer: Layer,
block_height: u64,
mix_node: MixNode,
profit_margin_percent: Option<u8>,
) -> Self {
MixNodeBond {
total_delegation: coin(0, &bond_amount.denom),
@@ -166,15 +58,9 @@ impl MixNodeBond {
layer,
block_height,
mix_node,
profit_margin_percent,
}
}
pub fn profit_margin(&self) -> U128 {
U128::from_num(self.profit_margin_percent.unwrap_or(DEFAULT_PROFIT_MARGIN))
/ U128::from_num(100)
}
pub fn identity(&self) -> &String {
&self.mix_node.identity_key
}
@@ -190,187 +76,6 @@ impl MixNodeBond {
pub fn mix_node(&self) -> &MixNode {
&self.mix_node
}
pub fn total_stake(&self) -> Option<u128> {
if self.bond_amount.denom != self.total_delegation.denom {
None
} else {
Some(self.bond_amount.amount.u128() + self.total_delegation.amount.u128())
}
}
pub fn total_delegation(&self) -> Coin {
self.total_delegation.clone()
}
pub fn bond_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.bond_amount().amount.u128()) / U128::from_num(circulating_supply)
}
pub fn total_stake_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.bond_amount().amount.u128() + self.total_delegation().amount.u128())
/ U128::from_num(circulating_supply)
}
pub fn lambda(&self, params: &NodeRewardParams) -> U128 {
// Ratio of a bond to the token circulating supply
let bond_to_circulating_supply_ratio =
self.bond_to_circulating_supply(params.circulating_supply());
bond_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn sigma(&self, params: &NodeRewardParams) -> U128 {
// Ratio of a delegation to the the token circulating supply
let total_stake_to_circulating_supply_ratio =
self.total_stake_to_circulating_supply(params.circulating_supply());
total_stake_to_circulating_supply_ratio.min(params.one_over_k())
}
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
// Assuming uniform work distribution across the network this is one_over_k * k
let omega_k = U128::from_num(1u128);
let lambda = self.lambda(params);
let sigma = self.sigma(params);
let reward = params.performance()
* params.period_reward_pool()
* (sigma * omega_k + params.alpha() * lambda * sigma * params.k())
/ (U128::from_num(1) + params.alpha());
NodeRewardResult {
reward,
lambda,
sigma,
}
}
pub fn node_profit(&self, params: &NodeRewardParams) -> U128 {
if self.reward(params).reward() < params.operator_cost() {
U128::from_num(0)
} else {
self.reward(params).reward() - params.operator_cost()
}
}
pub fn operator_reward(&self, params: &NodeRewardParams) -> u128 {
let reward = self.reward(params);
let profit = if reward.reward < params.operator_cost() {
U128::from_num(0)
} else {
reward.reward - params.operator_cost()
};
let operator_base_reward = reward.reward.min(params.operator_cost());
let operator_reward = (self.profit_margin()
+ (U128::from_num(1) - self.profit_margin()) * reward.lambda / reward.sigma)
* profit;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0));
if let Some(int_reward) = reward.checked_cast() {
int_reward
} else {
error!(
"Could not cast reward ({}) to u128, returning 0 - mixnode {}",
reward,
self.identity()
);
0u128
}
}
pub fn sigma_ratio(&self, params: &NodeRewardParams) -> U128 {
if self.total_stake_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
{
self.total_stake_to_circulating_supply(params.circulating_supply())
} else {
params.one_over_k()
}
}
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &NodeRewardParams) -> u128 {
let scaled_delegation_amount =
U128::from_num(delegation_amount.u128()) / U128::from_num(params.circulating_supply());
let delegator_reward = (U128::from_num(1) - self.profit_margin())
* scaled_delegation_amount
/ self.sigma(params)
* self.node_profit(params);
let reward = delegator_reward.max(U128::from_num(0));
if let Some(int_reward) = reward.checked_cast() {
int_reward
} else {
error!(
"Could not cast delegator reward ({}) to u128, returning 0 - mixnode {}",
reward,
self.identity()
);
0u128
}
}
}
impl PartialOrd for MixNodeBond {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// first remove invalid cases
if self.bond_amount.denom != self.total_delegation.denom {
return None;
}
if other.bond_amount.denom != other.total_delegation.denom {
return None;
}
if self.bond_amount.denom != other.bond_amount.denom {
return None;
}
// try to order by total bond + delegation
let total_cmp = (self.bond_amount.amount + self.total_delegation.amount)
.partial_cmp(&(self.bond_amount.amount + self.total_delegation.amount))?;
if total_cmp != Ordering::Equal {
return Some(total_cmp);
}
// then if those are equal, prefer higher bond over delegation
let bond_cmp = self
.bond_amount
.amount
.partial_cmp(&other.bond_amount.amount)?;
if bond_cmp != Ordering::Equal {
return Some(bond_cmp);
}
// then look at delegation (I'm not sure we can get here, but better safe than sorry)
let delegation_cmp = self
.total_delegation
.amount
.partial_cmp(&other.total_delegation.amount)?;
if delegation_cmp != Ordering::Equal {
return Some(delegation_cmp);
}
// then check block height
let height_cmp = self.block_height.partial_cmp(&other.block_height)?;
if height_cmp != Ordering::Equal {
return Some(height_cmp);
}
// finally go by the rest of the fields in order. It doesn't really matter at this point
// but we should be deterministic.
let owner_cmp = self.owner.partial_cmp(&other.owner)?;
if owner_cmp != Ordering::Equal {
return Some(owner_cmp);
}
let layer_cmp = self.layer.partial_cmp(&other.layer)?;
if layer_cmp != Ordering::Equal {
return Some(layer_cmp);
}
self.mix_node.partial_cmp(&other.mix_node)
}
}
impl Display for MixNodeBond {
@@ -409,100 +114,3 @@ pub struct MixOwnershipResponse {
pub address: Addr,
pub has_node: bool,
}
#[cfg(test)]
mod tests {
use super::*;
fn mixnode_fixture() -> MixNode {
MixNode {
host: "1.1.1.1".to_string(),
mix_port: 123,
verloc_port: 456,
http_api_port: 789,
sphinx_key: "sphinxkey".to_string(),
identity_key: "identitykey".to_string(),
version: "0.11.0".to_string(),
}
}
#[test]
fn mixnode_bond_partial_ord() {
let _150foos = Coin::new(150, "foo");
let _50foos = Coin::new(50, "foo");
let _0foos = Coin::new(0, "foo");
let mix1 = MixNodeBond {
bond_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo1"),
layer: Layer::One,
block_height: 100,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
};
let mix2 = MixNodeBond {
bond_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo2"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
};
let mix3 = MixNodeBond {
bond_amount: _50foos,
total_delegation: _150foos.clone(),
owner: Addr::unchecked("foo3"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
};
let mix4 = MixNodeBond {
bond_amount: _150foos.clone(),
total_delegation: _0foos.clone(),
owner: Addr::unchecked("foo4"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
};
let mix5 = MixNodeBond {
bond_amount: _0foos,
total_delegation: _150foos,
owner: Addr::unchecked("foo5"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
profit_margin_percent: Some(10),
};
// summary:
// mix1: 150bond + 50delegation, foo1, 100
// mix2: 150bond + 50delegation, foo2, 120
// mix3: 50bond + 150delegation, foo3, 120
// mix4: 150bond + 0delegation, foo4, 120
// mix5: 0bond + 150delegation, foo5, 120
// highest total bond+delegation is used
// then bond followed by delegation
// finally just the rest of the fields
// mix1 has higher total than mix4 or mix5
assert!(mix1 > mix4);
assert!(mix1 > mix5);
// mix1 has the same total as mix3, however, mix1 has more tokens in bond
assert!(mix1 > mix3);
// same case for mix4 and mix5
assert!(mix4 > mix5);
// same bond and delegation, so it's just ordered by height
assert!(mix1 < mix2);
}
}
+22 -26
View File
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardParams;
use crate::StateParams;
use crate::{Gateway, IdentityKey, MixNode};
use cosmwasm_std::Addr;
@@ -32,32 +31,24 @@ pub enum ExecuteMsg {
mix_identity: IdentityKey,
},
BeginMixnodeRewarding {
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
DelegateToGateway {
gateway_identity: IdentityKey,
},
UndelegateFromGateway {
gateway_identity: IdentityKey,
},
RewardMixnode {
identity: IdentityKey,
// percentage value in range 0-100
uptime: u32,
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
},
FinishMixnodeRewarding {
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
},
RewardMixnodeV2 {
RewardGateway {
identity: IdentityKey,
// percentage value in range 0-100
params: NodeRewardParams,
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
uptime: u32,
},
}
@@ -79,16 +70,11 @@ pub enum QueryMsg {
address: Addr,
},
StateParams {},
CurrentRewardingInterval {},
GetMixDelegations {
mix_identity: IdentityKey,
start_after: Option<Addr>,
limit: Option<u32>,
},
GetAllMixDelegations {
start_after: Option<Vec<u8>>,
limit: Option<u32>,
},
GetReverseMixDelegations {
delegation_owner: Addr,
start_after: Option<IdentityKey>,
@@ -98,11 +84,21 @@ pub enum QueryMsg {
mix_identity: IdentityKey,
address: Addr,
},
GetGatewayDelegations {
gateway_identity: IdentityKey,
start_after: Option<Addr>,
limit: Option<u32>,
},
GetReverseGatewayDelegations {
delegation_owner: Addr,
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
GetGatewayDelegation {
gateway_identity: IdentityKey,
address: Addr,
},
LayerDistribution {},
GetRewardPool {},
GetCirculatingSupply {},
GetEpochRewardPercent {},
GetSybilResistancePercent {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+11 -19
View File
@@ -26,29 +26,16 @@ impl LayerDistribution {
}
}
#[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)]
pub struct RewardingIntervalResponse {
pub current_rewarding_interval_starting_block: u64,
pub current_rewarding_interval_nonce: u32,
pub rewarding_in_progress: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct StateParams {
pub epoch_length: u32, // length of a rewarding epoch/interval, expressed in hours
pub epoch_length: u32, // length of an epoch, expressed in hours
pub minimum_mixnode_bond: Uint128, // minimum amount a mixnode must bond to get into the system
pub minimum_gateway_bond: Uint128, // minimum amount a gateway must bond to get into the system
pub mixnode_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub gateway_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub mixnode_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
// number of mixnode that are going to get rewarded during current rewarding interval (k_m)
// based on overall demand for private bandwidth-
pub mixnode_rewarded_set_size: u32,
// subset of rewarded mixnodes that are actively receiving mix traffic
// used to handle shorter-term (e.g. hourly) fluctuations of demand
pub gateway_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub mixnode_active_set_size: u32,
}
@@ -63,6 +50,11 @@ impl Display for StateParams {
"mixnode bond reward rate: {}; ",
self.mixnode_bond_reward_rate
)?;
write!(
f,
"gateway bond reward rate: {}; ",
self.gateway_bond_reward_rate
)?;
write!(
f,
"mixnode delegation reward rate: {}; ",
@@ -70,12 +62,12 @@ impl Display for StateParams {
)?;
write!(
f,
"mixnode rewarded set size: {}",
self.mixnode_rewarded_set_size
"gateway delegation reward rate: {}; ",
self.gateway_delegation_reward_rate
)?;
write!(
f,
"mixnode active set size: {}",
"mixnode active set size: {} ]",
self.mixnode_active_set_size
)
}
-2
View File
@@ -7,7 +7,5 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hex-literal = "0.3.3"
serde = {version = "1.0", features = ["derive"]}
url = "2.2"
time = { version = "0.3", features = ["macros"] }
-132
View File
@@ -1,132 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// This should be modified whenever an updated Ethereum contract is uploaded
pub const ETH_JSON_ABI: &str = r#"
[
{
"inputs": [
{
"internalType": "contract ERC20Burnable",
"name": "_erc20",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "Bandwidth",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
"name": "VerificationKey",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bytes",
"name": "SignedVerificationKey",
"type": "bytes"
}
],
"name": "Burned",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "verificationKey",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "signedVerificationKey",
"type": "bytes"
}
],
"name": "burnTokenForAccessCode",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "erc20",
"outputs": [
{
"internalType": "contract ERC20Burnable",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
"#;

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