Compare commits

..

202 Commits

Author SHA1 Message Date
Mark Sinclair ae0883241e Network Explorer: change prod env to round robin DNS 2021-12-17 13:07:16 +02:00
Mark Sinclair 11baa99c4b Network Explorer improvements:
- fix up API urls after Network Explorer API changes
- set currency denominations in `.env` file
- set API endpoints in `.env` file
2021-12-17 13:07:16 +02:00
Mark Sinclair 8bc23434ab Network Explorer API improvements:
- upgrade `okapi` for swagger generation across multiple resources
- switched `GET mix-node` to `GET mix-nodes`
- added error message when no geolocation env var is set and process continues
2021-12-17 13:07:16 +02:00
Mark Sinclair 8f6daf1e03 Network Explorer: configure URLs with .env file 2021-12-17 13:07:15 +02:00
Bogdan-Ștefan Neacșu 7f63377d22 Update contract addresses 2021-12-17 13:03:28 +02:00
Bogdan-Ștefan Neacșu 46cb3eca38 Do not set proxy only for this time 2021-12-17 13:02:01 +02:00
Bogdan-Ștefan Neacșu 7b22872c6b Short node identity signature check
Fix tests
2021-12-17 13:02:01 +02:00
Bogdan-Ștefan Neacșu 3342cb13c7 Update network defaults 2021-12-17 13:01:59 +02:00
Bogdan-Ștefan Neacșu 3cd5fc3b22 Make develop branch agnostic of the network 2021-12-17 12:48:29 +02:00
Fouad 0a30eb1c64 Merge pull request #968 from nymtech/feature/new-testnet-wallet-updates
Feature/new testnet wallet updates
2021-12-16 16:08:32 +00:00
Bogdan-Ștefan Neacşu 63bb35e1a1 Use the renamed balance function (#971) 2021-12-16 17:36:50 +02:00
Jędrzej Stuczyński c1e809fd99 Feature/ts client update (#956)
* Made client compile again + set auto fees

* Simplified client construction by allowing only a single URL

* wip

* Simplified signing assertion

* Initial implementation of queries

* Implemented all basic nymd queries

* Validator API queries

* Signing related queries

* Using default arguments

* Removed redundant else branches

* `eslint` and `prettier` formatting on Typescript validator client

* Removed cyclic import on Coin type

* Missing direct dependencies

* Ingoring cyclic imports

* Removed unused argument

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
2021-12-16 15:35:30 +00:00
Jędrzej Stuczyński 77ab22999c Feature/node info command (#972)
* Removed code duplication and introduced node-details command for mixnode

* ibid for the gateway

* Added port information
2021-12-16 14:55:03 +00:00
Bogdan-Ștefan Neacşu 747bf85ad8 Add custom denom balance query (#957)
* Add custom denom balance query

* Apply PR comment
2021-12-16 13:59:08 +02:00
Jędrzej Stuczyński cf4eadaa6b Introduced 'version' command to all relevant binaries (#969)
* Introduced 'version' command to all relevant binaries

* Removed separate 'version' subcommand in favour of clap's 'long_version' field
2021-12-16 11:48:50 +00:00
Jędrzej Stuczyński f43f07f0b9 Fixed invalid nodes being counted twice in unroutable category (#963) 2021-12-16 10:58:57 +00:00
fmtabbara c5cf7d19a3 add sample env 2021-12-16 10:10:39 +00:00
fmtabbara 7d7911c8e8 merge develop 2021-12-16 10:09:23 +00:00
Fouad 40d93e1eeb Merge pull request #964 from nymtech/feature/bond-details-handlers
Additional tauri commands to get bond details
2021-12-15 16:53:58 +00:00
Jędrzej Stuczyński 2f472c4e8e Additional tauri commands to get bond details 2021-12-15 16:05:31 +00:00
Bogdan-Ștefan Neacşu c93f3cfc4f Fix topology log (#962) 2021-12-15 13:19:34 +02:00
Mark Sinclair 87d18bbc16 Merge pull request #960 from nymtech/feature/network-explorer-ui-config
Network Explorer: configure URLs with `.env` file
2021-12-15 10:37:47 +00:00
Mark Sinclair 45ed46afa0 Network Explorer: add prod config 2021-12-15 10:14:41 +00:00
Mark Sinclair 9619e794b2 Network Explorer: configure URLs with .env file 2021-12-14 16:06:02 +00:00
Drazen Urch 305864aa0f Feature/vesting to wallet (#954)
* Better code structure, proper error handling, VestingSigningClient for wallet

* Add vesting contract queries to wallet

* Address review comments

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-14 12:25:48 +01:00
Jędrzej Stuczyński 8f152d42f0 Feature/simulate (#950)
* Raw scaffold for tx simulate

* Proper error handling in AbciResult parsing

* Simulate without actual signing operation

* Moved all-fee related functionalities to separate module

* Adding GasInfo to transaction results

* Automated gas estimation

* Slightly adjusted public API

* Using auto fees in eth events

* Removed old print statement

* Reorganised nymd client fee handling

* Put bandaid on wallet gas estimation

* Fixed operation re-export

* warning note on get_approximate_fee

* [ci skip] Generate TS types

* Refactored ProtoAbciResult parsing

* Explicit error on abci query failure

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-12-14 09:28:15 +00:00
fmtabbara 140cc3f769 correct profitPercent variable name 2021-12-13 21:11:12 +00:00
Drazen Urch 791d051537 Different workshare calculations for rewarded vs active set (#951)
* Add Makefile to make running all checks easier locally

* Different workshare calculation for active vs rewarded set

* Rework omega calculation, update tests

* Remove ZERO const

* unym -> DENOM

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-13 19:11:54 +01:00
Jędrzej Stuczyński f8099cb8c8 Bugfix/rewarding fixes (#953)
* Improved rewarding-related log messages

* Skipping empty mixnode chunks
2021-12-13 15:43:02 +00:00
fmtabbara 2045d0bafd update new fields 2021-12-13 15:26:35 +00:00
fmtabbara e68b48f296 use config so that env vars can be destructured 2021-12-13 14:39:26 +00:00
fmtabbara 3a2b553e38 use variable for currency 2021-12-13 13:04:17 +00:00
fmtabbara aa1955dc6b update webpack configs 2021-12-13 13:03:23 +00:00
Tommy Verrall f1e995b076 Merge pull request #947 from nymtech/feature/wallet-ui-updates-round-2
Desktop Wallet UI Updates
2021-12-13 11:11:30 +00:00
Bogdan-Ștefan Neacşu ed1fe22db2 Check the response for multiple sends (#955) 2021-12-10 09:22:37 +01:00
dependabot[bot] b1c45dc0f8 Bump next from 11.1.1 to 11.1.3 in /wallet-web (#952)
Bumps [next](https://github.com/vercel/next.js) from 11.1.1 to 11.1.3.
- [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.1...v11.1.3)

---
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>
2021-12-09 09:25:25 +00:00
Drazen Urch 0e0151f781 Feature/profit margin percent config (#949)
* Profit margin percent to config, fix vesting tests

* Add bonding bounds check

* Fix contract test
2021-12-08 20:17:31 +01:00
Drazen Urch 70e148b3ed Run CI for all contracts in one workflow (#948)
* Run CI for all contracts in one workflow

* Rename

* Fix vesting tests
2021-12-08 16:15:18 +01:00
fmtabbara 53138d6292 link to transaction hash 2021-12-07 22:39:13 +00:00
fmtabbara 39650de2bd style update 2021-12-07 21:46:02 +00:00
fmtabbara 7b04093cc5 style updates 2021-12-07 21:24:16 +00:00
fmtabbara a4c9a81399 refresh balance on balance page visit 2021-12-07 21:06:29 +00:00
Drazen Urch f42f76901a Add VestingExecute and VestingQuery client traits (#944)
* Add VestingExecute and VestingQuery client traits

* cover ed25519 verification

* cargo fmt

* `Unix` newline-style

* Remove newline force

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-07 19:56:10 +01:00
fmtabbara 875bcb4e63 update bonding success UI 2021-12-07 13:42:23 +00:00
fmtabbara fa62de5bc6 undelegate page update 2021-12-07 13:17:46 +00:00
fmtabbara 5c3b08e1cd update delegate page 2021-12-07 12:36:21 +00:00
Tommy Verrall 745af89019 Merge pull request #946 from nymtech/feature/docker-updates
Docker updates
2021-12-07 11:53:37 +00:00
Tommy Verrall eb0fb90127 update arg for commit hash 2021-12-07 11:45:46 +00:00
Tommy Verrall 527d5a5d9b update 2021-12-07 11:25:03 +00:00
Tommy Verrall 2dfa0a9d2a update 2021-12-07 11:23:44 +00:00
Tommy Verrall d9bfa4562e Update docker-files
- updated the network explorer
- updated new vesting contract
- updated the version of comos-sdk to be used
2021-12-07 11:22:57 +00:00
Jędrzej Stuczyński aec0239d87 Removed reliance on cosmrs fork (#943)
* Removed reliance on cosmrs fork

* Removed the accidental optional flag on cosmrs import in the wallet
2021-12-07 11:04:32 +00:00
Jędrzej Stuczyński a2324f98f8 Feature/identity verification (#930)
* Base58 representation of ed25519 signature

* Helper for verifying ed25519 signature on sender address

* Signature verification for gateway bonding

* Signature verification for mixnode bonding

* Added owner signatures for bonding in vesting contract

* Fixed choosing mixnode layer test

* Added owner signature fields to nymd client for bonding

* 'Updated' tauri wallet with new bond requirements

* Mixnode sign command with extra address validation

* Sign command for the gateway

* Signing own gateway's address derived with known mnemonic in not(coconut) case

* Fixed imports post-merge
2021-12-07 10:28:44 +00:00
fmtabbara 001d166477 update bond page 2021-12-07 10:23:37 +00:00
fmtabbara bb14e95e61 update send page 2021-12-06 17:32:29 +00:00
Jędrzej Stuczyński 5aa1c29409 Feature/terminology update (#941)
* Corrected used bond/pledge terminology

* ibid for the wallet and explorer

* [ci skip] Generate TS types

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-12-06 16:30:55 +00:00
Bogdan-Ștefan Neacşu db111490b6 Check the response for other transactions as well (#937) 2021-12-06 17:21:11 +01:00
Jędrzej Stuczyński fe0bb007c9 Don't reset total delegation on mixnode rebond (#940) 2021-12-06 17:20:41 +01:00
Tommy Verrall ddad6d73db Merge pull request #934 from nymtech/feature/client_on_behalf
Feature/client on behalf
2021-12-06 15:33:24 +00:00
fmtabbara 35c04014e5 undo hard-coded data 2021-12-06 15:32:09 +00:00
fmtabbara 138daddfed add custom nav icons 2021-12-06 15:21:34 +00:00
fmtabbara ab3cfe79bc use svgs as React components with svgr 2021-12-06 12:57:46 +00:00
fmtabbara 9fcf3105e0 balance page updates 2021-12-06 10:41:41 +00:00
fmtabbara 51bf117007 fix memory leak 2021-12-06 10:24:44 +00:00
Jędrzej Stuczyński a04d4503b5 Feature/pre cosmrs updates (#935)
* Updated nymd client

* Fixed contract upload and initialisation

* Increased default contract upload fee

* [ci skip] Generate TS types

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-12-06 09:25:53 +00:00
neacsu 9fb44f9672 [ci skip] Generate TS types 2021-12-03 18:16:55 +00:00
Bogdan-Ștefan Neacșu f542c28e89 Batching for sends 2021-12-03 19:05:21 +01:00
Bogdan-Ștefan Neacșu b809eb6c5f Batch delegations on behalf as well 2021-12-03 16:06:23 +01:00
Bogdan-Ștefan Neacșu 51e82fe930 Add gateway bond on behalf functions 2021-12-03 15:25:47 +01:00
Bogdan-Ștefan Neacșu 67f847e674 Add batched delegated mixnode bonds 2021-12-03 15:02:39 +01:00
Bogdan-Ștefan Neacșu d4736bac27 Fix gas for operations 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu 6417feaaed Fix clippy 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu 718170c651 Update tauri client 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu c10038e688 Add client-side Operations as well, in case fees need to be modified 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu 2f68439916 Unbond/undelegate mixnode with rust client 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu b175480ba5 Bond/delegate mixnode with rust client 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu 34c9726ac9 Add vesting contract address to validator client library 2021-12-03 15:02:19 +01:00
Bogdan-Ștefan Neacșu 11b2a7ad3d Rename contract_address to mixnet_contract_address 2021-12-03 15:02:19 +01:00
Drazen Urch 18978c7599 Merge branch 'develop' of https://github.com/nymtech/nym into develop 2021-12-03 10:14:59 +01:00
Drazen Urch 8e52a70685 Update vesting image 2021-12-03 10:14:47 +01:00
Drazen Urch 1f42ce57e3 Allow proxy gateway bonding (#936)
* Allow proxy gateway bonding

* Update msg text

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-02 18:16:26 +01:00
Dave Hrycyszyn 231fba34bc Spelling fix in variable name 2021-12-02 13:15:55 +00:00
Dave Hrycyszyn 0e05fc46c9 Merge remote-tracking branch 'origin/develop' into develop 2021-12-02 13:13:24 +00:00
Dave Hrycyszyn 52777efc53 Tiny spellcheck fix in comment 2021-12-02 13:13:03 +00:00
Jędrzej Stuczyński 54bc198885 Feature/mixnet contract further adjustments (#928)
* Upgraded code to be cosmwasm 1.0-beta.2 compatible (#923)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible

* [ci skip] Generate TS types

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>

* Feature/cosmwasm plus storage (#924)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible

* Added cw-storage-plus dependency

* Experimentally replaced storage for config and layers with cw plus Item

* The same for main mixnode storage

* Usingn IndexedMap for mixnodes

* Split delegations from mixnodes into separate module

* MixnodeIndex on Addr directly

* Moved namespace values to constants

* Outdated comment

* [ci skip] Generate TS types

* Removed redundant identity index on mixnodes

* IndexMap for gateways storage

* Moved total delegation into a Map

* Compiling contract code after delegation storage upgrades

Tests dont compile yet and neither, I would assume, the client code

* Delegation type cleanup

* Client fixes

* Migrated delegation tests + fixed them

* Moved Rewarding Status to rewards

* Reward pool

* Rewarding status migrated

* Made clippy happier

* Added explorer API to default workspace members

* Updated delegation types in explorer-api

* Fixed tauri wallet

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>

* Missing license notices

* Dead code removal

* Changed RewardMixnodeV2 to RewardMixnode

* Adjusted module visibility

* Setting rewarding validator address in init msg

* ContractSettings => ContractState

* Transaction-related cleanup

* Changed ownership queries to return full bond information instead of just a bool

* Function for updating post rewarding storage

* Changed the order of arguments in decrementing reward pool

* Helpers for updating storage after rewarding

* Removed redundant turbofish

* [ci skip] Generate TS types

* Changed bond/delegation validation

* Made clippy happier

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-12-02 12:16:16 +00:00
Tommy Verrall 577c5dc1ee Merge pull request #933 from nymtech/test/webpack-wallet
Webpack wallet prod configuration
2021-12-02 10:16:51 +00:00
Dave Hrycyszyn 7927846390 Merge branch 'develop' of github.com:nymtech/nym into develop 2021-12-02 10:12:09 +00:00
Dave Hrycyszyn e41f02ad0d Tiny readme fixup 2021-12-02 10:12:01 +00:00
tommyv1987 e57a5b73a2 [ci skip] Generate TS types 2021-12-02 09:54:58 +00:00
Tommy Verrall 608de11377 Merge pull request #932 from nymtech/feature/add-tx-hash-to-wallet-result
Adding tx_hash to wallet response
2021-12-02 09:44:54 +00:00
futurechimp b88153a2bd [ci skip] Generate TS types 2021-12-01 18:30:05 +00:00
Tommy Verrall 0fd2f946dc dev 2021-12-01 18:23:46 +00:00
Dave Hrycyszyn c5cdbd5bfe Params formatting 2021-12-01 18:23:11 +00:00
Tommy Verrall 3c05cf29e6 fix duplication 2021-12-01 18:22:07 +00:00
futurechimp 6cb58d1f1c Adding tx_hash to wallet response 2021-12-01 18:19:14 +00:00
Tommy Verrall 5727ac5161 minor changes, due to windows complaining on builds. 2021-12-01 18:00:47 +00:00
Tommy Verrall 804f254e16 Compiler complains...
The compiler complains, however, these references are needed to enabled menu actions on mac builds.
2021-12-01 17:30:54 +00:00
Tommy Verrall f03ce6e07b fix typo 2021-12-01 17:11:23 +00:00
Tommy Verrall 2631ed4d0c Add prod config for the wallet
When supplying `production` as the mode in the config for webpack, it complains about an unmet dependency issue with fav-icons. Then trying to supply a favicon.ico, returns a mimetype error. By using a png in alignment to the .ico. It builds. Tested on Linux.
2021-12-01 17:06:47 +00:00
Tommy Verrall 0f93dde8fc Merge pull request #925 from nymtech/feature/ui-enhancements
Feature/UI enhancements for Desktop Wallet
2021-12-01 14:47:21 +00:00
Drazen Urch eb93b428cf Release/1.0.0 pre1 (#931)
* Upgraded code to be cosmwasm 1.0-beta.2 compatible (#923)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible

* [ci skip] Generate TS types

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>

* Feature/cosmwasm plus storage (#924)

* Upgraded code to be cosmwasm 1.0-beta.2 compatible

* Added cw-storage-plus dependency

* Experimentally replaced storage for config and layers with cw plus Item

* The same for main mixnode storage

* Usingn IndexedMap for mixnodes

* Split delegations from mixnodes into separate module

* MixnodeIndex on Addr directly

* Moved namespace values to constants

* Outdated comment

* [ci skip] Generate TS types

* Removed redundant identity index on mixnodes

* IndexMap for gateways storage

* Moved total delegation into a Map

* Compiling contract code after delegation storage upgrades

Tests dont compile yet and neither, I would assume, the client code

* Delegation type cleanup

* Client fixes

* Migrated delegation tests + fixed them

* Moved Rewarding Status to rewards

* Reward pool

* Rewarding status migrated

* Made clippy happier

* Added explorer API to default workspace members

* Updated delegation types in explorer-api

* Fixed tauri wallet

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>

* Vesting contract (#900)

* Initial interface spec

* .gitignore

* Finalize implementation

* Correct assumptions, use wasm_execute

* Cleanup

* Track delegation balance

* Add delegation flow img

* Proper messaging from the vesting side

* Add proxy_address to RawDelegationData

* Wrap up (un)delegation

* Add proxy: Addr to MixNodeBond

* Stub in bonding/unbonding

* Migrate vesting to cosmwasm 1.0

* Rebase on top of 1.0.0-pre1

* Reimplement delegations tracking with a Map

* Migrate to cw-storage-plus

* Restructure code, add tests

* Streamline contract code, as per review

* Address review comments

* Pre-merge rebase

* Few more nits

* Few more nits

* Fix test

* cargo fmt

* Fix beta CI

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

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-01 15:42:34 +01:00
fmtabbara 04b6f83d99 pr updates 2021-12-01 12:35:41 +00:00
fmtabbara f5612cc64b update large button height 2021-11-30 13:39:51 +00:00
fmtabbara d525563a46 link to block explorer 2021-11-30 13:11:51 +00:00
fmtabbara 560b306d32 update admin nav item style 2021-11-30 10:08:29 +00:00
fmtabbara a4a4da1121 style updates 2021-11-29 22:31:11 +00:00
fmtabbara d69cb47c6c style updates 2021-11-29 22:11:28 +00:00
Aid19801 3e93b4ffd5 Merge pull request #926 from nymtech/copy-change-nodemap
typo copy change for nodemap
2021-11-29 13:15:22 +00:00
Aid Thompson 6da660623b typo copy change for nodemap 2021-11-29 13:09:13 +00:00
fmtabbara a85a67199a theme tweaks 2021-11-29 09:52:00 +00:00
fmtabbara 09b6d37ca6 [ci skip] Generate TS types 2021-11-27 22:59:48 +00:00
fmtabbara 4029b1b830 resolve conflict 2021-11-27 22:50:13 +00:00
fmtabbara e075759461 add tooltip hide delay 2021-11-27 22:36:16 +00:00
fmtabbara 1069032cd7 remove unecessary fullscreen property 2021-11-27 22:29:17 +00:00
fmtabbara b97b7410da update balance page 2021-11-27 22:15:57 +00:00
fmtabbara f259012faf update copy component 2021-11-27 22:15:42 +00:00
fmtabbara c00cdafc8c update getBalance hook name 2021-11-27 22:15:13 +00:00
fmtabbara 82c56351de add appbar component 2021-11-27 22:14:04 +00:00
Dave Hrycyszyn 5eeb55aa78 Fixing some clippy warnings (#922)
Co-authored-by: dave <dave@nym-mbp.lan>
2021-11-26 12:51:41 +00:00
Dave Hrycyszyn 94357c6132 Fixing go warning re unused btc lib (#921)
* Fixing go warning re unused btc lib

* Removed commented code

Co-authored-by: dave <dave@nym-mbp.lan>
2021-11-26 12:51:01 +00:00
Mark Sinclair 77c5296c39 testnet-faucet: fix line break in destructuring 2021-11-26 11:57:37 +00:00
Aid19801 6ee5b68b46 Merge pull request #920 from nymtech/nodemap-responsiveness
quick fix adding dimensions to nodemap page for consistency
2021-11-26 10:47:40 +00:00
Aid Thompson 56fa48215b changed country names from official to their aliases to fit in cells 2021-11-26 10:15:59 +00:00
Aid Thompson 26b0d9555f quick fix adding dimensions to nodemap page for consistency 2021-11-26 07:41:58 +00:00
fmtabbara 61a67ac334 add new sign in and create account pages 2021-11-25 23:22:14 +00:00
fmtabbara c2e268a1c9 update app layout to use mui v5 system 2021-11-25 23:21:19 +00:00
fmtabbara cfd0b7868a refine copytoclipboard component 2021-11-25 17:41:35 +00:00
fmtabbara 69b52ae629 new styling + new signin and create account pages 2021-11-25 17:41:15 +00:00
Jędrzej Stuczyński c361de62a5 Introduces query for contract build information (#919)
* Introduces query for contract build information

* Removed temporary test garbage

* Removed unused imports in validator-client if built without nymd-client feature
2021-11-25 15:17:56 +00:00
fmtabbara 7d3d0d8874 upgrade to mui v5 2021-11-25 14:35:26 +00:00
Jędrzej Stuczyński 39c7b131b6 Bugfix/remove mixnode bonding overwrite (#917)
* Disallow double bonding

* Adjusted test assertions
2021-11-25 10:27:31 +00:00
dependabot[bot] 4071a20bb5 Bump nth-check from 2.0.0 to 2.0.1 in /nym-wallet (#918)
Bumps [nth-check](https://github.com/fb55/nth-check) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/fb55/nth-check/releases)
- [Commits](https://github.com/fb55/nth-check/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: nth-check
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-25 10:16:14 +00:00
Tommy Verrall b4519cd77e Merge pull request #911 from nymtech/feature/faucet-page-react
Feature/faucet page react
2021-11-25 09:59:54 +00:00
Dave Hrycyszyn 35748e07c4 Feature/mixnet contract refactor (#910)
* Starting a refactor to cut the huge files into chunks

* Fixing some lints

* ibid

* Mixnode and gateway bonding tests moved

* All transaction test moved into submodules

* Finished splitting out transactions.rs from root into submodules.

* Moved mixnet params state into submodule

* Recombined modules for few top-level actions

* Moving mixnode bonding queries into their own file

* Removed some unused imports

* Got tests running again. Max limit tests not right.

* Fixed tests

* Started moving delegation queries into own module

* Finished moving delegation queries into their own module

* Cleanup

* Moving query limits into relevant modules

* Putting query limits back at top-level

* Using prefix to make storage usage a little more explicit

* Separating storage into smaller chunks

* More storage refactoring

* Finished moving all storage into modules

* Moved all storage prefixes into relevant modules and made them not-public

* Renamed the mostly-empty queries module to query_support

* ibid

* Fixed query support rename problems

* Started to move rewards-related helpers into their own module

* Started moving delegations-related helpers into their own module

* Moved more code from global helpers into delegations helpers

* Moved all remaining test helper code from main helpers file into test helpers

* Made use of test_helpers explicit via a module rename.

Also got rid of non-explicit usages

* Moved mixnode storage retrieval limits into mixnodes storage module

* Moved bond retrieval max limit into storage moduel

* Moved more storage limits into mixnodes storage file; fixed a gateways limit test.

* Added a note on gateways limits constants

I'll re-use the mixnodes values, but it doesn't have to be this way.

We could easily make a specific constant for gateways instaed.

* Renamed "state" to GlobalContractParams

* Pulled bit of test helper code up a level

* Small cleanup of zero spacing in constants

* Made a local helper method private

* Renaming GlobalContractParams to ContractSettings and StateParams to ContractSettingsParams

* ibid

* Renamed contract settings storage methods from "config" (which is a bit vage to "contract_settings"

* Indulging a fullword as a personal protest vs the Go programming language

* Renaming mixnet settings to mixnet contract settings

* Making validate_mixnode_bond private and moving it downwards in the file

* Moving gateway bond validation to the bottom of the tests file

* Getting the wallet compiling again.

* Updated TypeScript client with new types and contract method names

* Updating rust validator client with new contract method names and types

* Fixed type error in mixnet-contract shared msg.

* Used new contract method names and types

* Fixed warnings in non-test code

* All tests compiling

But not passing yet

* Fixed test compilation warnings

* Fixed tests

* Test-locked Delegations struct

Co-authored-by: dave <dave@nym-mbp.lan>
Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2021-11-24 22:14:38 +00:00
Aid19801 c8f2548090 Merge pull request #915 from nymtech/fix-mob-datagrids
Fix Mobile View for MUI data-grid (CARD 108)
2021-11-24 13:28:36 +00:00
Aid Thompson 62f20a905e Alignment on Overview page 2021-11-24 10:45:54 +00:00
Jędrzej Stuczyński a3dd94507a Feature/total delegation bucket (#913)
* Preliminary attempt at creating StoredMixnodeBond

* Fixed tests

* Removed dead code

* Moved StoredMixnodeBond to storage.rs

* Removed redundant clone
2021-11-23 16:36:48 +00:00
Aid Thompson dd36b329d5 renamed back to universal-datagrid 2021-11-23 16:03:39 +00:00
Aid Thompson 581af4a295 gateways page performant 2021-11-23 15:49:05 +00:00
Jędrzej Stuczyński 085761a9fb Feature/batch delegator rewarding (#898)
* Unnrolled the loop into separate function

* Ugly way of saving rewarding status

* Initial way of rewarding next page of delegators

* Attribute passing

* Promoted transactions to directory

* Moved rewarding-related functionalities into separate file

* [ci skip] Generate TS types

* Better errors on double rewarding attempt

* Removed old rewarding call

* Test fixes

* Some cleanup

* Paged mixnode rewarding test + serde fixes

* Tests for delegator rewarding

* ExecuteMsg for MixDelegatorRewarding

* Made validator-api code compliable

with bunch of todo!() macros

* Removed Option wrapper from params in MixnodeToReward

* Calculating uptime for entire epoch

* Created shared MIXNODE_DELEGATORS_PAGE_LIMIT constant

* Using new rewarding messages in validator API

* cargo fmt

* Updated wallet state types

* Additional test for correct rewarding information

* Query for rewarding status

* Additional test regarding delegator rewarding

* Client methods for obtaining rewarding status

* Validator API checking for full rewarding

* Removed unused field from validator api config template

* Waiting for MINIMUM number of test routes

* Waiting initialisation_backoff in the early return case

* Fixes crash condition in validator API when calculating last day uptime

* Fixed typo

* Dealing with the case of rewarding mixnode with 0 uptime

* Removed temporary unwrap

* Guarding against 0-size rewarded/active sets

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-11-23 15:36:20 +00:00
Aid Thompson 58537224df mixnode datagrid performant 2021-11-23 15:20:23 +00:00
fmtabbara 54e217ccea update logic 2021-11-23 15:01:05 +00:00
Jędrzej Stuczyński 78a5dbbf05 Fixes crash condition in validator API when calculating last day uptime (#909) 2021-11-23 14:47:49 +00:00
fmtabbara c8f81d118b fix bug with successful response ui 2021-11-23 12:16:07 +00:00
fmtabbara a193e948e6 link address to block explorer + refresh balance after tokens sent 2021-11-23 11:34:05 +00:00
AniaPiotrowska ab7f24dc1f Merge pull request #837 from nymtech/feature/vouchers
Feature/vouchers
2021-11-22 19:34:58 +00:00
fmtabbara cc77f6e392 add favicon 2021-11-22 14:48:37 +00:00
Tommy Verrall 58f02109be Add data-testids 2021-11-22 11:00:20 +00:00
Fouad 724e0f78c6 Update README.md 2021-11-20 21:27:07 +00:00
fmtabbara d764b11122 responsive tweaks 2021-11-20 21:24:30 +00:00
fmtabbara 4953b40a42 add readme file 2021-11-20 21:12:06 +00:00
fmtabbara 66e5afe485 add form validation and error handling 2021-11-20 21:04:44 +00:00
fmtabbara 9b17a1ca77 add responses 2021-11-19 22:46:32 +00:00
fmtabbara 5ce6147839 set up validator connection 2021-11-19 18:49:52 +00:00
fmtabbara dd3643a1bb set up form ui 2021-11-19 17:41:21 +00:00
fmtabbara 52fedd9866 create base project 2021-11-19 16:27:45 +00:00
Jędrzej Stuczyński 289605f36b Bugfix/monitor initial values wait (#907)
* Waiting for MINIMUM number of test routes

* Waiting initialisation_backoff in the early return case
2021-11-18 17:16:15 +00:00
Tommy Verrall 4f6bf3423b Merge pull request #905 from nymtech/update/readme
Update README.md
2021-11-18 09:59:55 +00:00
Tommy Verrall 04a32156d4 Merge pull request #891 from nymtech/update/remove-gateway-delegations
remove delegation and undelegation from gateways
2021-11-18 09:42:34 +00:00
Tommy Verrall 427880d23f Merge pull request #896 from nymtech/bug-fix/macos-keyboard-shortcuts
Bug fix/macos keyboard shortcuts
2021-11-18 09:22:33 +00:00
Tommy Verrall a5e47dead8 Update readme
. If `Webview2` is not installed on Windows, that app will not launch correctly. There's two ways to update if you're running on an older version of windows. 
- Update the Edge browser in your current OS
- Update via the installer now provided in the README documentation.
2021-11-18 09:18:04 +00:00
Tommy Verrall 297b7b2355 Merge pull request #904 from nymtech/bug-bond-denom
BUG: Bond cell denom
2021-11-17 12:30:45 +00:00
Tommy Verrall d7f3abfe7d Merge pull request #903 from nymtech/test/fix-detail-tables
Explorer UI tests missing data-testid
2021-11-17 12:29:34 +00:00
Aid Thompson 234952a6bb removed typo 2021-11-17 11:50:03 +00:00
Tommy Verrall 930561faf5 Explorer UI tests
. This `data-testid` was missing and is needed after changes to DataGrid into MUI-Table
2021-11-17 11:20:43 +00:00
Tommy Verrall 17507bc8c5 Merge pull request #899 from nymtech/test/wallet-readme-fix
Fix up Nym-Wallet README.md
2021-11-16 14:17:15 +00:00
Tommy Verrall 12f8e453ea Fix typo. 2021-11-16 10:20:50 +00:00
Tommy Verrall 97ef024b8a Fix up Nym-Wallet README.md
. Provide a descriptive overview
. Remove old references
2021-11-16 10:17:30 +00:00
Aid19801 3dc94223ae Bug mapp nodemap (#897)
* desktop onMouseEnter and leave handlers added

* clicking nav option closes drawer

* All AC met

* removed dead bool

* added hamburger to AppBar for mobile users small refactor

* nuts and bolts working needs a lot of tidying though

* Functionally complete.

* change hardcode colors to theming

* UI theme and border changes

* bug fix removed collapse nav flicker

* dead code

* resetting drawer to closed onload

* removal of hardcoded color

* made null functions into optional props

* Nested sx styling not global

* paperprops now working

* fixed breaking changes for DataGrid

* linting fix

* linting fix

* optional chaining for nodemap search

* leaner filter func

Co-authored-by: Aid Thompson <adrian@nymtech.net>
2021-11-12 18:22:03 +00:00
Aid19801 50cd18a926 Add a Mobile Nav to the Network Explorer (#895)
* desktop onMouseEnter and leave handlers added

* clicking nav option closes drawer

* All AC met

* removed dead bool

* added hamburger to AppBar for mobile users small refactor

* nuts and bolts working needs a lot of tidying though

* Functionally complete.

* change hardcode colors to theming

* UI theme and border changes

* bug fix removed collapse nav flicker

* dead code

* resetting drawer to closed onload

* removal of hardcoded color

* made null functions into optional props

* Nested sx styling not global

* paperprops now working

* fixed breaking changes for DataGrid

* linting fix

* linting fix

Co-authored-by: Aid Thompson <adrian@nymtech.net>
2021-11-12 16:14:37 +00:00
Tommy e5051f98c5 Adding a few more menu options 2021-11-12 11:23:36 +00:00
fmtabbara 2f69958e21 macos keyboard shortcut fix 2021-11-12 10:29:49 +00:00
fmtabbara d427591a66 dont commite dist folder contents 2021-11-12 10:29:33 +00:00
fmtabbara 0257c42ee9 update tauri bundle identifier 2021-11-12 10:29:04 +00:00
fmtabbara 8ebfc72da8 remove contents of dist folder 2021-11-12 10:28:39 +00:00
Aid19801 897d51cba0 Change MixnodeDetail page's datagrid into a reuseable table component (#887)
* basic table working

* broken out into sep component

* table is more dynamic now

* linting fixes

* Added types for Columns

* hangover from eslint work moving to diff PR

* Changed To DetailTable

* Killed unused flex val in Columns

* merge conflicts resolved

Co-authored-by: Aid Thompson <adrian@nymtech.net>
2021-11-12 10:28:12 +00:00
Tommy Verrall 1f1d91bd1e Adding data-test-ids for the explorer (#885)
* Adding all the data-test-ids

Test ID's for doing automation on explorer

* Applied lint-fix

Linting

* More data ids
2021-11-11 15:31:30 +00:00
Mark Sinclair 9d63a30b07 Merge pull request #890 from nymtech/feature/nym-wallet-rename
Feature/nym wallet rename
2021-11-11 15:00:55 +00:00
Tommy 5b22b1c298 getting branch up to date with tauri changes and webriver changes 2021-11-11 11:02:39 +00:00
Dave Hrycyszyn 0302d9ae5d Merge branch 'feature/nym-wallet-rename' of github.com:nymtech/nym into feature/nym-wallet-rename 2021-11-11 10:50:12 +00:00
Bogdan-Ștefan Neacşu eb6949528a Fix network monitor template (#892) 2021-11-11 10:36:43 +00:00
Mark Sinclair 41b77c3ad5 Merge pull request #894 from nymtech/feature-gate-ts-rs
Only use ts-rs in tests
2021-11-11 10:34:04 +00:00
Mark Sinclair 2f78f5eb26 Merge pull request #884 from nymtech/test/fix-gh-action
Fix path for github action running tauri-wallet-tests
2021-11-11 10:33:02 +00:00
Dave Hrycyszyn cdbd731f10 Changed all workflow references to nym-wallet 2021-11-11 10:25:51 +01:00
Dave Hrycyszyn 5cb4949d46 Renaming tauri-wallet to nym-wallet
Hopefully this will make it a bit clearer as to what people should
compile if they want the wallet.
2021-11-11 10:25:51 +01:00
Mark Sinclair 9ec0b30812 GitHub Actions: move explorer actions to k8s hosted runners 2021-11-10 16:08:15 +00:00
Jędrzej Stuczyński 7e1cf2f105 Feature/rewarding interval updates (#880)
* Introduced rewarding_interval_nonce to contract state

* Queries for ibid.

* Mixnode demanded set size

* Routes for obtaining demanded/active mixnode sets

* Testing only demanded nodes

* Typo

* Initial state

* Feature-locking unused imports

* Generating pseudorandom (with deterministic seed) demanded mixnodes set

* cargo fmt

* Fixed tauri state

* Renamed network monitor address to the rewarding validator

* [ci skip] Generate TS types

* Notice for the future

* Transactions to begin/finish mixnode rewarding + double rewarding protection

* Validator API using new contract calls

* Removed dead code from an old experiment

* [ci skip] Generate TS types

* Removed unused import

* Renamed 'demanded' set to 'rewarded' set

* Some renaming action

* [ci skip] Generate TS types

* Fixed post-merge dependency issue in tests

* Post merge test fix

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-11-10 15:28:16 +00:00
Mark Sinclair 04636a569a GitHub Actions: fix up reference to action 2021-11-10 15:24:47 +00:00
Mark Sinclair 52f5b980a4 GitHub Actions: use dynamic matrix for os types and rust versions 2021-11-10 15:15:31 +00:00
Mark Sinclair 0e4f833715 GitHub Actions: fix up 2021-11-10 14:44:52 +00:00
Mark Sinclair d22914c4dc GitHub Actions: fix up 2021-11-10 14:43:32 +00:00
Mark Sinclair 7d506e6e2a Slim down GitHub Actions 2021-11-10 14:34:02 +00:00
Drazen Urch c7007de1ea Only use ts-rs in tests 2021-11-10 15:02:59 +01:00
Bogdan-Ștefan Neacşu 10bf70b22b Feature/bandwidth token (#832)
* Remove check for bandwidth for incoming packets

We should only accunt for packets that the client inputs to the mixnet

* Introduce BandwidthController for both types of bandwidth creds

* Add some non-coconut token bandwidth handling

* Use thiserror for gateway-client lib

* Add error handling

* Unable to build for wasm for now

* Fix wasm strange error

* Disable non-coconut credentials for wasm client

* Check for status and throw the error up

* Send encrypted token cred from client

* Gateway receive message and signature validation

* Put the correct amount of tokens that were burned

* [ci skip] Generate TS types

* Eth endpoint and secret key as config parameters

* Add eth_endpoint config argument for gateway

* Update test as well

* Separate panicable code from the safe one

* Move some bandwidth controller panics up the call stack

* Save contract corresponding to the eth endpoint

* Fix template

* Pass the web3 interface as well

* Made event reads possible in gateway

* Add checks for event data

* Cosmos contract for double spending prevention

* Add workflow for the new contract

* Add validator rest URL to config

* Rename eth_events to erc20_bridge

* Pass cosmos mnemonic as well, and put the nymd client in ERC20Bridge

* Call cosmos contract for final verification

* Ask for config parameters in cli

* Fix various stuff

* Increase timeout to allow gateway to check the two chains

* Put some logs for the new flow

* Set consumed bandwidth invariantly of coconut feature

* Fix clippy error

* Add non-coconut checks

* Use 2018 rust instead of 2021

* More verbose nymd error

* Explicitly specify TOKENS_TO_BURN constant

* Put eth burn function in a constant

* Replace to_vec & append with iter & chain

* Test for (de)serialization of TokenCredential

* Minor rename

* Separate credential creation from bandwidth claiming

* Switch from panics to errors when claiming coconut bandwidth

* Another append changed to chain

* Update QA cosmos contract address

* Simplify build/test/clippy separation on coconut feature

* Fix bad features arg positioning

* Use the start_after in cosmos contract query

* Set a limit in line with a range on cosmos queries

* Added unit tests for new cosmos contract

* Fix bandwidth_remaining comparation

* Get remaining bandwidth from gateway

* Add contract build flag

* Add a useful info log

* Use a more robust eth depth for release builds

* Include recipt logs in error message

* Fix clippy for tests

* Use Arc instead of clone

* Rename as_bytes to to_bytes

* Make signature verification in contract more verbose

* Missed rename of paging constant

* Fix gateway start with coconut enabled

* Rename function to claim_token

* Simplify nymd client setup

* Check with block buffer on gateway as well

* Update comment of double spending protection

* Correct contract address

* Backup the keypairs used for buying tokens, in case of error cases

* Don't take any chances with the gateway timeout

* [ci skip] Generate TS types

* Updated cosmos contract to latest QA address

* Add cli options for eth

* Update network monitor timeout value as well

Co-authored-by: neacsu <neacsu@users.noreply.github.com>
2021-11-10 14:53:56 +02:00
Jędrzej Stuczyński d952f3233a Active sets => Rewarded + Active/Idle sets (#864)
* Introduced rewarding_interval_nonce to contract state

* Queries for ibid.

* Mixnode demanded set size

* Routes for obtaining demanded/active mixnode sets

* Testing only demanded nodes

* Typo

* Initial state

* Feature-locking unused imports

* Generating pseudorandom (with deterministic seed) demanded mixnodes set

* cargo fmt

* Fixed tauri state

* [ci skip] Generate TS types

* Renamed 'demanded' set to 'rewarded' set

* Some renaming action

* [ci skip] Generate TS types

* Fixed post-merge dependency issue in tests

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2021-11-10 12:52:13 +00:00
fmtabbara b1178b7ad5 remove delegation and undelegation from gateways 2021-11-10 10:42:45 +00:00
Jędrzej Stuczyński af4ac1b7c9 Reverted gateway registration handshake to its 0.11.0 version (#882) 2021-11-10 11:16:39 +01:00
Drazen Urch eae4276381 Tokenomics rewards (#802)
* Initial analysis

* Implement core rewards distribution

* Tests and refactoring for better testability

* Feature gate ts-rs in mixnet-contract

* No more floats

* Fix performance calculation

* Add migration

* Bandwidth fix, reduce inflation pool size

* Update tokenomics

* Refactor rewarding and replace num crate

* Address review comments

* Cleanup, better test values

* Simplify formula

* Cleanup, add rewarding formulas to README

* Address review comments

* Cleanup rebase

* [ci skip] Generate TS types

* fmt

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-11-10 11:14:06 +01:00
Dave Hrycyszyn d8fab74df6 Changed all workflow references to nym-wallet 2021-11-09 17:04:07 +00:00
Dave Hrycyszyn b8c184cc60 Renaming tauri-wallet to nym-wallet
Hopefully this will make it a bit clearer as to what people should
compile if they want the wallet.
2021-11-09 16:55:49 +00:00
Tommy 4ed9d3a297 Updated rust version for installation 2021-11-08 14:55:49 +00:00
Tommy 97dee2e1a0 Fix path for github action running tauri-wallet-tests
. Applied prettier to the code base
. Removed delegation to gateway tests
2021-11-08 14:54:01 +00:00
552 changed files with 46126 additions and 21356 deletions
+1 -1
View File
@@ -36,5 +36,5 @@ Cargo.* @durch @futurechimp @jstuczyn @neacsu
# Explorer and wallet should probably get looked by the product team
/explorer/ @nymtech/product
/tauri-wallet/ @nymtech/product
/nym-wallet/ @nymtech/product
/wallet-web/ @nymtech/product
+102
View File
@@ -0,0 +1,102 @@
name: Continuous integration
on:
push:
paths-ignore:
- 'explorer/**'
pull_request:
paths-ignore:
- 'explorer/**'
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' }}
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
- name: Install rust toolchain
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
with:
command: build
args: --all
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all
- name: Check formatting
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'
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
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
@@ -0,0 +1,50 @@
[
{
"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"
}
]
@@ -0,0 +1,14 @@
[
{
"rust":"stable",
"runOnEvent":"always"
},
{
"rust":"beta",
"runOnEvent":"pull_request"
},
{
"rust":"nightly",
"runOnEvent":"pull_request"
}
]
+64
View File
@@ -0,0 +1,64 @@
name: Contracts
on:
push:
paths-ignore:
- 'explorer/**'
pull_request:
paths-ignore:
- 'explorer/**'
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`]'
contracts:
# 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)}}
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/Cargo.toml --all --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/Cargo.toml --all -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
@@ -0,0 +1,23 @@
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
+59
View File
@@ -0,0 +1,59 @@
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
- name: Set environment from the example
run: cp .env.prod .env
- 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
+12
View File
@@ -0,0 +1,12 @@
name: Publish Nym Wallet
on:
push:
tags:
- nym-wallet-*
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Run a one-line script
run: echo Hello, world!
+29
View File
@@ -0,0 +1,29 @@
name: Generate TS types
on:
push:
paths-ignore:
- "explorer/**"
pull_request:
paths-ignore:
- "explorer/**"
jobs:
nym-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
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Generate TS
run: cd nym-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"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+77
View File
@@ -0,0 +1,77 @@
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
-136
View File
@@ -1,136 +0,0 @@
name: Publish Nym Wallet
on:
push:
tags:
- tauri-wallet-*
branches:
- feature/gh-action-build-tauri-wallet
jobs:
build:
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: tauri-wallet
strategy:
matrix:
os: [ubuntu-20.04, macos-10.15, macos-11, windows-latest]
outputs:
PACKAGE_VERSION: ${{ steps.package-node-version.outputs.version }}
steps:
- uses: actions/checkout@v2
- name: Tauri dependencies
if: ${{ startsWith(matrix.os, 'ubuntu') }}
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: Read node from package.json
uses: culshaw/read-package-node-version-actions@v1
with:
path: "${{github.workspace}}/tauri-wallet"
id: package-node-version
- name: Install yarn for building application
run: yarn install
- name: Build application (bundle)
run: yarn run webpack:build
- name: Build application (tauri)
run: yarn run tauri:build
# - name: Fake build
# run: |
# mkdir release-assets
# cp package.json release-assets/${{ matrix.os }}-${{ steps.package-node-version.outputs.version }}.zip
- name: Compress (MacOS / Linux)
if: ${{ matrix.os != 'windows-latest' }}
working-directory: tauri-wallet
env:
TARGET_FILE: ${{ matrix.os }}_v${{ steps.package-node-version.outputs.version }}.tar.gz
run: |
mkdir release-assets
pushd target/release/bundle
tar -cvzf ../../../release-assets/$TARGET_FILE .
- name: Compress (Windows)
if: ${{ matrix.os == 'windows-latest' }}
uses: thedoctor0/zip-release@master
with:
type: 'zip'
filename: '${{github.workspace}}/tauri-wallet/release-assets/${{ runner.os }}_v${{ steps.package-node-version.outputs.version }}.zip'
path: "${{github.workspace}}/tauri-wallet/target/release/bundle"
- name: Upload release assets
uses: actions/upload-artifact@v2
with:
name: release-assets-${{ matrix.os }}
path: "${{github.workspace}}/tauri-wallet/release-assets/*"
release:
needs: build
runs-on: ubuntu-latest
env:
PACKAGE_VERSION: ${{ needs.build.outputs.PACKAGE_VERSION }}
steps:
- uses: actions/checkout@v2
- name: Download assets
uses: actions/download-artifact@v2
with:
path: download-assets
- name: Display downloaded assets
run: ls -R download-assets
- name: Re-arrange downloaded assets
run: |
mkdir release-assets
find download-assets -mindepth 2 -type f -exec mv -t ${{github.workspace}}/release-assets -i '{}' +
rm -rf download-assets
- name: Hash files
id: file_hashes
# Put the multi-line string in an env var and create a new action env var by wrapping the multiline value
run: |
cd release-assets
ASSET_HASHES=$(find . -type f -exec sha256sum {} \;)
echo "ASSET_HASHES<<EOF" >> $GITHUB_ENV
echo "$ASSET_HASHES" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Display file hashes
run: echo $ASSET_HASHES
- uses: ncipollo/release-action@v1
with:
# The pipe syntax needs to stay to escape the wildcard correctly
artifacts: |
release-assets/*
token: ${{ secrets.GITHUB_TOKEN }}
tag: "nym-wallet-v${{env.PACKAGE_VERSION}}"
commit: feature/gh-action-build-tauri-wallet
draft: true
name: "Nym Wallet v${{ env.PACKAGE_VERSION }}"
body: |
This is a build of the Nym Wallet from commit `${{ github.sha }}` [(browse commit)](https://github.com/nymtech/nym/tree/${{ github.sha }}).
Installers for each operating system have the following SHA256 hashes:
```
${{ env.ASSET_HASHES }}
```
+48
View File
@@ -0,0 +1,48 @@
name: Wasm Client
on:
pull_request:
paths-ignore:
- 'explorer/**'
jobs:
wasm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: wasm32-unknown-unknown
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
# 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
# with:
# command: test
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path clients/webassembly/Cargo.toml -- --check
# 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
# with:
# command: clippy
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
+7
View File
@@ -24,8 +24,15 @@ v6-topology.json
/explorer/downloads/topology.json
/explorer/public/downloads/mixmining.json
/explorer/public/downloads/topology.json
/nym-wallet/dist/*
/clients/validator/examples/nym-driver-example/current-contract.txt
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
validator-api-config.toml
Generated
+675 -41
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -25,10 +25,12 @@ members = [
"common/config",
"common/credentials",
"common/crypto",
"common/erc20-bridge-contract",
"common/mixnet-contract",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
@@ -60,6 +62,7 @@ default-members = [
"service-providers/network-requester",
"mixnode",
"validator-api",
"explorer-api",
]
exclude = ["explorer", "contracts"]
exclude = ["explorer", "contracts", "tokenomics-py"]
+31
View File
@@ -0,0 +1,31 @@
all: clippy test fmt
clippy: clippy-main clippy-contracts clippy-wallet
test: test-main test-contracts test-wallet
fmt: fmt-main fmt-contracts fmt-wallet
clippy-main:
cargo clippy
clippy-contracts:
cargo clippy --manifest-path contracts/Cargo.toml
clippy-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml
test-main:
cargo test
test-contracts:
cargo test --manifest-path contracts/Cargo.toml
test-wallet:
cargo test --manifest-path nym-wallet/Cargo.toml
fmt-main:
cargo fmt --all
fmt-contracts:
cargo fmt --manifest-path contracts/Cargo.toml --all
fmt-wallet:
cargo fmt --manifest-path nym-wallet/Cargo.toml --all
+40 -3
View File
@@ -5,8 +5,6 @@ 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.
@@ -15,7 +13,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
* nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
* nym-explorer - a (projected) block explorer and (existing) mixnet viewer.
* nym-wallet (currently in development)- a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)
[![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
@@ -33,6 +31,45 @@ 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,3 +30,6 @@ validator-client = { path = "../../common/client-libs/validator-client" }
[dev-dependencies]
tempfile = "3.1.0"
[features]
coconut = []
+65 -1
View File
@@ -22,7 +22,10 @@ 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);
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
// 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);
pub fn missing_string_value() -> String {
MISSING_VALUE.to_string()
@@ -100,6 +103,17 @@ 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;
}
@@ -111,6 +125,16 @@ 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;
}
@@ -173,6 +197,21 @@ 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
@@ -268,6 +307,20 @@ 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,
@@ -292,6 +345,12 @@ 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(),
}
@@ -326,6 +385,11 @@ 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)]
+5 -1
View File
@@ -24,7 +24,7 @@ dirs = "3.0" # for determining default store directories in config
dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being)
log = "0.4" # self explanatory
pretty_env_logger = "0.4" # for formatting log messages
rand = {version = "0.7.3", features = ["wasm-bindgen"]} # rng-related traits + some rng implementation to use
rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use
serde = { version = "1.0.104", features = ["derive"] } # for config serialization/deserialization
sled = "0.34" # for storage of replySURB decryption keys
tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal"] } # async runtime
@@ -44,9 +44,13 @@ topology = { path = "../../common/topology" }
websocket-requests = { path = "websocket-requests" }
validator-client = { path = "../../common/client-libs/validator-client" }
version-checker = { path = "../../common/version-checker" }
network-defaults = { path = "../../common/network-defaults" }
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
[dev-dependencies]
serde_json = "1.0" # for the "textsend" example
[build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
fn main() {
vergen(Config::default()).expect("failed to extract build metadata")
}
@@ -3,6 +3,5 @@ module github.com/nymtech/nym/clients/native/examples/go
go 1.14
require (
github.com/btcsuite/btcutil v1.0.2 // indirect
github.com/gorilla/websocket v1.4.2
)
@@ -42,6 +42,17 @@ 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.
+16 -36
View File
@@ -1,8 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use crate::websocket;
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
@@ -24,6 +22,7 @@ use client_core::client::topology_control::{
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
@@ -35,10 +34,8 @@ use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use tokio::runtime::Runtime;
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(feature = "coconut")]
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
use crate::client::config::{Config, SocketType};
use crate::websocket;
pub(crate) mod config;
@@ -168,31 +165,6 @@ impl NymClient {
.start(self.runtime.handle())
}
#[cfg(feature = "coconut")]
async fn prepare_coconut_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,
@@ -212,7 +184,17 @@ impl NymClient {
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let coconut_credential = self.prepare_coconut_credential().await;
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 mut gateway_client = GatewayClient::new(
gateway_address,
@@ -222,13 +204,11 @@ impl NymClient {
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
gateway_client
.authenticate_and_start(
#[cfg(feature = "coconut")]
Some(coconut_credential),
)
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
+62 -7
View File
@@ -1,15 +1,23 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
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;
#[cfg(feature = "coconut")]
use coconut_interface::{hash_to_scalar, Credential, Parameters};
use config::NymConfig;
#[cfg(feature = "coconut")]
use credentials::coconut::bandwidth::{
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
};
#[cfg(feature = "coconut")]
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::{encryption, identity};
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
#[cfg(feature = "coconut")]
use network_defaults::BANDWIDTH_VALUE;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use rand::rngs::OsRng;
@@ -21,8 +29,11 @@ use std::time::Duration;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
App::new("init")
let app = App::new("init")
.about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id")
.long("id")
@@ -36,9 +47,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.takes_value(true)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
)
.arg(Arg::with_name("disable-socket")
.long("disable-socket")
@@ -54,7 +65,51 @@ 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
#[cfg(feature = "coconut")]
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 params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
serial_number: params.random_scalar(),
binding_number: params.random_scalar(),
voucher_value: hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
voucher_info: hash_to_scalar(String::from("BandwidthVoucher").as_bytes()),
};
let bandwidth_credential =
obtain_signature(&params, &bandwidth_credential_attributes, validators)
.await
.expect("could not obtain bandwidth credential");
prepare_for_spending(
raw_identity,
&bandwidth_credential,
&bandwidth_credential_attributes,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
}
async fn register_with_gateway(
+9
View File
@@ -43,5 +43,14 @@ 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
}
+14 -2
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> {
App::new("run")
let app = App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id")
.long("id")
@@ -38,7 +38,19 @@ 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
+35 -3
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{App, ArgMatches};
use clap::{crate_version, App, ArgMatches};
pub mod client;
pub mod commands;
@@ -13,7 +13,8 @@ fn main() {
println!("{}", banner());
let arg_matches = App::new("Nym Client")
.version(env!("CARGO_PKG_VERSION"))
.version(crate_version!())
.long_version(&*long_version())
.author("Nymtech")
.about("Implementation of the Nym Client")
.subcommand(commands::init::command_args())
@@ -50,7 +51,38 @@ fn banner() -> String {
(client - version {:})
"#,
env!("CARGO_PKG_VERSION")
crate_version!()
)
}
fn long_version() -> String {
format!(
r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
"Build Version:",
env!("VERGEN_BUILD_SEMVER"),
"Commit SHA:",
env!("VERGEN_GIT_SHA"),
"Commit Date:",
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
"Commit Branch:",
env!("VERGEN_GIT_BRANCH"),
"rustc Version:",
env!("VERGEN_RUSTC_SEMVER"),
"rustc Channel:",
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
)
}
+3 -3
View File
@@ -1,8 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod handler;
pub(crate) mod listener;
pub(crate) use handler::Handler;
pub(crate) use listener::Listener;
pub(crate) mod handler;
pub(crate) mod listener;
+5 -1
View File
@@ -32,13 +32,17 @@ crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-requests = { path = "../../gateway/gateway-requests" }
nymsphinx = { path = "../../common/nymsphinx" }
ordered-buffer = {path = "../../common/socks5/ordered-buffer"}
ordered-buffer = { path = "../../common/socks5/ordered-buffer" }
socks5-requests = { path = "../../common/socks5/requests" }
topology = { path = "../../common/topology" }
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" }
network-defaults = { path = "../../common/network-defaults" }
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
[build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
fn main() {
vergen(Config::default()).expect("failed to extract build metadata")
}
@@ -42,6 +42,17 @@ 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.
+19 -39
View File
@@ -1,11 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
use client_core::client::cover_traffic_stream::LoopCoverTrafficStream;
use client_core::client::inbound_messages::{
InputMessage, InputMessageReceiver, InputMessageSender,
@@ -25,6 +20,7 @@ use client_core::client::topology_control::{
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::{
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
MixnetMessageSender,
@@ -34,10 +30,11 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use tokio::runtime::Runtime;
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(feature = "coconut")]
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
use crate::client::config::Config;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
pub(crate) mod config;
@@ -156,31 +153,6 @@ impl NymClient {
.start(self.runtime.handle())
}
#[cfg(feature = "coconut")]
async fn prepare_coconut_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,
@@ -200,7 +172,17 @@ impl NymClient {
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let coconut_credential = self.prepare_coconut_credential().await;
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 mut gateway_client = GatewayClient::new(
gateway_address,
@@ -210,13 +192,11 @@ impl NymClient {
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
gateway_client
.authenticate_and_start(
#[cfg(feature = "coconut")]
Some(coconut_credential),
)
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
+62 -7
View File
@@ -1,15 +1,23 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
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;
#[cfg(feature = "coconut")]
use coconut_interface::{hash_to_scalar, Credential, Parameters};
use config::NymConfig;
#[cfg(feature = "coconut")]
use credentials::coconut::bandwidth::{
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
};
#[cfg(feature = "coconut")]
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::{encryption, identity};
use gateway_client::GatewayClient;
use gateway_requests::registration::handshake::SharedKeys;
#[cfg(feature = "coconut")]
use network_defaults::BANDWIDTH_VALUE;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use rand::{prelude::SliceRandom, rngs::OsRng, thread_rng};
@@ -19,8 +27,11 @@ use std::time::Duration;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
App::new("init")
let app = App::new("init")
.about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id")
.long("id")
@@ -40,9 +51,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.takes_value(true)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
)
.arg(Arg::with_name("port")
.short("p")
@@ -54,7 +65,51 @@ 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
#[cfg(feature = "coconut")]
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 params = Parameters::new(TOTAL_ATTRIBUTES).unwrap();
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
serial_number: params.random_scalar(),
binding_number: params.random_scalar(),
voucher_value: hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
voucher_info: hash_to_scalar("BandwidthVoucher"),
};
let bandwidth_credential =
obtain_signature(&params, &bandwidth_credential_attributes, validators)
.await
.expect("could not obtain bandwidth credential");
prepare_for_spending(
raw_identity,
&bandwidth_credential,
&bandwidth_credential_attributes,
&verification_key,
)
.expect("could not prepare out bandwidth credential for spending")
}
async fn register_with_gateway(
+9
View File
@@ -39,5 +39,14 @@ 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
}
+14 -2
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> {
App::new("run")
let app = App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id")
.long("id")
@@ -44,7 +44,19 @@ 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
+34 -2
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{App, ArgMatches};
use clap::{crate_version, App, ArgMatches};
pub mod client;
mod commands;
@@ -15,6 +15,7 @@ fn main() {
let arg_matches = App::new("Nym Socks5 Proxy")
.version(env!("CARGO_PKG_VERSION"))
.author("Nymtech")
.long_version(&*long_version())
.about("A Socks5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
@@ -50,7 +51,38 @@ fn banner() -> String {
(socks5 proxy - version {:})
"#,
env!("CARGO_PKG_VERSION")
crate_version!()
)
}
fn long_version() -> String {
format!(
r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
"Build Version:",
env!("VERGEN_BUILD_SEMVER"),
"Commit SHA:",
env!("VERGEN_GIT_SHA"),
"Commit Date:",
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
"Commit Branch:",
env!("VERGEN_GIT_BRANCH"),
"rustc Version:",
env!("VERGEN_RUSTC_SEMVER"),
"rustc Channel:",
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
)
}
+29 -17
View File
@@ -3,21 +3,24 @@
windows_subsystem = "windows"
)]
use std::sync::Arc;
use tokio::sync::RwLock;
use url::Url;
use coconut_interface::{
self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey,
};
use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key};
use std::sync::Arc;
use tokio::sync::RwLock;
use url::Url;
struct State {
signatures: Vec<Signature>,
n_attributes: u32,
params: Parameters,
public_attributes_bytes: Vec<Vec<u8>>,
public_attributes: Vec<Attribute>,
private_attributes: Vec<Attribute>,
serial_number: Attribute,
binding_number: Attribute,
voucher_value: Attribute,
voucher_info: Attribute,
aggregated_verification_key: Option<VerificationKey>,
}
@@ -37,9 +40,10 @@ impl State {
signatures: Vec::new(),
n_attributes,
params,
public_attributes_bytes,
public_attributes,
private_attributes,
serial_number: private_attributes[0],
binding_number: private_attributes[1],
voucher_value: public_attributes[0],
voucher_info: public_attributes[1],
aggregated_verification_key: None,
}
}
@@ -63,8 +67,8 @@ async fn randomise_credential(
) -> Result<Vec<Signature>, String> {
let mut state = state.write().await;
let signature = state.signatures.remove(idx);
let new = signature.randomise(&state.params);
state.signatures.insert(idx, new);
let (new_signature, _) = signature.randomise(&state.params);
state.signatures.insert(idx, new_signature);
Ok(state.signatures.clone())
}
@@ -117,14 +121,15 @@ async fn prove_credential(
let state = state.read().await;
if let Some(signature) = state.signatures.get(idx) {
match coconut_interface::prove_credential(
match coconut_interface::prove_bandwidth_credential(
&state.params,
&verification_key,
signature,
&state.private_attributes,
state.serial_number,
state.binding_number,
) {
Ok(theta) => Ok(theta),
Err(e) => Err(format!("{}", e)),
Err(e) => Err(format!("{:?}", e)),
}
} else {
Err("Got invalid Signature idx".to_string())
@@ -144,10 +149,15 @@ async fn verify_credential(
let state = state.read().await;
let public_attributes_bytes = vec![
state.voucher_value.to_bytes().to_vec(),
state.voucher_info.to_bytes().to_vec(),
];
let credential = Credential::new(
state.n_attributes,
theta,
state.public_attributes_bytes.clone(),
public_attributes_bytes,
state
.signatures
.get(idx)
@@ -164,11 +174,13 @@ async fn get_credential(
) -> Result<Vec<Signature>, String> {
let guard = state.read().await;
let parsed_urls = parse_url_validators(&validator_urls)?;
let public_attributes = vec![guard.voucher_value, guard.voucher_info];
let private_attributes = vec![guard.serial_number, guard.binding_number];
let signature = obtain_aggregate_signature(
&guard.params,
&guard.public_attributes,
&guard.private_attributes,
&public_attributes,
&private_attributes,
&parsed_urls,
)
.await
+69 -27
View File
@@ -1,41 +1,83 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"env": {
"browser": true,
"es6": true,
"node": true,
"mocha": true
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 2018,
"ecmaVersion": 2019,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["prettier", "mocha"],
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"prettier"],
"rules": {
"no-console": "off",
"linebreak-style": "off",
"quotes": [
"error",
"double",
{
"allowTemplateLiterals": true
}
],
"keyword-spacing": [
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": [
"error",
{
"before": true
"devDependencies": [
"**/*.test.[jt]s",
"**/*.spec.[jt]s"
]
}
],
"space-before-blocks": [
"error"
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"js": "never"
}
]
}
}
},
"overrides": [
{
"files": "**/*.ts",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.ts",
"**/*.spec.ts"
]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
]
}
+6
View File
@@ -0,0 +1,6 @@
{
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 2
}
-3848
View File
File diff suppressed because it is too large Load Diff
+21 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@nymproject/nym-validator-client",
"version": "0.18.0",
"version": "0.19.0",
"description": "A TypeScript client for interacting with smart contracts in Nym validators",
"repository": "https://github.com/nymtech/nym",
"main": "./dist/index.js",
@@ -9,7 +9,8 @@
"build": "tsc",
"test": "ts-mocha tests/**/*.test.ts",
"coverage": "nyc npm test",
"lint": "eslint \"**/*.ts\"",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"docs": "typedoc --out docs src/index.ts"
},
"keywords": [],
@@ -23,22 +24,33 @@
"@types/chai": "^4.2.15",
"@types/expect": "^24.3.0",
"@types/mocha": "^8.2.1",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.7.0",
"chai": "^4.2.0",
"eslint": "^7.18.0",
"eslint-config-airbnb": "^19.0.2",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-mocha": "^10.0.3",
"eslint-plugin-prettier": "^4.0.0",
"mocha": "^8.2.1",
"moq.ts": "^7.2.0",
"nyc": "^15.1.0",
"prettier": "^2.5.1",
"ts-mocha": "^8.0.0",
"typedoc": "^0.20.27",
"typescript": "^4.1.3"
},
"dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.27.0-rc2",
"@cosmjs/crypto": "^0.27.0-rc2",
"@cosmjs/math": "^0.27.0-rc2",
"@cosmjs/proto-signing": "^0.27.0-rc2",
"@cosmjs/stargate": "^0.27.0-rc2",
"@cosmjs/tendermint-rpc": "^0.27.0-rc2",
"axios": "^0.21.1",
"@cosmjs/cosmwasm-stargate": "^0.25.5",
"@cosmjs/stargate": "^0.25.5",
"@cosmjs/math": "^0.25.5",
"@cosmjs/proto-signing": "^0.25.5"
"cosmjs-types": "^0.4.0"
}
}
}
-41
View File
@@ -1,41 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"env": {
"es6": true,
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"no-console": "off",
"linebreak-style": "off",
"quotes": [
"error",
"double",
{
"allowTemplateLiterals": true
}
],
"keyword-spacing": [
"error",
{
"before": true
}
],
"space-before-blocks": [
"error"
]
}
}
-1
View File
@@ -1 +0,0 @@
15.0.1
-59
View File
@@ -1,59 +0,0 @@
import {GatewayBond, PagedGatewayResponse} from "../types";
import {INetClient} from "../net-client"
import {IQueryClient} from "../query-client";
import {VALIDATOR_API_GATEWAYS, VALIDATOR_API_PORT} from "../index";
import axios from "axios";
/**
* There are serious limits in smart contract systems, but we need to keep track of
* potentially thousands of nodes. GatewaysCache instances repeatedly make requests for
* paged data about what gateways exist, and keep them locally in memory so that they're
* available for querying.
**/
export default class GatewaysCache {
gateways: GatewayBond[]
client: INetClient | IQueryClient
perPage: number
constructor(client: INetClient | IQueryClient, perPage: number) {
this.client = client;
this.gateways = [];
this.perPage = perPage;
}
/// Makes repeated requests to assemble a full list of gateways.
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
/// returns true.
async refreshGateways(contractAddress: string): Promise<GatewayBond[]> {
let newGateways: GatewayBond[] = [];
let response: PagedGatewayResponse;
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getGateways(contractAddress, this.perPage, next);
newGateways = newGateways.concat(response.nodes)
next = response.start_next_after;
// if `start_next_after` is not set, we're done
if (!next) {
break
}
}
this.gateways = newGateways
return newGateways;
}
/// Makes requests to assemble a full list of gateways from validator-api
async refreshValidatorAPIGateways(urls: string[]): Promise<GatewayBond[]> {
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_GATEWAYS;
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")
}
}
-60
View File
@@ -1,60 +0,0 @@
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 axios from "axios";
export { MixnodesCache };
/**
* 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
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
}
}
this.mixNodes = newMixnodes
return this.mixNodes;
}
/// 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")
}
}
+33 -38
View File
@@ -1,73 +1,68 @@
import { Decimal } from "@cosmjs/math";
import { Coin } from ".";
import { Decimal } from '@cosmjs/math';
import { Coin } from '@cosmjs/stargate';
// NARROW NO-BREAK SPACE (U+202F)
const thinSpace = "\u202F";
const thinSpace = '\u202F';
export function printableCoin(coin?: Coin): string {
if (!coin) {
return "0";
}
if (coin.denom.startsWith("u")) {
const ticker = coin.denom.slice(1).toUpperCase();
return Decimal.fromAtomics(coin.amount, 6).toString() + thinSpace + ticker;
} else {
return coin.amount + thinSpace + coin.denom;
}
if (!coin) {
return '0';
}
if (coin.denom.startsWith('u')) {
const ticker = coin.denom.slice(1).toUpperCase();
return Decimal.fromAtomics(coin.amount, 6).toString() + thinSpace + ticker;
}
return coin.amount + thinSpace + coin.denom;
}
export function printableBalance(balance?: readonly Coin[]): string {
if (!balance || balance.length === 0) return "";
return balance.map(printableCoin).join(", ");
if (!balance || balance.length === 0) return '';
return balance.map(printableCoin).join(', ');
}
// converts display amount, such as "12.0346" to its native token representation,
// with 6 fractional digits. So in that case it would result in "12034600"
// Basically does the same job as `displayAmountToNative` but without the requirement
// of having the coinMap
export function printableBalanceToNative(amountToDisplay: string): string {
const decimalAmount = Decimal.fromUserInput(amountToDisplay, 6);
return decimalAmount.atomics;
export function printableBalanceToNative(amountToDisplay: string): string {
const decimalAmount = Decimal.fromUserInput(amountToDisplay, 6);
return decimalAmount.atomics;
}
// reciprocal of `printableBalanceToNative`, takes, for example 10000000 and returns 10
export function nativeToPrintable(nativeValue: string): string {
return Decimal.fromAtomics(nativeValue, 6).toString()
return Decimal.fromAtomics(nativeValue, 6).toString();
}
export interface MappedCoin {
readonly denom: string;
readonly fractionalDigits: number;
readonly denom: string;
readonly fractionalDigits: number;
}
export interface CoinMap {
readonly [key: string]: MappedCoin;
readonly [key: string]: MappedCoin;
}
export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin {
if (!coinMap) return coin;
if (!coinMap) return coin;
const coinToDisplay = coinMap[coin.denom];
if (!coinToDisplay) return coin;
const coinToDisplay = coinMap[coin.denom];
if (!coinToDisplay) return coin;
const amountToDisplay = Decimal.fromAtomics(coin.amount, coinToDisplay.fractionalDigits).toString();
const amountToDisplay = Decimal.fromAtomics(coin.amount, coinToDisplay.fractionalDigits).toString();
return { denom: coinToDisplay.denom, amount: amountToDisplay };
return { denom: coinToDisplay.denom, amount: amountToDisplay };
}
// display amount is eg "12.0346", return is in native tokens
// with 6 fractional digits, this would be eg. "12034600"
export function displayAmountToNative(
amountToDisplay: string,
coinMap: CoinMap,
nativeDenom: string,
): string {
const fractionalDigits = coinMap[nativeDenom]?.fractionalDigits;
if (fractionalDigits) {
// use https://github.com/CosmWasm/cosmjs/blob/v0.22.2/packages/math/src/decimal.ts
const decimalAmount = Decimal.fromUserInput(amountToDisplay, fractionalDigits);
return decimalAmount.atomics;
}
export function displayAmountToNative(amountToDisplay: string, coinMap: CoinMap, nativeDenom: string): string {
const fractionalDigits = coinMap[nativeDenom]?.fractionalDigits;
if (fractionalDigits) {
// use https://github.com/CosmWasm/cosmjs/blob/v0.22.2/packages/math/src/decimal.ts
const decimalAmount = Decimal.fromUserInput(amountToDisplay, fractionalDigits);
return decimalAmount.atomics;
}
return amountToDisplay;
return amountToDisplay;
}
File diff suppressed because it is too large Load Diff
-207
View File
@@ -1,207 +0,0 @@
import { SigningCosmWasmClient, SigningCosmWasmClientOptions } from "@cosmjs/cosmwasm-stargate";
import {
Delegation,
GatewayOwnershipResponse,
MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
StateParams
} from "./types";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
import { Coin, StdFee } from "@cosmjs/stargate";
import { BroadcastTxResponse } from "@cosmjs/stargate"
import { nymGasLimits, nymGasPrice } from "./stargate-helper"
import {
ExecuteResult,
InstantiateOptions,
InstantiateResult,
MigrateResult,
UploadMeta,
UploadResult
} from "@cosmjs/cosmwasm-stargate";
export interface INetClient {
clientAddress: string;
getBalance(address: string, denom: string): Promise<Coin | null>;
getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse>;
getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation>
getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse>
getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation>
ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(contractAddress: string): Promise<StateParams>;
signAndBroadcast(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string): Promise<BroadcastTxResponse>;
executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult>;
instantiate(senderAddress: string, codeId: number, initMsg: Record<string, unknown>, label: string, options?: InstantiateOptions): Promise<InstantiateResult>;
sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise<BroadcastTxResponse>;
upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise<UploadResult>;
changeValidator(newUrl: string): Promise<void>
}
/**
* Takes care of network communication between this code and the validator.
* Depends on `SigningCosmWasClient`, which signs all requests using keypairs
* derived from on bech32 mnemonics.
*
* Wraps several methods from CosmWasmSigningClient so we can mock them for
* unit testing.
*/
export default class NetClient implements INetClient {
clientAddress: string;
private cosmClient: SigningCosmWasmClient;
// helpers for changing validators without having to remake the wallet
private readonly wallet: DirectSecp256k1HdWallet;
private readonly signerOptions: SigningCosmWasmClientOptions;
private constructor(clientAddress: string, cosmClient: SigningCosmWasmClient, wallet: DirectSecp256k1HdWallet, signerOptions: SigningCosmWasmClientOptions) {
this.clientAddress = clientAddress;
this.cosmClient = cosmClient;
this.wallet = wallet;
this.signerOptions = signerOptions;
}
public static async connect(wallet: DirectSecp256k1HdWallet, url: string, prefix: string): Promise<INetClient> {
const [{address}] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
gasPrice: nymGasPrice(prefix),
gasLimits: nymGasLimits,
};
const client = await SigningCosmWasmClient.connectWithSigner(url, wallet, signerOptions);
return new NetClient(address, client, wallet, signerOptions);
}
async changeValidator(url: string): Promise<void> {
this.cosmClient = await SigningCosmWasmClient.connectWithSigner(url, this.wallet, this.signerOptions);
}
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit, start_after}});
}
}
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit, start_after}});
}
}
public getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegations: {
mix_identity: mixIdentity,
limit
}
});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegations: {
mix_identity: mixIdentity,
limit,
start_after
}
});
}
}
public getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegation: {
mix_identity: mixIdentity,
address: delegatorAddress
}
});
}
public getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegations: {
gateway_identity: gatewayIdentity,
limit
}
});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegations: {
gateway_identity: gatewayIdentity,
limit,
start_after
}
});
}
}
public getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegation: {
gateway_identity: gatewayIdentity,
address: delegatorAddress
}
});
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_mixnode: {address}});
}
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_gateway: {address}});
}
public getBalance(address: string, denom: string): Promise<Coin | null> {
return this.cosmClient.getBalance(address, denom);
}
public getStateParams(contractAddress: string): Promise<StateParams> {
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
}
public executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult> {
return this.cosmClient.execute(senderAddress, contractAddress, handleMsg, memo, transferAmount);
}
public signAndBroadcast(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string): Promise<BroadcastTxResponse> {
return this.cosmClient.signAndBroadcast(signerAddress, messages, fee, memo)
}
public sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise<BroadcastTxResponse> {
return this.cosmClient.sendTokens(senderAddress, recipientAddress, transferAmount, memo);
}
public upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise<UploadResult> {
return this.cosmClient.upload(senderAddress, wasmCode, meta, memo);
}
public instantiate(senderAddress: string, codeId: number, initMsg: Record<string, unknown>, label: string, options?: InstantiateOptions): Promise<InstantiateResult> {
return this.cosmClient.instantiate(senderAddress, codeId, initMsg, label, options);
}
public migrate(senderAddress: string, contractAddress: string, codeId: number, migrateMsg: Record<string, unknown>, memo?: string): Promise<MigrateResult> {
return this.cosmClient.migrate(senderAddress, contractAddress, codeId, migrateMsg, memo)
}
}
+182
View File
@@ -0,0 +1,182 @@
/*
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import { JsonObject } from '@cosmjs/cosmwasm-stargate/build/queries';
// eslint-disable-next-line import/no-cycle
import { INymdQuery } from './query-client';
import {
ContractStateParams,
Delegation,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingIntervalResponse,
RewardingStatus,
} from './types';
interface SmartContractQuery {
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export default class NymdQuerier implements INymdQuery {
client: SmartContractQuery;
constructor(client: SmartContractQuery) {
this.client = client;
}
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_contract_version: {},
});
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mix_nodes: {
limit,
start_after: startAfter,
},
});
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_gateways: {
limit,
start_after: startAfter,
},
});
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
owns_mixnode: {
address,
},
});
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
owns_gateway: {
address,
},
});
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.client.queryContractSmart(mixnetContractAddress, {
state_params: {},
});
}
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
current_rewarding_interval: {},
});
}
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_all_network_delegations: {
start_after: startAfter,
limit,
},
});
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mixnode_delegations: {
mix_identity: mixIdentity,
start_after: startAfter,
limit,
},
});
}
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_delegator_delegations: {
delegator,
start_after: startAfter,
limit,
},
});
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_delegation_details: {
mix_identity: mixIdentity,
delegator,
},
});
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.client.queryContractSmart(mixnetContractAddress, {
layer_distribution: {},
});
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_reward_pool: {},
});
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_circulating_supply: {},
});
}
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_epoch_reward_percent: {},
});
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_sybil_resistance_percent: {},
});
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_rewarding_status: {
mix_identity: mixIdentity,
rewarding_interval_nonce: rewardingIntervalNonce,
},
});
}
}
+210 -139
View File
@@ -1,147 +1,218 @@
import { Coin } from "@cosmjs/stargate";
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
import {
Delegation,
GatewayOwnershipResponse,
MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
StateParams
} from "./types";
Account,
Block,
Coin,
DeliverTxResponse,
IndexedTx,
SearchTxFilter,
SearchTxQuery,
SequenceResponse,
} from '@cosmjs/stargate';
import { JsonObject } from '@cosmjs/cosmwasm-stargate/build/queries';
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
// eslint-disable-next-line import/no-cycle
import NymdQuerier from './nymd-querier';
import {
ContractStateParams,
Delegation,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNodeBond,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingIntervalResponse,
RewardingStatus,
} from './types';
import ValidatorApiQuerier, { IValidatorApiQuery } from './validator-api-querier';
export interface IQueryClient {
getBalance(address: string, stakeDenom: string): Promise<Coin | null>;
getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse>;
getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation>
getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse>
getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation>
ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(contractAddress: string): Promise<StateParams>;
changeValidator(newUrl: string): Promise<void>
export interface ICosmWasmQuery {
// methods exposed by `CosmWasmClient`
getChainId(): Promise<string>;
getHeight(): Promise<number>;
getAccount(searchAddress: string): Promise<Account | null>;
getSequence(address: string): Promise<SequenceResponse>;
getBlock(height?: number): Promise<Block>;
getBalance(address: string, searchDenom: string): Promise<Coin>;
getTx(id: string): Promise<IndexedTx | null>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
disconnect(): void;
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>;
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>;
getContracts(codeId: number): Promise<readonly string[]>;
getContract(address: string): Promise<Contract>;
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
/**
* Takes care of network communication between this code and the validator.
* Depends on `SigningCosmWasClient`, which signs all requests using keypairs
* derived from on bech32 mnemonics.
*
* Wraps several methods from CosmWasmSigningClient so we can mock them for
* unit testing.
*/
export default class QueryClient implements IQueryClient {
private cosmClient: CosmWasmClient;
export interface INymdQuery {
// nym-specific implemented inside NymQuerier
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
private constructor(cosmClient: CosmWasmClient) {
this.cosmClient = cosmClient;
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse>;
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>;
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams>;
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse>;
public static async connect(url: string): Promise<IQueryClient> {
const client = await CosmWasmClient.connect(url)
return new QueryClient(client)
}
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse>;
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse>;
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse>;
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation>;
async changeValidator(url: string): Promise<void> {
this.cosmClient = await CosmWasmClient.connect(url)
}
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit, start_after}});
}
}
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit, start_after}});
}
}
public getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegations: {
mix_identity: mixIdentity,
limit
}
});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegations: {
mix_identity: mixIdentity,
limit,
start_after
}
});
}
}
public getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegation: {
mix_identity: mixIdentity,
address: delegatorAddress
}
});
}
public getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegations: {
gateway_identity: gatewayIdentity,
limit
}
});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegations: {
gateway_identity: gatewayIdentity,
limit,
start_after
}
});
}
}
public getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegation: {
gateway_identity: gatewayIdentity,
address: delegatorAddress
}
});
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_mixnode: {address}});
}
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_gateway: {address}});
}
public getBalance(address: string, stakeDenom: string): Promise<Coin | null> {
return this.cosmClient.getBalance(address, stakeDenom);
}
public getStateParams(contractAddress: string): Promise<StateParams> {
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>;
getRewardPool(mixnetContractAddress: string): Promise<string>;
getCirculatingSupply(mixnetContractAddress: string): Promise<string>;
getEpochRewardPercent(mixnetContractAddress: string): Promise<number>;
getSybilResistancePercent(mixnetContractAddress: string): Promise<number>;
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus>;
}
export interface IQueryClient extends ICosmWasmQuery, INymdQuery, IValidatorApiQuery {}
export default class QueryClient extends CosmWasmClient implements IQueryClient {
private nymdQuerier: NymdQuerier;
private validatorApiQuerier: ValidatorApiQuerier;
private constructor(tmClient: Tendermint34Client, validatorApiUrl: string) {
super(tmClient);
this.nymdQuerier = new NymdQuerier(this);
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl);
}
public static async connectWithNym(nymdUrl: string, validatorApiUrl: string): Promise<QueryClient> {
const tmClient = await Tendermint34Client.connect(nymdUrl);
return new QueryClient(tmClient, validatorApiUrl);
}
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.nymdQuerier.getContractVersion(mixnetContractAddress);
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nymdQuerier.ownsGateway(mixnetContractAddress, address);
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.nymdQuerier.getStateParams(mixnetContractAddress);
}
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
return this.nymdQuerier.getCurrentRewardingInterval(mixnetContractAddress);
}
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
}
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse> {
return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nymdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getEpochRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
}
getCachedGateways(): Promise<GatewayBond[]> {
return this.validatorApiQuerier.getCachedGateways();
}
getCachedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getActiveMixnodes();
}
getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getRewardedMixnodes();
}
}
+467
View File
@@ -0,0 +1,467 @@
import {
ExecuteResult,
InstantiateOptions,
InstantiateResult,
MigrateResult,
SigningCosmWasmClient,
SigningCosmWasmClientOptions,
UploadResult,
} from '@cosmjs/cosmwasm-stargate';
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
import { Coin, DeliverTxResponse, SignerData, StdFee } from '@cosmjs/stargate';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
import { ChangeAdminResult } from '@cosmjs/cosmwasm-stargate/build/signingcosmwasmclient';
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { nymGasPrice } from './stargate-helper';
import { IQueryClient } from './query-client';
import NymdQuerier from './nymd-querier';
import {
ContractStateParams,
Delegation,
Gateway,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNode,
MixNodeBond,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingIntervalResponse,
RewardingStatus,
} from './types';
import ValidatorApiQuerier from './validator-api-querier';
// methods exposed by `SigningCosmWasmClient`
export interface ICosmWasmSigning {
simulate(signerAddress: string, messages: readonly EncodeObject[], memo: string | undefined): Promise<number>;
upload(
senderAddress: string,
wasmCode: Uint8Array,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<UploadResult>;
instantiate(
senderAddress: string,
codeId: number,
msg: Record<string, unknown>,
label: string,
fee: StdFee | 'auto' | number,
options?: InstantiateOptions,
): Promise<InstantiateResult>;
updateAdmin(
senderAddress: string,
contractAddress: string,
newAdmin: string,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<ChangeAdminResult>;
clearAdmin(
senderAddress: string,
contractAddress: string,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<ChangeAdminResult>;
migrate(
senderAddress: string,
contractAddress: string,
codeId: number,
migrateMsg: Record<string, unknown>,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<MigrateResult>;
execute(
senderAddress: string,
contractAddress: string,
msg: Record<string, unknown>,
fee: StdFee | 'auto' | number,
memo?: string,
funds?: readonly Coin[],
): Promise<ExecuteResult>;
sendTokens(
senderAddress: string,
recipientAddress: string,
amount: readonly Coin[],
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
delegateTokens(
delegatorAddress: string,
validatorAddress: string,
amount: Coin,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
undelegateTokens(
delegatorAddress: string,
validatorAddress: string,
amount: Coin,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
withdrawRewards(
delegatorAddress: string,
validatorAddress: string,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
signAndBroadcast(
signerAddress: string,
messages: readonly EncodeObject[],
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
sign(
signerAddress: string,
messages: readonly EncodeObject[],
fee: StdFee,
memo: string,
explicitSignerData?: SignerData,
): Promise<TxRaw>;
}
export interface INymSigning {
clientAddress: string;
}
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning {
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
ownerSignature: string,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
unbondMixNode(mixnetContractAddress: string, fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult>;
bondGateway(
mixnetContractAddress: string,
gateway: Gateway,
ownerSignature: string,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
unbondGateway(mixnetContractAddress: string, fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult>;
delegateToMixNode(
mixnetContractAddress: string,
mixIdentity: string,
amount: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
undelegateFromMixNode(
mixnetContractAddress: string,
mixIdentity: string,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
updateContractStateParams(
mixnetContractAddress: string,
newParams: ContractStateParams,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
// I don't see any point in exposing rewarding / vesting-related (INSIDE mixnet contract, like "BondMixnodeOnBehalf")
// functionalities in our typescript client. However, if for some reason, we find we need them
// they're rather trivial to add.
}
export default class SigningClient extends SigningCosmWasmClient implements ISigningClient {
private nymdQuerier: NymdQuerier;
private validatorApiQuerier: ValidatorApiQuerier;
clientAddress: string;
private constructor(
clientAddress: string,
validatorApiUrl: string,
tmClient: Tendermint34Client,
wallet: DirectSecp256k1HdWallet,
signerOptions: SigningCosmWasmClientOptions,
) {
super(tmClient, wallet, signerOptions);
this.clientAddress = clientAddress;
this.nymdQuerier = new NymdQuerier(this);
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl);
}
public static async connectWithNymSigner(
wallet: DirectSecp256k1HdWallet,
nymdUrl: string,
validatorApiUrl: string,
prefix: string,
): Promise<SigningClient> {
const [{ address }] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
gasPrice: nymGasPrice(prefix),
};
const tmClient = await Tendermint34Client.connect(nymdUrl);
return new SigningClient(address, validatorApiUrl, tmClient, wallet, signerOptions);
}
// query related:
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.nymdQuerier.getContractVersion(mixnetContractAddress);
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nymdQuerier.ownsGateway(mixnetContractAddress, address);
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.nymdQuerier.getStateParams(mixnetContractAddress);
}
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
return this.nymdQuerier.getCurrentRewardingInterval(mixnetContractAddress);
}
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
}
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse> {
return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nymdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getEpochRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
}
getCachedGateways(): Promise<GatewayBond[]> {
return this.validatorApiQuerier.getCachedGateways();
}
getCachedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getActiveMixnodes();
}
getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getRewardedMixnodes();
}
// signing related:
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
ownerSignature: string,
pledge: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Bonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
bond_mixnode: {
mix_node: mixNode,
owner_signature: ownerSignature,
},
},
fee,
memo,
[pledge],
);
}
unbondMixNode(
mixnetContractAddress: string,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Unbonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
unbond_mixnode: {},
},
fee,
memo,
);
}
bondGateway(
mixnetContractAddress: string,
gateway: Gateway,
ownerSignature: string,
pledge: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default Gateway Bonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
bond_gateway: {
gateway,
owner_signature: ownerSignature,
},
},
fee,
memo,
[pledge],
);
}
unbondGateway(
mixnetContractAddress: string,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default Gateway Unbonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
unbond_gateway: {},
},
fee,
memo,
);
}
delegateToMixNode(
mixnetContractAddress: string,
mixIdentity: string,
amount: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Delegation from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
delegate_to_mixnode: {
mix_identity: mixIdentity,
},
},
fee,
memo,
[amount],
);
}
undelegateFromMixNode(
mixnetContractAddress: string,
mixIdentity: string,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Undelegation from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
undelegate_from_mixnode: {
mix_identity: mixIdentity,
},
},
fee,
memo,
);
}
updateContractStateParams(
mixnetContractAddress: string,
newParams: ContractStateParams,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default Contract State Params Update from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
update_contract_state_params: newParams,
},
fee,
memo,
);
}
}
+8 -20
View File
@@ -1,26 +1,14 @@
import axios from "axios";
import { GasLimits, GasPrice } from "@cosmjs/stargate";
import { CosmWasmFeeTable, defaultGasLimits } from "@cosmjs/cosmwasm-stargate";
export const nymGasLimits: GasLimits<CosmWasmFeeTable> = {
...defaultGasLimits,
upload: 2_500_000,
init: 500_000,
migrate: 200_000,
exec: 250_000,
send: 80_000,
changeAdmin: 80_000,
};
import axios from 'axios';
import { GasPrice } from '@cosmjs/stargate';
export function nymGasPrice(prefix: string): GasPrice {
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
}
export const downloadWasm = async (url: string): Promise<Uint8Array> => {
const r = await axios.get(url, {responseType: "arraybuffer"});
if (r.status !== 200) {
throw new Error(`Download error: ${r.status}`);
}
return r.data;
const r = await axios.get(url, { responseType: 'arraybuffer' });
if (r.status !== 200) {
throw new Error(`Download error: ${r.status}`);
}
return r.data;
};
+134 -97
View File
@@ -1,125 +1,162 @@
import { Coin } from "@cosmjs/stargate";
import { Coin } from '@cosmjs/stargate';
// TODO: ideally we'd have re-exported those using that fancy crate that builds ts types from rust
export type MixnetContractVersion = {
build_timestamp: string;
build_version: string;
commit_sha: string;
commit_timestamp: string;
commit_branch: string;
rustc_version: string;
};
/// 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
}
nodes: MixNodeBond[];
per_page: number;
start_next_after?: string;
};
// 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
}
nodes: GatewayBond[];
per_page: number;
start_next_after?: string;
};
export type MixOwnershipResponse = {
address: string,
has_node: boolean,
}
address: string;
mixnode?: MixNodeBond;
};
export type GatewayOwnershipResponse = {
address: string,
has_gateway: boolean,
}
address: string;
gateway?: GatewayBond;
};
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 ContractStateParams = {
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_pledge: string;
minimum_gateway_pledge: string;
mixnode_rewarded_set_size: number;
mixnode_active_set_size: number;
};
export type RewardingIntervalResponse = {
current_rewarding_interval_starting_block: number;
current_rewarding_interval_nonce: number;
rewarding_in_progress: boolean;
};
export type LayerDistribution = {
gateways: number;
layer1: number;
layer2: number;
layer3: number;
};
export type Delegation = {
owner: string,
amount: Coin,
}
owner: string;
node_identity: string;
amount: Coin;
block_height: number;
proxy?: string;
};
export type PagedMixDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
delegations: Delegation[];
start_next_after?: string;
};
export type PagedGatewayDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
export type PagedDelegatorDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedAllDelegationsResponse = {
delegations: Delegation[];
start_next_after?: [string, string];
};
export type RewardingResult = {
operator_reward: string;
total_delegator_reward: string;
};
export type NodeRewardParams = {
period_reward_pool: string;
k: string;
reward_blockstamp: number;
circulating_supply: string;
uptime: string;
sybil_resistance_percent: number;
};
export type DelegatorRewardParams = {
node_reward_params: NodeRewardParams;
sigma: number;
profit_margin: number;
node_profit: number;
};
export type PendingDelegatorRewarding = {
running_results: RewardingResult;
next_start: string;
rewarding_params: DelegatorRewardParams;
};
export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding };
export type MixnodeRewardingStatusResponse = {
status?: RewardingStatus;
};
export enum Layer {
Gateway,
One,
Two,
Three,
Gateway,
One,
Two,
Three,
}
export type MixNodeBond = { // TODO: change name to MixNodeBond
owner: string,
mix_node: MixNode, // TODO: camelCase this later once everything else works
layer: Layer,
bond_amount: Coin,
total_delegation: Coin,
}
export type MixNodeBond = {
owner: string;
mix_node: MixNode;
layer: Layer;
bond_amount: Coin;
total_delegation: Coin;
};
export type MixNode = {
host: string,
mix_port: number,
verloc_port: number,
http_api_port: number,
sphinx_key: string, // TODO: camelCase this later once everything else works
identity_key: string,
version: string,
}
host: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
sphinx_key: string;
identity_key: string;
version: string;
};
export type GatewayBond = {
owner: string
gateway: Gateway,
bond_amount: Coin,
total_delegation: Coin,
}
owner: string;
gateway: Gateway;
bond_amount: Coin;
total_delegation: Coin;
};
export type Gateway = {
host: string,
mix_port: number,
clients_port: number,
location: string,
sphinx_key: string,
identity_key: string,
version: string
}
host: string;
mix_port: number;
clients_port: number;
location: string;
sphinx_key: string;
identity_key: string;
version: string;
};
export type SendRequest = {
senderAddress: string,
recipientAddress: string,
transferAmount: readonly Coin[]
}
senderAddress: string;
recipientAddress: string;
transferAmount: readonly Coin[];
};
+16 -12
View File
@@ -1,14 +1,18 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types
import { Coin } from "@cosmjs/stargate";
import { EncodeObject } from "@cosmjs/proto-signing";
import { Coin } from '@cosmjs/stargate';
import { EncodeObject } from '@cosmjs/proto-signing';
export function makeBankMsgSend(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[]): EncodeObject {
return {
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: {
fromAddress: senderAddress,
toAddress: recipientAddress,
amount: transferAmount,
},
};
}
export function makeBankMsgSend(
senderAddress: string,
recipientAddress: string,
transferAmount: readonly Coin[],
): EncodeObject {
return {
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
value: {
fromAddress: senderAddress,
toAddress: recipientAddress,
amount: transferAmount,
},
};
}
@@ -0,0 +1,75 @@
/*
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import axios from 'axios';
import { GatewayBond, MixNodeBond } from './types';
export const VALIDATOR_API_VERSION = '/v1';
export const VALIDATOR_API_GATEWAYS_PATH = `${VALIDATOR_API_VERSION}/gateways`;
export const VALIDATOR_API_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes`;
export const VALIDATOR_API_ACTIVE_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/active`;
export const VALIDATOR_API_REWARDED_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/rewarded`;
export interface IValidatorApiQuery {
getCachedMixnodes(): Promise<MixNodeBond[]>;
getCachedGateways(): Promise<GatewayBond[]>;
getActiveMixnodes(): Promise<MixNodeBond[]>;
getRewardedMixnodes(): Promise<MixNodeBond[]>;
}
export default class ValidatorApiQuerier implements IValidatorApiQuery {
validatorApiUrl: string;
constructor(validatorApiUrl: string) {
this.validatorApiUrl = validatorApiUrl;
}
async getCachedMixnodes(): Promise<MixNodeBond[]> {
const url = new URL(this.validatorApiUrl);
url.pathname += VALIDATOR_API_MIXNODES_PATH;
const response = await axios.get(url.toString());
if (response.status === 200) {
return response.data;
}
throw new Error('None of the provided validator APIs seem to be alive');
}
async getCachedGateways(): Promise<GatewayBond[]> {
const url = new URL(this.validatorApiUrl);
url.pathname += VALIDATOR_API_GATEWAYS_PATH;
const response = await axios.get(url.toString());
if (response.status === 200) {
return response.data;
}
throw new Error('None of the provided validator APIs seem to be alive');
}
async getActiveMixnodes(): Promise<MixNodeBond[]> {
const url = new URL(this.validatorApiUrl);
url.pathname += VALIDATOR_API_ACTIVE_MIXNODES_PATH;
const response = await axios.get(url.toString());
if (response.status === 200) {
return response.data;
}
throw new Error('None of the provided validator APIs seem to be alive');
}
async getRewardedMixnodes(): Promise<MixNodeBond[]> {
const url = new URL(this.validatorApiUrl);
url.pathname += VALIDATOR_API_REWARDED_MIXNODES_PATH;
const response = await axios.get(url.toString());
if (response.status === 200) {
return response.data;
}
throw new Error('None of the provided validator APIs seem to be alive');
}
}
@@ -1,100 +0,0 @@
import { assert } from "chai";
import INetClient from "../../src/net-client";
import { Fixtures } from "../fixtures"
import { Mock, Times } from "moq.ts";
import GatewaysCache from "../../src/caches/gateways";
describe("Caching gateways: when the validator returns", () => {
context("an empty list", () => {
it("Should return an empty list", async () => {
const perPage = 100;
const contractAddress = "mockContractAddress";
const emptyPromise = Promise.resolve(Fixtures.GatewaysResp.empty());
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(emptyPromise);
const cache = new GatewaysCache(mockClient.object(), perPage);
await cache.refreshGateways(contractAddress);
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
assert.deepEqual([], cache.gateways);
});
})
context("a list of gatways that fits in a page", () => {
it("Should return that one page list", async () => {
const perPage = 2;
const contractAddress = "mockContractAddress";
const onePagePromise = Promise.resolve(Fixtures.GatewaysResp.onePage());
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(onePagePromise);
const cache = new GatewaysCache(mockClient.object(), perPage);
await cache.refreshGateways(contractAddress);
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
assert.deepEqual(Fixtures.Gateways.list2(), cache.gateways);
})
})
context("a list of gateways that is longer than one page", () => {
it("Should return the full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult = Fixtures.GatewaysResp.page1of2();
const halfPageResult = Fixtures.GatewaysResp.halfPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(instance => instance.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult));
mockClient.setup(instance => instance.getGateways(contractAddress, perPage, fullPageResult.start_next_after)).returns(Promise.resolve(halfPageResult));
const cache = new GatewaysCache(mockClient.object(), perPage);
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
mockClient.verify(instance => instance.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
mockClient.verify(instance => instance.getGateways(contractAddress, perPage, fullPageResult.start_next_after), Times.Exactly(1));
assert.deepEqual(Fixtures.Gateways.list3(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
context("a list of gateways that is two filled pages", () => {
it("Should return the full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult1 = Fixtures.GatewaysResp.page1of2();
const fullPageResult2 = Fixtures.GatewaysResp.fullPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
const cache = new GatewaysCache(mockClient.object(), perPage);
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
assert.deepEqual(Fixtures.Gateways.list4(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
context("refreshing the cache twice", () => {
it("returns one full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult1 = Fixtures.GatewaysResp.page1of2();
const fullPageResult2 = Fixtures.GatewaysResp.fullPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
const cache = new GatewaysCache(mockClient.object(), perPage);
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
await cache.refreshGateways(contractAddress);
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(2));
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(2));
assert.deepEqual(Fixtures.Gateways.list4(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
});
@@ -1,96 +0,0 @@
import { assert } from "chai";
import INetClient from "../../src/net-client";
import { Fixtures } from "../fixtures"
import { Mock, Times } from "moq.ts";
import { MixnodesCache } from "../../src/caches/mixnodes"
describe("Caching mixnodes: when the validator returns", () => {
context("an empty list", () => {
it("Should return an empty list", async () => {
const perPage = 100;
const contractAddress = "mockContractAddress";
const emptyPromise = Promise.resolve(Fixtures.MixNodesResp.empty());
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(emptyPromise);
const cache = new MixnodesCache(mockClient.object(), perPage);
await cache.refreshMixNodes(contractAddress);
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
assert.deepEqual([], cache.mixNodes);
});
})
context("a list of nodes that fits in a page", () => {
it("Should return that one page list", async () => {
const perPage = 2;
const contractAddress = "mockContractAddress";
const onePagePromise = Promise.resolve(Fixtures.MixNodesResp.onePage());
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(onePagePromise);
const cache = new MixnodesCache(mockClient.object(), perPage);
await cache.refreshMixNodes(contractAddress);
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
assert.deepEqual(Fixtures.MixNodes.list2(), cache.mixNodes);
})
})
context("a list of nodes that is longer than one page", () => {
it("Should return the full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult = Fixtures.MixNodesResp.page1of2();
const halfPageResult = Fixtures.MixNodesResp.halfPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(instance => instance.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult));
mockClient.setup(instance => instance.getMixNodes(contractAddress, perPage, fullPageResult.start_next_after)).returns(Promise.resolve(halfPageResult));
const cache = new MixnodesCache(mockClient.object(), perPage);
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
mockClient.verify(instance => instance.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
mockClient.verify(instance => instance.getMixNodes(contractAddress, perPage, fullPageResult.start_next_after), Times.Exactly(1));
assert.deepEqual(Fixtures.MixNodes.list3(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
context("a list of nodes that is two filled pages", () => {
it("Should return the full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult1 = Fixtures.MixNodesResp.page1of2();
const fullPageResult2 = Fixtures.MixNodesResp.fullPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
const cache = new MixnodesCache(mockClient.object(), perPage);
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
assert.deepEqual(Fixtures.MixNodes.list4(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
context("refreshing the cache twice", () => {
it("returns one full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult1 = Fixtures.MixNodesResp.page1of2();
const fullPageResult2 = Fixtures.MixNodesResp.fullPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
const cache = new MixnodesCache(mockClient.object(), perPage);
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
await cache.refreshMixNodes(contractAddress);
// mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
// mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
assert.deepEqual(Fixtures.MixNodes.list4(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
});
-156
View File
@@ -1,156 +0,0 @@
import { coins } from "@cosmjs/launchpad";
import {PagedGatewayResponse, PagedMixnodeResponse} from "../src/net-client";
import {GatewayBond, MixNodeBond} from "../src/types"
export namespace Fixtures {
export class MixNodes {
static single(): MixNodeBond {
return {
amount: coins(666, "unym"),
owner: "bob",
mix_node: {
host: "1.1.1.1",
layer: 1,
location: "London, UK",
sphinx_key: "foo",
version: "0.10.0",
}
};
}
static list1(): MixNodeBond[] {
return [MixNodes.single()]
}
static list2(): MixNodeBond[] {
return [MixNodes.single(), MixNodes.single()]
}
static list3(): MixNodeBond[] {
return [MixNodes.single(), MixNodes.single(), MixNodes.single()]
}
static list4(): MixNodeBond[] {
return [MixNodes.single(), MixNodes.single(), MixNodes.single(), MixNodes.single()]
}
}
export class MixNodesResp {
static empty(): PagedResponse {
return {
nodes: [],
per_page: 2,
start_next_after: null,
}
}
static onePage(): PagedResponse {
return {
nodes: MixNodes.list2(),
per_page: 2,
start_next_after: null
}
}
static page1of2(): PagedResponse {
return {
nodes: MixNodes.list2(),
per_page: 2,
start_next_after: "2"
}
}
static halfPage2of2(): PagedResponse {
return {
nodes: MixNodes.list1(),
per_page: 2,
start_next_after: null
}
}
static fullPage2of2(): PagedResponse {
return {
nodes: MixNodes.list2(),
per_page: 2,
start_next_after: null,
}
}
}
export class Gateways {
static single(): GatewayBond {
return {
amount: coins(666, "unym"),
owner: "bob",
gateway: {
mix_host: "1.1.1.1:1234",
clients_host: "ws://1.1.1.1:1235",
location: "London, UK",
identity_key: "bar",
sphinx_key: "foo",
version: "0.10.0",
}
};
}
static list1(): GatewayBond[] {
return [Gateways.single()]
}
static list2(): GatewayBond[] {
return [Gateways.single(), Gateways.single()]
}
static list3(): GatewayBond[] {
return [Gateways.single(), Gateways.single(), Gateways.single()]
}
static list4(): GatewayBond[] {
return [Gateways.single(), Gateways.single(), Gateways.single(), Gateways.single()]
}
}
export class GatewaysResp {
static empty(): PagedGatewayResponse {
return {
nodes: [],
per_page: 2,
start_next_after: "",
}
}
static onePage(): PagedGatewayResponse {
return {
nodes: Gateways.list2(),
per_page: 2,
start_next_after: "",
}
}
static page1of2(): PagedGatewayResponse {
return {
nodes: Gateways.list2(),
per_page: 2,
start_next_after: "2"
}
}
static halfPage2of2(): PagedGatewayResponse {
return {
nodes: Gateways.list1(),
per_page: 2,
start_next_after: "",
}
}
static fullPage2of2(): PagedGatewayResponse {
return {
nodes: Gateways.list2(),
per_page: 2,
start_next_after: "",
}
}
}
}
+2936 -2422
View File
File diff suppressed because it is too large Load Diff
+10 -36
View File
@@ -3,6 +3,7 @@
use crypto::asymmetric::{encryption, identity};
use futures::channel::mpsc;
use gateway_client::bandwidth::BandwidthController;
use gateway_client::GatewayClient;
use nymsphinx::acknowledgements::AckKey;
use nymsphinx::addressing::clients::Recipient;
@@ -17,11 +18,6 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::{console_log, console_warn};
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(feature = "coconut")]
use credentials::{bandwidth::prepare_for_spending, obtain_aggregate_verification_key};
pub(crate) mod received_processor;
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
@@ -103,35 +99,15 @@ impl NymClient {
self.self_recipient().to_string()
}
#[cfg(feature = "coconut")]
async fn prepare_coconut_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 coconut_credential = {
let validator_server = self.validator_server.clone();
let identity_public_key = self.identity.public_key().clone();
Self::prepare_coconut_credential(
&vec![validator_server],
&identity_public_key.to_bytes(),
)
.await
};
let bandwidth_controller = Some(BandwidthController::new(
vec![self.validator_server.clone()],
*self.identity.public_key(),
));
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = None;
let mut client = self.get_and_update_topology().await;
let gateway = client.choose_gateway();
@@ -147,13 +123,11 @@ impl NymClient {
mixnet_messages_sender,
ack_sender,
DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
bandwidth_controller,
);
gateway_client
.authenticate_and_start(
#[cfg(feature = "coconut")]
Some(coconut_credential),
)
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
@@ -275,7 +249,7 @@ impl NymClient {
let validator_client = validator_client::ApiClient::new(self.validator_server.clone());
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
Err(err) => panic!("{}", err),
Err(err) => panic!("{:?}", err),
Ok(mixes) => mixes,
};
@@ -10,14 +10,19 @@ 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" }
[dependencies.tungstenite]
version = "0.13"
@@ -31,6 +36,12 @@ 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"
@@ -0,0 +1,247 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "coconut")]
use credentials::coconut::{
bandwidth::{
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
},
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;
use network_defaults::BANDWIDTH_VALUE;
#[cfg(not(feature = "coconut"))]
use network_defaults::{
eth_contract::ETH_JSON_ABI, 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,
};
use crate::error::GatewayClientError;
#[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 params = coconut_interface::Parameters::new(TOTAL_ATTRIBUTES).unwrap();
// TODO: Decide what is the value and additional info associated with the bandwidth voucher
let bandwidth_credential_attributes = BandwidthVoucherAttributes {
serial_number: params.random_scalar(),
binding_number: params.random_scalar(),
voucher_value: coconut_interface::hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes()),
voucher_info: coconut_interface::hash_to_scalar(
String::from("BandwidthVoucher").as_bytes(),
),
};
let bandwidth_credential = obtain_signature(
&params,
&bandwidth_credential_attributes,
&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,
&bandwidth_credential_attributes,
&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 network_defaults::ETH_EVENT_NAME;
use super::*;
#[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());
}
}
+97 -58
View File
@@ -1,6 +1,7 @@
// 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;
@@ -8,6 +9,10 @@ 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;
@@ -30,15 +35,11 @@ use fluvio_wasm_timer as wasm_timer;
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
const DEFAULT_RECONNECTION_ATTEMPTS: usize = 10;
const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
pub struct GatewayClient {
authenticated: bool,
#[cfg(feature = "coconut")]
bandwidth_remaining: i64,
gateway_address: String,
gateway_identity: identity::PublicKey,
@@ -47,6 +48,7 @@ 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.
@@ -69,20 +71,19 @@ impl GatewayClient {
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController>,
) -> Self {
GatewayClient {
authenticated: false,
#[cfg(feature = "coconut")]
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
local_identity,
shared_key,
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,
@@ -118,10 +119,7 @@ impl GatewayClient {
GatewayClient {
authenticated: false,
#[cfg(feature = "coconut")]
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
local_identity,
@@ -129,6 +127,7 @@ 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,
@@ -139,7 +138,6 @@ impl GatewayClient {
self.gateway_identity
}
#[cfg(feature = "coconut")]
pub fn remaining_bandwidth(&self) -> i64 {
self.bandwidth_remaining
}
@@ -208,14 +206,7 @@ impl GatewayClient {
for i in 1..self.reconnection_attempts {
info!("attempt {}...", i);
if self
.authenticate_and_start(
#[cfg(feature = "coconut")]
None,
)
.await
.is_ok()
{
if self.authenticate_and_start().await.is_ok() {
info!("managed to reconnect!");
return Ok(());
}
@@ -234,13 +225,7 @@ impl GatewayClient {
// final attempt (done separately to be able to return a proper error)
info!("attempt {}", self.reconnection_attempts);
match self
.authenticate_and_start(
#[cfg(feature = "coconut")]
None,
)
.await
{
match self.authenticate_and_start().await {
Ok(_) => {
info!("managed to reconnect!");
Ok(())
@@ -451,8 +436,12 @@ impl GatewayClient {
ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into();
match self.send_websocket_message(msg).await? {
ServerResponse::Authenticate { status } => {
ServerResponse::Authenticate {
status,
bandwidth_remaining,
} => {
self.authenticated = status;
self.bandwidth_remaining = bandwidth_remaining;
Ok(())
}
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
@@ -478,22 +467,15 @@ impl GatewayClient {
}
#[cfg(feature = "coconut")]
pub async fn claim_coconut_bandwidth(
async fn claim_coconut_bandwidth(
&mut self,
coconut_credential: Credential,
credential: Credential,
) -> Result<(), GatewayClientError> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if self.shared_key.is_none() {
return Err(GatewayClientError::NoSharedKeyAvailable);
}
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential(
&coconut_credential,
&credential,
self.shared_key.as_ref().unwrap(),
iv,
)
@@ -507,7 +489,64 @@ impl GatewayClient {
Ok(())
}
#[cfg(feature = "coconut")]
#[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);
}
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");
#[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()
@@ -522,9 +561,16 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
#[cfg(feature = "coconut")]
if self.estimate_required_bandwidth(&packets) < self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth);
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.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -590,9 +636,16 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
#[cfg(feature = "coconut")]
if (mix_packet.sphinx_packet().len() as i64) > self.bandwidth_remaining {
return Err(GatewayClientError::NotEnoughBandwidth);
// 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.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
@@ -629,10 +682,6 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
#[cfg(feature = "coconut")]
if self.bandwidth_remaining <= 0 {
return Err(GatewayClientError::NotEnoughBandwidth);
}
if self.connection.is_partially_delegated() {
return Ok(());
}
@@ -660,22 +709,12 @@ impl GatewayClient {
Ok(())
}
pub async fn authenticate_and_start(
&mut self,
#[cfg(feature = "coconut")] coconut_credential: Option<Credential>,
) -> Result<Arc<SharedKeys>, GatewayClientError> {
pub async fn authenticate_and_start(&mut self) -> Result<Arc<SharedKeys>, GatewayClientError> {
if !self.connection.is_established() {
self.establish_connection().await?;
}
let shared_key = self.perform_initial_authentication().await?;
#[cfg(feature = "coconut")]
{
if let Some(coconut_credential) = coconut_credential {
self.claim_coconut_bandwidth(coconut_credential).await?;
}
}
// this call is NON-blocking
self.start_listening_for_mixnet_messages()?;
+63 -76
View File
@@ -2,39 +2,83 @@
// 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)]
#[derive(Debug, Error)]
pub enum GatewayClientError {
#[error("Connection to the gateway is not established")]
ConnectionNotEstablished,
#[error("Gateway returned an error response - {0}")]
GatewayError(String),
NetworkError(WsError),
#[error("There was a network error - {0}")]
NetworkError(#[from] WsError),
// TODO: see if `JsValue` is a reasonable type for this
#[cfg(target_arch = "wasm32")]
#[error("There was a network error")]
NetworkErrorWasm(JsValue),
NoSharedKeyAvailable,
ConnectionAbruptlyClosed,
MalformedResponse,
SerializeCredential,
NotAuthenticated,
NotEnoughBandwidth,
UnexpectedResponse,
ConnectionInInvalidState,
RegistrationFailure(HandshakeError),
AuthenticationFailure,
Timeout,
}
#[cfg(not(feature = "coconut"))]
#[error("Could not backup keypair - {0}")]
IOError(#[from] std::io::Error),
impl From<WsError> for GatewayClientError {
fn from(err: WsError) -> Self {
GatewayClientError::NetworkError(err)
}
#[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")]
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 GatewayClientError {
@@ -54,60 +98,3 @@ 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,6 +8,7 @@ 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()
{
panic!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
warn!("failed to send back `mixnet_receiver_future` result on the oneshot channel")
}
};
@@ -10,6 +10,7 @@ rust-version = "1.56"
[dependencies]
base64 = "0.13"
mixnet-contract = { path="../../../common/mixnet-contract" }
vesting-contract = { path="../../../contracts/vesting" }
serde = { version="1", features=["derive"] }
serde_json = "1"
reqwest = { version="0.11", features=["json"] }
@@ -26,13 +27,14 @@ 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 }
#cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev="e5a1872083abb3d88fa62dda966e7f5408deba58", features = ["rpc", "bip32", "cosmwasm"], optional = true }
prost = { version = "0.9", 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 = "3.0"
cosmwasm-std = { version = "1.0.0-beta2", optional = true }
ts-rs = {version = "5.1", optional = true}
[features]
nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
@@ -6,13 +6,18 @@ use crate::nymd::{
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
#[cfg(feature = "nymd-client")]
use mixnet_contract::StateParams;
use mixnet_contract::ContractStateParams;
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
#[cfg(feature = "nymd-client")]
use mixnet_contract::RawDelegationData;
use mixnet_contract::{
Delegation, MixnetContractVersion, MixnodeRewardingStatusResponse, RewardingIntervalResponse,
};
use mixnet_contract::{GatewayBond, MixNodeBond};
#[cfg(feature = "nymd-client")]
use std::str::FromStr;
use url::Url;
#[cfg(feature = "nymd-client")]
@@ -20,6 +25,7 @@ pub struct Config {
api_url: Url,
nymd_url: Url,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
@@ -32,10 +38,12 @@ impl Config {
nymd_url: Url,
api_url: Url,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
) -> Self {
Config {
nymd_url,
mixnet_contract_address,
vesting_contract_address,
api_url,
mixnode_page_limit: None,
gateway_page_limit: None,
@@ -62,6 +70,7 @@ impl Config {
#[cfg(feature = "nymd-client")]
pub struct Client<C> {
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
mnemonic: Option<bip39::Mnemonic>,
mixnode_page_limit: Option<u32>,
@@ -83,11 +92,14 @@ impl Client<SigningNymdClient> {
let nymd_client = NymdClient::connect_with_mnemonic(
config.nymd_url.as_str(),
config.mixnet_contract_address.clone(),
config.vesting_contract_address.clone(),
mnemonic.clone(),
None,
)?;
Ok(Client {
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
mnemonic: Some(mnemonic),
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -101,7 +113,9 @@ impl Client<SigningNymdClient> {
self.nymd = NymdClient::connect_with_mnemonic(
new_endpoint.as_ref(),
self.mixnet_contract_address.clone(),
self.vesting_contract_address.clone(),
self.mnemonic.clone().unwrap(),
None,
)?;
Ok(())
}
@@ -113,16 +127,19 @@ impl Client<QueryNymdClient> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
let nymd_client = NymdClient::connect(
config.nymd_url.as_str(),
config
.mixnet_contract_address
.clone()
.ok_or(ValidatorClientError::NymdError(
NymdError::NoContractAddressAvailable,
))?,
config.mixnet_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(network_defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS)
.unwrap()
}),
config.vesting_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(network_defaults::DEFAULT_VESTING_CONTRACT_ADDRESS)
.unwrap()
}),
)?;
Ok(Client {
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -136,6 +153,7 @@ impl Client<QueryNymdClient> {
self.nymd = NymdClient::connect(
new_endpoint.as_ref(),
self.mixnet_contract_address.clone().unwrap(),
self.vesting_contract_address.clone().unwrap(),
)?;
Ok(())
}
@@ -165,11 +183,69 @@ impl<C> Client<C> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_state_params(&self) -> Result<StateParams, ValidatorClientError>
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_state_params().await?)
Ok(self.nymd.get_contract_settings().await?)
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_mixnet_contract_version().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_rewarding_status(
&self,
mix_identity: mixnet_contract::IdentityKey,
rewarding_interval_nonce: u32,
) -> Result<MixnodeRewardingStatusResponse, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_rewarding_status(mix_identity, rewarding_interval_nonce)
.await?)
}
pub async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_reward_pool().await?.u128())
}
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_circulating_supply().await?.u128())
}
pub async fn get_sybil_resistance_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_sybil_resistance_percent().await?)
}
pub async fn get_epoch_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_epoch_reward_percent().await?)
}
// basically handles paging for us
@@ -249,9 +325,7 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_mixnode_delegations(
&self,
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -260,7 +334,7 @@ impl<C> Client<C> {
loop {
let mut paged_response = self
.nymd
.get_all_mix_delegations_paged(
.get_all_network_delegations_paged(
start_after.take(),
self.mixnode_delegations_page_limit,
)
@@ -277,10 +351,10 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_reverse_mixnode_delegations(
pub async fn get_all_delegator_delegations(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -289,13 +363,13 @@ impl<C> Client<C> {
loop {
let mut paged_response = self
.nymd
.get_reverse_mix_delegations_paged(
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
.get_delegator_delegations_paged(
delegation_owner.to_string(),
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegated_nodes);
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
@@ -307,28 +381,6 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_mixnode_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_mixnode_delegations(delegation_owner)
.await?
{
let delegation = self
.nymd
.get_mix_delegation(node_identity, delegation_owner)
.await?;
delegations.push(delegation);
}
Ok(delegations)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -4,7 +4,7 @@
use crate::nymd::cosmwasm_client::helpers::create_pagination;
use crate::nymd::cosmwasm_client::types::{
Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId,
SequenceResponse,
SequenceResponse, SimulateResponse,
};
use crate::nymd::error::NymdError;
use async_trait::async_trait;
@@ -14,15 +14,19 @@ use cosmrs::proto::cosmos::auth::v1beta1::{
use cosmrs::proto::cosmos::bank::v1beta1::{
QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse,
};
use cosmrs::proto::cosmwasm::wasm::v1beta1::*;
use cosmrs::proto::cosmos::tx::v1beta1::{
SimulateRequest, SimulateResponse as ProtoSimulateResponse,
};
use cosmrs::proto::cosmwasm::wasm::v1::*;
use cosmrs::rpc::endpoint::block::Response as BlockResponse;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::endpoint::tx::Response as TxResponse;
use cosmrs::rpc::query::Query;
use cosmrs::rpc::{self, HttpClient, Order};
use cosmrs::tendermint::abci::Code as AbciCode;
use cosmrs::tendermint::abci::Transaction;
use cosmrs::tendermint::{abci, block, chain};
use cosmrs::{tx, AccountId, Coin, Denom};
use cosmrs::{tx, AccountId, Coin, Denom, Tx};
use prost::Message;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
@@ -49,6 +53,11 @@ pub trait CosmWasmClient: rpc::Client {
let res = self.abci_query(path, buf, None, false).await?;
match res.code {
AbciCode::Err(code) => return Err(NymdError::AbciError(code, res.log)),
AbciCode::Ok => (),
}
Ok(Res::decode(res.value.as_ref())?)
}
@@ -213,7 +222,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_codes(&self) -> Result<Vec<Code>, NymdError> {
let path = Some("/cosmwasm.wasm.v1beta1.Query/Codes".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1.Query/Codes".parse().unwrap());
let mut raw_codes = Vec::new();
let mut pagination = None;
@@ -240,7 +249,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_code_details(&self, code_id: ContractCodeId) -> Result<CodeDetails, NymdError> {
let path = Some("/cosmwasm.wasm.v1beta1.Query/Code".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1.Query/Code".parse().unwrap());
let req = QueryCodeRequest { code_id };
@@ -255,11 +264,7 @@ pub trait CosmWasmClient: rpc::Client {
}
}
async fn get_contracts(&self, code_id: ContractCodeId) -> Result<Vec<AccountId>, NymdError> {
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/ContractsByCode"
.parse()
.unwrap(),
);
let path = Some("/cosmwasm.wasm.v1.Query/ContractsByCode".parse().unwrap());
let mut raw_contracts = Vec::new();
let mut pagination = None;
@@ -290,7 +295,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_contract(&self, address: &AccountId) -> Result<Contract, NymdError> {
let path = Some("/cosmwasm.wasm.v1beta1.Query/ContractInfo".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1.Query/ContractInfo".parse().unwrap());
let req = QueryContractInfoRequest {
address: address.to_string(),
@@ -315,11 +320,7 @@ pub trait CosmWasmClient: rpc::Client {
&self,
address: &AccountId,
) -> Result<Vec<ContractCodeHistoryEntry>, NymdError> {
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/ContractHistory"
.parse()
.unwrap(),
);
let path = Some("/cosmwasm.wasm.v1.Query/ContractHistory".parse().unwrap());
let mut raw_entries = Vec::new();
let mut pagination = None;
@@ -353,11 +354,7 @@ pub trait CosmWasmClient: rpc::Client {
address: &AccountId,
query_data: Vec<u8>,
) -> Result<Vec<u8>, NymdError> {
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/RawContractState"
.parse()
.unwrap(),
);
let path = Some("/cosmwasm.wasm.v1.Query/RawContractState".parse().unwrap());
let req = QueryRawContractStateRequest {
address: address.to_string(),
@@ -381,7 +378,7 @@ pub trait CosmWasmClient: rpc::Client {
for<'a> T: Deserialize<'a>,
{
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/SmartContractState"
"/cosmwasm.wasm.v1.Query/SmartContractState"
.parse()
.unwrap(),
);
@@ -400,4 +397,27 @@ pub trait CosmWasmClient: rpc::Client {
Ok(serde_json::from_slice(&res.data)?)
}
// deprecation warning is due to the fact the protobuf files built were based on cosmos-sdk 0.44,
// where they prefer using tx_bytes directly. However, in 0.42, which we are using at the time
// of writing this, the option does not work
#[allow(deprecated)]
async fn query_simulate(
&self,
tx: Option<Tx>,
tx_bytes: Vec<u8>,
) -> Result<SimulateResponse, NymdError> {
let path = Some("/cosmos.tx.v1beta1.Service/Simulate".parse().unwrap());
let req = SimulateRequest {
tx: tx.map(Into::into),
tx_bytes,
};
let res = self
.make_abci_query::<_, ProtoSimulateResponse>(path, req)
.await?;
res.try_into()
}
}
@@ -29,7 +29,7 @@ pub(crate) fn find_attribute<'a>(
) -> Option<&'a cosmwasm_std::Attribute> {
logs.iter()
.flat_map(|log| log.events.iter())
.find(|event| event.kind == event_type)?
.find(|event| event.ty == event_type)?
.attributes
.iter()
.find(|attr| attr.key == attribute_key)
@@ -61,7 +61,7 @@ mod tests {
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0].msg_index, 0);
assert_eq!(parsed[0].events.len(), 1);
assert_eq!(parsed[0].events[0].kind, "message");
assert_eq!(parsed[0].events[0].ty, "message");
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
assert_eq!(parsed[0].events[0].attributes[3].value, "1");
}
@@ -76,12 +76,12 @@ mod tests {
assert_eq!(parsed[2].msg_index, 2);
assert_eq!(parsed[0].events.len(), 1);
assert_eq!(parsed[0].events[0].kind, "message");
assert_eq!(parsed[0].events[0].ty, "message");
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
assert_eq!(parsed[0].events[0].attributes[3].value, "9");
assert_eq!(parsed[2].events.len(), 1);
assert_eq!(parsed[2].events[0].kind, "message");
assert_eq!(parsed[2].events[0].ty, "message");
assert_eq!(parsed[2].events[0].attributes[2].key, "signer");
assert_eq!(
parsed[2].events[0].attributes[2].value,
@@ -3,6 +3,7 @@
use crate::nymd::error::NymdError;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::nymd::GasPrice;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
use std::convert::TryInto;
@@ -23,9 +24,10 @@ where
pub fn connect_with_signer<U>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
) -> Result<signing_client::Client, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
signing_client::Client::connect_with_signer(endpoint, signer)
signing_client::Client::connect_with_signer(endpoint, signer, gas_price)
}
@@ -1,37 +1,100 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::convert::TryInto;
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{self, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, AccountId, Any, Coin, Tx};
use log::debug;
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
use crate::nymd::cosmwasm_client::types::*;
use crate::nymd::error::NymdError;
use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
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 serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use std::convert::TryInto;
use crate::nymd::{CosmosCoin, GasPrice};
// we need to have **a** valid secp256k1 signature for simulation purposes.
// it doesn't matter what it is as long as it parses correctly
const DUMMY_SECP256K1_SIGNATURE: &[u8] = &[
54, 167, 169, 61, 100, 173, 231, 87, 1, 113, 179, 49, 102, 141, 67, 22, 170, 153, 52, 88, 178,
159, 200, 11, 37, 138, 76, 221, 187, 70, 104, 123, 98, 216, 190, 249, 149, 81, 1, 158, 0, 220,
32, 147, 101, 60, 64, 77, 44, 83, 221, 119, 170, 124, 109, 177, 73, 116, 46, 57, 102, 181, 98,
91,
];
#[async_trait]
pub trait SigningCosmWasmClient: CosmWasmClient {
fn signer(&self) -> &DirectSecp256k1HdWallet;
fn gas_price(&self) -> &GasPrice;
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
let signer_accounts = self.signer().try_derive_accounts().ok()?;
let account_from_signer = signer_accounts
.iter()
.find(|account| &account.address == signer_address)?;
let public_key = account_from_signer.public_key;
Some(public_key.into())
}
async fn simulate(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
memo: impl Into<String> + Send + 'static,
) -> Result<SimulateResponse, NymdError> {
let public_key = self.signer_public_key(signer_address);
let sequence_response = self.get_sequence(signer_address).await?;
let partial_tx = Tx {
body: tx::Body {
messages,
memo: memo.into(),
timeout_height: 0u32.into(),
extension_options: vec![],
non_critical_extension_options: vec![],
},
auth_info: tx::AuthInfo {
signer_infos: vec![tx::SignerInfo {
public_key,
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
mode: SignMode::Unspecified,
}),
sequence: sequence_response.sequence,
}],
fee: tx::Fee::from_amount_and_gas(
CosmosCoin {
denom: "".parse().unwrap(),
amount: 0u64.into(),
},
0,
),
},
signatures: vec![DUMMY_SECP256K1_SIGNATURE.try_into().unwrap()],
};
self.query_simulate(Some(partial_tx), Vec::new()).await
}
async fn upload(
&self,
sender_address: &AccountId,
wasm_code: Vec<u8>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
mut meta: Option<UploadMeta>,
) -> Result<UploadResult, NymdError> {
let compressed = compress_wasm_code(&wasm_code)?;
let compressed_size = compressed.len();
@@ -42,14 +105,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let upload_msg = cosmwasm::MsgStoreCode {
sender: sender_address.clone(),
wasm_byte_code: compressed,
source: meta
.as_mut()
.map(|meta| meta.source.take())
.unwrap_or_default(),
builder: meta
.as_mut()
.map(|meta| meta.builder.take())
.unwrap_or_default(),
instantiate_permission: Default::default(),
}
.to_any()
@@ -61,12 +116,13 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()?;
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
// TODO: should those strings be extracted into some constants?
// the reason I think unwrap here is fine is that if the transaction succeeded and those
// fields do not exist or code_id is not a number, there's no way we can recover, we're probably connected
// to wrong validator or something
let code_id = logs::find_attribute(&logs, "message", "code_id")
let code_id = logs::find_attribute(&logs, "store_code", "code_id")
.unwrap()
.value
.parse()
@@ -80,6 +136,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
code_id,
logs,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -111,7 +168,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
// now this is a weird one. the protobuf files say this field is optional,
// but if you omit it, the initialisation will fail CheckTx
label: Some(label),
init_msg: serde_json::to_vec(msg)?,
msg: serde_json::to_vec(msg)?,
funds: options.map(|options| options.funds).unwrap_or_default(),
}
.to_any()
@@ -123,12 +180,13 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()?;
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
// TODO: should those strings be extracted into some constants?
// the reason I think unwrap here is fine is that if the transaction succeeded and those
// fields do not exist or address is malformed, there's no way we can recover, we're probably connected
// to wrong validator or something
let contract_address = logs::find_attribute(&logs, "message", "contract_address")
let contract_address = logs::find_attribute(&logs, "instantiate", "_contract_address")
.unwrap()
.value
.parse()
@@ -138,6 +196,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
contract_address,
logs,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -162,9 +221,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -187,9 +249,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -209,7 +274,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
sender: sender_address.clone(),
contract: contract_address.clone(),
code_id,
migrate_msg: serde_json::to_vec(msg)?,
msg: serde_json::to_vec(msg)?,
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
@@ -219,9 +284,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(MigrateResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -251,9 +319,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(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,
gas_info,
})
}
@@ -288,14 +359,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
debug!(
"gas wanted: {:?}, gas used: {:?}",
tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used
);
let gas_info = GasInfo::new(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,
gas_info,
})
}
@@ -316,7 +385,36 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
.await
.await?
.check_response()
}
async fn send_tokens_multiple<I>(
&self,
sender_address: &AccountId,
msgs: I,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError>
where
I: IntoIterator<Item = (AccountId, Vec<Coin>)> + Send,
{
let messages = msgs
.into_iter()
.map(|(to_address, amount)| {
MsgSend {
from_address: sender_address.clone(),
to_address,
amount,
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
})
.collect::<Result<_, _>>()?;
self.sign_and_broadcast_commit(sender_address, messages, fee, memo)
.await?
.check_response()
}
async fn delegate_tokens(
@@ -336,7 +434,8 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
.await
.await?
.check_response()
}
async fn undelegate_tokens(
@@ -356,7 +455,8 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
.await
.await?
.check_response()
}
async fn withdraw_rewards(
@@ -374,7 +474,45 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
.await
.await?
.check_response()
}
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
#[allow(clippy::ptr_arg)]
async fn determine_transaction_fee(
&self,
signer_address: &AccountId,
messages: &[Any],
fee: Fee,
memo: &String,
) -> Result<tx::Fee, NymdError> {
let fee = match fee {
Fee::Manual(fee) => fee,
Fee::Auto(multiplier) => {
debug!("Trying to simulate gas costs...");
// from what I've seen in manual testing, gas estimation does not exist if transaction
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
let gas_estimation = self
.simulate(signer_address, messages.to_vec(), memo.clone())
.await?
.gas_info
.ok_or(NymdError::GasEstimationFailure)?
.gas_used;
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
let gas = ((gas_estimation.value() as f32 * multiplier) as u64).into();
debug!("Gas estimation: {}", gas_estimation);
debug!("Multiplying the estimation by {}", multiplier);
debug!("Final gas limit used: {}", gas);
let fee = self.gas_price() * gas;
tx::Fee::from_amount_and_gas(fee, gas)
}
};
debug!("Fee used for the transaction: {:?}", fee);
Ok(fee)
}
/// Broadcast a transaction, returning immediately.
@@ -385,6 +523,10 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_async::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -401,6 +543,10 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_sync::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -417,6 +563,11 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -429,7 +580,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
&self,
signer_address: &AccountId,
messages: Vec<Any>,
fee: Fee,
fee: tx::Fee,
memo: impl Into<String> + Send + 'static,
signer_data: SignerData,
) -> Result<tx::Raw, NymdError> {
@@ -467,7 +618,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
&self,
signer_address: &AccountId,
messages: Vec<Any>,
fee: Fee,
fee: tx::Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<tx::Raw, NymdError> {
// TODO: Future optimisation: rather than grabbing current account_number and sequence
@@ -485,22 +636,28 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Client {
rpc_client: HttpClient,
signer: DirectSecp256k1HdWallet,
gas_price: GasPrice,
}
impl Client {
pub fn connect_with_signer<U>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
) -> Result<Self, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let rpc_client = HttpClient::new(endpoint)?;
Ok(Client { rpc_client, signer })
Ok(Client {
rpc_client,
signer,
gas_price: gas_price.unwrap_or_default(),
})
}
}
@@ -522,4 +679,8 @@ impl SigningCosmWasmClient for Client {
fn signer(&self) -> &DirectSecp256k1HdWallet {
&self.signer
}
fn gas_price(&self) -> &GasPrice {
&self.gas_price
}
}
@@ -7,15 +7,19 @@ use crate::nymd::cosmwasm_client::logs::Log;
use crate::nymd::error::NymdError;
use cosmrs::crypto::PublicKey;
use cosmrs::proto::cosmos::auth::v1beta1::BaseAccount;
use cosmrs::proto::cosmwasm::wasm::v1beta1::{
use cosmrs::proto::cosmos::base::abci::v1beta1::{
GasInfo as ProtoGasInfo, Result as ProtoAbciResult,
};
use cosmrs::proto::cosmos::tx::v1beta1::SimulateResponse as ProtoSimulateResponse;
use cosmrs::proto::cosmwasm::wasm::v1::{
CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry,
ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo,
};
use cosmrs::tendermint::chain;
use cosmrs::tx::{AccountNumber, SequenceNumber};
use cosmrs::tendermint::{abci, chain};
use cosmrs::tx::{AccountNumber, Gas, SequenceNumber};
use cosmrs::{tx, AccountId, Coin};
use serde::Serialize;
use std::convert::TryFrom;
use std::convert::{TryFrom, TryInto};
pub type ContractCodeId = u64;
@@ -70,18 +74,6 @@ pub struct Code {
/// sha256 hash of the code stored
pub data_hash: Vec<u8>,
/// An URL to a .tar.gz archive of the source code of the contract,
/// which can be used to reproducibly build the Wasm bytecode.
///
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub source: Option<String>,
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
///
/// @example ```cosmwasm/rust-optimizer:0.8.0```
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub builder: Option<String>,
}
impl TryFrom<CodeInfoResponse> for Code {
@@ -92,31 +84,16 @@ impl TryFrom<CodeInfoResponse> for Code {
code_id,
creator,
data_hash,
source,
builder,
} = value;
let creator = creator
.parse()
.map_err(|_| NymdError::MalformedAccountAddress(creator))?;
let source = if source.is_empty() {
None
} else {
Some(source)
};
let builder = if builder.is_empty() {
None
} else {
Some(builder)
};
Ok(Code {
code_id,
creator,
data_hash,
source,
builder,
})
}
}
@@ -242,6 +219,101 @@ impl TryFrom<ProtoContractCodeHistoryEntry> for ContractCodeHistoryEntry {
}
}
#[derive(Debug)]
pub struct GasInfo {
/// GasWanted is the maximum units of work we allow this tx to perform.
pub gas_wanted: Gas,
/// GasUsed is the amount of gas actually consumed.
pub gas_used: Gas,
}
impl From<ProtoGasInfo> for GasInfo {
fn from(value: ProtoGasInfo) -> Self {
GasInfo {
gas_wanted: value.gas_wanted.into(),
gas_used: value.gas_used.into(),
}
}
}
impl GasInfo {
pub fn new(gas_wanted: Gas, gas_used: Gas) -> Self {
GasInfo {
gas_wanted,
gas_used,
}
}
}
#[derive(Debug)]
pub struct AbciResult {
/// Data is any data returned from message or handler execution. It MUST be
/// length prefixed in order to separate data from multiple message executions.
pub data: Vec<u8>,
/// Log contains the log information from message or handler execution.
// todo: try to parse into Log?
pub log: String,
/// Events contains a slice of Event objects that were emitted during message
/// or handler execution.
pub events: Vec<abci::Event>,
}
impl TryFrom<ProtoAbciResult> for AbciResult {
type Error = NymdError;
fn try_from(value: ProtoAbciResult) -> Result<Self, Self::Error> {
let mut events = Vec::with_capacity(value.events.len());
for proto_event in value.events.into_iter() {
let type_str = proto_event.r#type;
let mut attributes = Vec::with_capacity(proto_event.attributes.len());
for proto_attribute in proto_event.attributes.into_iter() {
let stringified_ked = String::from_utf8(proto_attribute.key)
.map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?;
let stringified_value = String::from_utf8(proto_attribute.value)
.map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?;
attributes.push(abci::tag::Tag {
key: stringified_ked.parse().unwrap(),
value: stringified_value.parse().unwrap(),
})
}
events.push(abci::Event {
type_str,
attributes,
})
}
Ok(AbciResult {
data: value.data,
log: value.log,
events,
})
}
}
#[derive(Debug)]
pub struct SimulateResponse {
pub gas_info: Option<GasInfo>,
pub result: Option<AbciResult>,
}
impl TryFrom<ProtoSimulateResponse> for SimulateResponse {
type Error = NymdError;
fn try_from(value: ProtoSimulateResponse) -> Result<Self, Self::Error> {
Ok(SimulateResponse {
gas_info: value.gas_info.map(|gas_info| gas_info.into()),
result: value.result.map(|result| result.try_into()).transpose()?,
})
}
}
// ##############################################################################
// types specific to the signing client (perhaps they should go to separate file)
// ##############################################################################
@@ -254,21 +326,6 @@ pub struct SignerData {
pub chain_id: chain::Id,
}
#[derive(Debug)]
pub struct UploadMeta {
/// An URL to a .tar.gz archive of the source code of the contract,
/// which can be used to reproducibly build the Wasm bytecode.
///
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub source: Option<String>,
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
///
/// @example ```cosmwasm/rust-optimizer:0.8.0```
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub builder: Option<String>,
}
#[derive(Debug)]
pub struct UploadResult {
/// Size of the original wasm code in bytes
@@ -290,6 +347,8 @@ pub struct UploadResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -316,6 +375,8 @@ pub struct InstantiateResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -324,6 +385,8 @@ pub struct ChangeAdminResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -332,6 +395,8 @@ pub struct MigrateResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -340,4 +405,6 @@ pub struct ExecuteResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::cosmwasm_client::types::ContractCodeId;
use cosmrs::tendermint::block;
use cosmrs::tendermint::{abci, block};
use cosmrs::{bip32, tx, AccountId};
use std::io;
use thiserror::Error;
@@ -102,6 +102,12 @@ pub enum NymdError {
#[error("The provided gas price is malformed")]
MalformedGasPrice,
#[error("Failed to estimate gas price for the transaction")]
GasEstimationFailure,
#[error("Abci query failed with code {0} - {1}")]
AbciError(u32, abci::Log),
}
impl NymdError {
@@ -38,7 +38,7 @@ impl<'a> Mul<Gas> for &'a GasPrice {
// 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 {
if limit_uint128 * gas_price_numerator > amount * gas_price_denominator {
amount += Uint128::new(1);
}
@@ -6,9 +6,9 @@ use cosmrs::tx::{Fee, Gas};
use cosmrs::Coin;
use serde::{Deserialize, Serialize};
use std::fmt;
use ts_rs::TS;
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, TS)]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum Operation {
Upload,
Init,
@@ -17,16 +17,29 @@ pub enum Operation {
Send,
BondMixnode,
BondMixnodeOnBehalf,
UnbondMixnode,
UnbondMixnodeOnBehalf,
DelegateToMixnode,
DelegateToMixnodeOnBehalf,
UndelegateFromMixnode,
UndelegateFromMixnodeOnBehalf,
BondGateway,
BondGatewayOnBehalf,
UnbondGateway,
DelegateToGateway,
UndelegateFromGateway,
UnbondGatewayOnBehalf,
UpdateStateParams,
UpdateContractSettings,
BeginMixnodeRewarding,
FinishMixnodeRewarding,
TrackUnbondGateway,
TrackUnbondMixnode,
WithdrawVestedCoins,
TrackUndelegation,
CreatePeriodicVestingAccount,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
@@ -42,14 +55,27 @@ impl fmt::Display for Operation {
Operation::ChangeAdmin => f.write_str("ChangeAdmin"),
Operation::Send => f.write_str("Send"),
Operation::BondMixnode => f.write_str("BondMixnode"),
Operation::BondMixnodeOnBehalf => f.write_str("BondMixnodeOnBehalf"),
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::UnbondMixnodeOnBehalf => f.write_str("UnbondMixnodeOnBehalf"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::BondGatewayOnBehalf => f.write_str("BondGatewayOnBehalf"),
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::UnbondGatewayOnBehalf => f.write_str("UnbondGatewayOnBehalf"),
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
Operation::DelegateToMixnodeOnBehalf => f.write_str("DelegateToMixnodeOnBehalf"),
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::UndelegateFromMixnodeOnBehalf => {
f.write_str("UndelegateFromMixnodeOnBehalf")
}
Operation::UpdateContractSettings => f.write_str("UpdateContractSettings"),
Operation::BeginMixnodeRewarding => f.write_str("BeginMixnodeRewarding"),
Operation::FinishMixnodeRewarding => f.write_str("FinishMixnodeRewarding"),
Operation::TrackUnbondGateway => f.write_str("TrackUnbondGateway"),
Operation::TrackUnbondMixnode => f.write_str("TrackUnbondMixnode"),
Operation::WithdrawVestedCoins => f.write_str("WithdrawVestedCoins"),
Operation::TrackUndelegation => f.write_str("TrackUndelegation"),
Operation::CreatePeriodicVestingAccount => f.write_str("CreatePeriodicVestingAccount"),
}
}
}
@@ -58,23 +84,34 @@ impl Operation {
// TODO: some value tweaking
pub fn default_gas_limit(&self) -> Gas {
match self {
Operation::Upload => 2_500_000u64.into(),
Operation::Upload => 3_000_000u64.into(),
Operation::Init => 500_000u64.into(),
Operation::Migrate => 200_000u64.into(),
Operation::ChangeAdmin => 80_000u64.into(),
Operation::Send => 80_000u64.into(),
Operation::BondMixnode => 175_000u64.into(),
Operation::BondMixnodeOnBehalf => 200_000u64.into(),
Operation::UnbondMixnode => 175_000u64.into(),
Operation::UnbondMixnodeOnBehalf => 175_000u64.into(),
Operation::DelegateToMixnode => 175_000u64.into(),
Operation::DelegateToMixnodeOnBehalf => 175_000u64.into(),
Operation::UndelegateFromMixnode => 175_000u64.into(),
Operation::UndelegateFromMixnodeOnBehalf => 175_000u64.into(),
Operation::BondGateway => 175_000u64.into(),
Operation::BondGatewayOnBehalf => 200_000u64.into(),
Operation::UnbondGateway => 175_000u64.into(),
Operation::DelegateToGateway => 175_000u64.into(),
Operation::UndelegateFromGateway => 175_000u64.into(),
Operation::UnbondGatewayOnBehalf => 200_000u64.into(),
Operation::UpdateStateParams => 175_000u64.into(),
Operation::UpdateContractSettings => 175_000u64.into(),
Operation::BeginMixnodeRewarding => 175_000u64.into(),
Operation::FinishMixnodeRewarding => 175_000u64.into(),
Operation::TrackUnbondGateway => 175_000u64.into(),
Operation::TrackUnbondMixnode => 175_000u64.into(),
Operation::WithdrawVestedCoins => 175_000u64.into(),
Operation::TrackUndelegation => 175_000u64.into(),
Operation::CreatePeriodicVestingAccount => 175_000u64.into(),
}
}
@@ -88,9 +125,8 @@ impl Operation {
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)
pub fn default_fee(&self, gas_price: &GasPrice) -> Fee {
Self::determine_custom_fee(gas_price, self.default_gas_limit())
}
}
@@ -0,0 +1,33 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmrs::tx;
pub mod gas_price;
pub mod helpers;
pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: f32 = 1.3;
#[derive(Debug, Clone)]
pub enum Fee {
Manual(tx::Fee),
Auto(Option<f32>),
}
impl From<tx::Fee> for Fee {
fn from(fee: tx::Fee) -> Self {
Fee::Manual(fee)
}
}
impl From<f32> for Fee {
fn from(multiplier: f32) -> Self {
Fee::Auto(Some(multiplier))
}
}
impl Default for Fee {
fn default() -> Self {
Fee::Auto(Some(DEFAULT_SIMULATED_GAS_MULTIPLIER))
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod vesting_query_client;
mod vesting_signing_client;
pub use vesting_query_client::VestingQueryClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -0,0 +1,176 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::error::NymdError;
use crate::nymd::NymdClient;
use async_trait::async_trait;
use cosmwasm_std::{Coin, Timestamp};
use vesting_contract::messages::QueryMsg as VestingQueryMsg;
#[async_trait]
pub trait VestingQueryClient {
async fn locked_coins(
&self,
address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn spendable_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vested_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vesting_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vesting_start_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError>;
async fn vesting_end_time(&self, vesting_account_address: &str)
-> Result<Timestamp, NymdError>;
async fn original_vesting(&self, vesting_account_address: &str) -> Result<Coin, NymdError>;
async fn delegated_free(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn delegated_vesting(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn locked_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::LockedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn spendable_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::SpendableCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vested_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetVestedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetVestingCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_start_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError> {
let request = VestingQueryMsg::GetStartTime {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_end_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError> {
let request = VestingQueryMsg::GetEndTime {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn original_vesting(&self, vesting_account_address: &str) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetOriginalVesting {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn delegated_free(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetDelegatedFree {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn delegated_vesting(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetDelegatedVesting {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
}
@@ -0,0 +1,294 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::fee::helpers::Operation;
use crate::nymd::{cosmwasm_coin_to_cosmos_coin, NymdClient};
use async_trait::async_trait;
use cosmwasm_std::Coin;
use mixnet_contract::{Gateway, IdentityKey, IdentityKeyRef, MixNode};
use vesting_contract::messages::ExecuteMsg as VestingExecuteMsg;
#[async_trait]
pub trait VestingSigningClient {
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_gateway(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_mixnode(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn withdraw_vested_coins(&self, amount: Coin) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
) -> Result<ExecuteResult, NymdError>;
async fn create_periodic_vesting_account(
&self,
address: &str,
start_time: Option<u64>,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient<C> {
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondGateway);
let req = VestingExecuteMsg::BondGateway {
gateway,
owner_signature: owner_signature.to_string(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::BondGateway",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
)
.await
}
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UnbondGateway);
let req = VestingExecuteMsg::UnbondGateway {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UnbondGateway",
vec![],
)
.await
}
async fn vesting_track_unbond_gateway(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUnbondGateway);
let req = VestingExecuteMsg::TrackUnbondGateway {
owner: owner.to_string(),
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUnbondGateway",
vec![],
)
.await
}
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondMixnode);
let req = VestingExecuteMsg::BondMixnode {
mix_node,
owner_signature: owner_signature.to_string(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::BondMixnode",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
)
.await
}
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UnbondMixnode);
let req = VestingExecuteMsg::UnbondMixnode {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UnbondMixnode",
vec![],
)
.await
}
async fn vesting_track_unbond_mixnode(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUnbondMixnode);
let req = VestingExecuteMsg::TrackUnbondMixnode {
owner: owner.to_string(),
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUnbondMixnode",
vec![],
)
.await
}
async fn withdraw_vested_coins(&self, amount: Coin) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::WithdrawVestedCoins);
let req = VestingExecuteMsg::WithdrawVestedCoins { amount };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::WithdrawVested",
vec![],
)
.await
}
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUndelegation);
let req = VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_identity,
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUndelegation",
vec![],
)
.await
}
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::DelegateToMixnode);
let req = VestingExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::DeledateToMixnode",
vec![cosmwasm_coin_to_cosmos_coin(amount.to_owned())],
)
.await
}
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UndelegateFromMixnode);
let req = VestingExecuteMsg::UndelegateFromMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UndelegateFromMixnode",
vec![],
)
.await
}
async fn create_periodic_vesting_account(
&self,
address: &str,
start_time: Option<u64>,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::CreatePeriodicVestingAccount);
let req = VestingExecuteMsg::CreateAccount {
address: address.to_string(),
start_time,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::CreatePeriodicVestingAccount",
vec![cosmwasm_coin_to_cosmos_coin(amount)],
)
.await
}
}
@@ -10,7 +10,7 @@ use cosmrs::tx::SignDoc;
use cosmrs::{tx, AccountId};
/// Derivation information required to derive a keypair and an address from a mnemonic.
#[derive(Debug)]
#[derive(Debug, Clone)]
struct Secp256k1Derivation {
hd_path: DerivationPath,
prefix: String,
@@ -40,7 +40,7 @@ impl AccountData {
type Secp256k1Keypair = (SigningKey, PublicKey);
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct DirectSecp256k1HdWallet {
/// Base secret
secret: bip39::Mnemonic,
@@ -207,9 +207,9 @@ mod tests {
fn generating_account_addresses() {
// test vectors produced from our js wallet
let mnemonic_address = vec![
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2"),
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn"),
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", "punk17n9flp6jflljg6fp05dsy07wcprf2uuujse962")
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94"),
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv"),
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", "nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4")
];
for (mnemonic, address) in mnemonic_address.into_iter() {
@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
+1 -1
View File
@@ -8,4 +8,4 @@ description = "Crutch library until there is proper SerDe support for coconut st
serde = { version = "1.0", features = ["derive"] }
getset = "0.1.1"
coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.5.0" }
nymcoconut = {path = "../nymcoconut" }
+6 -6
View File
@@ -4,7 +4,7 @@
use getset::{CopyGetters, Getters};
use serde::{Deserialize, Serialize};
pub use coconut_rs::*;
pub use nymcoconut::*;
#[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)]
pub struct Credential {
@@ -42,7 +42,7 @@ impl Credential {
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
coconut_rs::verify_credential(&params, verification_key, &self.theta, &public_attributes)
nymcoconut::verify_credential(&params, verification_key, &self.theta, &public_attributes)
}
}
@@ -84,7 +84,7 @@ pub struct BlindSignRequestBody {
#[getset(get = "pub")]
blind_sign_request: BlindSignRequest,
#[getset(get = "pub")]
public_key: coconut_rs::PublicKey,
public_key: nymcoconut::PublicKey,
public_attributes: Vec<String>,
#[getset(get = "pub")]
total_params: u32,
@@ -92,13 +92,13 @@ pub struct BlindSignRequestBody {
impl BlindSignRequestBody {
pub fn new(
blind_sign_request: BlindSignRequest,
public_key: &coconut_rs::PublicKey,
blind_sign_request: &BlindSignRequest,
public_key: &nymcoconut::PublicKey,
public_attributes: &[Attribute],
total_params: u32,
) -> BlindSignRequestBody {
BlindSignRequestBody {
blind_sign_request,
blind_sign_request: blind_sign_request.clone(),
public_key: public_key.clone(),
public_attributes: public_attributes
.iter()
+2
View File
@@ -11,4 +11,6 @@ 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" }
-48
View File
@@ -1,48 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// for time being assume the bandwidth credential consists of public identity of the requester
// and private (though known... just go along with it) infinite bandwidth value
// right now this has no double-spending protection, spender binding, etc
// it's the simplest possible case
use url::Url;
use crate::error::Error;
use crate::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
use coconut_interface::{hash_to_scalar, Credential, Parameters, Signature, VerificationKey};
const BANDWIDTH_VALUE: u64 = 10 * 1024 * 1024 * 1024; // 10 GB
pub const PUBLIC_ATTRIBUTES: u32 = 1;
pub const PRIVATE_ATTRIBUTES: u32 = 1;
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
// TODO: this definitely has to be moved somewhere else. It's just a temporary solution
pub async fn obtain_signature(raw_identity: &[u8], validators: &[Url]) -> Result<Signature, Error> {
let public_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes())];
let private_attributes = vec![hash_to_scalar(raw_identity)];
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
obtain_aggregate_signature(&params, &public_attributes, &private_attributes, validators).await
}
pub fn prepare_for_spending(
raw_identity: &[u8],
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let public_attributes = vec![BANDWIDTH_VALUE.to_be_bytes().to_vec()];
let private_attributes = vec![raw_identity.to_vec()];
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
prepare_credential_for_spending(
&params,
public_attributes,
private_attributes,
signature,
verification_key,
)
}
@@ -0,0 +1,77 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// for time being assume the bandwidth credential consists of public identity of the requester
// and private (though known... just go along with it) infinite bandwidth value
// right now this has no double-spending protection, spender binding, etc
// it's the simplest possible case
use coconut_interface::{
Credential, Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey,
};
use network_defaults::BANDWIDTH_VALUE;
use url::Url;
use crate::error::Error;
use super::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
pub const PUBLIC_ATTRIBUTES: u32 = 2;
pub const PRIVATE_ATTRIBUTES: u32 = 2;
pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
pub struct BandwidthVoucherAttributes {
// a random secret value generated by the client used for double-spending detection
pub serial_number: PrivateAttribute,
// a random secret value generated by the client used to bind multiple credentials together
pub binding_number: PrivateAttribute,
// the value (e.g., bandwidth) encoded in this voucher
pub voucher_value: PublicAttribute,
// a field with public information, e.g., type of voucher, epoch etc.
pub voucher_info: PublicAttribute,
}
impl BandwidthVoucherAttributes {
pub fn get_public_attributes(&self) -> Vec<PublicAttribute> {
vec![self.voucher_value, self.voucher_info]
}
pub fn get_private_attributes(&self) -> Vec<PrivateAttribute> {
vec![self.serial_number, self.binding_number]
}
}
// TODO: this definitely has to be moved somewhere else. It's just a temporary solution
pub async fn obtain_signature(
params: &Parameters,
attributes: &BandwidthVoucherAttributes,
validators: &[Url],
) -> Result<Signature, Error> {
let public_attributes = attributes.get_public_attributes();
let private_attributes = attributes.get_private_attributes();
obtain_aggregate_signature(params, &public_attributes, &private_attributes, validators).await
}
pub fn prepare_for_spending(
raw_identity: &[u8],
signature: &Signature,
attributes: &BandwidthVoucherAttributes,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let public_attributes = vec![
raw_identity.to_vec(),
BANDWIDTH_VALUE.to_be_bytes().to_vec(),
];
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
prepare_credential_for_spending(
&params,
public_attributes,
attributes.serial_number,
attributes.binding_number,
signature,
verification_key,
)
}
+5
View File
@@ -0,0 +1,5 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod bandwidth;
pub mod utils;
@@ -1,14 +1,16 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Error;
use coconut_interface::{
aggregate_signature_shares, aggregate_verification_keys, hash_to_scalar, prepare_blind_sign,
prove_credential, Attribute, BlindSignRequestBody, Credential, Parameters, Signature,
aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign,
prove_bandwidth_credential, Attribute, BlindSignRequestBody, Credential, Parameters, Signature,
SignatureShare, VerificationKey,
};
use url::Url;
use crate::coconut::bandwidth::PRIVATE_ATTRIBUTES;
use crate::error::Error;
/// Contacts all provided validators and then aggregate their verification keys.
///
/// # Arguments
@@ -63,17 +65,18 @@ async fn obtain_partial_credential(
public_attributes: &[Attribute],
private_attributes: &[Attribute],
client: &validator_client::ApiClient,
validator_vk: &VerificationKey,
) -> Result<Signature, Error> {
let elgamal_keypair = coconut_interface::elgamal_keygen(params);
let blind_sign_request = prepare_blind_sign(
params,
elgamal_keypair.public_key(),
&elgamal_keypair,
private_attributes,
public_attributes,
)?;
let blind_sign_request_body = BlindSignRequestBody::new(
blind_sign_request,
&blind_sign_request,
elgamal_keypair.public_key(),
public_attributes,
(public_attributes.len() + private_attributes.len()) as u32,
@@ -83,7 +86,17 @@ async fn obtain_partial_credential(
.blind_sign(&blind_sign_request_body)
.await?
.blinded_signature;
Ok(blinded_signature.unblind(elgamal_keypair.private_key()))
let unblinded_signature = blinded_signature.unblind(
params,
elgamal_keypair.private_key(),
validator_vk,
private_attributes,
public_attributes,
&blind_sign_request.get_commitment_hash(),
)?;
Ok(unblinded_signature)
}
pub async fn obtain_aggregate_signature(
@@ -97,40 +110,76 @@ pub async fn obtain_aggregate_signature(
}
let mut shares = Vec::with_capacity(validators.len());
let mut validators_partial_vks: Vec<VerificationKey> = Vec::with_capacity(validators.len());
let mut client = validator_client::ApiClient::new(validators[0].clone());
let first =
obtain_partial_credential(params, public_attributes, private_attributes, &client).await?;
let validator_partial_vk = client.get_coconut_verification_key().await?;
validators_partial_vks.push(validator_partial_vk.key.clone());
let first = obtain_partial_credential(
params,
public_attributes,
private_attributes,
&client,
&validator_partial_vk.key,
)
.await?;
shares.push(SignatureShare::new(first, 0));
for (id, validator_url) in validators.iter().enumerate().skip(1) {
client.change_validator_api(validator_url.clone());
let signature =
obtain_partial_credential(params, public_attributes, private_attributes, &client)
.await?;
let validator_partial_vk = client.get_coconut_verification_key().await?;
validators_partial_vks.push(validator_partial_vk.key.clone());
let signature = obtain_partial_credential(
params,
public_attributes,
private_attributes,
&client,
&validator_partial_vk.key,
)
.await?;
let share = SignatureShare::new(signature, id as u64);
shares.push(share)
}
Ok(aggregate_signature_shares(&shares)?)
let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len());
attributes.extend_from_slice(private_attributes);
attributes.extend_from_slice(public_attributes);
let mut indices: Vec<u64> = Vec::with_capacity(validators_partial_vks.len());
for i in 1..validators_partial_vks.len() {
indices.push(i as u64);
}
let verification_key =
aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?;
Ok(aggregate_signature_shares(
params,
&verification_key,
&attributes,
&shares,
)?)
}
// TODO: better type flow
pub fn prepare_credential_for_spending(
params: &Parameters,
public_attributes: Vec<Vec<u8>>,
private_attributes: Vec<Vec<u8>>,
serial_number: Attribute,
binding_number: Attribute,
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let private_attributes = private_attributes
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
let theta = prove_credential(params, verification_key, signature, &private_attributes)?;
let theta = prove_bandwidth_credential(
params,
verification_key,
signature,
serial_number,
binding_number,
)?;
Ok(Credential::new(
(public_attributes.len() + private_attributes.len()) as u32,
(public_attributes.len() + PRIVATE_ATTRIBUTES as usize) as u32,
theta,
public_attributes,
signature,
+3 -3
View File
@@ -1,8 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod bandwidth;
pub mod coconut;
pub mod error;
mod utils;
pub mod token;
pub use utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key};
+140
View File
@@ -0,0 +1,140 @@
// 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
@@ -0,0 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod bandwidth;
+27 -18
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
pub use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
pub use ed25519_dalek::SignatureError;
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
use nymsphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
@@ -10,33 +10,33 @@ use rand::{CryptoRng, RngCore};
use std::fmt::{self, Display, Formatter};
#[derive(Debug)]
pub enum KeyRecoveryError {
pub enum Ed25519RecoveryError {
MalformedBytes(SignatureError),
MalformedString(bs58::decode::Error),
}
impl From<SignatureError> for KeyRecoveryError {
impl From<SignatureError> for Ed25519RecoveryError {
fn from(err: SignatureError) -> Self {
KeyRecoveryError::MalformedBytes(err)
Ed25519RecoveryError::MalformedBytes(err)
}
}
impl From<bs58::decode::Error> for KeyRecoveryError {
impl From<bs58::decode::Error> for Ed25519RecoveryError {
fn from(err: bs58::decode::Error) -> Self {
KeyRecoveryError::MalformedString(err)
Ed25519RecoveryError::MalformedString(err)
}
}
impl fmt::Display for KeyRecoveryError {
impl fmt::Display for Ed25519RecoveryError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
KeyRecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err),
KeyRecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err),
Ed25519RecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err),
Ed25519RecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err),
}
}
}
impl std::error::Error for KeyRecoveryError {}
impl std::error::Error for Ed25519RecoveryError {}
/// Keypair for usage in ed25519 EdDSA.
pub struct KeyPair {
@@ -62,7 +62,7 @@ impl KeyPair {
&self.public_key
}
pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result<Self, KeyRecoveryError> {
pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result<Self, Ed25519RecoveryError> {
Ok(KeyPair {
private_key: PrivateKey::from_bytes(priv_bytes)?,
public_key: PublicKey::from_bytes(pub_bytes)?,
@@ -115,7 +115,7 @@ impl PublicKey {
self.0.to_bytes()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
Ok(PublicKey(ed25519_dalek::PublicKey::from_bytes(b)?))
}
@@ -123,7 +123,7 @@ impl PublicKey {
bs58::encode(self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, KeyRecoveryError> {
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
@@ -134,7 +134,7 @@ impl PublicKey {
}
impl PemStorableKey for PublicKey {
type Error = KeyRecoveryError;
type Error = Ed25519RecoveryError;
fn pem_type() -> &'static str {
"ED25519 PUBLIC KEY"
@@ -170,7 +170,7 @@ impl PrivateKey {
self.0.to_bytes()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
Ok(PrivateKey(ed25519_dalek::SecretKey::from_bytes(b)?))
}
@@ -178,7 +178,7 @@ impl PrivateKey {
bs58::encode(&self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, KeyRecoveryError> {
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
@@ -192,7 +192,7 @@ impl PrivateKey {
}
impl PemStorableKey for PrivateKey {
type Error = KeyRecoveryError;
type Error = Ed25519RecoveryError;
fn pem_type() -> &'static str {
"ED25519 PRIVATE KEY"
@@ -211,11 +211,20 @@ impl PemStorableKey for PrivateKey {
pub struct Signature(ed25519_dalek::Signature);
impl Signature {
pub fn to_base58_string(&self) -> String {
bs58::encode(&self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
pub fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] {
self.0.to_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureError> {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Ed25519RecoveryError> {
Ok(Signature(ed25519_dalek::Signature::from_bytes(bytes)?))
}
}
+10
View File
@@ -0,0 +1,10 @@
[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
@@ -0,0 +1,45 @@
// 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
@@ -0,0 +1,3 @@
pub mod keys;
pub mod msg;
pub mod payment;
+30
View File
@@ -0,0 +1,30 @@
// 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 {}
@@ -0,0 +1,73 @@
// 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,
}
}
}

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