Compare commits

...

327 Commits

Author SHA1 Message Date
Jędrzej Stuczyński ca6cea4acf Added #[serde(default)] on fields we dont care about anymore 2022-01-26 11:10:57 +00:00
Tommy Verrall 2da6fdc2e8 Merge pull request #1067 from nymtech/feature/wallet-inclusion-probability
set-up inclusion probability
2022-01-25 17:05:01 +00:00
Jędrzej Stuczyński f7574924a8 Mixnet Contract constants extraction (#1060)
* Extracted constants that could realistically be controlled by governance to constants.rs

Also made interval control be more explicit in the contract

* Extracted active set work factor to a constant

* Required type changes in wallet code

* [ci skip] Generate TS types

* Missing change in test code

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2022-01-25 16:46:46 +00:00
fmtabbara 5d07115706 refinements and small bug fix 2022-01-25 13:58:15 +00:00
fmtabbara 9e994dfd55 set-up inclusion probability 2022-01-25 11:51:36 +00:00
Jon Häggblad 59bc7cb53d Upgrade Clap and use declarative argument parsing for nym-mixnode (#1047)
* mixnode: upgrade clap and use declarative cli parsing

* mixnode: map argument to enum for sign

* mixnode: address review comments
2022-01-25 12:51:26 +01:00
Jędrzej Stuczyński 655ff9bffb De-'float'-ing Interval (Display impl + serde) (#1065)
* Updated time to 0.3.6

* Changed Display impl for Interval so it doesnt use floats

* Explicit rfc3339 datetime serialization

* Typo

* Changed 'visit_borrwed_str' to 'visit_str'
2022-01-25 11:17:44 +00:00
Bogdan-Ștefan Neacşu a03cb1ef9f Feature/wasm client (#1066)
* Fix wasm client

* Re-enable CI on wasm client

There is an `unused-unit` lint that will fail for now, but this is
regarded as a false positive and should eventualy get fixed:

https://github.com/rustwasm/wasm-bindgen/issues/2774

* The wasm tests would be run under native arch
2022-01-24 18:48:44 +02:00
Jędrzej Stuczyński 60f965ec52 Made contract addresses for query NymdClient construction optional (#1055)
Similarly as it is the case when creating SigningNymdClient
2022-01-24 15:43:20 +00:00
Jędrzej Stuczyński 8d26e48a5b Introduced RPC query for total token supply (#1053)
* Introduced RPC query for total token supply

* Cargo fmt
2022-01-24 15:43:10 +00:00
Jędrzej Stuczyński 94527ab594 Changed bech32_prefix from punk to nymt (#1064) 2022-01-24 12:40:36 +00:00
Tommy Verrall e312a28aad Merge pull request #1059 from martinyung/develop
fix: make explorer footer year dynamic
2022-01-24 11:39:42 +00:00
dependabot[bot] e84a0c4438 Bump nanoid from 3.1.30 to 3.2.0 in /nym-wallet (#1062)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.2.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.2.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-24 10:18:11 +00:00
dependabot[bot] 6f1a0d987d Bump nanoid from 3.1.30 to 3.2.0 in /testnet-faucet (#1063)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.2.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.2.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-24 10:18:02 +00:00
Yung Chun Ern Martin 3caa4c15ca fix: make explorer footer year dynamic 2022-01-22 00:11:13 +08:00
Tommy Verrall 741131f376 Merge pull request #1058 from nymtech/bug-fix/display-client-address
display client address on wallet creation
2022-01-21 15:54:59 +00:00
fmtabbara ae6f161cd6 display client address on wallet creation 2022-01-21 15:49:01 +00:00
Tommy Verrall b940c87d64 Merge pull request #1057 from nymtech/feature/fix_wallet_mnemonic
Add mnemonic just on creation, to display it
2022-01-21 15:41:28 +00:00
Drazen Urch fe6c685ab1 Feature/hourly set updates (#1012)
* Rename function/variables mixnodes->set

* Stub utility interface

* Rewarded set contract interface

* Move epoch to common, epoch to contract

* Move epoch to the chain

* Rewarded set validator-api

* [ci skip] Generate TS types

* Epoch queries

* Moved new code to a new module

* Restored cosmwasm dependencies to their beta.3 versions for better compatibility with the rest of the codebase

* Rewarded set write reorganisation

* Stub for validator api module  responsible for rewarded set updates

* Reorganised validator api cache

* Pending contract changes

* Relevant updates to the validator client

* Updating rewarded set based on contract state

* Advancing/Setting current epoch in the contract

* Using blocktime as 'now' at startup

* Adjusted validator-api side rewarding code

* Contract cleanup + query for epoch rewarded set heights

* [ci skip] Generate TS types

* Simplified rewarder processing loop and initial sync

* [ci skip] Generate TS types

* Fixed EXISTING query-related unit tests

* Fixed existing unit tests for rewarding-related transactions

* Cargo fmt

* Removed some dead code

* Using cosmwasm 1.0.0-beta3 for compatibility [with cw-storage-plus and rest of codebase]

* Missing TryInto import

* Additional storage and query related unit tests + a bug fix

* Transaction-related unit tests + bug fixes

* Required migration code

* Update common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs

Co-authored-by: Drazen Urch <drazen@urch.eu>

* Update common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs

Co-authored-by: Drazen Urch <drazen@urch.eu>

* Constant renaming

* Changed determining previous epoch return type to Option<Epoch> if they would precede the genesis

* Exposed the new endpoint to the wallet

* Epoch-related unit tests fixes

* Recommended #[must_use] on next_epoch method

* Renamed all epoch occurences to interval

As they refer to the 'rewarding interval'

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
2022-01-21 14:04:23 +00:00
Bogdan-Ștefan Neacșu c64c36022f Add mnemonic just on creation, to display it 2022-01-21 14:57:42 +02:00
Mark Sinclair e52fe65985 Network Explorer: updates to API and UI to show the active set (#1056)
* Add identicons package

* Tidy up styling and move methods into component directories with better naming

* Add mixnode status colours to theme

* Mixnode status and icon components

* Add status to mixnode types

* Add API method to get mixnode details

* Add mixnode details to state

* Add status and name+description section to mixnode detail page

* Wrap with div instead of p

* Limit width of description and link to new tab

* Limit length of link button and truncate with elipsis

* Replace `filter` with `find`

* Move mix node detail components to a location that is better named

* Refactor mixnode detail state and separate into an independent context from main state.

This prevents the mixnode detail page from showing stale data when switching between mix nodes.

* Tidy up mixnode detail page adding new state provider and a guard component to handle loading, error and not found states

* Layout changes to mixnode description header section

* Add methods to Explorer API client to get a mixnode by id, active set by status and overview summary

* Add color prop to StatsCard and make count optional

* Add optional start and end children to TableToolbar

* Tidy up naming

* Add summary overview and getting mixnodes by active set status to main state

* Add mix node status overview cards

* Add mix node status to routes

* Mixnode list has a dropdown component to select the active set status

* Clean up caching code

* Add resource to get a single mixnode by id

* Add API resources to get `active`, `inactive` and `standby` mixnodes

* Add mixnode summary to API

* Add overview summary endpoint to API

* Fix OpenAPI/swagger base url

* Make clippy happy

* Add method to get validators

* Add methods to get active and rewarded mixnodes

* Fix naming

* Move client creation to crate root

* Move cache to module

* Delete unused files

* Add validators API resource

* Add gateways API resource

* Move tasks to crate root

* Add new HTTP resources for validators and gateways to routes

* Tidy up naming and locations for mixnodes

* Add validator and gateways to state, and tidy up naming

* Add gateways and validator modules to main

* Overview shows validator and gateway summaries from state

* Bundle variable weight Open Sans fonts

* Fix up font weights and sizes

* Fix up typing

* Fix up social icons

* Fix navbar colour

* Fix paper colour in dark mode and border radius

* Fix up stats card

* Tidy up Nym icons

* Fix up overview

* Fix up spacing and padding for overview

* Add light mode shades that are darker for mixnode status values

* Review feedback
2022-01-21 11:28:59 +00:00
Tommy Verrall fea64d4d4f Merge pull request #1052 from nymtech/feature/tokio-console
Feature/tokio console
2022-01-20 12:23:29 +00:00
durch 6ff02bc2a1 Fix wallet clippy lints 2022-01-20 11:31:35 +01:00
durch 8b166f12f8 Instrument tokio console 2022-01-20 11:17:33 +01:00
Jędrzej Stuczyński ecdbe034fa Implemented beta clippy lint recommendations (#1051) 2022-01-19 20:32:48 +01:00
Tommy Verrall 3e46c8630d Merge pull request #1050 from nymtech/update/validator-client-profit-percentage
add new function to update profit percentage
2022-01-19 16:18:47 +00:00
fmtabbara 93e962524a update types 2022-01-19 14:05:43 +00:00
dependabot[bot] 5b6c1c032c Bump shelljs in /contracts/basic-bandwidth-generation (#1043)
Bumps [shelljs](https://github.com/shelljs/shelljs) from 0.8.4 to 0.8.5.
- [Release notes](https://github.com/shelljs/shelljs/releases)
- [Changelog](https://github.com/shelljs/shelljs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/shelljs/shelljs/compare/v0.8.4...v0.8.5)

---
updated-dependencies:
- dependency-name: shelljs
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-19 14:00:19 +00:00
dependabot[bot] 135f1a6e7f Bump follow-redirects in /contracts/basic-bandwidth-generation (#1041)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.4 to 1.14.7.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.4...v1.14.7)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-19 14:00:12 +00:00
dependabot[bot] c61f89144e Bump follow-redirects from 1.14.5 to 1.14.7 in /testnet-faucet (#1040)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.5 to 1.14.7.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.5...v1.14.7)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-19 14:00:06 +00:00
Tommy Verrall 67fe368e65 Merge pull request #1033 from nymtech/feature/configurable_wallet
Feature/configurable wallet
2022-01-19 10:36:17 +00:00
Bogdan-Ștefan Neacşu 522229459b Fix clippy on relevant lints (#1044)
* Fix clippy on relevant lints

return_self_not_must_use still produces errors, but that will be
auto-fixed once the change to move it to pedantic is released to beta
channel

* Run fmt
2022-01-18 21:37:51 +01:00
Drazen Urch e3d8b71ea2 Endpoint for rewarded set inclusion probabilities (#1042)
* Add validator-api endpoint

* Add validator-client method

* Make it a bit nicer

* Address review comments

* Cap probability at 1.
2022-01-18 21:26:15 +01:00
Fouad 4f98fde362 Merge pull request #1046 from nymtech/feature/additional-bond-validation
Feature/additional bond validation
2022-01-18 12:57:14 +00:00
fmtabbara aa75e54419 dont display warnings on successful bond or delegate 2022-01-18 11:45:27 +00:00
fmtabbara 5190a541a6 add warning to delegate page 2022-01-18 11:30:56 +00:00
fmtabbara 3a39fff006 PR updates 2022-01-18 10:16:36 +00:00
fmtabbara 0e302b83ab add warning for unbonding 2022-01-17 18:42:13 +00:00
Fouad 0d0637fe19 Merge pull request #1036 from nymtech/feature/node-settings-update
Feature/node settings update
2022-01-17 18:16:24 +00:00
fmtabbara 1f0c0090dc merge develop 2022-01-17 17:53:14 +00:00
fmtabbara 4f960330b1 Merge branch 'develop' into feature/additional-bond-validation
merge develop
2022-01-17 17:36:41 +00:00
fmtabbara a273980aa0 refactor 2022-01-17 17:34:39 +00:00
neacsu 13a55f00d8 [ci skip] Generate TS types 2022-01-17 15:50:06 +00:00
Bogdan-Ștefan Neacșu 4feec780d4 Use fn new() for Account 2022-01-17 17:39:21 +02:00
Bogdan-Ștefan Neacșu 35c044c340 Store all clients and discard mnemonic 2022-01-17 17:24:52 +02:00
Bogdan-Ștefan Neacșu ac5539a0fa Export ts type file 2022-01-17 14:44:43 +02:00
Bogdan-Ștefan Neacșu 9c569cbb62 Expose switch_network to frontend 2022-01-17 14:13:15 +02:00
Bogdan-Ștefan Neacșu 72485cacd3 Pass possible network values using Network type
Export a Network type to TS and make seamless transitions between this
type and the network defaults one. We may have more networks supported
in the backend then in the frontend at a certain moment in time.
2022-01-17 13:38:10 +02:00
Drazen Urch 56d36d2c46 Migrate to cw-storage-plus 0.11.1 (#1035) 2022-01-14 20:59:40 +01:00
Drazen Urch 8fb54dd4e7 Feature/downcast reward estimation (#1031)
* Downcast u128 to u64

* fmt

* Fix status

* fmt
2022-01-14 20:57:51 +01:00
dependabot[bot] 44d59ff8c2 Bump @openzeppelin/contracts in /contracts/basic-bandwidth-generation (#1034)
Bumps [@openzeppelin/contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) from 4.4.1 to 4.4.2.
- [Release notes](https://github.com/OpenZeppelin/openzeppelin-contracts/releases)
- [Changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md)
- [Commits](https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.4.1...v4.4.2)

---
updated-dependencies:
- dependency-name: "@openzeppelin/contracts"
  dependency-type: direct:production
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-14 15:50:57 +00:00
fmtabbara a8caf19f8c additional balance check before bonding 2022-01-14 14:32:16 +00:00
fmtabbara 53b44fb9c6 remove unused import 2022-01-13 17:30:11 +00:00
fmtabbara c0959e853e spacing updates 2022-01-13 17:07:18 +00:00
fmtabbara 144df00782 fix conflicts and refine UI 2022-01-13 16:40:59 +00:00
Fouad be4708cc84 Merge pull request #1028 from nymtech/feature/ui-updates
Wallet UI updates
2022-01-13 15:45:36 +00:00
fmtabbara d5cddec03c merge develop 2022-01-13 15:36:06 +00:00
fmtabbara 7c26cab4e6 get updated mixnode details after bonding/unbonding txs 2022-01-13 15:32:29 +00:00
Bogdan-Ștefan Neacșu f0bcf8c36f Update wallet backend 2022-01-13 17:20:17 +02:00
Bogdan-Ștefan Neacșu ac2f0a172e Store multiple network values in one place 2022-01-13 17:20:17 +02:00
Bogdan-Ștefan Neacșu 898070bc87 Rename some defaults 2022-01-13 17:20:17 +02:00
Bogdan-Ștefan Neacșu cc707660aa Expose both default and specific network values 2022-01-13 17:20:17 +02:00
fmtabbara 31624cf4e4 fix Rust u128 -> TS BiInt issue 2022-01-13 11:22:04 +00:00
fmtabbara e6a69170a4 raise mixnodedetails to main context 2022-01-13 11:22:04 +00:00
Bogdan-Ștefan Neacşu bc5e19514e Remove migration code (#1027) 2022-01-13 11:59:56 +01:00
fmtabbara 5c76b8483e add new function to update profit percentage 2022-01-13 10:10:42 +00:00
fmtabbara a9526c216f [ci skip] Generate TS types 2022-01-12 21:49:08 +00:00
fmtabbara 903af16a6b update settings state 2022-01-12 21:39:12 +00:00
fmtabbara 0de79b6953 create fee component 2022-01-12 21:38:06 +00:00
fmtabbara fd2fafb52a [ci skip] Generate TS types 2022-01-12 20:39:36 +00:00
fmtabbara fadb5b4ff9 update ui and state 2022-01-12 20:30:56 +00:00
fmtabbara 955583d0f0 merge develop 2022-01-12 16:04:48 +00:00
Fouad 3924c53d09 Merge pull request #1014 from nymtech/feature/update-profit-percentage
update frontend to use new profit update api
2022-01-12 15:33:28 +00:00
fmtabbara c0025ee9c6 update getfee function 2022-01-12 15:30:47 +00:00
fmtabbara 7dd0516b63 only request when settings open 2022-01-12 14:07:46 +00:00
fmtabbara d3cd3c9157 Merge branch 'develop' into feature/update-profit-percentage
merge develop
2022-01-12 12:49:59 +00:00
fmtabbara 83680473e0 Merge branch 'develop' into feature/additional-node-details
merge develop
2022-01-12 11:38:08 +00:00
fmtabbara 7f9a9f7a0a update node icon 2022-01-12 11:03:48 +00:00
fmtabbara e7ccb38502 use fullwidth input 2022-01-12 10:13:44 +00:00
Bogdan-Ștefan Neacşu 1f4c19d396 Add network defaults for qa (#1017)
* Add network defaults for qa

* update contract addresses

- have not updated the bandwith credential address - currently vesting

Co-authored-by: Tommy Verrall <tommyvez@protonmail.com>
2022-01-12 09:56:20 +01:00
fmtabbara 64842f40d7 integrate with new api 2022-01-11 19:41:16 +00:00
fmtabbara 2ec18721fc Merge branch 'develop' into feature/ui-updates
merge develop
2022-01-11 18:14:11 +00:00
fmtabbara 2ef1ac452f fix bug - send from address not showing 2022-01-11 18:11:46 +00:00
Jędrzej Stuczyński 6b3700aefe Feature/expanded events (#1015)
* Expanded emitted events for delegation-related transactions

* Expanded emitted events for gateway-related transactions

* Expanded emitted events for mixnode-related transactions

* Expanded emitted events for settings update transaction

* Expanded emitted events for rewarding-related transactions

* Fixed attribute look up in tests

* Making linter happier

* Reorganised cosmwasm contract-related modules

* Introduced similar event handling to the vesting contract
2022-01-11 16:56:12 +00:00
Jędrzej Stuczyński e2e06df4e6 Feature/validator api client endpoints (#1024)
* Moved mixnode status route to node status api module

* Introduced validator-api endpoint for estimating mixnode's reward

* Stake saturation endpoint

* kebab-cased coconut routes

* Created separate crate for validator API models

* Additional routes in validator API client

* Introduced support for new queries in the wallet

* Typescript type derivation

* Fixed up date in license notice
2022-01-11 16:37:07 +00:00
Jędrzej Stuczyński 835d4f46f6 Introduced denom check when trying to withdraw vested coins (#1018)
* Introduced denom check when trying to withdraw vested coins

* Using correct denom in the relevant unit test
2022-01-11 16:31:39 +00:00
Jędrzej Stuczyński d71ef635e2 Restricted blake3 dependency (#1025) 2022-01-11 16:22:55 +00:00
Bogdan-Ștefan Neacşu 6e3773a095 Feature/remove unused profit margin (#1011)
* Remove unused field, to avoid confusion

* Add migration code

* Update tests

* Make clippy happy
2022-01-11 16:05:15 +01:00
Jędrzej Stuczyński 050d370396 Updated cosmrs to 0.4.1 (#1023) 2022-01-11 11:26:34 +00:00
Jędrzej Stuczyński 29340ed00c Feature/additional mixnode endpoints (#1019)
* Moved mixnode status route to node status api module

* Introduced validator-api endpoint for estimating mixnode's reward

* Stake saturation endpoint
2022-01-11 09:38:39 +00:00
fmtabbara 2b062b3e5b add link to network explorer 2022-01-10 23:00:17 +00:00
fmtabbara b405adb9e5 add icons to card headers and balance padding 2022-01-10 22:36:12 +00:00
fmtabbara 5c3c0ac39e remove border lines and grey bg for card component 2022-01-10 21:17:47 +00:00
fmtabbara 1cc06ef349 update get_approx_fee to new function name _outdated_get_approx_fee 2022-01-10 17:31:43 +00:00
fmtabbara 2bef1603ab style updates 2022-01-10 17:27:01 +00:00
Mx 11a458a43d Merge pull request #1020 from RiccardoMasutti/patch-1
Fix 404 link
2022-01-10 18:00:58 +01:00
Jędrzej Stuczyński 1fbf37e0ec Changed wallet's client to a full validator client (#1021)
So that it could use validator API calls in the near future
2022-01-10 11:17:08 +00:00
Riccardo Masutti bc8efda08f Add wallet build instructions
Added wallet link
2022-01-09 16:00:17 +01:00
Riccardo Masutti cecd0b2b0a Fix 404 link 2022-01-09 15:56:45 +01:00
Jędrzej Stuczyński 62fa2ae5e4 Feature/node state endpoint (#1013)
* Introduced route to check mixnode's status (active/standby/inactive/not_found)

* Restored default validator API caching interval to a more sane value

* Changed status route
2022-01-07 11:57:38 +00:00
fmtabbara db2ce8070c display mixnode update fee 2022-01-06 16:16:32 +00:00
fmtabbara 70138ff54a update frontend to use new profit update api 2022-01-06 13:00:50 +00:00
Bogdan-Ștefan Neacşu 30e93c33bb Feature/configure profit (#1008)
* Introduce a method to update mixnode configuration

Right now, only for profit_margin_percent

* Check that the new profit margin is valid

* Extend a bit the test coverage of mixnode update

* Create validator client function

* [ci skip] Generate TS types

* Update wallet

* Update the bond height as well, as if a rebond was made

Co-authored-by: neacsu <neacsu@users.noreply.github.com>
2022-01-06 13:03:14 +01:00
Jędrzej Stuczyński ec4955f814 Feature/explorer node status (#1010)
* Restored mixnode refresh rate to a more sane value

* Moved PrettyMixNodeBondWithLocation to models.rs

* Renaming

* Exposed ability to query for rewarded mixnodes in the validator client

* Reorganised mix_nodes module

* Determining mixnode status (active/standby/inactive)

* Moved LocationCache to separate lock

* Minor cleanup

* Changed serialization case of status enum

* Made clippy happier

* Slightly better grammar
2022-01-06 10:38:14 +00:00
Drazen Urch e013517348 Use serial integer instead of random (#1009)
* Use serial integer instead of random

* [ci skip] Generate TS types

* cargo fmt

* Return u32
2022-01-05 15:28:17 +01:00
Bogdan-Ștefan Neacșu 2041b03046 Missed takes_value() call 2022-01-04 17:12:41 +01:00
Bogdan-Ștefan Neacşu 0b6adf59ce Feature/fix gateway sign (#1004)
* Include version check in sign command

* Ask for wallet address the same way as mixnode

The reason for this is that the cosmos mnemonic that is asked at
init is for the address that gets rewarded for gateway usage.
Since that address is not necessarly set now and it can take a
default value, we won't be using that to derive the address in the
signing process.
2022-01-04 13:49:24 +01:00
Jędrzej Stuczyński d95df4b286 Feature/implicit runtime (#973)
* Made mixnode runtime implicit

* Ibid for native and socks5 clients
2022-01-04 11:00:48 +00:00
Bogdan-Ștefan Neacşu 4f109169af Fix clippy (#1003) 2022-01-03 14:32:37 +01:00
Bogdan-Ștefan Neacșu 0cef1abbb2 Update mixnode version 2021-12-23 15:05:11 +02:00
Tommy Verrall 1871c6b2e3 Merge pull request #998 from nymtech/feature/wallet-update-version
Update wallet version
2021-12-23 10:01:13 +00:00
Tommy Verrall 75ad2a113f Update wallet version
- keep all the binaries aligned
2021-12-23 10:00:13 +00:00
Bogdan-Ștefan Neacşu 1d1496aa49 Make the separation between testnet-mode and erc20 bandwidth mode clearer (#994)
* Make the separation between testnet-mode and erc20 bandwidth mode more clear

* Update Cargo.toml

* Remove eth bw from native client under a feature flag

* Remove eth bw from socks5 client under a feature flag

* Remove eth bw from gateway under a feature flag

* Update gateway version

* Fix coconut build warnings
2021-12-23 11:55:01 +02:00
Tommy Verrall a48e06fe51 Fix wallet build instructions (#997)
* Fix wallet build instructions

- Supplying the .env file requirement

* re-added the admin - address
2021-12-23 11:54:34 +02:00
Drazen Urch 614b99a36e Differentiate staking and ownership (#961)
* Differentiate staking and ownership

* Ownership transfer, tests

* Consistent random keys

* Improve account tests

* Update Cargo.lock

* Make everybody happy

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
2021-12-22 13:22:46 +01:00
dependabot[bot] d8cb6199e0 Bump @openzeppelin/contracts in /contracts/basic-bandwidth-generation (#983)
Bumps [@openzeppelin/contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) from 3.4.0 to 4.4.1.
- [Release notes](https://github.com/OpenZeppelin/openzeppelin-contracts/releases)
- [Changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md)
- [Commits](https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.4.0...v4.4.1)

---
updated-dependencies:
- dependency-name: "@openzeppelin/contracts"
  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-22 11:42:37 +01:00
Tommy Verrall 424c1695b3 Merge pull request #991 from nymtech/feature/change-wallet-version
Update wallet to align with versioning on nodes and gateways
2021-12-21 11:39:12 +00:00
Tommy Verrall 3ceb6d711f Update wallet to align with versioning on nodes and gateways 2021-12-21 11:38:13 +00:00
Dave Hrycyszyn 23d2279549 Merge branch 'develop' of github.com:nymtech/nym into develop 2021-12-21 11:21:28 +00:00
Dave Hrycyszyn 84d1909b18 Changelog for v0.12.0 2021-12-21 11:21:20 +00:00
Dave Hrycyszyn 29a22e95e6 Adding github_changelog_generator config files 2021-12-21 11:21:00 +00:00
Jess 0e0f9ed270 Update README.md 2021-12-21 10:34:29 +00:00
Jess 4c0c0bc49f Update README.md 2021-12-21 10:33:32 +00:00
Tommy Verrall ea350ef7dd Merge pull request #990 from nymtech/feature/fix-success-text
Fix success view messages.
2021-12-21 10:16:41 +00:00
Bogdan-Ștefan Neacșu 112820ad7b Fix rebase going wrong 2021-12-21 12:15:07 +02:00
Tommy Verrall fe27cbe7e2 Fix success view messages. 2021-12-21 10:07:35 +00:00
Bogdan-Ștefan Neacșu bd892e00bd Switch to sandbox as default build 2021-12-21 11:16:30 +02:00
Dave Hrycyszyn 8d2863e085 Back down on caching and we're good to redeploy 2021-12-20 18:01:19 +00:00
Dave Hrycyszyn 89cb931775 Cranking caching back up 2021-12-20 17:25:29 +00:00
Dave Hrycyszyn 0f58fb6437 Cutting client-side caching from 3.5 minutes to 5 seconds 2021-12-20 16:40:26 +00:00
Dave Hrycyszyn 837575c8d3 Knocking down cache interval some more 2021-12-20 15:19:48 +00:00
Dave Hrycyszyn 4cbe789f42 Merge branch 'develop' of github.com:nymtech/nym into develop 2021-12-20 15:13:29 +00:00
Dave Hrycyszyn 822c993f24 Setting cache to 2 seconds for explorer api 2021-12-20 15:13:22 +00:00
Bogdan-Ștefan Neacşu 9480233ca3 Feature/enable signature check (#989)
* Revert "Do not set proxy only for this time"

This reverts commit 47946ad79e.

* Reinstate signature check

* Enable migrate entry point
2021-12-20 16:09:42 +02:00
Dave Hrycyszyn 72944905cd Knocking down cache validation time 2021-12-20 14:08:49 +00:00
Dave Hrycyszyn effb756e2f Setting mixnode cache refresh to 30 seconds 2021-12-20 14:07:40 +00:00
Dave Hrycyszyn 583f5083e5 Merge branch 'develop' of github.com:nymtech/nym into develop 2021-12-20 14:00:04 +00:00
Dave Hrycyszyn 941e91d250 Re-enabling owner signature form on settings page 2021-12-20 13:59:52 +00:00
Bogdan-Ștefan Neacşu 0f1b9d138e Update mixnet contract address (#988) 2021-12-20 15:52:34 +02:00
mfahampshire 265696103c updated env sample 2021-12-20 13:58:38 +01:00
Bogdan-Ștefan Neacşu 22ce25d821 Fix verloc print (#987) 2021-12-20 12:52:32 +02:00
Dave Hrycyszyn 363f784714 Changing build command to match readme 2021-12-20 10:51:47 +00:00
Dave Hrycyszyn 1f360a5a27 Knocking validator-api refresh interval to 30 seconds 2021-12-20 10:36:43 +00:00
Dave Hrycyszyn ea3f2e9beb Moving country data refresh interval 15 minutes again 2021-12-20 10:35:04 +00:00
Dave Hrycyszyn 84924133b5 Knocking down the mixnode refresh interval to 30 seconds 2021-12-20 10:30:45 +00:00
Dave Hrycyszyn 860afc9086 Removing unused signing test module 2021-12-20 09:30:36 +00:00
Dave Hrycyszyn 0aab508633 Removing Dalek stuff, jst has already built this using cw apis 2021-12-19 16:18:08 +00:00
Dave Hrycyszyn bdfce8f663 Merge branch 'develop' of github.com:nymtech/nym into develop 2021-12-19 15:55:20 +00:00
Dave Hrycyszyn b5bb09588d Fixing Cargo.lock 2021-12-19 15:55:13 +00:00
Dave Hrycyszyn 983322d273 Feature/refactor mixnet contract test helpers (#986)
* Refactored test helpers

* Renaming mixnode "bond" coins to "pledge"

* Renaming gateway "bond" coins to "pledge"

* ibid

* Commenting out new tests, they will go in next PR
2021-12-19 15:54:11 +00:00
Dave Hrycyszyn e761989c6a Making the terminology consistent between mixnode/gateway output and … (#985)
* Making the terminology consistent between mixnode/gateway output and wallet display

* [ci skip] Generate TS types
2021-12-18 13:12:10 +00:00
futurechimp bc981873ff [ci skip] Generate TS types 2021-12-18 12:54:44 +00:00
Dave Hrycyszyn 8e99ae8979 Feature/add wallet to gateway init (#984)
* Moving sign_text method into common/crypto to dry it up

* Moved bech32 address validation into common/crypto

* ibid

* Gateway now requires a --wallet-address arg on init
2021-12-18 12:45:55 +00:00
Dave Hrycyszyn ed2b515a83 Feature/add wallet address to init (#982)
* Add a --wallet-adress parameter to init

* Rearranging signing code locations

* A bit more refactoring

* ibid

* Exiting if the stored bech32 address isn't valid at node start

* A few docs comments

* Moved crypto crate up to root src level

* Friendlier startup messages for node verification code

* Switching punk and nymt addresses in test
2021-12-18 10:44:30 +00:00
Fouad aca31dbaac Feature/wallet settings area (#974)
* set up Settings component

* Update network defaults

* Short node identity signature check

Fix tests

* Do not set proxy only for this time

* Update contract addresses

* file restructure

* file updates

* add settings tab panels

* update them color for nym fee

* rework layout

* update bond form to include signature and profit percent

* create info tooltip + make status component optional

* fix overflow

* update sys vars tab

* get mixnode bond details

* use mixnode id in settings

* set up profit percentage value on sys vars tab

* profit percentage styling

* add fix for delegations list

* fix unbond UI bug

* minor style updates

* dont allow profit percent on gateway bonding

* webpack prod fix

* update profit percentage from settings area

* hardcode signature for profit percentage update

Co-authored-by: Bogdan-Ștefan Neacșu <bogdan@nymtech.net>
2021-12-18 07:54:53 +00:00
Mx f8fb6f524e Move cleaned up smart contracts to main code repo (#929)
* moved contracts from gitlab to main codebase

* added missing event param

* removed erroneous  from description

* updates:
* changed maths of token -> MB conversion
* new tests for changed maths
* length check on cosmos address
* begun code doc

* code documentation

* small comment cleanup

* cont. w tests, may have found bug in maths re: using not whole tokens: investigating

* finished code doc

* included requested changes to contract

* change to maths operations, shrunk test error to < .9

* updates:
* updated tests
* updated readme

* removed commented out code, changed variable name to be more informative

* removed unnecessary byte32 length check
2021-12-18 01:32:47 +00:00
Bogdan-Ștefan Neacşu 036369226b Bump version to 0.12.0 (#980) 2021-12-18 01:13:30 +00:00
Bogdan-Ștefan Neacşu 4972ad8c53 Feature/rename erc20 (#979)
* Rename erc20-bridge to bandwidth-claim

* Rename contract constant in network defaults
2021-12-18 01:13:11 +00:00
Dave Hrycyszyn a09581eea9 Removed web wallet (#978) 2021-12-18 01:09:57 +00:00
Tommy Verrall 4d447706fc Update message to bond mixnode (#981)
- This prevents the user navigating to the deprecated web-wallet.
2021-12-17 17:51:07 +00:00
Jędrzej Stuczyński 6c6e16035a Feature/optional bandwidth bypass (#965)
* Removed outdated constant

* ClaimFreeTestnetBandwidth ClientControlRequest

* Configuration option for the testnet mode in gateway

* Made testnet mode deserialize to default value if not present

* Fixed testnet mode override

* Testnet config options for clients and validator api

* Changed error message for when gateway is not using testnet mode

* Incorporated testnet mode into gateway client

* Activating testnet mode based on config values

* Allowing clippy warnings

* Fixed use of moved value in wasm build

Co-authored-by: Bogdan-Ștefan Neacșu <bogdan@nymtech.net>
2021-12-17 18:55:33 +02:00
Tommy Verrall 96aa814a61 Merge pull request #977 from nymtech/bugfix/network-explorer-uptime-graph
Network Explorer: fix uptime history display to use new API response
2021-12-17 14:30:04 +00:00
Mark Sinclair 1fbf437786 Network Explorer: fix uptime history display to use new API response 2021-12-17 13:41:41 +00:00
Bogdan-Ștefan Neacşu 852d12b440 Make develop branch agnostic of the network (#976)
* Make develop branch agnostic of the network

* Update network defaults

* Short node identity signature check

Fix tests

* Do not set proxy only for this time

* Update contract addresses

* Network Explorer: configure URLs with `.env` file

* 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

* Network Explorer improvements:
- fix up API urls after Network Explorer API changes
- set currency denominations in `.env` file
- set API endpoints in `.env` file

* Network Explorer: change prod env to round robin DNS

* Update test

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
2021-12-17 15:36:35 +02:00
Bogdan-Ștefan Neacşu 865759254f Fix windows fmt (#975) 2021-12-17 13:23:44 +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 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
fmtabbara b1178b7ad5 remove delegation and undelegation from gateways 2021-11-10 10:42:45 +00:00
676 changed files with 74460 additions and 58251 deletions
@@ -1,4 +1,4 @@
name: Mixnet Contract
name: Contracts
on:
push:
@@ -21,7 +21,7 @@ jobs:
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
mixnet-contract:
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
@@ -45,20 +45,20 @@ jobs:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/mixnet/Cargo.toml --target wasm32-unknown-unknown
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/mixnet/Cargo.toml
args: --manifest-path contracts/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/mixnet/Cargo.toml -- --check
args: --manifest-path contracts/Cargo.toml --all -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/mixnet/Cargo.toml -- -D warnings
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
@@ -1,58 +0,0 @@
name: ERC20 Bridge Contract
on: [ push, pull_request ]
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
erc20-bridge-contract:
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
env:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/erc20-bridge/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/erc20-bridge/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- -D warnings
+2
View File
@@ -22,6 +22,8 @@ jobs:
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
@@ -1,4 +1,4 @@
name: Publish Tauri Wallet
name: Publish Nym Wallet
on:
push:
tags:
+12 -15
View File
@@ -19,30 +19,27 @@ jobs:
override: true
components: rustfmt, clippy
# token credentials (non-coconut) don't work for wasm right now
# - uses: actions-rs/cargo@v1
# with:
# command: build
# args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
- 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: test
args: --manifest-path clients/webassembly/Cargo.toml
- 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
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
+3
View File
@@ -0,0 +1,3 @@
unreleased=true
future-release=v0.12.0
since-tag=v0.11.0
+3 -1
View File
@@ -24,6 +24,7 @@ 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
@@ -33,4 +34,5 @@ contracts/mixnet/code_id
contracts/mixnet/Justfile
contracts/mixnet/Makefile
validator-config
*.patch
*.patch
validator-api-config.toml
+1
View File
@@ -0,0 +1 @@
2.7.5
+228 -879
View File
File diff suppressed because it is too large Load Diff
Generated
+922 -225
View File
File diff suppressed because it is too large Load Diff
+8 -5
View File
@@ -17,7 +17,6 @@ members = [
"clients/native/websocket-requests",
"clients/socks5",
"clients/tauri-client/src-tauri",
"clients/webassembly",
"common/client-libs/gateway-client",
"common/client-libs/mixnet-client",
"common/client-libs/validator-client",
@@ -25,11 +24,14 @@ members = [
"common/config",
"common/credentials",
"common/crypto",
"common/erc20-bridge-contract",
"common/mixnet-contract",
"common/bandwidth-claim-contract",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
"common/nymcoconut",
"common/nymsphinx",
"common/nymsphinx/acknowledgements",
"common/nymsphinx/addressing",
@@ -51,16 +53,17 @@ members = [
"mixnode",
"service-providers/network-requester",
"validator-api",
"validator-api/validator-api-requests",
]
default-members = [
"clients/native",
"clients/socks5",
# "clients/webassembly",
"gateway",
"service-providers/network-requester",
"mixnode",
"validator-api",
"explorer-api",
]
exclude = ["explorer", "contracts", "tokenomics-py"]
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly"]
+45
View File
@@ -0,0 +1,45 @@
all: clippy-all test fmt
happy: clippy-happy test fmt
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet
test: test-main test-contracts test-wallet
fmt: fmt-main fmt-contracts fmt-wallet
clippy-happy-main:
cargo clippy
clippy-happy-contracts:
cargo clippy --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown
clippy-happy-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml
clippy-all-main:
cargo clippy --all-features -- -D warnings
clippy-all-contracts:
cargo clippy --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
clippy-all-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
test-main:
cargo test --all-features
test-contracts:
cargo test --manifest-path contracts/Cargo.toml --all-features
test-wallet:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
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
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
+8 -7
View File
@@ -13,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)
@@ -21,7 +21,8 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
### Building
Platform build instructions are available on [our docs site](https://nymtech.net/docs/0.11.0/overview/index/).
Platform build instructions are available on [our docs site](https://nymtech.net/docs/stable/run-nym-nodes/build-nym).
Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/stable/nym-apps/wallet#for-developers).
### Developing
@@ -40,13 +41,13 @@ Node, node operator and delegator rewards are determined according to the princi
|<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=\lambda_{i}">|ratio of stake operator has pledged 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`.
|<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 `reward set` size, and set to 720 in testnet Sandbox.
|<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=PM_{i}">|declared profit margin of operator `i`, defaults to 10% in.
|<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.
|<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 NYMT.
Node reward for node `i` is determined as:
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "0.11.0"
version = "0.12.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2018"
@@ -13,7 +13,6 @@ use nymsphinx::utils::sample_poisson_duration;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::pin::Pin;
use std::sync::Arc;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
use tokio::time;
@@ -165,8 +164,8 @@ impl LoopCoverTrafficStream<OsRng> {
}
}
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
self.run().await;
})
}
@@ -79,9 +79,9 @@ impl KeyManager {
))?;
let gateway_shared_key: SharedKeys =
pemstore::load_key(&client_pathfinder.gateway_shared_key().to_owned())?;
pemstore::load_key(client_pathfinder.gateway_shared_key())?;
let ack_key: AckKey = pemstore::load_key(&client_pathfinder.ack_key().to_owned())?;
let ack_key: AckKey = pemstore::load_key(client_pathfinder.ack_key())?;
// TODO: ack key is never stored so it is generated now. But perhaps it should be stored
// after all for consistency sake?
@@ -6,7 +6,6 @@ use futures::StreamExt;
use gateway_client::GatewayClient;
use log::*;
use nymsphinx::forwarding::packet::MixPacket;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
@@ -72,8 +71,8 @@ impl MixTrafficController {
}
}
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
self.run().await;
})
}
@@ -22,7 +22,6 @@ use nymsphinx::addressing::clients::Recipient;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
use std::time::Duration;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
mod acknowledgement_control;
@@ -170,10 +169,8 @@ impl RealMessagesController<OsRng> {
self.ack_control = Some(ack_control_fut.await.unwrap());
}
// &Handle is only passed for consistency sake with other client modules, but I think
// when we get to refactoring, we should apply gateway approach and make it implicit
pub fn start(mut self, handle: &Handle) -> JoinHandle<Self> {
handle.spawn(async move {
pub fn start(mut self) -> JoinHandle<Self> {
tokio::spawn(async move {
self.run().await;
self
})
@@ -15,7 +15,6 @@ use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorith
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
use std::collections::HashSet;
use std::sync::Arc;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
@@ -291,8 +290,8 @@ impl RequestReceiver {
}
}
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
while let Some(request) = self.query_receiver.next().await {
match request {
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
@@ -322,8 +321,8 @@ impl FragmentedMessageReceiver {
mixnet_packet_receiver,
}
}
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
self.received_buffer.handle_new_received(new_messages).await;
}
@@ -355,9 +354,9 @@ impl ReceivedMessagesBufferController {
}
}
pub fn start(self, handle: &Handle) {
pub fn start(self) {
// TODO: should we do anything with JoinHandle(s) returned by start methods?
self.fragmented_message_receiver.start(handle);
self.request_receiver.start(handle);
self.fragmented_message_receiver.start();
self.request_receiver.start();
}
}
@@ -59,7 +59,7 @@ impl ReplyKeyStorage {
) -> Result<(), ReplyKeyStorageError> {
let digest = encryption_key.compute_digest();
let insertion_result = match self.db.insert(digest.to_vec(), encryption_key.to_bytes()) {
let insertion_result = match self.db.insert(digest, encryption_key.to_bytes()) {
Err(e) => Err(ReplyKeyStorageError::DbWriteError(e)),
Ok(existing_key) => {
if existing_key.is_some() {
@@ -79,7 +79,7 @@ impl ReplyKeyStorage {
&self,
key_digest: EncryptionKeyDigest,
) -> Result<Option<SurbEncryptionKey>, ReplyKeyStorageError> {
let removal_result = match self.db.remove(&key_digest.to_vec()) {
let removal_result = match self.db.remove(key_digest) {
Err(e) => Err(ReplyKeyStorageError::DbReadError(e)),
Ok(existing_key) => {
Ok(existing_key.map(|existing_key| self.read_encryption_key(existing_key)))
@@ -10,7 +10,6 @@ use std::ops::Deref;
use std::sync::Arc;
use std::time;
use std::time::Duration;
use tokio::runtime::Handle;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use topology::{nym_topology_from_bonds, NymTopology};
@@ -304,8 +303,8 @@ impl TopologyRefresher {
self.topology_accessor.is_routable().await
}
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
loop {
tokio::time::sleep(self.refresh_rate).await;
self.refresh().await;
+14
View File
@@ -117,6 +117,10 @@ impl<T: NymConfig> Config<T> {
self.client.id = id;
}
pub fn with_testnet_mode(&mut self, testnet_mode: bool) {
self.client.testnet_mode = testnet_mode;
}
pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) {
self.client.gateway_id = id.into();
}
@@ -153,6 +157,10 @@ impl<T: NymConfig> Config<T> {
self.client.id.clone()
}
pub fn get_testnet_mode(&self) -> bool {
self.client.testnet_mode
}
pub fn get_nym_root_directory(&self) -> PathBuf {
self.client.nym_root_directory.clone()
}
@@ -273,6 +281,11 @@ pub struct Client<T> {
/// ID specifies the human readable ID of this particular client.
id: String,
/// Indicates whether this client is running in a testnet mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
#[serde(default)]
testnet_mode: bool,
/// Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls: Vec<Url>,
@@ -335,6 +348,7 @@ impl<T: NymConfig> Default for Client<T> {
Client {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
testnet_mode: false,
validator_api_urls: default_api_endpoints(),
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
+7 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "0.11.0"
version = "0.12.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
rust-version = "1.56"
@@ -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,14 @@ 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"]
eth = []
[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
)
@@ -35,7 +35,7 @@ async fn send_file_with_reply() {
let (mut ws_stream, _) = connect_async(uri).await.unwrap();
let recipient = get_self_address(&mut ws_stream).await;
println!("our full address is: {}", recipient.to_string());
println!("our full address is: {}", recipient);
let read_data = std::fs::read("examples/dummy_file").unwrap();
@@ -83,7 +83,7 @@ async fn send_file_without_reply() {
let (mut ws_stream, _) = connect_async(uri).await.unwrap();
let recipient = get_self_address(&mut ws_stream).await;
println!("our full address is: {}", recipient.to_string());
println!("our full address is: {}", recipient);
let read_data = std::fs::read("examples/dummy_file").unwrap();
@@ -36,7 +36,7 @@ async fn send_text_with_reply() {
let (mut ws_stream, _) = connect_async(uri).await.unwrap();
let recipient = get_self_address(&mut ws_stream).await;
println!("our full address is: {}", recipient.to_string());
println!("our full address is: {}", recipient);
let send_request = json!({
"type" : "send",
@@ -76,7 +76,7 @@ async fn send_text_without_reply() {
let (mut ws_stream, _) = connect_async(uri).await.unwrap();
let recipient = get_self_address(&mut ws_stream).await;
println!("our full address is: {}", recipient.to_string());
println!("our full address is: {}", recipient);
let send_request = json!({
"type" : "send",
+5 -1
View File
@@ -5,7 +5,7 @@ pub(crate) fn config_template() -> &'static str {
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs in verloc.
// Note: any changes to the template must be reflected in the appropriate structs.
r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
@@ -19,6 +19,10 @@ version = '{{ client.version }}'
# Human readable ID of this particular client.
id = '{{ client.id }}'
# Indicates whether this client is running in a testnet mode, thus attempting
# to claim bandwidth without presenting bandwidth credentials.
testnet_mode = {{ client.testnet_mode }}
# Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [
{{#each client.validator_api_urls }}
+52 -66
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,
@@ -33,9 +32,9 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use tokio::runtime::Runtime;
use gateway_client::bandwidth::BandwidthController;
use crate::client::config::{Config, SocketType};
use crate::websocket;
pub(crate) mod config;
@@ -44,11 +43,6 @@ pub struct NymClient {
/// key filepaths, etc.
config: Config,
/// Tokio runtime used for futures execution.
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
// gateway.
runtime: Runtime,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
@@ -68,7 +62,6 @@ impl NymClient {
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
NymClient {
runtime: Runtime::new().unwrap(),
config,
key_manager,
input_tx: None,
@@ -94,9 +87,6 @@ impl NymClient {
mix_tx: BatchMixMessageSender,
) {
info!("Starting loop cover traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor which HAS TO be called within context of a tokio runtime
let _guard = self.runtime.enter();
LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
@@ -109,7 +99,7 @@ impl NymClient {
self.as_mix_recipient(),
topology_accessor,
)
.start(self.runtime.handle());
.start();
}
fn start_real_traffic_controller(
@@ -131,10 +121,6 @@ impl NymClient {
);
info!("Starting real traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
// When refactoring this restriction should definitely be removed.
let _guard = self.runtime.enter();
RealMessagesController::new(
controller_config,
@@ -144,7 +130,7 @@ impl NymClient {
topology_accessor,
reply_key_storage,
)
.start(self.runtime.handle());
.start();
}
// buffer controlling all messages fetched from provider
@@ -162,10 +148,10 @@ impl NymClient {
mixnet_receiver,
reply_key_storage,
)
.start(self.runtime.handle())
.start()
}
fn start_gateway_client(
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
@@ -182,43 +168,44 @@ impl NymClient {
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
if self.config.get_base().get_testnet_mode() {
gateway_client.set_testnet_mode(true)
}
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
})
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -229,13 +216,10 @@ impl NymClient {
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
self.runtime.block_on(topology_refresher.refresh());
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !self
.runtime
.block_on(topology_refresher.is_topology_routable())
{
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
@@ -243,7 +227,7 @@ impl NymClient {
}
info!("Starting topology refresher...");
topology_refresher.start(self.runtime.handle());
topology_refresher.start();
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -256,7 +240,7 @@ impl NymClient {
gateway_client: GatewayClient,
) {
info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
MixTrafficController::new(mix_rx, gateway_client).start();
}
fn start_websocket_listener(
@@ -269,8 +253,7 @@ impl NymClient {
let websocket_handler =
websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient());
websocket::Listener::new(self.config.get_listening_port())
.start(self.runtime.handle(), websocket_handler);
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
}
/// EXPERIMENTAL DIRECT RUST API
@@ -317,9 +300,9 @@ impl NymClient {
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub fn run_forever(&mut self) {
self.start();
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
pub async fn run_forever(&mut self) {
self.start().await;
if let Err(e) = tokio::signal::ctrl_c().await {
error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e
@@ -331,7 +314,7 @@ impl NymClient {
);
}
pub fn start(&mut self) {
pub async fn start(&mut self) {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -363,14 +346,17 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone());
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_key_storage.clone(),
);
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
self.start_real_traffic_controller(
+83 -32
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,6 +29,15 @@ use std::time::Duration;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
TESTNET_MODE_ARG_NAME,
};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init")
.about("Initialise a Nym client. Do this first!")
@@ -36,9 +53,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")
@@ -55,22 +72,62 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.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(feature = "eth")]
#[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")
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
.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")
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.required(true));
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
.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(
gateway: &gateway::Node,
our_identity: Arc<identity::KeyPair>,
@@ -162,7 +219,7 @@ fn show_address(config: &Config) {
println!("\nThe address of this client is: {}", client_recipient);
}
pub fn execute(matches: &ArgMatches) {
pub async fn execute(matches: ArgMatches<'static>) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
@@ -180,7 +237,7 @@ pub fn execute(matches: &ArgMatches) {
// TODO: ideally that should be the last thing that's being done to config.
// However, we are later further overriding it with gateway id
config = override_config(config, matches);
config = override_config(config, &matches);
if matches.is_present("fastmode") {
config.get_base_mut().set_high_default_traffic_volume();
}
@@ -193,26 +250,20 @@ pub fn execute(matches: &ArgMatches) {
let chosen_gateway_id = matches.value_of("gateway");
let registration_fut = async {
let gate_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.clients_address())
};
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
let rt = tokio::runtime::Runtime::new().unwrap();
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
let gateway_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_listener(gateway_listener);
.with_gateway_id(gateway_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
config
.get_base_mut()
.with_gateway_listener(gateway_details.clients_address());
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
+26 -2
View File
@@ -5,6 +5,18 @@ use crate::client::config::{Config, SocketType};
use clap::ArgMatches;
use url::Url;
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
@@ -44,12 +56,24 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of("eth_endpoint") {
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
} else {
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of("eth_private_key") {
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
config.get_base_mut().with_eth_private_key(eth_private_key);
} else {
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
}
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
config.get_base_mut().with_testnet_mode(true)
}
config
+19 -9
View File
@@ -4,6 +4,9 @@
use crate::client::config::Config;
use crate::client::NymClient;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use log::*;
@@ -39,15 +42,22 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Port for the socket (if applicable) to listen on")
.takes_value(true)
);
#[cfg(feature = "eth")]
#[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")
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.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")
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true));
app
@@ -72,7 +82,7 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub fn execute(matches: &ArgMatches) {
pub async fn execute(matches: ArgMatches<'static>) {
let id = matches.value_of("id").unwrap();
let mut config = match Config::load_from_file(Some(id)) {
@@ -83,12 +93,12 @@ pub fn execute(matches: &ArgMatches) {
}
};
config = override_config(config, matches);
config = override_config(config, &matches);
if !version_check(&config) {
error!("failed the local version check");
return;
}
NymClient::new(config).run_forever();
NymClient::new(config).run_forever().await;
}
+41 -8
View File
@@ -1,19 +1,21 @@
// 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;
pub mod websocket;
fn main() {
#[tokio::main]
async fn main() {
dotenv::dotenv().ok();
setup_logging();
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())
@@ -21,13 +23,13 @@ fn main() {
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches);
execute(arg_matches).await;
}
fn execute(matches: ArgMatches) {
async fn execute(matches: ArgMatches<'static>) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("init", Some(m)) => commands::init::execute(m.clone()).await,
("run", Some(m)) => commands::run::execute(m.clone()).await,
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
@@ -50,7 +52,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"),
)
}
+2 -3
View File
@@ -5,7 +5,6 @@ use super::handler::Handler;
use log::*;
use std::{net::SocketAddr, process, sync::Arc};
use tokio::io::AsyncWriteExt;
use tokio::runtime;
use tokio::{sync::Notify, task::JoinHandle};
enum State {
@@ -87,9 +86,9 @@ impl Listener {
}
}
pub(crate) fn start(mut self, rt_handle: &runtime::Handle, handler: Handler) -> JoinHandle<()> {
pub(crate) fn start(mut self, handler: Handler) -> JoinHandle<()> {
info!("Running websocket on {:?}", self.address.to_string());
rt_handle.spawn(async move { self.run(handler).await })
tokio::spawn(async move { self.run(handler).await })
}
}
+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;
+7 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "0.11.0"
version = "0.12.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2018"
rust-version = "1.56"
@@ -32,13 +32,18 @@ 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"]
eth = []
[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")
}
+5 -1
View File
@@ -5,7 +5,7 @@ pub(crate) fn config_template() -> &'static str {
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs in verloc.
// Note: any changes to the template must be reflected in the appropriate structs.
r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
@@ -19,6 +19,10 @@ version = '{{ client.version }}'
# Human readable ID of this particular client.
id = '{{ client.id }}'
# Indicates whether this client is running in a testnet mode, thus attempting
# to claim bandwidth without presenting bandwidth credentials.
testnet_mode = {{ client.testnet_mode }}
# Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [
{{#each client.validator_api_urls }}
+55 -69
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,
@@ -32,9 +28,12 @@ use gateway_client::{
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use tokio::runtime::Runtime;
use gateway_client::bandwidth::BandwidthController;
use crate::client::config::Config;
use crate::socks::{
authentication::{AuthenticationMethods, Authenticator, User},
server::SphinxSocksServer,
};
pub(crate) mod config;
@@ -43,11 +42,6 @@ pub struct NymClient {
/// key filepaths, etc.
config: Config,
/// Tokio runtime used for futures execution.
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
// gateway.
runtime: Runtime,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
}
@@ -58,7 +52,6 @@ impl NymClient {
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
NymClient {
runtime: Runtime::new().unwrap(),
config,
key_manager,
}
@@ -82,9 +75,6 @@ impl NymClient {
mix_tx: BatchMixMessageSender,
) {
info!("Starting loop cover traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor which HAS TO be called within context of a tokio runtime
let _guard = self.runtime.enter();
LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
@@ -97,7 +87,7 @@ impl NymClient {
self.as_mix_recipient(),
topology_accessor,
)
.start(self.runtime.handle());
.start();
}
fn start_real_traffic_controller(
@@ -119,10 +109,6 @@ impl NymClient {
);
info!("Starting real traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
// When refactoring this restriction should definitely be removed.
let _guard = self.runtime.enter();
RealMessagesController::new(
controller_config,
@@ -132,7 +118,7 @@ impl NymClient {
topology_accessor,
reply_key_storage,
)
.start(self.runtime.handle());
.start();
}
// buffer controlling all messages fetched from provider
@@ -150,10 +136,10 @@ impl NymClient {
mixnet_receiver,
reply_key_storage,
)
.start(self.runtime.handle())
.start()
}
fn start_gateway_client(
async fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
@@ -170,43 +156,44 @@ impl NymClient {
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
if self.config.get_base().get_testnet_mode() {
gateway_client.set_testnet_mode(true)
}
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
})
gateway_client
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -217,13 +204,10 @@ impl NymClient {
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
self.runtime.block_on(topology_refresher.refresh());
topology_refresher.refresh().await;
// TODO: a slightly more graceful termination here
if !self
.runtime
.block_on(topology_refresher.is_topology_routable())
{
if !topology_refresher.is_topology_routable().await {
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
@@ -231,7 +215,7 @@ impl NymClient {
}
info!("Starting topology refresher...");
topology_refresher.start(self.runtime.handle());
topology_refresher.start();
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -244,7 +228,7 @@ impl NymClient {
gateway_client: GatewayClient,
) {
info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
MixTrafficController::new(mix_rx, gateway_client).start();
}
fn start_socks5_listener(
@@ -263,14 +247,13 @@ impl NymClient {
self.config.get_provider_mix_address(),
self.as_mix_recipient(),
);
self.runtime
.spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
tokio::spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub fn run_forever(&mut self) {
self.start();
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
pub async fn run_forever(&mut self) {
self.start().await;
if let Err(e) = tokio::signal::ctrl_c().await {
error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e
@@ -282,7 +265,7 @@ impl NymClient {
);
}
pub fn start(&mut self) {
pub async fn start(&mut self) {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -314,14 +297,17 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone());
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_key_storage.clone(),
);
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
self.start_real_traffic_controller(
+83 -32
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,6 +27,15 @@ use std::time::Duration;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
TESTNET_MODE_ARG_NAME,
};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init")
.about("Initialise a Nym client. Do this first!")
@@ -40,9 +57,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")
@@ -55,22 +72,62 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.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(feature = "eth")]
#[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")
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
.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")
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.required(true));
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
.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(
gateway: &gateway::Node,
our_identity: Arc<identity::KeyPair>,
@@ -162,7 +219,7 @@ fn show_address(config: &Config) {
println!("\nThe address of this client is: {}", client_recipient);
}
pub fn execute(matches: &ArgMatches) {
pub async fn execute(matches: ArgMatches<'static>) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
@@ -181,7 +238,7 @@ pub fn execute(matches: &ArgMatches) {
// TODO: ideally that should be the last thing that's being done to config.
// However, we are later further overriding it with gateway id
config = override_config(config, matches);
config = override_config(config, &matches);
if matches.is_present("fastmode") {
config.get_base_mut().set_high_default_traffic_volume();
}
@@ -194,26 +251,20 @@ pub fn execute(matches: &ArgMatches) {
let chosen_gateway_id = matches.value_of("gateway");
let registration_fut = async {
let gate_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.clients_address())
};
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
let rt = tokio::runtime::Runtime::new().unwrap();
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
let gateway_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_listener(gateway_listener);
.with_gateway_id(gateway_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
config
.get_base_mut()
.with_gateway_listener(gateway_details.clients_address());
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
+26 -2
View File
@@ -9,6 +9,18 @@ pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
fn parse_validators(raw: &str) -> Vec<Url> {
raw.split(',')
.map(|raw_validator| {
@@ -40,12 +52,24 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of("eth_endpoint") {
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
} else {
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of("eth_private_key") {
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
config.get_base_mut().with_eth_private_key(eth_private_key);
} else {
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
}
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
config.get_base_mut().with_testnet_mode(true)
}
config
+19 -9
View File
@@ -4,6 +4,9 @@
use crate::client::config::Config;
use crate::client::NymClient;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use log::*;
@@ -45,15 +48,22 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Port for the socket to listen on")
.takes_value(true)
);
#[cfg(feature = "eth")]
#[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")
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.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")
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true));
app
@@ -78,7 +88,7 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub fn execute(matches: &ArgMatches) {
pub async fn execute(matches: ArgMatches<'static>) {
let id = matches.value_of("id").unwrap();
let mut config = match Config::load_from_file(Some(id)) {
@@ -89,12 +99,12 @@ pub fn execute(matches: &ArgMatches) {
}
};
config = override_config(config, matches);
config = override_config(config, &matches);
if !version_check(&config) {
error!("failed the local version check");
return;
}
NymClient::new(config).run_forever();
NymClient::new(config).run_forever().await;
}
+40 -7
View File
@@ -1,13 +1,14 @@
// 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;
pub mod socks;
fn main() {
#[tokio::main]
async fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
@@ -15,19 +16,20 @@ 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())
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches);
execute(arg_matches).await;
}
fn execute(matches: ArgMatches) {
async fn execute(matches: ArgMatches<'static>) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("init", Some(m)) => commands::init::execute(m.clone()).await,
("run", Some(m)) => commands::run::execute(m.clone()).await,
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
@@ -50,7 +52,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)
}
}
+175
View File
@@ -0,0 +1,175 @@
/*
* 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,
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: {},
});
}
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: {},
});
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_interval_reward_percent: {},
});
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_sybil_resistance_percent: {},
});
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_rewarding_status: {
mix_identity: mixIdentity,
rewarding_interval_nonce: rewardingIntervalNonce,
},
});
}
}
+204 -139
View File
@@ -1,147 +1,212 @@
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,
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>;
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>;
getIntervalRewardPercent(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);
}
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);
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getIntervalRewardPercent(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();
}
}
+483
View File
@@ -0,0 +1,483 @@
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,
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>;
updateMixnodeConfig(
mixnetContractAddress: string,
mixIdentity: string,
profitMarginPercent: number,
fee: StdFee | 'auto' | number,
): 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);
}
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);
}
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getIntervalRewardPercent(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,
);
}
updateMixnodeConfig(
mixnetContractAddress: string,
mixIdentity: string,
profitMarginPercent: number,
fee: StdFee | 'auto' | number,
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{ update_mixnode_config: { profit_margin_percent: profitMarginPercent, mix_identity: mixIdentity } },
fee,
);
}
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;
};
+129 -97
View File
@@ -1,125 +1,157 @@
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 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;
profit_margin_percent: number;
};
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
+2 -2
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "0.11.0"
version = "0.12.0"
edition = "2018"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -32,7 +32,7 @@ credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
nymsphinx = { path = "../../common/nymsphinx" }
topology = { path = "../../common/topology" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" }
+15 -4
View File
@@ -17,8 +17,6 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use wasm_utils::{console_log, console_warn};
use gateway_client::bandwidth::BandwidthController;
pub(crate) mod received_processor;
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
@@ -28,6 +26,7 @@ const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
#[wasm_bindgen]
pub struct NymClient {
validator_server: Url,
testnet_mode: bool,
// TODO: technically this doesn't need to be an Arc since wasm is run on a single thread
// however, once we eventually combine this code with the native-client's, it will make things
@@ -73,6 +72,7 @@ impl NymClient {
on_message: None,
on_gateway_connect: None,
testnet_mode: false,
}
}
@@ -85,6 +85,11 @@ impl NymClient {
self.on_gateway_connect = Some(on_connect)
}
pub fn set_testnet_mode(&mut self, testnet_mode: bool) {
console_log!("Setting testnet mode to {}", testnet_mode);
self.testnet_mode = testnet_mode;
}
fn self_recipient(&self) -> Recipient {
Recipient::new(
*self.identity.public_key(),
@@ -102,8 +107,10 @@ impl NymClient {
// Right now it's impossible to have async exported functions to take `&self` rather than self
pub async fn initial_setup(self) -> Self {
let testnet_mode = self.testnet_mode;
#[cfg(feature = "coconut")]
let bandwidth_controller = Some(BandwidthController::new(
let bandwidth_controller = Some(gateway_client::bandwidth::BandwidthController::new(
vec![self.validator_server.clone()],
*self.identity.public_key(),
));
@@ -127,6 +134,10 @@ impl NymClient {
bandwidth_controller,
);
if testnet_mode {
gateway_client.set_testnet_mode(true)
}
gateway_client
.authenticate_and_start()
.await
@@ -250,7 +261,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,
};
@@ -1,5 +1,5 @@
[package]
name = "erc20-bridge-contract"
name = "bandwidth-claim-contract"
version = "0.1.0"
edition = "2018"
+5 -7
View File
@@ -15,6 +15,8 @@ log = "0.4"
thiserror = "1.0"
url = "2.2"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
secp256k1 = "0.20.3"
web3 = { version = "0.17.0", default-features = false }
# internal
credentials = { path = "../../credentials" }
@@ -36,12 +38,6 @@ features = ["macros", "rt", "net", "sync", "time"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
version = "0.14"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.secp256k1]
version = "0.20.3"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.web3]
version = "0.17.0"
# wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2"
@@ -70,4 +66,6 @@ features = ["js"]
#url = "2.1"
[features]
coconut = ["gateway-requests/coconut", "coconut-interface"]
coconut = ["gateway-requests/coconut", "coconut-interface"]
wasm = ["web3/wasm", "web3/http", "web3/signing"]
default = ["web3/default"]
@@ -1,10 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::GatewayClientError;
#[cfg(feature = "coconut")]
use credentials::coconut::{
bandwidth::{obtain_signature, prepare_for_spending},
bandwidth::{
obtain_signature, prepare_for_spending, BandwidthVoucherAttributes, TOTAL_ATTRIBUTES,
},
utils::obtain_aggregate_verification_key,
};
#[cfg(not(feature = "coconut"))]
@@ -12,10 +13,11 @@ 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, BANDWIDTH_VALUE, ETH_BURN_FUNCTION_NAME, ETH_CONTRACT_ADDRESS,
ETH_MIN_BLOCK_DEPTH, TOKENS_TO_BURN,
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;
@@ -33,6 +35,8 @@ use web3::{
Web3,
};
use crate::error::GatewayClientError;
#[cfg(not(feature = "coconut"))]
pub fn eth_contract(web3: Web3<Http>) -> Contract<Http> {
Contract::from_json(
@@ -108,15 +112,31 @@ impl BandwidthController {
&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();
let bandwidth_credential =
obtain_signature(&self.identity.to_bytes(), &self.validator_endpoints).await?;
// 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,
)?)
}
@@ -183,7 +203,7 @@ impl BandwidthController {
&self.eth_private_key,
)
.await?;
if Some(U64::from(0)) == recipt.status {
if Some(U64::from(0u64)) == recipt.status {
Err(GatewayClientError::BurnTokenError(
web3::Error::InvalidResponse(format!(
"Transaction status is 0 (failure): {:?}",
@@ -203,9 +223,10 @@ impl BandwidthController {
#[cfg(not(feature = "coconut"))]
#[cfg(test)]
mod tests {
use super::*;
use network_defaults::ETH_EVENT_NAME;
use super::*;
#[test]
fn parse_contract() {
let transport =
@@ -40,6 +40,7 @@ const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
pub struct GatewayClient {
authenticated: bool,
testnet_mode: bool,
bandwidth_remaining: i64,
gateway_address: String,
gateway_identity: identity::PublicKey,
@@ -75,6 +76,7 @@ impl GatewayClient {
) -> Self {
GatewayClient {
authenticated: false,
testnet_mode: false,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
@@ -90,6 +92,10 @@ impl GatewayClient {
}
}
pub fn set_testnet_mode(&mut self, testnet_mode: bool) {
self.testnet_mode = testnet_mode
}
// TODO: later convert into proper builder methods
pub fn with_reconnection_on_failure(&mut self, should_reconnect_on_failure: bool) {
self.should_reconnect_on_failure = should_reconnect_on_failure
@@ -119,6 +125,7 @@ impl GatewayClient {
GatewayClient {
authenticated: false,
testnet_mode: false,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
@@ -513,6 +520,17 @@ impl GatewayClient {
Ok(())
}
async fn try_claim_testnet_bandwidth(&mut self) -> Result<(), GatewayClientError> {
let msg = ClientControlRequest::ClaimFreeTestnetBandwidth.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);
@@ -525,6 +543,10 @@ impl GatewayClient {
}
warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while");
if self.testnet_mode {
info!("The client is running in testnet mode - attempting to claim bandwidth without a credential");
return self.try_claim_testnet_bandwidth().await;
}
#[cfg(feature = "coconut")]
let credential = self
+24 -7
View File
@@ -9,16 +9,18 @@ rust-version = "1.56"
[dependencies]
base64 = "0.13"
mixnet-contract = { path="../../../common/mixnet-contract" }
serde = { version="1", features=["derive"] }
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
vesting-contract = { path = "../../../contracts/vesting" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version="0.11", features=["json"] }
reqwest = { version = "0.11", features = ["json"] }
thiserror = "1"
log = "0.4"
url = { version = "2.2", features = ["serde"] }
coconut-interface = { path = "../../coconut-interface" }
network-defaults = { path = "../../network-defaults" }
validator-api-requests = { path = "../../../validator-api/validator-api-requests" }
# required for nymd-client
# at some point it might be possible to make it wasm-compatible
@@ -26,13 +28,28 @@ 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.4.1", 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 = {version = "5.1", optional = true}
cosmwasm-std = { version = "1.0.0-beta3", optional = true }
ts-rs = { version = "5.1", optional = true }
[features]
nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
nymd-client = [
"async-trait",
"bip39",
"config",
"cosmrs",
"prost",
"flate2",
"sha2",
"itertools",
"cosmwasm-std",
]
typescript-types = ["ts-rs", "validator-api-requests/ts-rs"]
+272 -51
View File
@@ -6,24 +6,40 @@ use crate::nymd::{
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
#[cfg(feature = "nymd-client")]
use mixnet_contract::StateParams;
use mixnet_contract_common::ContractStateParams;
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract::{GatewayBond, MixNodeBond};
#[cfg(feature = "nymd-client")]
use mixnet_contract::{RawDelegationData, RewardingIntervalResponse};
use mixnet_contract_common::{
Delegation, Interval, MixnetContractVersion, MixnodeRewardingStatusResponse,
};
use mixnet_contract_common::{
GatewayBond, IdentityKey, IdentityKeyRef, MixNodeBond, RewardedSetNodeStatus,
RewardedSetUpdateDetails,
};
use std::collections::{HashMap, HashSet};
#[cfg(feature = "nymd-client")]
use std::str::FromStr;
use url::Url;
use validator_api_requests::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
};
#[cfg(feature = "nymd-client")]
#[must_use]
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>,
mixnode_delegations_page_limit: Option<u32>,
rewarded_set_page_limit: Option<u32>,
}
#[cfg(feature = "nymd-client")]
@@ -32,14 +48,17 @@ 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,
mixnode_delegations_page_limit: None,
rewarded_set_page_limit: None,
}
}
@@ -57,16 +76,23 @@ impl Config {
self.mixnode_delegations_page_limit = limit;
self
}
pub fn with_rewarded_set_page_limit(mut self, limit: Option<u32>) -> Config {
self.rewarded_set_page_limit = limit;
self
}
}
#[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>,
gateway_page_limit: Option<u32>,
mixnode_delegations_page_limit: Option<u32>,
rewarded_set_page_limit: Option<u32>,
// ideally they would have been read-only, but unfortunately rust doesn't have such features
pub validator_api: validator_api::Client,
@@ -83,15 +109,19 @@ 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,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
rewarded_set_page_limit: None,
validator_api: validator_api_client,
nymd: nymd_client,
})
@@ -101,7 +131,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,20 +145,24 @@ 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,
))?,
Some(config.mixnet_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(network_defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS)
.unwrap()
})),
Some(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,
mixnode_delegations_page_limit: config.mixnode_delegations_page_limit,
rewarded_set_page_limit: config.rewarded_set_page_limit,
validator_api: validator_api_client,
nymd: nymd_client,
})
@@ -135,7 +171,8 @@ impl Client<QueryNymdClient> {
pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
self.nymd = NymdClient::connect(
new_endpoint.as_ref(),
self.mixnet_contract_address.clone().unwrap(),
self.mixnet_contract_address.clone(),
self.vesting_contract_address.clone(),
)?;
Ok(())
}
@@ -161,24 +198,48 @@ impl<C> Client<C> {
Ok(self.validator_api.get_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
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_current_rewarding_interval(
&self,
) -> Result<RewardingIntervalResponse, ValidatorClientError>
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_rewarding_interval().await?)
Ok(self.nymd.get_mixnet_contract_version().await?)
}
pub async fn get_rewarding_status(
&self,
mix_identity: mixnet_contract_common::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>
@@ -188,6 +249,13 @@ impl<C> Client<C> {
Ok(self.nymd.get_reward_pool().await?.u128())
}
pub async fn get_current_interval(&self) -> Result<Interval, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_interval().await?)
}
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
@@ -202,14 +270,136 @@ impl<C> Client<C> {
Ok(self.nymd.get_sybil_resistance_percent().await?)
}
pub async fn get_epoch_reward_percent(&self) -> Result<u8, ValidatorClientError>
pub async fn get_active_set_work_factor(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_epoch_reward_percent().await?)
Ok(self.nymd.get_active_set_work_factor().await?)
}
pub async fn get_interval_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_interval_reward_percent().await?)
}
pub async fn get_current_rewarded_set_update_details(
&self,
) -> Result<RewardedSetUpdateDetails, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.query_current_rewarded_set_update_details()
.await?)
}
// basically handles paging for us
pub async fn get_all_nymd_rewarded_set_mixnode_identities(
&self,
) -> Result<Vec<(IdentityKey, RewardedSetNodeStatus)>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut identities = Vec::new();
let mut start_after = None;
let mut height = None;
loop {
let mut paged_response = self
.nymd
.get_rewarded_set_identities_paged(
start_after.take(),
self.rewarded_set_page_limit,
height,
)
.await?;
identities.append(&mut paged_response.identities);
if height.is_none() {
// keep using the same height (the first query happened at the most recent height)
height = Some(paged_response.at_height)
}
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(identities)
}
pub async fn get_nymd_rewarded_and_active_sets(
&self,
) -> Result<Vec<(MixNodeBond, RewardedSetNodeStatus)>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let rewarded_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.collect::<HashMap<_, _>>();
Ok(all_mixnodes
.into_iter()
.filter_map(|node| {
rewarded_set_identities
.get(node.identity())
.map(|status| (node, *status))
})
.collect())
}
/// If you need both rewarded and the active set, consider using [Self::get_nymd_rewarded_and_active_sets] instead
pub async fn get_nymd_rewarded_set(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let rewarded_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.map(|(identity, _status)| identity)
.collect::<HashSet<_>>();
Ok(all_mixnodes
.into_iter()
.filter(|node| rewarded_set_identities.contains(node.identity()))
.collect())
}
/// If you need both rewarded and the active set, consider using [Self::get_nymd_rewarded_and_active_sets] instead
pub async fn get_nymd_active_set(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let active_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.filter_map(|(identity, status)| {
if status.is_active() {
Some(identity)
} else {
None
}
})
.collect::<HashSet<_>>();
Ok(all_mixnodes
.into_iter()
.filter(|node| active_set_identities.contains(node.identity()))
.collect())
}
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
@@ -258,8 +448,8 @@ impl<C> Client<C> {
pub async fn get_all_nymd_single_mixnode_delegations(
&self,
identity: mixnet_contract::IdentityKey,
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
identity: IdentityKey,
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -286,9 +476,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,
{
@@ -297,7 +485,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,
)
@@ -314,10 +502,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,
{
@@ -326,13 +514,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)
@@ -344,28 +532,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,
@@ -405,6 +571,12 @@ impl ApiClient {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
@@ -413,6 +585,55 @@ impl ApiClient {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_gateway_core_status_count(identity, since)
.await?)
}
pub async fn get_mixnode_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_core_status_count(identity, since)
.await?)
}
pub async fn get_mixnode_status(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<MixnodeStatusResponse, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_status(identity).await?)
}
pub async fn get_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<RewardEstimationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_reward_estimation(identity)
.await?)
}
pub async fn get_mixnode_stake_saturation(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<StakeSaturationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_stake_saturation(identity)
.await?)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -9,6 +9,7 @@ pub mod validator_api;
pub use crate::client::ApiClient;
pub use crate::error::ValidatorClientError;
pub use validator_api_requests::*;
#[cfg(feature = "nymd-client")]
pub use client::{Client, Config};
@@ -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;
@@ -13,16 +13,22 @@ use cosmrs::proto::cosmos::auth::v1beta1::{
};
use cosmrs::proto::cosmos::bank::v1beta1::{
QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse,
QueryTotalSupplyRequest, QueryTotalSupplyResponse,
};
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 cosmwasm_std::Coin as CosmWasmCoin;
use prost::Message;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
@@ -49,6 +55,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())?)
}
@@ -153,6 +164,43 @@ pub trait CosmWasmClient: rpc::Client {
.map_err(|_| NymdError::SerializationError("Coins".to_owned()))
}
// this is annoyingly and inconsistently returning `Vec<CosmWasmCoin>` rather than
// Vec<Coin>, since cosmrs::Coin can't deal with IBC denoms.
// Presumably after https://github.com/cosmos/cosmos-rust/issues/173 is resolved,
// the code could be adjusted
async fn get_total_supply(&self) -> Result<Vec<CosmWasmCoin>, NymdError> {
let path = Some("/cosmos.bank.v1beta1.Query/TotalSupply".parse().unwrap());
let mut supply = Vec::new();
let mut pagination = None;
loop {
let req = QueryTotalSupplyRequest { pagination };
let mut res = self
.make_abci_query::<_, QueryTotalSupplyResponse>(path.clone(), req)
.await?;
supply.append(&mut res.supply);
if let Some(pagination_info) = res.pagination {
pagination = Some(create_pagination(pagination_info.next_key))
} else {
break;
}
}
supply
.into_iter()
.map(|coin| {
coin.amount.parse().map(|amount| CosmWasmCoin {
denom: coin.denom,
amount,
})
})
.collect::<Result<_, _>>()
.map_err(|_| NymdError::SerializationError("Coins".to_owned()))
}
async fn get_tx(&self, id: tx::Hash) -> Result<TxResponse, NymdError> {
Ok(self.tx(id, false).await?)
}
@@ -213,7 +261,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 +288,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 +303,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 +334,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 +359,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 +393,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 +417,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 +436,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,
})
}
@@ -103,15 +160,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
{
let init_msg = cosmwasm::MsgInstantiateContract {
sender: sender_address.clone(),
admin: options
.as_mut()
.map(|options| options.admin.take())
.flatten(),
admin: options.as_mut().and_then(|options| options.admin.take()),
code_id,
// 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 +177,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 +193,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
contract_address,
logs,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -162,9 +218,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 +246,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 +271,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 +281,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 +316,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 +356,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 +382,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 +431,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 +452,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 +471,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 +520,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 +540,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 +560,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 +577,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 +615,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 +633,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 +676,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);
}
@@ -17,17 +17,34 @@ pub enum Operation {
Send,
BondMixnode,
BondMixnodeOnBehalf,
UnbondMixnode,
UnbondMixnodeOnBehalf,
UpdateMixnodeConfig,
DelegateToMixnode,
DelegateToMixnodeOnBehalf,
UndelegateFromMixnode,
UndelegateFromMixnodeOnBehalf,
BondGateway,
BondGatewayOnBehalf,
UnbondGateway,
UnbondGatewayOnBehalf,
UpdateStateParams,
UpdateContractSettings,
BeginMixnodeRewarding,
FinishMixnodeRewarding,
TrackUnbondGateway,
TrackUnbondMixnode,
WithdrawVestedCoins,
TrackUndelegation,
CreatePeriodicVestingAccount,
AdvanceCurrentInterval,
WriteRewardedSet,
ClearRewardedSet,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
@@ -43,14 +60,31 @@ 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::UpdateMixnodeConfig => f.write_str("UpdateMixnodeConfig"),
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::UnbondGatewayOnBehalf => f.write_str("UnbondGatewayOnBehalf"),
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
Operation::DelegateToMixnodeOnBehalf => f.write_str("DelegateToMixnodeOnBehalf"),
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::UpdateStateParams => f.write_str("UpdateStateParams"),
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"),
Operation::AdvanceCurrentInterval => f.write_str("AdvanceCurrentInterval"),
Operation::WriteRewardedSet => f.write_str("WriteRewardedSet"),
Operation::ClearRewardedSet => f.write_str("ClearRewardedSet"),
}
}
}
@@ -59,23 +93,38 @@ 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::UpdateMixnodeConfig => 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::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(),
Operation::AdvanceCurrentInterval => 175_000u64.into(),
Operation::WriteRewardedSet => 175_000u64.into(),
Operation::ClearRewardedSet => 175_000u64.into(),
}
}
@@ -89,9 +138,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,297 @@
// 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_common::{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,
owner_address: &str,
staking_address: Option<String>,
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,
owner_address: &str,
staking_address: Option<String>,
start_time: Option<u64>,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::CreatePeriodicVestingAccount);
let req = VestingExecuteMsg::CreateAccount {
owner_address: owner_address.to_string(),
staking_address,
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,
@@ -133,6 +133,7 @@ impl DirectSecp256k1HdWallet {
}
}
#[must_use]
pub struct DirectSecp256k1HdWalletBuilder {
/// The password to use when deriving a BIP39 seed from a mnemonic.
bip39_password: String,
@@ -202,14 +203,28 @@ impl DirectSecp256k1HdWalletBuilder {
#[cfg(test)]
mod tests {
use super::*;
use network_defaults::BECH32_PREFIX;
#[test]
fn generating_account_addresses() {
let (addr1, addr2, addr3) = match BECH32_PREFIX {
"punk" => (
"punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2",
"punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn",
"punk17n9flp6jflljg6fp05dsy07wcprf2uuujse962",
),
"nymt" => (
"nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94",
"nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv",
"nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4",
),
_ => panic!("Test needs to be updated with new bech32 prefix"),
};
// 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", addr1),
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", addr2),
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", addr3)
];
for (mnemonic, address) in mnemonic_address.into_iter() {
@@ -1,16 +1,25 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::validator_api::error::ValidatorAPIError;
use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract::{GatewayBond, MixNodeBond};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;
use validator_api_requests::models::{
CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
};
pub mod error;
pub(crate) mod routes;
type PathSegments<'a> = &'a [&'a str];
type Params<'a, K, V> = &'a [(K, V)];
const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
pub struct Client {
url: Url,
@@ -30,24 +39,33 @@ impl Client {
self.url = new_url
}
async fn query_validator_api<T>(&self, path: PathSegments<'_>) -> Result<T, ValidatorAPIError>
async fn query_validator_api<T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, ValidatorAPIError>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let url = create_api_url(&self.url, path);
let url = create_api_url(&self.url, path, params);
Ok(self.reqwest_client.get(url).send().await?.json().await?)
}
async fn post_validator_api<B, T>(
async fn post_validator_api<B, T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, ValidatorAPIError>
where
B: Serialize + ?Sized,
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let url = create_api_url(&self.url, path);
let url = create_api_url(&self.url, path, params);
Ok(self
.reqwest_client
.post(url)
@@ -59,18 +77,176 @@ impl Client {
}
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES])
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
.await
}
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS])
self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE])
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
NO_PARAMS,
)
.await
}
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
NO_PARAMS,
)
.await
}
pub async fn get_probs_mixnode_rewarded(
&self,
mixnode_id: &str,
) -> Result<HashMap<String, f32>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::MIXNODES,
routes::REWARDED,
routes::INCLUSION_CHANCE,
mixnode_id,
],
NO_PARAMS,
)
.await
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::GATEWAY,
identity,
CORE_STATUS_COUNT,
],
&[(SINCE_ARG, since.to_string())],
)
.await
} else {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::GATEWAY,
identity,
],
NO_PARAMS,
)
.await
}
}
pub async fn get_mixnode_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
CORE_STATUS_COUNT,
],
&[(SINCE_ARG, since.to_string())],
)
.await
} else {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
],
NO_PARAMS,
)
.await
}
}
pub async fn get_mixnode_status(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<MixnodeStatusResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::STATUS,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::REWARD_ESTIMATION,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_stake_saturation(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<StakeSaturationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::STAKE_SATURATION,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_inclusion_probability(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<InclusionProbabilityResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::INCLUSION_CHANCE,
],
NO_PARAMS,
)
.await
}
pub async fn blind_sign(
@@ -79,6 +255,7 @@ impl Client {
) -> Result<BlindedSignatureResponse, ValidatorAPIError> {
self.post_validator_api(
&[routes::API_VERSION, routes::COCONUT_BLIND_SIGN],
NO_PARAMS,
request_body,
)
.await
@@ -87,13 +264,20 @@ impl Client {
pub async fn get_coconut_verification_key(
&self,
) -> Result<VerificationKeyResponse, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::COCONUT_VERIFICATION_KEY])
.await
self.query_validator_api(
&[routes::API_VERSION, routes::COCONUT_VERIFICATION_KEY],
NO_PARAMS,
)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
fn create_api_url(base: &Url, segments: PathSegments<'_>) -> Url {
fn create_api_url<K: AsRef<str>, V: AsRef<str>>(
base: &Url,
segments: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Url {
let mut url = base.clone();
let mut path_segments = url
.path_segments_mut()
@@ -108,6 +292,10 @@ fn create_api_url(base: &Url, segments: PathSegments<'_>) -> Url {
// and can be dropped
drop(path_segments);
if !params.is_empty() {
url.query_pairs_mut().extend_pairs(params);
}
url
}
@@ -122,51 +310,66 @@ mod tests {
// works with 1 segment
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["foo"]).as_str()
create_api_url(&base_url, &["foo"], NO_PARAMS).as_str()
);
// works with 2 segments
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "bar"]).as_str()
create_api_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
);
// works with leading slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["/foo"]).as_str()
create_api_url(&base_url, &["/foo"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["/foo", "bar"]).as_str()
create_api_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "/bar"]).as_str()
create_api_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
);
// works with trailing slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["foo/"]).as_str()
create_api_url(&base_url, &["foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo/", "bar"]).as_str()
create_api_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "bar/"]).as_str()
create_api_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
);
// works with both leading and trailing slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["/foo/"]).as_str()
create_api_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["/foo/", "/bar/"]).as_str()
create_api_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
);
// adds params
assert_eq!(
"http://foomp.com/foo/bar?foomp=baz",
create_api_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
create_api_url(
&base_url,
&["/foo/", "/bar/"],
&[("arg1", "val1"), ("arg2", "val2")]
)
.as_str()
);
}
}
@@ -8,6 +8,19 @@ pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const ACTIVE: &str = "active";
pub const REWARDED: &str = "rewarded";
pub const COCONUT_BLIND_SIGN: &str = "blind_sign";
pub const COCONUT_VERIFICATION_KEY: &str = "verification_key";
pub const COCONUT_BLIND_SIGN: &str = "blind-sign";
pub const COCONUT_VERIFICATION_KEY: &str = "verification-key";
pub const STATUS_ROUTES: &str = "status";
pub const MIXNODE: &str = "mixnode";
pub const GATEWAY: &str = "gateway";
pub const CORE_STATUS_COUNT: &str = "core-status-count";
pub const SINCE_ARG: &str = "since";
pub const STATUS: &str = "status";
pub const REWARD_ESTIMATION: &str = "reward-estimation";
pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
@@ -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()
@@ -0,0 +1,10 @@
[package]
name = "contracts-common"
version = "0.1.0"
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0-beta3"

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